From 5ebdf612b8291106370bba5e79996b28a7452e67 Mon Sep 17 00:00:00 2001
From: wangrong <wangrong@shsening.com>
Date: 星期一, 12 十月 2026 00:04:33 +0800
Subject: [PATCH] 首页图表做下钻
---
src/modules/report/views/screen/overview.vue | 37 +-
src/modules/report/views/screen/carbon.vue | 35 +-
src/modules/report/views/screen/resource.vue | 79 +----
src/modules/report/views/dailysite.vue | 20
src/modules/report/views/dailydepartment.vue | 21
src/modules/report/data/screen-structure.ts | 1
src/modules/report/components/screen-drill-chart.vue | 426 ++++++++++++++++++++++++++++++++
src/modules/report/data/screen.ts | 2
src/modules/report/data/screen-chart.ts | 2
src/modules/report/views/screen/map.vue | 123 ++------
10 files changed, 537 insertions(+), 209 deletions(-)
diff --git a/src/modules/report/components/screen-drill-chart.vue b/src/modules/report/components/screen-drill-chart.vue
new file mode 100644
index 0000000..574e685
--- /dev/null
+++ b/src/modules/report/components/screen-drill-chart.vue
@@ -0,0 +1,426 @@
+<template>
+ <div class="screen-drill" v-loading="loading">
+ <div v-if="level > 0" class="screen-drill__nav">
+ <el-button text type="primary" size="small" @click="goBack">杩斿洖涓婁竴绾�</el-button>
+ <span class="screen-drill__crumb">{{ crumb }}</span>
+ </div>
+ <v-chart
+ :option="option"
+ :update-options="{ notMerge: true }"
+ autoresize
+ class="screen-drill__chart"
+ :class="{ 'is-drilled': level > 0 }"
+ @click="onChartClick"
+ />
+ </div>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, watch } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useCool } from '/@/cool';
+import { chartSeries, pieTooltipFormatter, toPieData } from '../data/screen-chart';
+import { ITEM_COLORS } from '../data/screen-structure';
+import { useChartTheme, useScreenData } from '../hooks/use-screen';
+
+type Metric = 'weight' | 'value' | 'count' | 'carbon';
+type ChartType = 'pie' | 'bar';
+
+type SourceItem = {
+ name: string;
+ code?: string;
+ color?: string;
+ weight?: number;
+ value?: number;
+ count?: number;
+ carbon?: number;
+ [key: string]: any;
+};
+
+type DrillItem = {
+ code?: string;
+ name: string;
+ value: number;
+ color?: string;
+ even?: boolean;
+ id?: number;
+ percent?: number;
+};
+
+const props = withDefaults(
+ defineProps<{
+ type: ChartType;
+ source: SourceItem[];
+ metric: Metric;
+ unit?: string;
+ legend?: boolean;
+ center?: [string, string];
+ radius?: [string, string];
+ label?: string;
+ nameSuffix?: string;
+ evenSplit?: boolean;
+ }>(),
+ {
+ unit: '',
+ legend: false,
+ center: () => ['50%', '50%'],
+ radius: () => ['38%', '62%'],
+ label: '{b}\n{d}%',
+ nameSuffix: '',
+ evenSplit: undefined
+ }
+);
+
+const { service } = useCool();
+const { timeRange, loading: screenLoading } = useScreenData();
+const { textColor, mutedColor, splitColor, primary, tooltip } = useChartTheme();
+
+const loading = ref(false);
+const level = ref(0);
+const categoryCode = ref('');
+const categoryName = ref('');
+const categoryColor = ref('');
+const departmentId = ref<number | null>(null);
+const departmentName = ref('');
+const drillItems = ref<DrillItem[]>([]);
+const deptCache = ref<DrillItem[]>([]);
+
+const isCarbon = computed(() => props.metric === 'carbon');
+const evenSplit = computed(() =>
+ props.evenSplit != null ? props.evenSplit : props.type === 'pie'
+);
+
+const rootItems = computed<DrillItem[]>(() =>
+ chartSeries(
+ props.source || [],
+ e => Number(e?.[props.metric] || 0),
+ { carbon: isCarbon.value, evenSplit: evenSplit.value }
+ ).map(e => ({
+ code: e.item.code,
+ name: e.item.name + (props.nameSuffix || ''),
+ value: e.value,
+ color: e.item.color,
+ even: e.even
+ }))
+);
+
+const rows = computed(() => (level.value === 0 ? rootItems.value : drillItems.value));
+
+const crumb = computed(() =>
+ [categoryName.value, level.value >= 2 ? departmentName.value : '']
+ .filter(Boolean)
+ .join(' 路 ')
+);
+
+function drillColor(i: number) {
+ if (i === 0 && categoryColor.value) return categoryColor.value;
+ return ITEM_COLORS[i % ITEM_COLORS.length];
+}
+
+const option = computed(() => {
+ const list = rows.value;
+ if (props.type === 'pie') {
+ return {
+ tooltip: {
+ trigger: 'item',
+ ...tooltip(),
+ formatter: pieTooltipFormatter(props.unit)
+ },
+ legend: props.legend
+ ? { bottom: 0, textStyle: { color: mutedColor.value } }
+ : undefined,
+ series: [
+ {
+ type: 'pie',
+ radius: props.radius,
+ center: props.center,
+ padAngle: 3,
+ cursor: level.value >= 2 ? 'default' : 'pointer',
+ itemStyle: { borderRadius: 6 },
+ label: {
+ color: textColor.value,
+ formatter:
+ level.value > 0
+ ? `{b}\n{c}${props.unit ? ' ' + props.unit : ''}\n{d}%`
+ : props.label
+ },
+ data: toPieData(
+ list.map(e => ({
+ item: { ...e, color: e.color || primary.value },
+ value: e.value,
+ even: !!e.even
+ })),
+ e => e.name,
+ e => e.color
+ )
+ }
+ ]
+ };
+ }
+
+ const barRows = [...list].reverse();
+ const total = barRows.reduce((s, e) => s + (e.even ? 0 : e.value), 0);
+ return {
+ grid: { containLabel: true, left: 8, right: level.value > 0 ? 80 : 24, top: 12, bottom: 8 },
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: { type: 'shadow' },
+ ...tooltip(),
+ formatter: (params: any) => {
+ const p = Array.isArray(params) ? params[0] : params;
+ const row = barRows[p?.dataIndex];
+ if (!row) return '';
+ const val = row.even ? 0 : row.value;
+ const u = props.unit ? ` ${props.unit}` : '';
+ const pct =
+ level.value > 0 && total
+ ? ` (${((val / total) * 100).toFixed(1)}%)`
+ : '';
+ return `${row.name}<br/>${Number(val || 0).toLocaleString()}${u}${pct}`;
+ }
+ },
+ xAxis: {
+ type: 'value',
+ name: props.unit,
+ nameTextStyle: { color: mutedColor.value },
+ splitLine: { lineStyle: { color: splitColor.value, type: 'dashed' } },
+ axisLabel: { color: mutedColor.value },
+ axisLine: { show: false },
+ axisTick: { show: false }
+ },
+ yAxis: {
+ type: 'category',
+ data: barRows.map(e => e.name),
+ axisLabel: { color: textColor.value },
+ axisLine: { show: false },
+ axisTick: { show: false }
+ },
+ series: [
+ {
+ type: 'bar',
+ barWidth: 14,
+ cursor: level.value >= 2 ? 'default' : 'pointer',
+ label:
+ level.value > 0
+ ? {
+ show: true,
+ position: 'right',
+ color: textColor.value,
+ fontSize: 11,
+ formatter: (p: any) => {
+ const val = Number(p.value || 0);
+ const pct = total ? ((val / total) * 100).toFixed(1) : '0.0';
+ const u = props.unit ? ` ${props.unit}` : '';
+ return `${val.toLocaleString()}${u} ${pct}%`;
+ }
+ }
+ : { show: false },
+ data: barRows.map(e => ({
+ value: e.value,
+ name: e.name,
+ code: e.code,
+ id: e.id,
+ even: e.even,
+ itemStyle: {
+ borderRadius: [0, 8, 8, 0],
+ color: e.color || primary.value
+ }
+ }))
+ }
+ ]
+ };
+});
+
+function reset() {
+ level.value = 0;
+ categoryCode.value = '';
+ categoryName.value = '';
+ categoryColor.value = '';
+ departmentId.value = null;
+ departmentName.value = '';
+ drillItems.value = [];
+ deptCache.value = [];
+}
+
+async function requestDrill(deptId?: number) {
+ const payload = {
+ range: timeRange.value,
+ code: categoryCode.value,
+ metric: props.metric,
+ departmentId: deptId
+ };
+ const screen = service.report?.screen;
+ return screen?.drill
+ ? await screen.drill(payload)
+ : await service.request({
+ url: 'admin/report/screen/drill',
+ method: 'POST',
+ data: payload
+ });
+}
+
+function paint(items: { id: number; name: string; value: number; percent?: number }[]) {
+ return (items || []).map((e, i) => ({
+ id: e.id,
+ name: e.name,
+ value: Number(e.value) || 0,
+ percent: e.percent,
+ color: drillColor(i),
+ even: false,
+ code: categoryCode.value
+ }));
+}
+
+async function enterDepartment(item: DrillItem) {
+ if (!item.code) {
+ ElMessage.warning('璇ョ被娌℃湁鍙笅閽荤殑缂栫爜');
+ return;
+ }
+ loading.value = true;
+ try {
+ categoryCode.value = item.code;
+ categoryName.value = item.name;
+ categoryColor.value = item.color || '';
+ const res = await requestDrill();
+ const items = paint(res?.items || []);
+ if (!items.length) {
+ ElMessage.info('璇ュ垎绫绘殏鏃犻儴闂ㄦ暟鎹�');
+ return;
+ }
+ drillItems.value = items;
+ deptCache.value = items;
+ departmentId.value = null;
+ departmentName.value = '';
+ level.value = 1;
+ } catch (e: any) {
+ ElMessage.error(e?.message || '涓嬮捇澶辫触');
+ } finally {
+ loading.value = false;
+ }
+}
+
+async function enterSite(item: DrillItem) {
+ if (item.id == null) return;
+ loading.value = true;
+ try {
+ const res = await requestDrill(item.id);
+ const items = paint(res?.items || []);
+ if (!items.length) {
+ ElMessage.info('璇ラ儴闂ㄦ殏鏃犵珯鐐规暟鎹�');
+ return;
+ }
+ departmentId.value = item.id;
+ departmentName.value = res?.departmentName || item.name;
+ drillItems.value = items;
+ level.value = 2;
+ } catch (e: any) {
+ ElMessage.error(e?.message || '涓嬮捇澶辫触');
+ } finally {
+ loading.value = false;
+ }
+}
+
+async function reloadLevel() {
+ if (level.value === 1) {
+ loading.value = true;
+ try {
+ const res = await requestDrill();
+ const items = paint(res?.items || []);
+ drillItems.value = items;
+ deptCache.value = items;
+ } finally {
+ loading.value = false;
+ }
+ } else if (level.value === 2 && departmentId.value != null) {
+ loading.value = true;
+ try {
+ const res = await requestDrill(departmentId.value);
+ drillItems.value = paint(res?.items || []);
+ } finally {
+ loading.value = false;
+ }
+ }
+}
+
+function onChartClick(params: any) {
+ if (params?.componentType !== 'series') return;
+ if (loading.value || screenLoading.value) return;
+ if (level.value >= 2) return;
+ const data = params.data || {};
+ if (data.even) return;
+ if (level.value === 0) {
+ const hit =
+ rootItems.value.find(e => e.code && e.code === data.code) ||
+ rootItems.value.find(e => e.name === (data.name || params.name));
+ if (!hit || hit.even) return;
+ enterDepartment(hit);
+ return;
+ }
+ const hit =
+ drillItems.value.find(e => e.id != null && e.id === data.id) ||
+ drillItems.value.find(e => e.name === (data.name || params.name));
+ if (!hit) return;
+ enterSite(hit);
+}
+
+function goBack() {
+ if (level.value >= 2) {
+ level.value = 1;
+ departmentId.value = null;
+ departmentName.value = '';
+ if (deptCache.value.length) {
+ drillItems.value = deptCache.value;
+ return;
+ }
+ reloadLevel();
+ return;
+ }
+ reset();
+}
+
+watch(timeRange, () => reset());
+watch(
+ () => props.metric,
+ () => {
+ if (level.value > 0) reloadLevel();
+ }
+);
+watch(screenLoading, (now, prev) => {
+ if (prev && !now && level.value > 0) reloadLevel();
+});
+</script>
+
+<style lang="scss" scoped>
+.screen-drill {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
+ min-height: inherit;
+
+ &__nav {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex-shrink: 0;
+ padding: 0 8px 4px;
+ min-height: 28px;
+ }
+
+ &__crumb {
+ font-size: 12px;
+ color: var(--el-text-color-secondary);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ &__chart {
+ flex: 1;
+ min-height: 0;
+ width: 100%;
+ height: 100%;
+ }
+}
+</style>
diff --git a/src/modules/report/data/screen-chart.ts b/src/modules/report/data/screen-chart.ts
index 6cf1685..8083f4e 100644
--- a/src/modules/report/data/screen-chart.ts
+++ b/src/modules/report/data/screen-chart.ts
@@ -50,6 +50,8 @@
name: name(row.item),
value: row.value,
even: row.even,
+ code: (row.item as any)?.code,
+ id: (row.item as any)?.id,
itemStyle: { color: color(row.item) }
}));
}
diff --git a/src/modules/report/data/screen-structure.ts b/src/modules/report/data/screen-structure.ts
index c35f682..9d37e2b 100644
--- a/src/modules/report/data/screen-structure.ts
+++ b/src/modules/report/data/screen-structure.ts
@@ -120,6 +120,7 @@
})),
recycleItems: RECYCLE_NAMES.map((name, i) => ({
name,
+ code: `SW62${String(i + 1).padStart(3, '0')}`,
weight: 0,
value: 0,
carbon: 0,
diff --git a/src/modules/report/data/screen.ts b/src/modules/report/data/screen.ts
index 6c6bde9..dbb90d9 100644
--- a/src/modules/report/data/screen.ts
+++ b/src/modules/report/data/screen.ts
@@ -35,7 +35,7 @@
axis: string[];
kpis: Record<string, number>;
wasteMix: { name: string; code?: string; weight: number; value: number; count: number; color: string }[];
- recycleItems: { name: string; weight: number; value: number; carbon: number; color: string }[];
+ recycleItems: { name: string; code?: string; weight: number; value: number; carbon: number; color: string }[];
trend: { weight: number[]; value: number[]; carbon: number[] };
yearCarbon: number[];
deptRank: any[];
diff --git a/src/modules/report/views/dailydepartment.vue b/src/modules/report/views/dailydepartment.vue
index e42de03..9cf0c0e 100644
--- a/src/modules/report/views/dailydepartment.vue
+++ b/src/modules/report/views/dailydepartment.vue
@@ -461,25 +461,28 @@
sortable: "custom",
component: { name: "cl-date-text", props: { format: "YYYY-MM-DD" } }
},
- { label: t('閮ㄩ棬鍚嶇О'), prop: "departmentName", minWidth: 140 },
+ { label: t('閮ㄩ棬鍚嶇О'), prop: "departmentName", minWidth: 140, sortable: "custom" },
{ label: t('鎬婚噸閲�'), prop: "weightTotal", minWidth: 140, sortable: "custom" },
- { label: t('sw60閲嶉噺'), prop: "weightSw60", minWidth: 140, sortable: "custom" },
+ { label: t('sw64閲嶉噺'), prop: "weightSw64", minWidth: 140, sortable: "custom" },
{ label: t('sw61閲嶉噺'), prop: "weightSw61", minWidth: 140, sortable: "custom" },
{ label: t('sw62閲嶉噺'), prop: "weightSw62", minWidth: 140, sortable: "custom" },
+ { label: t('sw60閲嶉噺'), prop: "weightSw60", minWidth: 140, sortable: "custom" },
{ label: t('sw63閲嶉噺'), prop: "weightSw63", minWidth: 140, sortable: "custom" },
- { label: t('sw64閲嶉噺'), prop: "weightSw64", minWidth: 140, sortable: "custom" },
+
{ label: t('鎬荤О閲嶆鏁�'), prop: "timeTotal", minWidth: 140, sortable: "custom" },
- { label: t('sw60娆℃暟'), prop: "timeSw60", minWidth: 140, sortable: "custom" },
- { label: t('sw61娆℃暟'), prop: "timeSw61", minWidth: 140, sortable: "custom" },
- { label: t('sw62娆℃暟'), prop: "timeSw62", minWidth: 140, sortable: "custom" },
- { label: t('sw63娆℃暟'), prop: "timeSw63", minWidth: 140, sortable: "custom" },
{ label: t('sw64娆℃暟'), prop: "timeSw64", minWidth: 140, sortable: "custom" },
+ { label: t('sw61娆℃暟'), prop: "timeSw61", minWidth: 140, sortable: "custom" },
+ { label: t('sw62娆℃暟'), prop: "timeSw62", minWidth: 140, sortable: "custom" },
+ { label: t('sw60娆℃暟'), prop: "timeSw60", minWidth: 140, sortable: "custom" },
+ { label: t('sw63娆℃暟'), prop: "timeSw63", minWidth: 140, sortable: "custom" },
+
{ label: t('鎬荤⒊鎺掗噺'), prop: "carbonTotal", minWidth: 140, sortable: "custom" },
- { label: t('sw60纰虫帓閲�'), prop: "carbonSw60", minWidth: 140, sortable: "custom" },
+ { label: t('sw64纰虫帓閲�'), prop: "carbonSw64", minWidth: 140, sortable: "custom" },
{ label: t('sw61纰虫帓閲�'), prop: "carbonSw61", minWidth: 140, sortable: "custom" },
{ label: t('sw62纰虫帓閲�'), prop: "carbonSw62", minWidth: 140, sortable: "custom" },
+ { label: t('sw60纰虫帓閲�'), prop: "carbonSw60", minWidth: 140, sortable: "custom" },
{ label: t('sw63纰虫帓閲�'), prop: "carbonSw63", minWidth: 140, sortable: "custom" },
- { label: t('sw64纰虫帓閲�'), prop: "carbonSw64", minWidth: 140, sortable: "custom" },
+
{
label: t('sw62浼扮畻閿�鍞'),
prop: "estimatedSalesSw62",
diff --git a/src/modules/report/views/dailysite.vue b/src/modules/report/views/dailysite.vue
index 6073e61..3579180 100644
--- a/src/modules/report/views/dailysite.vue
+++ b/src/modules/report/views/dailysite.vue
@@ -464,26 +464,26 @@
sortable: "custom",
component: { name: "cl-date-text", props: { format: "YYYY-MM-DD" } }
},
- { label: t('绔欑偣鍚嶇О'), prop: "siteName", minWidth: 140 },
- { label: t('鎵�灞炲崟浣�'), prop: "departmentName", minWidth: 140 },
+ { label: t('绔欑偣鍚嶇О'), prop: "siteName", minWidth: 140, sortable: "custom" },
+ { label: t('鎵�灞炲崟浣�'), prop: "departmentName", minWidth: 140, sortable: "custom" },
{ label: t('鎬婚噸閲�'), prop: "weightTotal", minWidth: 140, sortable: "custom" },
- { label: t('sw60閲嶉噺'), prop: "weightSw60", minWidth: 140, sortable: "custom" },
+ { label: t('sw64閲嶉噺'), prop: "weightSw64", minWidth: 140, sortable: "custom" },
{ label: t('sw61閲嶉噺'), prop: "weightSw61", minWidth: 140, sortable: "custom" },
{ label: t('sw62閲嶉噺'), prop: "weightSw62", minWidth: 140, sortable: "custom" },
+ { label: t('sw60閲嶉噺'), prop: "weightSw60", minWidth: 140, sortable: "custom" },
{ label: t('sw63閲嶉噺'), prop: "weightSw63", minWidth: 140, sortable: "custom" },
- { label: t('sw64閲嶉噺'), prop: "weightSw64", minWidth: 140, sortable: "custom" },
{ label: t('鎬荤О閲嶆鏁�'), prop: "timeTotal", minWidth: 140, sortable: "custom" },
- { label: t('sw60娆℃暟'), prop: "timeSw60", minWidth: 140, sortable: "custom" },
- { label: t('sw61娆℃暟'), prop: "timeSw61", minWidth: 140, sortable: "custom" },
- { label: t('sw62娆℃暟'), prop: "timeSw62", minWidth: 140, sortable: "custom" },
- { label: t('sw63娆℃暟'), prop: "timeSw63", minWidth: 140, sortable: "custom" },
{ label: t('sw64娆℃暟'), prop: "timeSw64", minWidth: 140, sortable: "custom" },
+ { label: t('sw61娆℃暟'), prop: "timeSw61", minWidth: 140, sortable: "custom" },
+ { label: t('sw62娆℃暟'), prop: "timeSw62", minWidth: 140, sortable: "custom" },
+ { label: t('sw60娆℃暟'), prop: "timeSw60", minWidth: 140, sortable: "custom" },
+ { label: t('sw63娆℃暟'), prop: "timeSw63", minWidth: 140, sortable: "custom" },
{ label: t('鎬荤⒊鎺掗噺'), prop: "carbonTotal", minWidth: 140, sortable: "custom" },
- { label: t('sw60纰虫帓閲�'), prop: "carbonSw60", minWidth: 140, sortable: "custom" },
+ { label: t('sw64纰虫帓閲�'), prop: "carbonSw64", minWidth: 140, sortable: "custom" },
{ label: t('sw61纰虫帓閲�'), prop: "carbonSw61", minWidth: 140, sortable: "custom" },
{ label: t('sw62纰虫帓閲�'), prop: "carbonSw62", minWidth: 140, sortable: "custom" },
+ { label: t('sw60纰虫帓閲�'), prop: "carbonSw60", minWidth: 140, sortable: "custom" },
{ label: t('sw63纰虫帓閲�'), prop: "carbonSw63", minWidth: 140, sortable: "custom" },
- { label: t('sw64纰虫帓閲�'), prop: "carbonSw64", minWidth: 140, sortable: "custom" },
{
label: t('sw62浼扮畻閿�鍞'),
prop: "estimatedSalesSw62",
diff --git a/src/modules/report/views/screen/carbon.vue b/src/modules/report/views/screen/carbon.vue
index b58c3ec..72d8b00 100644
--- a/src/modules/report/views/screen/carbon.vue
+++ b/src/modules/report/views/screen/carbon.vue
@@ -12,7 +12,17 @@
</el-col>
<el-col :lg="10" :xs="24">
<panel-card title="纰冲噺鎺掓潵婧�" sub="鎸夊彲鍥炴敹瀛愮被鎷嗗垎" :demo="isDemoSection('recycleItems')">
- <v-chart :option="sourceOption" autoresize class="chart" />
+ <screen-drill-chart
+ class="chart"
+ type="pie"
+ :source="data.recycleItems"
+ metric="carbon"
+ unit="kgCO鈧俥"
+ name-suffix="鍥炴敹"
+ legend
+ :center="['50%', '46%']"
+ :radius="['36%', '60%']"
+ />
</panel-card>
</el-col>
</el-row>
@@ -46,12 +56,12 @@
import KpiCard from '../../components/kpi-card.vue';
import PanelCard from '../../components/panel-card.vue';
import { TIME_RANGE_OPTIONS } from '../../data/screen';
-import { chartSeries, pieTooltipFormatter, toPieData } from '../../data/screen-chart';
+import ScreenDrillChart from '../../components/screen-drill-chart.vue';
import { useChartTheme, useScreenData, useScreenFieldSource } from '../../hooks/use-screen';
const { data, timeRange } = useScreenData();
const { isDemoSection } = useScreenFieldSource();
-const { textColor, mutedColor, splitColor, primary, baseGrid, axisStyle, tooltip } = useChartTheme();
+const { mutedColor, splitColor, primary, baseGrid, axisStyle, tooltip } = useChartTheme();
const rangeLabel = computed(
() => TIME_RANGE_OPTIONS.find(e => e.value === timeRange.value)?.label || '鏈尯闂�'
@@ -127,25 +137,6 @@
]
}));
-const sourceOption = computed(() => ({
- tooltip: { trigger: 'item', ...tooltip(), formatter: pieTooltipFormatter('kgCO鈧俥') },
- legend: { bottom: 0, textStyle: { color: mutedColor.value } },
- series: [
- {
- type: 'pie',
- radius: ['36%', '60%'],
- center: ['50%', '46%'],
- padAngle: 3,
- itemStyle: { borderRadius: 6 },
- label: { color: textColor.value, formatter: '{b}\n{d}%' },
- data: toPieData(
- chartSeries(data.value.recycleItems, e => e.carbon, { carbon: true }),
- e => e.name + '鍥炴敹',
- e => e.color
- )
- }
- ]
-}));
</script>
<style lang="scss" scoped>
diff --git a/src/modules/report/views/screen/map.vue b/src/modules/report/views/screen/map.vue
index 0582eda..b626f2f 100755
--- a/src/modules/report/views/screen/map.vue
+++ b/src/modules/report/views/screen/map.vue
@@ -17,7 +17,15 @@
<template #extra>
<cl-select-button v-model="mixMetric" :options="mixOptions" small />
</template>
- <v-chart :option="mixOption" autoresize class="chart" />
+ <screen-drill-chart
+ class="chart"
+ type="pie"
+ :source="data.wasteMix"
+ :metric="mixMetric"
+ :unit="mixUnit"
+ :center="['50%', '50%']"
+ :radius="['38%', '62%']"
+ />
</panel-card>
<panel-card
@@ -25,7 +33,16 @@
:demo="isDemoSection('recycleItems')"
class="dash-card dash-card--flex"
>
- <v-chart :option="carbonOption" autoresize class="chart" />
+ <screen-drill-chart
+ class="chart"
+ type="pie"
+ :source="data.recycleItems"
+ metric="carbon"
+ unit="kgCO鈧俥"
+ name-suffix="鍥炴敹"
+ :center="['50%', '50%']"
+ :radius="['36%', '60%']"
+ />
</panel-card>
</div>
@@ -57,7 +74,14 @@
<template #extra>
<cl-select-button v-model="itemMetric" :options="itemOptions" small />
</template>
- <v-chart :option="itemOption" autoresize class="chart chart--items" />
+ <screen-drill-chart
+ class="chart chart--items"
+ type="bar"
+ :source="data.recycleItems"
+ :metric="itemMetric"
+ :unit="itemUnit"
+ :even-split="false"
+ />
</panel-card>
</div>
</div>
@@ -85,12 +109,12 @@
import ScreenMapPanel from '../../components/screen-map-panel.vue';
import CameraMonitor from '../../../monitor/views/cameras.vue';
import { TIME_RANGE_OPTIONS } from '../../data/screen';
-import { chartSeries, pieTooltipFormatter, toPieData } from '../../data/screen-chart';
+import ScreenDrillChart from '../../components/screen-drill-chart.vue';
import { useChartTheme, useScreenData, useScreenFieldSource } from '../../hooks/use-screen';
const { data, timeRange } = useScreenData();
const { isDemoSection } = useScreenFieldSource();
-const { textColor, mutedColor, splitColor, primary, baseGrid, axisStyle, tooltip } = useChartTheme();
+const { mutedColor, splitColor, primary, baseGrid, axisStyle, tooltip } = useChartTheme();
const mixMetric = ref<'weight' | 'value' | 'count'>('weight');
const trendMetric = ref<'weight' | 'value' | 'carbon'>('weight');
@@ -141,45 +165,11 @@
return 'kg';
});
-/** 鍨冨溇鍒嗙被缁撴瀯锛堥ゼ锛� */
-const mixOption = computed(() => ({
- tooltip: { trigger: 'item', ...tooltip(), formatter: pieTooltipFormatter() },
- series: [
- {
- type: 'pie',
- radius: ['38%', '62%'],
- center: ['50%', '50%'],
- padAngle: 3,
- itemStyle: { borderRadius: 6 },
- label: { color: textColor.value, formatter: '{b}\n{d}%' },
- data: toPieData(
- chartSeries(data.value.wasteMix, e => e[mixMetric.value]),
- e => e.name,
- e => e.color
- )
- }
- ]
-}));
-
-/** 纰冲噺鎺掓潵婧愶紙楗硷級锛氱⒊鍊煎彲涓鸿礋锛屾寜缁濆鍊煎睍绀哄噺鎺掗噺 */
-const carbonOption = computed(() => ({
- tooltip: { trigger: 'item', ...tooltip(), formatter: pieTooltipFormatter('kgCO鈧俥') },
- series: [
- {
- type: 'pie',
- radius: ['36%', '60%'],
- center: ['50%', '50%'],
- padAngle: 3,
- itemStyle: { borderRadius: 6 },
- label: { color: textColor.value, formatter: '{b}\n{d}%' },
- data: toPieData(
- chartSeries(data.value.recycleItems, e => e.carbon, { carbon: true }),
- e => e.name + '鍥炴敹',
- e => e.color
- )
- }
- ]
-}));
+const mixUnit = computed(() => {
+ if (mixMetric.value === 'value') return '鍏�';
+ if (mixMetric.value === 'count') return '娆�';
+ return 'kg';
+});
/** 鍥炴敹瓒嬪娍锛堟姌绾匡級 */
const trendOption = computed(() => ({
@@ -207,51 +197,6 @@
]
}));
-/** 鍚勭被鍥炴敹閲忥紙妯悜鏉″舰锛� */
-const itemOption = computed(() => {
- const carbon = itemMetric.value === 'carbon';
- const rows = chartSeries(
- data.value.recycleItems,
- e => Number(e[itemMetric.value] || 0),
- { carbon, evenSplit: false }
- ).reverse();
- return {
- grid: { containLabel: true, left: 8, right: 24, top: 12, bottom: 8 },
- tooltip: {
- trigger: 'axis',
- axisPointer: { type: 'shadow' },
- valueFormatter: (v: number) => `${Number(v || 0).toLocaleString()} ${itemUnit.value}`,
- ...tooltip()
- },
- xAxis: {
- type: 'value',
- name: itemUnit.value,
- nameTextStyle: { color: mutedColor.value },
- splitLine: { lineStyle: { color: splitColor.value, type: 'dashed' } },
- axisLabel: { color: mutedColor.value },
- axisLine: { show: false },
- axisTick: { show: false }
- },
- yAxis: {
- type: 'category',
- data: rows.map(e => e.item.name),
- axisLabel: { color: textColor.value },
- axisLine: { show: false },
- axisTick: { show: false }
- },
- series: [
- {
- type: 'bar',
- barWidth: 14,
- data: rows.map(e => e.value),
- itemStyle: {
- borderRadius: [0, 8, 8, 0],
- color: (params: any) => rows[params.dataIndex]?.item.color || primary.value
- }
- }
- ]
- };
-});
</script>
<style lang="scss" scoped>
diff --git a/src/modules/report/views/screen/overview.vue b/src/modules/report/views/screen/overview.vue
index 2c12d09..0051a15 100644
--- a/src/modules/report/views/screen/overview.vue
+++ b/src/modules/report/views/screen/overview.vue
@@ -10,7 +10,16 @@
<template #extra>
<cl-select-button v-model="mixMetric" :options="mixOptions" small />
</template>
- <v-chart :option="mixOption" autoresize class="chart" />
+ <screen-drill-chart
+ class="chart"
+ type="pie"
+ :source="data.wasteMix"
+ :metric="mixMetric"
+ :unit="mixUnit"
+ legend
+ :center="['50%', '46%']"
+ :radius="['38%', '62%']"
+ />
</panel-card>
</el-col>
<el-col :lg="10" :xs="24">
@@ -52,7 +61,7 @@
import PanelCard from '../../components/panel-card.vue';
import ScreenCameraPreview from '../../components/screen-camera-preview.vue';
import { TIME_RANGE_OPTIONS } from '../../data/screen';
-import { chartSeries, pieTooltipFormatter, toPieData } from '../../data/screen-chart';
+import ScreenDrillChart from '../../components/screen-drill-chart.vue';
import { useChartTheme, useScreenData, useScreenFieldSource } from '../../hooks/use-screen';
const { data, timeRange } = useScreenData();
@@ -88,25 +97,11 @@
{ field: 'kpis.totalParticipants', label: '绱鍙備笌浜烘暟', value: data.value.kpis.totalParticipants, suffix: '浜�', fixed: 0, hint: '鍘婚噸鏍″洯鐢ㄦ埛' }
]);
-const mixOption = computed(() => ({
- tooltip: { trigger: 'item', ...tooltip(), formatter: pieTooltipFormatter() },
- legend: { bottom: 0, textStyle: { color: mutedColor.value } },
- series: [
- {
- type: 'pie',
- radius: ['38%', '62%'],
- center: ['50%', '46%'],
- padAngle: 3,
- itemStyle: { borderRadius: 6 },
- label: { color: textColor.value, formatter: '{b}\n{d}%' },
- data: toPieData(
- chartSeries(data.value.wasteMix, e => e[mixMetric.value]),
- e => e.name,
- e => e.color
- )
- }
- ]
-}));
+const mixUnit = computed(() => {
+ if (mixMetric.value === 'value') return '鍏�';
+ if (mixMetric.value === 'count') return '娆�';
+ return 'kg';
+});
const trendHint = computed(() => {
const map = { today: '杩� 7 鏃�', week: '鎸夋棩', month: '鎸夋棩', quarter: '鎸夋棩', year: '鎸夋湀' };
diff --git a/src/modules/report/views/screen/resource.vue b/src/modules/report/views/screen/resource.vue
index cc65457..8e0c854 100644
--- a/src/modules/report/views/screen/resource.vue
+++ b/src/modules/report/views/screen/resource.vue
@@ -7,12 +7,29 @@
<el-row :gutter="10" class="mid-row">
<el-col :lg="10" :xs="24">
<panel-card title="鍙洖鏀剁墿鍝佺被鍗犳瘮" sub="鍚屼竴鏃堕棿鑼冨洿锛岀湅缁撴瀯" :demo="isDemoSection('recycleItems')">
- <v-chart :option="pieOption" autoresize class="chart" />
+ <screen-drill-chart
+ class="chart"
+ type="pie"
+ :source="data.recycleItems"
+ metric="weight"
+ unit="kg"
+ legend
+ :center="['50%', '46%']"
+ :radius="['36%', '60%']"
+ label="{b} {d}%"
+ />
</panel-card>
</el-col>
<el-col :lg="14" :xs="24">
<panel-card title="鍚勭被鍥炴敹閲�" sub="妯悜鏌辩姸鍥炬洿閫傚悎闀垮搧绫诲悕" :demo="isDemoSection('recycleItems')">
- <v-chart :option="weightOption" autoresize class="chart" />
+ <screen-drill-chart
+ class="chart"
+ type="bar"
+ :source="data.recycleItems"
+ metric="weight"
+ unit="kg"
+ :even-split="false"
+ />
</panel-card>
</el-col>
</el-row>
@@ -35,12 +52,13 @@
import { computed } from 'vue';
import KpiCard from '../../components/kpi-card.vue';
import PanelCard from '../../components/panel-card.vue';
-import { chartSeries, pieTooltipFormatter, toPieData } from '../../data/screen-chart';
+import ScreenDrillChart from '../../components/screen-drill-chart.vue';
+import { chartSeries } from '../../data/screen-chart';
import { useChartTheme, useScreenData, useScreenFieldSource } from '../../hooks/use-screen';
const { data } = useScreenData();
const { isDemoSection } = useScreenFieldSource();
-const { textColor, mutedColor, splitColor, primary, tooltip } = useChartTheme();
+const { mutedColor, splitColor, primary, tooltip } = useChartTheme();
const kpis = computed(() => [
{ field: 'kpis.periodWeightTon', label: '鎬婚噺', value: data.value.kpis.periodWeightTon, suffix: '鍚�', fixed: 2, hint: '鍏ㄩ儴鍒嗙被閲嶉噺鍚堣' },
@@ -50,59 +68,6 @@
{ field: 'kpis.fabricTon', label: '搴熸棫缁囩墿', value: data.value.kpis.fabricTon, suffix: '鍚�', fixed: 2, hint: '鍙洖鏀跺瓙绫�' }
]);
-const pieOption = computed(() => ({
- tooltip: { trigger: 'item', ...tooltip(), formatter: pieTooltipFormatter('kg') },
- legend: { bottom: 0, textStyle: { color: mutedColor.value } },
- series: [
- {
- type: 'pie',
- radius: ['36%', '60%'],
- center: ['50%', '46%'],
- padAngle: 3,
- itemStyle: { borderRadius: 6 },
- label: { color: textColor.value, formatter: '{b} {d}%' },
- data: toPieData(
- chartSeries(data.value.recycleItems, e => e.weight),
- e => e.name,
- e => e.color
- )
- }
- ]
-}));
-
-const weightRows = computed(() =>
- chartSeries(data.value.recycleItems, e => e.weight, { evenSplit: false }).reverse()
-);
-
-const weightOption = computed(() => ({
- grid: { containLabel: true, left: 8, right: 28, top: 12, bottom: 8 },
- tooltip: { trigger: 'axis', ...tooltip() },
- xAxis: {
- type: 'value',
- name: 'kg',
- splitLine: { lineStyle: { color: splitColor.value, type: 'dashed' } },
- axisLabel: { color: mutedColor.value },
- axisLine: { show: false },
- axisTick: { show: false }
- },
- yAxis: {
- type: 'category',
- data: weightRows.value.map(e => e.item.name),
- axisLabel: { color: textColor.value },
- axisLine: { show: false },
- axisTick: { show: false }
- },
- series: [
- {
- type: 'bar',
- barWidth: 16,
- data: weightRows.value.map(e => ({
- value: e.value,
- itemStyle: { color: e.item.color, borderRadius: [0, 8, 8, 0] }
- }))
- }
- ]
-}));
const valueRows = computed(() =>
chartSeries(data.value.recycleItems, e => e.value, { evenSplit: false })
--
Gitblit v1.9.1