|
|
@@ -8,7 +8,7 @@ import * as echarts from 'echarts'
|
|
|
dayjs.extend(quarterOfYear)
|
|
|
|
|
|
const chartRef = ref(null)
|
|
|
-const myChart = ref<echarts.EChartsType | null>(null)
|
|
|
+let myChart: echarts.ECharts | null = null
|
|
|
const currentTimeType = ref('month')
|
|
|
|
|
|
const timeOptions = [
|
|
|
@@ -24,23 +24,8 @@ const fieldConfig = [
|
|
|
{ key: 'cumulativeWorkingLayers', name: '压裂层数' }
|
|
|
]
|
|
|
|
|
|
-const colorPalette = ['#5470c6', '#f1d209', '#e14f0f', '#91cc75']
|
|
|
-
|
|
|
-const hexToRgba = (hex: string, opacity: number) => {
|
|
|
- let c: any
|
|
|
- if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
|
|
|
- c = hex.substring(1).split('')
|
|
|
- if (c.length === 3) {
|
|
|
- c = [c[0], c[0], c[1], c[1], c[2], c[2]]
|
|
|
- }
|
|
|
- c = '0x' + c.join('')
|
|
|
- return 'rgba(' + [(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',') + ',' + opacity + ')'
|
|
|
- }
|
|
|
- return hex
|
|
|
-}
|
|
|
-
|
|
|
const getDateRange = (type: 'year' | 'quarter' | 'month') => {
|
|
|
- const now = dayjs().subtract(1, 'month')
|
|
|
+ const now = dayjs()
|
|
|
let start: dayjs.Dayjs, end: dayjs.Dayjs
|
|
|
|
|
|
if (type === 'year') {
|
|
|
@@ -61,9 +46,9 @@ const getDateRange = (type: 'year' | 'quarter' | 'month') => {
|
|
|
}
|
|
|
|
|
|
const fetchData = async () => {
|
|
|
- if (myChart.value) {
|
|
|
- myChart.value.showLoading({
|
|
|
- text: '',
|
|
|
+ if (myChart) {
|
|
|
+ myChart.showLoading({
|
|
|
+ text: '加载中 ...',
|
|
|
color: '#409eff',
|
|
|
textColor: '#B6C8DA',
|
|
|
maskColor: 'rgba(0, 0, 0, 0.2)'
|
|
|
@@ -94,109 +79,225 @@ const fetchData = async () => {
|
|
|
} catch (error) {
|
|
|
console.error('Workload API Error:', error)
|
|
|
} finally {
|
|
|
- myChart.value?.hideLoading()
|
|
|
+ myChart?.hideLoading()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const renderChart = (data: any[]) => {
|
|
|
- if (!myChart.value) return
|
|
|
-
|
|
|
+ if (!myChart) return
|
|
|
const isYear = currentTimeType.value === 'year'
|
|
|
|
|
|
- const xAxisData = data.map((item) => (isYear ? item.reportDate : item.projectDeptName))
|
|
|
-
|
|
|
- const series = fieldConfig.map<any>((field, index) => {
|
|
|
- const color = colorPalette[index]
|
|
|
-
|
|
|
- // 数据清洗,防止 null/undefined 报错
|
|
|
- const seriesData = data.map((item) => {
|
|
|
- const val = item[field.key]
|
|
|
- return val === null || val === undefined || isNaN(val) ? 0 : val
|
|
|
- })
|
|
|
-
|
|
|
- console.log('seriesData :>> ', seriesData)
|
|
|
-
|
|
|
- return {
|
|
|
- name: field.name,
|
|
|
- type: 'line',
|
|
|
- smooth: true,
|
|
|
- symbol: 'circle',
|
|
|
- symbolSize: 8,
|
|
|
- itemStyle: { color },
|
|
|
- data: seriesData
|
|
|
+ // --- 高亮配色方案 ---
|
|
|
+ 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'
|
|
|
}
|
|
|
+ ]
|
|
|
|
|
|
- if (isYear) {
|
|
|
- } else {
|
|
|
- }
|
|
|
- })
|
|
|
+ const xAxisData = data.map((item) =>
|
|
|
+ isYear ? item.reportDate : item.projectDeptName.replace('项目部', '')
|
|
|
+ )
|
|
|
|
|
|
- let option: echarts.EChartsOption
|
|
|
-
|
|
|
- option = {
|
|
|
+ const option: echarts.EChartsOption = {
|
|
|
tooltip: {
|
|
|
trigger: 'axis',
|
|
|
+ backgroundColor: 'rgba(18, 26, 44, 0.9)', // 更深色的背景,对比更强
|
|
|
+ borderColor: '#409eff',
|
|
|
+ textStyle: { color: '#fff' },
|
|
|
+ padding: [10, 15],
|
|
|
axisPointer: {
|
|
|
- type: 'cross',
|
|
|
- label: {
|
|
|
- backgroundColor: '#283b56'
|
|
|
- }
|
|
|
+ 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: '5%',
|
|
|
+ top: '18%', // 留出更多空间给图例
|
|
|
left: '2%',
|
|
|
- right: '5%',
|
|
|
- bottom: '0%',
|
|
|
+ right: '2%',
|
|
|
+ bottom: '2%',
|
|
|
containLabel: true
|
|
|
},
|
|
|
xAxis: {
|
|
|
- type: 'category',
|
|
|
- boundaryGap: false,
|
|
|
data: xAxisData,
|
|
|
+ type: 'category',
|
|
|
+ boundaryGap: !isYear,
|
|
|
axisLabel: {
|
|
|
- color: '#B6C8DA'
|
|
|
- },
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#B6C8DA' // X轴线白色半透明
|
|
|
+ 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',
|
|
|
- scale: true,
|
|
|
- axisLabel: {
|
|
|
- color: '#B6C8DA'
|
|
|
- },
|
|
|
+ axisLabel: { color: '#D1D5DB' },
|
|
|
splitLine: {
|
|
|
- show: true,
|
|
|
- lineStyle: {
|
|
|
- color: '#457794',
|
|
|
- type: 'dashed'
|
|
|
+ lineStyle: { color: '#374151', type: 'dashed', opacity: 0.5 } // 网格线稍微亮一点
|
|
|
+ }
|
|
|
+ },
|
|
|
+ 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' }
|
|
|
}
|
|
|
- },
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#B6C8DA'
|
|
|
+ } 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
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- },
|
|
|
- series
|
|
|
+ }) as any
|
|
|
}
|
|
|
|
|
|
- myChart.value.setOption(option, true)
|
|
|
+ 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.value?.resize()
|
|
|
+const resizeChart = () => myChart?.resize()
|
|
|
|
|
|
onMounted(() => {
|
|
|
nextTick(() => {
|
|
|
- myChart.value = echarts.init(chartRef.value)
|
|
|
+ myChart = echarts.init(chartRef.value, undefined, { renderer: 'canvas' })
|
|
|
fetchData()
|
|
|
window.addEventListener('resize', resizeChart)
|
|
|
})
|
|
|
@@ -204,14 +305,14 @@ onMounted(() => {
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
window.removeEventListener('resize', resizeChart)
|
|
|
- myChart.value?.dispose()
|
|
|
+ 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="text-[#b6c8da] text-lg font-bold">工单数量统计</div>
|
|
|
+ <div class="text-[#b6c8da] text-lg font-bold">工作量汇总</div>
|
|
|
<el-segmented
|
|
|
size="default"
|
|
|
v-model="currentTimeType"
|