|
|
@@ -0,0 +1,361 @@
|
|
|
+<script lang="ts" setup>
|
|
|
+import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
|
|
+import { IotStatApi } from '@/api/pms/stat'
|
|
|
+import dayjs from 'dayjs'
|
|
|
+import quarterOfYear from 'dayjs/plugin/quarterOfYear'
|
|
|
+import * as echarts from 'echarts'
|
|
|
+
|
|
|
+dayjs.extend(quarterOfYear)
|
|
|
+
|
|
|
+const chartRef = ref(null)
|
|
|
+let myChart: echarts.ECharts | null = null
|
|
|
+const currentTimeType = ref('month')
|
|
|
+
|
|
|
+const timeOptions = [
|
|
|
+ { label: '本月', value: 'month' },
|
|
|
+ { label: '本季度', value: 'quarter' },
|
|
|
+ { label: '本年', value: 'year' }
|
|
|
+]
|
|
|
+
|
|
|
+const fieldConfig = [
|
|
|
+ { key: 'ylWellCount', name: '压裂井数' },
|
|
|
+ { key: 'lyWellCount', name: '连油井数' },
|
|
|
+ { key: 'cumulativePumpTrips', name: '泵车台次' },
|
|
|
+ { key: 'cumulativeWorkingLayers', name: '压裂层数' }
|
|
|
+]
|
|
|
+
|
|
|
+const getDateRange = (type: 'year' | 'quarter' | 'month') => {
|
|
|
+ const now = dayjs()
|
|
|
+ let start: dayjs.Dayjs, end: dayjs.Dayjs
|
|
|
+
|
|
|
+ if (type === 'year') {
|
|
|
+ start = now.startOf('year')
|
|
|
+ end = now.endOf('year')
|
|
|
+ } else if (type === 'quarter') {
|
|
|
+ start = now.startOf('quarter')
|
|
|
+ end = now.endOf('quarter')
|
|
|
+ } else {
|
|
|
+ start = now.startOf('month')
|
|
|
+ end = now.endOf('month')
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ start: start.format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ end: end.format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const fetchData = async () => {
|
|
|
+ if (myChart) {
|
|
|
+ myChart.showLoading({
|
|
|
+ text: '加载中 ...',
|
|
|
+ color: '#409eff',
|
|
|
+ textColor: '#B6C8DA',
|
|
|
+ maskColor: 'rgba(0, 0, 0, 0.2)'
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ const { start, end } = getDateRange(currentTimeType.value as 'year' | 'quarter' | 'month')
|
|
|
+
|
|
|
+ const params = {
|
|
|
+ deptId: 163,
|
|
|
+ 'createTime[0]': start,
|
|
|
+ 'createTime[1]': end,
|
|
|
+ timeType: currentTimeType.value
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ let list: any[] = []
|
|
|
+
|
|
|
+ if (currentTimeType.value === 'year') {
|
|
|
+ const res = await IotStatApi.getRdWorkloadYear(params)
|
|
|
+ if (res && Array.isArray(res)) list = res
|
|
|
+ } else {
|
|
|
+ const res = await IotStatApi.getRdWorkload(params)
|
|
|
+ if (res && res.list) list = res.list
|
|
|
+ }
|
|
|
+
|
|
|
+ renderChart(list)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Workload API Error:', error)
|
|
|
+ } finally {
|
|
|
+ myChart?.hideLoading()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const renderChart = (data: any[]) => {
|
|
|
+ if (!myChart) return
|
|
|
+ const isYear = currentTimeType.value === 'year'
|
|
|
+
|
|
|
+ // --- 高亮配色方案 ---
|
|
|
+ const colorPalettes = [
|
|
|
+ // 1. 冰蓝霓虹 (压裂井数) - 极亮青色 -> 深蓝
|
|
|
+ {
|
|
|
+ line: '#00E5FF',
|
|
|
+ start: '#00E5FF',
|
|
|
+ end: '#2979FF'
|
|
|
+ },
|
|
|
+ // 2. 日落流金 (连油井数) - 亮黄 -> 亮橙
|
|
|
+ {
|
|
|
+ line: '#FFD740',
|
|
|
+ start: '#FFD740',
|
|
|
+ end: '#FF6D00'
|
|
|
+ },
|
|
|
+ // 3. 赛博紫 (泵车台次) - 亮粉紫 -> 深紫
|
|
|
+ {
|
|
|
+ line: '#EA80FC',
|
|
|
+ start: '#EA80FC',
|
|
|
+ end: '#651FFF'
|
|
|
+ },
|
|
|
+ // 4. 极光绿 (压裂层数) - 荧光绿 -> 青绿
|
|
|
+ {
|
|
|
+ line: '#69F0AE',
|
|
|
+ start: '#69F0AE',
|
|
|
+ end: '#00C853'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+
|
|
|
+ const xAxisData = data.map((item) =>
|
|
|
+ isYear ? item.reportDate : item.projectDeptName.replace('项目部', '')
|
|
|
+ )
|
|
|
+
|
|
|
+ const option: echarts.EChartsOption = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ backgroundColor: 'rgba(18, 26, 44, 0.9)', // 更深色的背景,对比更强
|
|
|
+ borderColor: '#409eff',
|
|
|
+ textStyle: { color: '#fff' },
|
|
|
+ padding: [10, 15],
|
|
|
+ axisPointer: {
|
|
|
+ type: isYear ? 'line' : 'shadow',
|
|
|
+ lineStyle: { color: '#fff', type: 'dashed' },
|
|
|
+ shadowStyle: { color: 'rgba(255, 255, 255, 0.1)' }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: fieldConfig.map((item) => item.name),
|
|
|
+ textStyle: { color: '#E0E0E0' }, // 图例文字调亮
|
|
|
+ top: 0,
|
|
|
+ itemWidth: 14,
|
|
|
+ itemHeight: 14
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ top: '18%', // 留出更多空间给图例
|
|
|
+ left: '2%',
|
|
|
+ right: '2%',
|
|
|
+ bottom: '2%',
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ data: xAxisData,
|
|
|
+ type: 'category',
|
|
|
+ boundaryGap: !isYear,
|
|
|
+ axisLabel: {
|
|
|
+ color: '#D1D5DB', // X轴文字调亮
|
|
|
+ interval: 0,
|
|
|
+ fontSize: 12,
|
|
|
+ formatter: (value: string) => {
|
|
|
+ // 如果名字太长换行显示
|
|
|
+ return value.length > 4 ? value.slice(0, 4) + '\n' + value.slice(4) : value
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLine: { lineStyle: { color: '#4B5563' } },
|
|
|
+ axisTick: { show: false }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ axisLabel: { color: '#D1D5DB' },
|
|
|
+ splitLine: {
|
|
|
+ lineStyle: { color: '#457794', type: 'dashed' } // 网格线稍微亮一点
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: fieldConfig.map((item, index) => {
|
|
|
+ const palette = colorPalettes[index % colorPalettes.length]
|
|
|
+
|
|
|
+ const seriesBase = {
|
|
|
+ name: item.name,
|
|
|
+ data: data.map((d) => {
|
|
|
+ const val = d[item.key]
|
|
|
+ return val === null || val === undefined || isNaN(val) ? 0 : val
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isYear) {
|
|
|
+ // --- 折线图 (年) ---
|
|
|
+ return {
|
|
|
+ ...seriesBase,
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ showSymbol: false,
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 8,
|
|
|
+ // 线条非常亮
|
|
|
+ lineStyle: { width: 3, color: palette.line, shadowColor: palette.line, shadowBlur: 10 },
|
|
|
+ itemStyle: { color: palette.line, borderColor: '#fff', borderWidth: 2 },
|
|
|
+ areaStyle: {
|
|
|
+ opacity: 0.5, // 区域透明度适中
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: palette.start }, // 顶部颜色同线条
|
|
|
+ { offset: 1, color: 'rgba(0,0,0,0)' }
|
|
|
+ ])
|
|
|
+ },
|
|
|
+ emphasis: { focus: 'series' }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // --- 柱状图 (月/季) ---
|
|
|
+ return {
|
|
|
+ ...seriesBase,
|
|
|
+ type: 'bar',
|
|
|
+ barMaxWidth: 14,
|
|
|
+ barGap: '30%',
|
|
|
+ itemStyle: {
|
|
|
+ borderRadius: [4, 4, 0, 0],
|
|
|
+ // 柱子使用实色渐变,不再过度透明,显得“脏”
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: palette.start }, // 100% 亮色
|
|
|
+ { offset: 1, color: palette.end } // 底部颜色,透明度由 hex 决定(这里是实色)
|
|
|
+ ]),
|
|
|
+ // 给柱子加一点光晕
|
|
|
+ shadowColor: palette.end,
|
|
|
+ shadowBlur: 5
|
|
|
+ },
|
|
|
+ emphasis: {
|
|
|
+ focus: 'series',
|
|
|
+ itemStyle: {
|
|
|
+ // 鼠标悬停时变得更亮
|
|
|
+ color: palette.line
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }) as any
|
|
|
+ }
|
|
|
+
|
|
|
+ myChart.setOption(option)
|
|
|
+}
|
|
|
+// const renderChart = (data: any[]) => {
|
|
|
+// if (!myChart) return
|
|
|
+// const isYear = currentTimeType.value === 'year'
|
|
|
+
|
|
|
+// const color = ['#5470c6', '#f1d209', '#e14f0f', '#91cc75']
|
|
|
+
|
|
|
+// const xAxisData = data.map((item) =>
|
|
|
+// isYear ? item.reportDate : item.projectDeptName.replace('项目部', '')
|
|
|
+// )
|
|
|
+
|
|
|
+// const option: echarts.EChartsOption = {
|
|
|
+// tooltip: {
|
|
|
+// trigger: 'axis',
|
|
|
+// axisPointer: {
|
|
|
+// type: isYear ? 'cross' : 'shadow',
|
|
|
+// label: { backgroundColor: '#6a7985' }
|
|
|
+// }
|
|
|
+// },
|
|
|
+// legend: {
|
|
|
+// data: fieldConfig.map((item) => item.name),
|
|
|
+// textStyle: { color: '#B6C8DA' }
|
|
|
+// },
|
|
|
+// grid: { top: '15%', left: '2%', right: '2%', bottom: '2%', containLabel: true },
|
|
|
+// xAxis: {
|
|
|
+// data: xAxisData,
|
|
|
+// type: 'category',
|
|
|
+// boundaryGap: !isYear,
|
|
|
+// axisLabel: { color: '#B6C8DA', interval: 0 },
|
|
|
+// axisLine: { lineStyle: { color: '#B6C8DA' } }
|
|
|
+// },
|
|
|
+// yAxis: {
|
|
|
+// type: 'value',
|
|
|
+// axisLabel: { color: '#B6C8DA' },
|
|
|
+// splitLine: { lineStyle: { color: '#457794', type: 'dashed' } },
|
|
|
+// axisLine: { lineStyle: { color: '#B6C8DA' } }
|
|
|
+// },
|
|
|
+// series: fieldConfig.map((item, index) => ({
|
|
|
+// name: item.name,
|
|
|
+// type: isYear ? 'line' : 'bar',
|
|
|
+// smooth: true,
|
|
|
+// barMaxWidth: 30,
|
|
|
+// symbol: 'circle',
|
|
|
+// symbolSize: 8,
|
|
|
+// itemStyle: { color: color[index] },
|
|
|
+// emphasis: { focus: 'series' },
|
|
|
+// lineStyle: { width: 3 },
|
|
|
+// data: data.map((d) => {
|
|
|
+// const val = d[item.key]
|
|
|
+// return val === null || val === undefined || isNaN(val) ? 0 : val
|
|
|
+// })
|
|
|
+// }))
|
|
|
+// }
|
|
|
+
|
|
|
+// myChart.setOption(option)
|
|
|
+// }
|
|
|
+
|
|
|
+const handleTimeChange = () => {
|
|
|
+ fetchData()
|
|
|
+}
|
|
|
+
|
|
|
+const resizeChart = () => myChart?.resize()
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ nextTick(() => {
|
|
|
+ myChart = echarts.init(chartRef.value, undefined, { renderer: 'canvas' })
|
|
|
+ fetchData()
|
|
|
+ window.addEventListener('resize', resizeChart)
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ window.removeEventListener('resize', resizeChart)
|
|
|
+ myChart?.dispose()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="card size-full rounded-lg p-4 flex flex-col">
|
|
|
+ <div class="flex justify-between items-center mb-4">
|
|
|
+ <div class="flex items-center gap-2 items-center">
|
|
|
+ <div class="w-1 h-4 bg-[#00E5FF] rounded-full shadow-[0_0_8px_#00E5FF]"></div>
|
|
|
+ <div class="text-[#e0e0e0] text-lg font-bold">工作量汇总</div>
|
|
|
+ </div>
|
|
|
+ <el-segmented
|
|
|
+ size="default"
|
|
|
+ v-model="currentTimeType"
|
|
|
+ :options="timeOptions"
|
|
|
+ @change="handleTimeChange"
|
|
|
+ class="dark-segmented w-50!"
|
|
|
+ block
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div ref="chartRef" class="flex-1 w-full min-h-0"></div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.card {
|
|
|
+ background-color: rgb(0 0 0 / 30%);
|
|
|
+ box-shadow: 0 2px 12px rgb(0 0 0 / 50%);
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.dark-segmented {
|
|
|
+ --el-segmented-item-selected-color: #e5eaf3;
|
|
|
+ --el-border-radius-base: 16px;
|
|
|
+ --el-segmented-color: #cfd3dc;
|
|
|
+ --el-segmented-bg-color: #262727;
|
|
|
+ --el-segmented-item-selected-bg-color: #409eff;
|
|
|
+ --el-segmented-item-selected-disabled-bg-color: rgb(42 89 138);
|
|
|
+ --el-segmented-item-hover-color: #e5eaf3;
|
|
|
+ --el-segmented-item-hover-bg-color: #39393a;
|
|
|
+ --el-segmented-item-active-bg-color: #424243;
|
|
|
+ --el-segmented-item-disabled-color: #8d9095;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-segmented__item) {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+</style>
|