|
@@ -1,13 +1,322 @@
|
|
|
-<script lang="ts" setup></script>
|
|
|
|
|
|
|
+<script lang="ts" setup>
|
|
|
|
|
+import * as echarts from 'echarts'
|
|
|
|
|
+import {
|
|
|
|
|
+ ANIMATION,
|
|
|
|
|
+ ChartData,
|
|
|
|
|
+ createLegend,
|
|
|
|
|
+ createTooltip,
|
|
|
|
|
+ FONT_FAMILY,
|
|
|
|
|
+ formatSeriesName,
|
|
|
|
|
+ THEME
|
|
|
|
|
+} from '@/utils/kb'
|
|
|
|
|
+import { IotStatApi } from '@/api/pms/stat'
|
|
|
|
|
+
|
|
|
|
|
+type ActivePanel = 'zj' | 'xj'
|
|
|
|
|
+
|
|
|
|
|
+const activePanel = ref<ActivePanel>('zj')
|
|
|
|
|
+const chartData = ref<ChartData>({
|
|
|
|
|
+ xAxis: [],
|
|
|
|
|
+ series: []
|
|
|
|
|
+})
|
|
|
|
|
+const chartRef = ref<HTMLDivElement>()
|
|
|
|
|
+
|
|
|
|
|
+let chart: echarts.ECharts | null = null
|
|
|
|
|
+
|
|
|
|
|
+const panelOptions: Array<{ label: string; value: ActivePanel }> = [
|
|
|
|
|
+ {
|
|
|
|
|
+ label: '钻井',
|
|
|
|
|
+ value: 'zj'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: '修井',
|
|
|
|
|
+ value: 'xj'
|
|
|
|
|
+ }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+const activeTitle = computed(() =>
|
|
|
|
|
+ activePanel.value === 'zj' ? '钻井历史工作量' : '修井历史工作量'
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+function getBarStyle(color: (typeof THEME.color)[keyof typeof THEME.color]) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ borderRadius: [12, 12, 0, 0],
|
|
|
|
|
+ shadowBlur: 12,
|
|
|
|
|
+ shadowColor: color.bg,
|
|
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
+ { offset: 0, color: color.light },
|
|
|
|
|
+ { offset: 0.55, color: color.mid },
|
|
|
|
|
+ { offset: 1, color: color.line }
|
|
|
|
|
+ ])
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function formatValue(value: number | string) {
|
|
|
|
|
+ return Number(value || 0).toLocaleString('en-US')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isFootageSeries(name: string) {
|
|
|
|
|
+ const displayName = formatSeriesName(name)
|
|
|
|
|
+
|
|
|
|
|
+ return name.includes('footage') || displayName.includes('进尺')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isFinishedWellsSeries(name: string) {
|
|
|
|
|
+ const displayName = formatSeriesName(name)
|
|
|
|
|
+
|
|
|
|
|
+ return name.includes('finishedWells') || displayName.includes('完井')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function hasMixedUnits(data: ChartData) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ data.series.some((item) => isFootageSeries(item.name)) &&
|
|
|
|
|
+ data.series.some((item) => isFinishedWellsSeries(item.name))
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getYAxisOption(data: ChartData): echarts.EChartsOption['yAxis'] {
|
|
|
|
|
+ const baseAxis = {
|
|
|
|
|
+ type: 'value' as const,
|
|
|
|
|
+ splitNumber: 4,
|
|
|
|
|
+ axisLine: {
|
|
|
|
|
+ show: false
|
|
|
|
|
+ },
|
|
|
|
|
+ axisTick: {
|
|
|
|
|
+ show: false
|
|
|
|
|
+ },
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ color: THEME.text.regular,
|
|
|
|
|
+ fontSize: 12,
|
|
|
|
|
+ fontFamily: FONT_FAMILY,
|
|
|
|
|
+ formatter(value: number) {
|
|
|
|
|
+ return formatValue(value)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ splitLine: {
|
|
|
|
|
+ lineStyle: {
|
|
|
|
|
+ color: THEME.split,
|
|
|
|
|
+ type: 'dashed' as const
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!hasMixedUnits(data)) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...baseAxis,
|
|
|
|
|
+ name: data.series.some((item) => isFootageSeries(item.name))
|
|
|
|
|
+ ? '累计进尺(m)'
|
|
|
|
|
+ : '累计完井数(口)',
|
|
|
|
|
+ nameTextStyle: {
|
|
|
|
|
+ color: THEME.text.regular,
|
|
|
|
|
+ fontSize: 13,
|
|
|
|
|
+ fontFamily: FONT_FAMILY,
|
|
|
|
|
+ align: 'left'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ {
|
|
|
|
|
+ ...baseAxis,
|
|
|
|
|
+ name: '累计进尺(m)',
|
|
|
|
|
+ position: 'left',
|
|
|
|
|
+ nameTextStyle: {
|
|
|
|
|
+ color: THEME.text.regular,
|
|
|
|
|
+ fontSize: 13,
|
|
|
|
|
+ fontFamily: FONT_FAMILY,
|
|
|
|
|
+ align: 'left'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ ...baseAxis,
|
|
|
|
|
+ name: '累计完井数(口)',
|
|
|
|
|
+ position: 'right',
|
|
|
|
|
+ nameTextStyle: {
|
|
|
|
|
+ color: THEME.text.regular,
|
|
|
|
|
+ fontSize: 13,
|
|
|
|
|
+ fontFamily: FONT_FAMILY,
|
|
|
|
|
+ align: 'right'
|
|
|
|
|
+ },
|
|
|
|
|
+ splitLine: {
|
|
|
|
|
+ show: false
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getChartOption(data: ChartData): echarts.EChartsOption {
|
|
|
|
|
+ const xAxisData = data.xAxis || []
|
|
|
|
|
+ const seriesData = data.series || []
|
|
|
|
|
+ const colorList = [THEME.color.orange, THEME.color.blue, THEME.color.green]
|
|
|
|
|
+ const mixedUnits = hasMixedUnits(data)
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...ANIMATION,
|
|
|
|
|
+ grid: {
|
|
|
|
|
+ ...THEME.grid,
|
|
|
|
|
+ top: 50,
|
|
|
|
|
+ right: mixedUnits ? 38 : THEME.grid.right,
|
|
|
|
|
+ bottom: 10
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: createTooltip({
|
|
|
|
|
+ trigger: 'axis',
|
|
|
|
|
+ axisPointer: {
|
|
|
|
|
+ type: 'shadow',
|
|
|
|
|
+ shadowStyle: {
|
|
|
|
|
+ color: THEME.split
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ valueFormatter(value: number | string) {
|
|
|
|
|
+ return formatValue(value)
|
|
|
|
|
+ }
|
|
|
|
|
+ }),
|
|
|
|
|
+ legend: createLegend(
|
|
|
|
|
+ {
|
|
|
|
|
+ top: 4,
|
|
|
|
|
+ itemWidth: 12,
|
|
|
|
|
+ itemHeight: 12
|
|
|
|
|
+ },
|
|
|
|
|
+ seriesData.map((item) => formatSeriesName(item.name))
|
|
|
|
|
+ ),
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: xAxisData,
|
|
|
|
|
+ axisLine: {
|
|
|
|
|
+ show: false
|
|
|
|
|
+ },
|
|
|
|
|
+ axisTick: {
|
|
|
|
|
+ show: false
|
|
|
|
|
+ },
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ interval: 0,
|
|
|
|
|
+ color: THEME.text.regular,
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ fontFamily: FONT_FAMILY
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: getYAxisOption(data),
|
|
|
|
|
+ series: seriesData.map((item, index) => {
|
|
|
|
|
+ const color = colorList[index % colorList.length]
|
|
|
|
|
+ const useRightAxis = mixedUnits && isFinishedWellsSeries(item.name)
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ name: formatSeriesName(item.name),
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ yAxisIndex: useRightAxis ? 1 : 0,
|
|
|
|
|
+ data: item.data || [],
|
|
|
|
|
+ barWidth: 24,
|
|
|
|
|
+ barMaxWidth: 32,
|
|
|
|
|
+ showBackground: true,
|
|
|
|
|
+ backgroundStyle: {
|
|
|
|
|
+ color: THEME.split,
|
|
|
|
|
+ borderRadius: [12, 12, 0, 0]
|
|
|
|
|
+ },
|
|
|
|
|
+ itemStyle: getBarStyle(color),
|
|
|
|
|
+ label: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ position: 'top',
|
|
|
|
|
+ distance: 10,
|
|
|
|
|
+ color: color.strong,
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ fontWeight: 700,
|
|
|
|
|
+ fontFamily: FONT_FAMILY,
|
|
|
|
|
+ formatter(params: any) {
|
|
|
|
|
+ return Number(params.value) ? formatValue(params.value) : ''
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ emphasis: {
|
|
|
|
|
+ focus: 'series',
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ ...getBarStyle(color),
|
|
|
|
|
+ shadowColor: color.shadow,
|
|
|
|
|
+ shadowBlur: 18
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function initChart() {
|
|
|
|
|
+ if (!chartRef.value) return
|
|
|
|
|
+ chart?.dispose()
|
|
|
|
|
+ chart = echarts.init(chartRef.value, undefined, {
|
|
|
|
|
+ renderer: 'svg'
|
|
|
|
|
+ })
|
|
|
|
|
+ renderChart()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function renderChart() {
|
|
|
|
|
+ if (!chart) return
|
|
|
|
|
+ chart.setOption(getChartOption(chartData.value), true)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resizeChart() {
|
|
|
|
|
+ chart?.resize()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function destroyChart() {
|
|
|
|
|
+ chart?.dispose()
|
|
|
|
|
+ chart = null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function loadChart() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res =
|
|
|
|
|
+ activePanel.value === 'zj'
|
|
|
|
|
+ ? await IotStatApi.getRyHiZjDailyReports()
|
|
|
|
|
+ : await IotStatApi.getRyHiXjDailyReports()
|
|
|
|
|
+
|
|
|
|
|
+ chartData.value = {
|
|
|
|
|
+ xAxis: (res?.xAxis || []).map((item) => `${item}`),
|
|
|
|
|
+ series: (res?.series || []).map((item) => ({
|
|
|
|
|
+ name: item.name,
|
|
|
|
|
+ data: item.data || []
|
|
|
|
|
+ }))
|
|
|
|
|
+ }
|
|
|
|
|
+ renderChart()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`获取${activeTitle.value}失败:`, error)
|
|
|
|
|
+ chartData.value = {
|
|
|
|
|
+ xAxis: [],
|
|
|
|
|
+ series: []
|
|
|
|
|
+ }
|
|
|
|
|
+ renderChart()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+watch(activePanel, () => {
|
|
|
|
|
+ loadChart()
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ initChart()
|
|
|
|
|
+ loadChart()
|
|
|
|
|
+ window.addEventListener('resize', resizeChart)
|
|
|
|
|
+ window.addEventListener('rykb:resize', resizeChart)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+onUnmounted(() => {
|
|
|
|
|
+ window.removeEventListener('resize', resizeChart)
|
|
|
|
|
+ window.removeEventListener('rykb:resize', resizeChart)
|
|
|
|
|
+ destroyChart()
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
|
|
|
<template>
|
|
<template>
|
|
|
<div class="panel flex flex-col">
|
|
<div class="panel flex flex-col">
|
|
|
- <div class="panel-title">
|
|
|
|
|
- <div class="icon-decorator">
|
|
|
|
|
- <span></span>
|
|
|
|
|
- <span></span>
|
|
|
|
|
|
|
+ <div class="panel-title flex items-center justify-between">
|
|
|
|
|
+ <div class="kb-panel-title-text flex items-center">
|
|
|
|
|
+ <div class="icon-decorator">
|
|
|
|
|
+ <span></span>
|
|
|
|
|
+ <span></span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {{ activeTitle }}
|
|
|
</div>
|
|
</div>
|
|
|
- 历史工作量数据
|
|
|
|
|
|
|
+ <el-segmented
|
|
|
|
|
+ v-model="activePanel"
|
|
|
|
|
+ :options="panelOptions"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ class="historical-workload-switch" />
|
|
|
</div>
|
|
</div>
|
|
|
<div ref="chartRef" class="flex-1 min-h-0"></div>
|
|
<div ref="chartRef" class="flex-1 min-h-0"></div>
|
|
|
</div>
|
|
</div>
|
|
@@ -15,4 +324,24 @@
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
<style lang="scss" scoped>
|
|
|
@import url('@/styles/kb.scss');
|
|
@import url('@/styles/kb.scss');
|
|
|
|
|
+
|
|
|
|
|
+.historical-workload-switch {
|
|
|
|
|
+ --el-segmented-item-selected-color: #03409b;
|
|
|
|
|
+ --el-segmented-item-selected-bg-color: rgb(255 255 255 / 86%);
|
|
|
|
|
+ --el-segmented-bg-color: rgb(31 91 184 / 10%);
|
|
|
|
|
+ --el-segmented-item-hover-bg-color: rgb(255 255 255 / 56%);
|
|
|
|
|
+
|
|
|
|
|
+ min-height: calc(26px * var(--kb-scale, 1));
|
|
|
|
|
+ padding: calc(2px * var(--kb-scale, 1));
|
|
|
|
|
+ border: 1px solid rgb(31 91 184 / 12%);
|
|
|
|
|
+ transform: translateY(calc(-2px * var(--kb-scale, 1)));
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-segmented__item) {
|
|
|
|
|
+ min-height: calc(22px * var(--kb-scale, 1));
|
|
|
|
|
+ padding: 0 calc(8px * var(--kb-scale, 1));
|
|
|
|
|
+ font-size: calc(13px * var(--kb-scale, 1));
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #29527f;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|