变更数据中心页面完成

This commit is contained in:
2026-09-29 14:47:10 +08:00
parent b48d89d389
commit 6f64a39586
7 changed files with 357 additions and 266 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ const { domRef, updateOptions } = useEcharts(() => ({
rate: {
align: 'left',
},
money: {
fee: {
width: 80,
align: 'right',
fontWeight: 600,
-1
View File
@@ -42,7 +42,6 @@ const homeData = ref<any>(null);
// 时间范围切换
const timeChange = (val: string) => {
console.log(val);
dateRange.value = val;
if(val === 'custom'){
from.value = null;
+259 -240
View File
@@ -1,280 +1,299 @@
<script setup lang="ts">
import { ref, computed } from 'vue';
import { ref, computed, onMounted } from 'vue';
import { useAppStore } from '@/store/modules/app';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { getColorWithOpacity } from "@/utils/common";
import { useRouterPush } from '@/hooks/common/router';
import { getColorWithOpacity, formatTimestamp, getTimeRangeStamp } from "@/utils/common";
import ColumnChart from './modules/columnChart.vue';
import RingChart from "./modules/ringChart.vue";
import RingChart2 from "./modules/ringChart2.vue";
import { changeDataCenterApi } from "@/service/api/statistics";
const appStore = useAppStore();
const { routerPushByKey } = useRouterPush();
const themeStore = useThemeStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16));
const emit = defineEmits<{ go: [v: string, preset?: string]; openChange: [c: any] }>();
const range = ref("近一年");
const rangeOptions = [
{label: "近一年", value: "近一年"},
{label: "近半年", value: "近半年"},
{label: "近三个月", value: "近三个月"},
{label: "近一个月", value: "近一个月"},
];
const DATE_RANGE_OPTIONS = [
{ label: "近一年", value: 'year' },
{ label: "近半年", value: 'half_year' },
{ label: "近三个月", value: 'quarter' },
{ label: "近一个月", value: 'month' },
]
const changeData = ref([
{
name: '一车间',
value: 42,
},
{
name: '二车间',
value: 35,
},
{
name: '三车间',
value: 28,
},
{
name: '供应部',
value: 12,
},
{
name: '质量部',
value: 9,
},
]);
const loading = ref<boolean>(false);
const homeData = ref<any>(null);
// 日期范围
const dateRange = ref<string>('year');
// 日期
const from = ref<number | null>(getTimeRangeStamp(dateRange.value)?.from ?? null);
const to = ref<number | null>(getTimeRangeStamp(dateRange.value)?.to ?? null);
const DEPT_ACCEPT = [
{ name: "工艺", value: 58 },
{ name: "设备", value: 41 },
{ name: "管理", value: 27 },
];
const ACCEPT_COLORS = ["#2080f0", "#faad14", "#52c41a"];
// 匹配日期范围
const dateLabel = computed(() => {
return DATE_RANGE_OPTIONS.find(item => item.value === dateRange.value)?.label || '';
})
const DEPT_ACCEPT2 = [
{ name: "完全达到", value: 13, money: 61.2, rate: 68 },
{ name: "基本达到", value: 4, money: 19.8, rate: 21 },
{ name: "未达到", value: 2, money: 5.4, rate: 11 },
];
const ACCEPT_COLORS2 = ["#52c41a", "#faad14", "#ff4d4f"];
// 时间范围切换
const timeChange = (val: string) => {
dateRange.value = val;
from.value = getTimeRangeStamp(val)?.from ?? null;
to.value = getTimeRangeStamp(val)?.to ?? null;
getData();
}
const residualHigh = ref<{id: string, title: string}>({id:'MOC-2026-R01-0035',title:'R-201 反应釜搅拌器电机更换(55kW→75kW 防爆电机)'});
// 导出数据
const exportData = () => {
window.$message?.info('还差接口');
}
// 跳转页面
const jumpTo = (key: string, qid: number = 0) => {
if(key==='details_index' && qid){
routerPushByKey(key,{ query: { id: qid.toString(),key:'my_datachangecenter' } });
}else{
routerPushByKey(key as any);
}
}
// 获取数据
const getData = async () => {
loading.value = true;
const {data,error} = await changeDataCenterApi({
from: formatTimestamp(from.value,'yyyy-MM-dd'),
to: formatTimestamp(to.value,'yyyy-MM-dd'),
});
if(!error){
homeData.value = data;
}
loading.value = false;
}
onMounted(() => {
getData();
})
</script>
<template>
<NSpace vertical :size="16">
<!-- 我的工作台 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex flex-wrap items-center gap-2">
<p class="text-sm font-bold text-slate-800">全厂变更数据中心</p>
<p class="text-[11px] text-slate-400 mt-[1px]">时间相关数据统一在此查询 · 统计时段</p>
<NSelect v-model:value="range" :options="rangeOptions" size="tiny" class="w-28" />
<NButton ghost size="small" class="ml-auto text-xs">导出</NButton>
</div>
</NCard>
<!-- 卡片数据 -->
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="gap" :y-gap="16"
:style="{
'--theme-color': themeStore.themeColor,
'--theme-bg': getColorWithOpacity(themeStore.themeColor, 0.2),
'--warning-bg': getColorWithOpacity(themeStore.otherColor.warning, 0.2),
'--warning-color': themeStore.otherColor.warning,
'--success-color': themeStore.otherColor.success,
'--success-bg': getColorWithOpacity(themeStore.otherColor.success, 0.2),
'--error-color': themeStore.otherColor.error,
'--error-bg': getColorWithOpacity(themeStore.otherColor.error, 0.2),
}"
>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="emit('go', 'tasks')"
>
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--theme-bg)]">
<Icon icon="akar-icons:file" class="size-24px text-[--theme-color]" />
</span>
<div class="flex flex-col">
<CountTo
:start-value="0"
:end-value="126"
class="text-26px font-600"
/>
<p class="text-[11px] font-semibold text-slate-400">变更总数 · {{ range }}</p>
</div>
<NSpace vertical :size="16">
<NSpin :show="loading" size="small">
<!-- 我的工作台 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex flex-wrap items-center gap-2">
<p class="text-sm font-bold text-slate-800">全厂变更数据中心</p>
<p class="text-[11px] text-slate-400 mt-[1px]">时间相关数据统一在此查询 · 统计时段</p>
<NSelect :value="dateRange" :options="DATE_RANGE_OPTIONS" size="tiny" class="w-28" :on-update:value="timeChange" />
<NButton ghost size="small" class="ml-auto text-xs" @click="exportData">导出</NButton>
</div>
</NGi>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="emit('go', 'tasks')"
>
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--warning-bg)]">
<Icon icon="lucide:shield-check" class="size-24px text-[--warning-color]" />
</span>
<div class="flex flex-col">
<CountTo
:start-value="0"
:end-value="18"
class="text-26px font-600"
/>
<p class="text-[11px] font-semibold text-slate-400">重要变更数量 · {{ range }} · 占比 14%</p>
</div>
</div>
</NGi>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="emit('go', 'tasks')"
>
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--warning-bg)]">
<Icon icon="lucide:shield-check" class="size-24px text-[--warning-color]" />
</span>
<div class="flex flex-col">
<div class="flex items-center">
</NCard>
<!-- 卡片数据 -->
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="gap" :y-gap="16"
:style="{
'--theme-color': themeStore.themeColor,
'--theme-bg': getColorWithOpacity(themeStore.themeColor, 0.2),
'--warning-bg': getColorWithOpacity(themeStore.otherColor.warning, 0.2),
'--warning-color': themeStore.otherColor.warning,
'--success-color': themeStore.otherColor.success,
'--success-bg': getColorWithOpacity(themeStore.otherColor.success, 0.2),
'--error-color': themeStore.otherColor.error,
'--error-bg': getColorWithOpacity(themeStore.otherColor.error, 0.2),
}"
>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--theme-bg)]">
<Icon icon="akar-icons:file" class="size-24px text-[--theme-color]" />
</span>
<div class="flex flex-col">
<CountTo
:start-value="0"
:end-value="4"
class="text-26px font-600 text-[var(--error-color)]"
:start-value="0"
:end-value="homeData?.summary?.total || 0"
class="text-26px font-600"
/>
<p class="text-26px font-600 text-[var(--error-color)]"> 高 /</p>
<p class="text-[11px] font-semibold text-slate-400">变更总数 · {{ dateLabel }}</p>
</div>
</div>
</NGi>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--warning-bg)]">
<Icon icon="lucide:shield-check" class="size-24px text-[--warning-color]" />
</span>
<div class="flex flex-col">
<CountTo
:start-value="0"
:end-value="homeData?.summary?.important_total || 0"
class="text-26px font-600"
/>
<p class="text-[11px] font-semibold text-slate-400">重要变更数量 · {{ dateLabel }} · 占比 {{homeData?.summary?.important_pct || 0}}%</p>
</div>
</div>
</NGi>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_highrisk')"
>
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--warning-bg)]">
<Icon icon="lucide:shield-check" class="size-24px text-[--warning-color]" />
</span>
<div class="flex flex-col">
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.risk?.inherent_high || 0"
class="text-26px font-600 text-[var(--error-color)]"
/>
<p class="text-26px font-600 text-[var(--error-color)]"> 高 /</p>
<CountTo
:start-value="0"
:end-value="homeData?.risk?.inherent_mid || 0"
class="text-26px font-600 text-[var(--error-color)]"
/><p class="text-26px font-600 text-[var(--error-color)]"> 中</p>
</div>
<p class="text-[11px] font-semibold text-slate-400">变更新增风险 · 剩余高风险 {{homeData?.risk?.residual_high || 0}} · 剩余中风险 {{homeData?.risk?.residual_mid || 0}}</p>
</div>
</div>
</NGi>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_effect')"
>
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--success-bg)]">
<Icon icon="material-symbols:check-circle-outline" class="size-24px text-[--success-color]" />
</span>
<div class="flex flex-col">
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="11"
class="text-26px font-600 text-[var(--error-color)]"
/><p class="text-26px font-600 text-[var(--error-color)]"> 中</p>
:end-value="homeData?.invest?.total_fee || 0"
:decimals="1"
class="text-26px font-600"
/><p class="text-26px font-600 ml-1">万元</p>
</div>
<p class="text-[11px] font-semibold text-slate-400">验收投入合计 · 已验收 {{homeData?.invest?.total_count || 0}} 起 · 完全达标率 {{homeData?.invest?.full_rate || 0}}%</p>
</div>
<p class="text-[11px] font-semibold text-slate-400">变更新增风险 · 剩余高风险 1 · 剩余中风险 6</p>
</div>
</div>
</NGi>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="emit('go', 'tasks')"
>
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--success-bg)]">
<Icon icon="material-symbols:check-circle-outline" class="size-24px text-[--success-color]" />
</span>
<div class="flex flex-col">
<CountTo
:start-value="0"
:end-value="86.4"
class="text-26px font-600"
/>
<p class="text-[11px] font-semibold text-slate-400">验收投入合计 · 已验收 19 起 · 完全达标率 68%</p>
</NGi>
</NGrid>
<NGrid
:x-gap="gap" :y-gap="16" responsive="screen" item-responsive
:style="{
'--theme-color':themeStore.themeColor
}"
>
<NGi span="24 s:24 m:24 l:14">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
<p class="text-13px font-700 text-#64749a">各车间变更数量</p>
<p class="text-11px text-#94a3b8 mt-2px">{{dateLabel}} · 按申请部门</p>
</div>
</div>
</NGi>
</NGrid>
<ColumnChart v-if="homeData" :data="homeData?.workshop_counts?.items" style="height: 100%;" />
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:10">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
<p class="text-13px font-700 text-#64749a">变更类型分布</p>
<p class="text-11px text-#94a3b8 mt-2px">{{dateLabel}} · 工艺 / 设备 / 管理</p>
</div>
<RingChart v-if="homeData" :data="homeData?.type_dist?.items" :range="dateLabel" />
</NCard>
</NGi>
</NGrid>
<NGrid
:x-gap="gap" :y-gap="16" responsive="screen" item-responsive
:style="{
'--theme-color':themeStore.themeColor
}"
>
<NGi span="24 s:24 m:24 l:14">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
<p class="text-13px font-700 text-#64749a">各车间变更数量</p>
<p class="text-11px text-#94a3b8 mt-2px">{{range}} · 按申请部门</p>
</div>
<ColumnChart :data="changeData" style="height: 100%;" />
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:10">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
<p class="text-13px font-700 text-#64749a">变更类型分布</p>
<p class="text-11px text-#94a3b8 mt-2px">{{range}} · 工艺 / 设备 / 管理</p>
</div>
<RingChart :data="DEPT_ACCEPT" :colors="ACCEPT_COLORS" :range="range" />
</NCard>
</NGi>
</NGrid>
<NGrid
:x-gap="gap" :y-gap="16" responsive="screen" item-responsive
:style="{
'--error-color':themeStore.otherColor.error,
'--warning-color':themeStore.otherColor.warning,
'--success-color':themeStore.otherColor.success,
'--theme-color':themeStore.themeColor
}"
>
<NGi span="24 s:24 m:24 l:10">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="lucide:shield-alert" class="size-16px text-[--theme-color]" />
<p class="text-13px font-700 text-#64749a">风险增量与管控</p>
<p class="text-11px text-#94a3b8 mt-2px">{{range}} · 变更新增风险与管控落实</p>
</div>
<div class="space-y-2.5">
<div class="grid grid-cols-2 gap-2">
<div class="rounded-lg bg-red-50 px-3 py-2">
<div class="text-[11px] text-[var(--error-color)]">新增固有高风险项</div>
<div class="text-xl font-bold text-[var(--error-color)]">4</div>
<NGrid
:x-gap="gap" :y-gap="16" responsive="screen" item-responsive
:style="{
'--error-color':themeStore.otherColor.error,
'--warning-color':themeStore.otherColor.warning,
'--success-color':themeStore.otherColor.success,
'--theme-color':themeStore.themeColor
}"
>
<NGi span="24 s:24 m:24 l:10">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="lucide:shield-alert" class="size-16px text-[--theme-color]" />
<p class="text-13px font-700 text-#64749a">风险增量与管控</p>
<p class="text-11px text-#94a3b8 mt-2px">{{dateLabel}} · 变更新增风险与管控落实</p>
</div>
<div class="space-y-2.5">
<div class="grid grid-cols-2 gap-2">
<div class="rounded-lg bg-red-50 px-3 py-2">
<div class="text-[11px] text-[var(--error-color)]">新增固有高风险项</div>
<div class="text-xl font-bold text-[var(--error-color)]">{{homeData?.risk?.inherent_high || 0}}</div>
</div>
<div class="rounded-lg bg-amber-50 px-3 py-2">
<div class="text-[11px] text-[var(--warning-color)]">新增中风险项</div>
<div class="text-xl font-bold text-[var(--warning-color)]">{{homeData?.risk?.inherent_mid || 0}}</div>
</div>
<div class="rounded-lg bg-slate-50 px-3 py-2">
<div class="text-[11px] text-slate-500">剩余高风险项</div>
<div class="text-xl font-bold text-slate-800">{{homeData?.risk?.residual_high || 0}}</div>
</div>
<div class="rounded-lg bg-slate-50 px-3 py-2">
<div class="text-[11px] text-slate-500">剩余中风险项</div>
<div class="text-xl font-bold text-slate-800">{{homeData?.risk?.residual_mid || 0}}</div>
</div>
</div>
<div class="rounded-lg bg-amber-50 px-3 py-2">
<div class="text-[11px] text-[var(--warning-color)]">新增中风险项</div>
<div class="text-xl font-bold text-[var(--warning-color)]">11</div>
</div>
<div class="rounded-lg bg-slate-50 px-3 py-2">
<div class="text-[11px] text-slate-500">剩余高风险项</div>
<div class="text-xl font-bold text-slate-800">1</div>
</div>
<div class="rounded-lg bg-slate-50 px-3 py-2">
<div class="text-[11px] text-slate-500">剩余中风险项</div>
<div class="text-xl font-bold text-slate-800">6</div>
<!-- <div>
<div class="flex justify-between text-[11px] text-slate-500">
<span>管控措施落实率</span>
<span class="font-semibold text-[var(--success-color)]">96%</span>
</div>
<div class="mt-1 h-2 w-full rounded-full bg-slate-100">
<div class="h-2 rounded-full bg-[var(--success-color)]" style="width: 96%" />
</div>
</div> -->
<div v-if="homeData?.risk?.high_item" @click="jumpTo('details_index', homeData?.risk?.high_item?.change_id)"
class="cursor-pointer w-full rounded-lg border border-red-100 bg-[#FFF2F0] px-3 py-2 text-left transition hover:border-red-300">
<span class="block text-[11px] font-medium text-[var(--error-color)]">剩余高风险 · 措施落实中</span>
<span class="mt-0.5 block truncate text-xs font-medium text-slate-800">{{ homeData?.risk?.high_item?.title }}</span>
<span class="mt-0.5 block text-[11px] text-slate-400">{{ homeData?.risk?.high_item?.change_no }}</span>
</div>
</div>
<div>
<div class="flex justify-between text-[11px] text-slate-500">
<span>管控措施落实率</span>
<span class="font-semibold text-[var(--success-color)]">96%</span>
</div>
<div class="mt-1 h-2 w-full rounded-full bg-slate-100">
<div class="h-2 rounded-full bg-[var(--success-color)]" style="width: 96%" />
</div>
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:14">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="material-symbols:check-circle-outline" class="size-16px text-[--theme-color]" />
<p class="text-13px font-700 text-#64749a">变更投入和效果</p>
<p class="text-11px text-#94a3b8 mt-2px">{{dateLabel}} · 支持查询验收明细</p>
</div>
<div v-if="residualHigh.id" @click="emit('openChange', residualHigh)"
class="cursor-pointer w-full rounded-lg border border-red-100 bg-[#FFF2F0] px-3 py-2 text-left transition hover:border-red-300">
<span class="block text-[11px] font-medium text-[var(--error-color)]">剩余高风险 · 措施落实中</span>
<span class="mt-0.5 block truncate text-xs font-medium text-slate-800">{{ residualHigh.title }}</span>
<span class="mt-0.5 block text-[11px] text-slate-400">{{ residualHigh.id }} · 电机吊装作业管控措施执行跟踪</span>
<RingChart2 v-if="homeData" :data="homeData?.invest?.items" :range="dateLabel" />
<div class="flex items-center justify-between border-slate-200 pt-1.5">
<span class="text-[11px] text-slate-400">投入合计(变更材料和实施费) · 完全达到率 {{homeData?.invest?.full_rate || 0}}%</span>
<span class="text-xs font-bold text-[var(--theme-color)]">{{homeData?.invest?.total_fee || 0}} 万元</span>
</div>
</div>
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:14">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="material-symbols:check-circle-outline" class="size-16px text-[--theme-color]" />
<p class="text-13px font-700 text-#64749a">变更投入和效果</p>
<p class="text-11px text-#94a3b8 mt-2px">{{range}} · 支持查询验收明细</p>
</div>
<RingChart2 :data="DEPT_ACCEPT2" :colors="ACCEPT_COLORS2" style="height: 210px;" />
<div class="flex items-center justify-between border-slate-200 pt-1.5">
<span class="text-[11px] text-slate-400">投入合计(变更材料和实施费) · 完全达到率 68%</span>
<span class="text-xs font-bold text-[var(--theme-color)]">86.4 万元</span>
</div>
<div @click="emit('go', 'invest')"
class="cursor-pointer mt-3 flex w-full items-center justify-center gap-1 rounded-lg border border-dashed py-2 text-[11px] text-slate-400 transition hover:border-[#6E93C6] hover:text-[#1D4E9C]">
验收明细(名称 / 日期链 / 费用 / 结论)见左边栏「变更效果跟踪」<ChevronRight class="h-3 w-3" />
</div>
</NCard>
</NGi>
</NGrid>
</NSpace>
<div @click="jumpTo('my_effect')"
class="cursor-pointer mt-3 flex w-full items-center justify-center gap-1 rounded-lg border border-dashed py-2 text-[11px] text-slate-400 transition hover:border-[#6E93C6] hover:text-[#1D4E9C]">
验收明细(名称 / 日期链 / 费用 / 结论)见左边栏「变更效果跟踪」<ChevronRight class="h-3 w-3" />
</div>
</NCard>
</NGi>
</NGrid>
</NSpin>
</NSpace>
</template>
<style scoped></style>
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref } from 'vue';
import { ref, watch } from 'vue';
import { useEcharts } from '@/hooks/common/echarts';
defineOptions({
@@ -8,6 +8,10 @@ defineOptions({
const props = defineProps<{ data: { name: string; value: number; }[]; }>();
watch(() => props.data, (newVal:any) => {
init();
});
const { domRef, updateOptions } = useEcharts(() => ({
tooltip: {
trigger: "axis",
@@ -30,7 +34,7 @@ const { domRef, updateOptions } = useEcharts(() => ({
},
},
grid: {
top: "5%",
top: "10%",
left: "5%",
right: "5%",
bottom: "5%",
@@ -72,9 +76,13 @@ const { domRef, updateOptions } = useEcharts(() => ({
function init() {
updateOptions(opts => {
let allMonth = props.data.map((item:any) => item.name);
let values = props.data.map((item:any) => item.value);
opts.xAxis[0].data = allMonth;
const result:any = props.data?.map((item:any) => ({
name: item.org_name,
value: item.count,
}));
let allOrg = result.map((item:any) => item.name);
let values = result.map((item:any) => item.value);
opts.xAxis[0].data = allOrg;
opts.series[0].data = values;
return opts;
});
@@ -1,13 +1,16 @@
<script setup lang="ts">
import { ref } from 'vue';
import { ref, watch } from 'vue';
import { useEcharts } from '@/hooks/common/echarts';
defineOptions({
name: 'RingChart'
});
const props = defineProps<{ data?: { name: string; value: number }[]; colors?: string[]; range?: string }>();
const range = ref("近一年");
const props = defineProps<{ data?: { name: string; value: number }[]; range?: string }>();
watch(() => props.data, (newVal:any) => {
init();
});
const chartData = ref<{ name: string; value: number; fee: number; rate: number }[]>([]);
const { domRef, updateOptions } = useEcharts(() => ({
tooltip: {
@@ -23,9 +26,28 @@ const { domRef, updateOptions } = useEcharts(() => ({
itemStyle: {
borderWidth: 0
},
textStyle: {
rich: {
name: {
padding: [0, 0, 0, 0],
},
count: {
padding: [0, 0, 0, 5],
fontWeight: 600,
},
}
},
formatter: function (name: string) {
var info = chartData.value?.find((item: { name: string }) => item.name === name);
if(info){
return '{name|'+name+'} {count|'+(info.value)+'}';
}else{
return name;
}
}
},
title: {
text: `{a|126}\n{b|${range.value}}`,
text: '',
textStyle: {
rich: {
a: {
@@ -73,9 +95,24 @@ const { domRef, updateOptions } = useEcharts(() => ({
function init() {
updateOptions(opts => {
range.value = props.range || "近一年";
opts.series[0].data = props.data || [];
opts.series[0].color = props.colors || [];
const total = props.data?.reduce((sum:number, item:any) => sum + item.count, 0);
const result:any = props.data?.map((item:any) => {
// 1. 避免除以 0 的情况
const safeTotal = total || 0;
// 2. 计算 rate,最多保留两位小数 (乘10000四舍五入再除以100)
// 如果 total 为 0,则 rate 直接为 0
const rate = safeTotal === 0 ? 0 : Math.round((item.count / safeTotal) * 10000) / 100;
return {
name: item.label,
value: item.count,
rate: rate
}
});
chartData.value = result;
opts.title.text = `{a|${total}}\n{b|${props.range}}`;
opts.series[0].data = result;
opts.series[0].color = ["#2080f0", "#faad14", "#52c41a"];
return opts;
});
}
@@ -84,7 +121,7 @@ init();
</script>
<template>
<div ref="domRef" class="w-full h-220px overflow-hidden"></div>
<div ref="domRef" class="w-full h-210px overflow-hidden"></div>
</template>
<style scoped></style>
@@ -1,12 +1,16 @@
<script setup lang="ts">
import { ref } from 'vue';
import { ref, watch } from 'vue';
import { useEcharts } from '@/hooks/common/echarts';
defineOptions({
name: 'RingChart'
});
const props = defineProps<{ data?: { name: string; value: number; money: number; rate: number }[]; colors?: string[]; range?: string }>();
const props = defineProps<{ data?: { name: string; value: number; }[]; range?: string }>();
watch(() => props.data, (newVal:any) => {
init();
});
const chartData = ref<{ name: string; value: number; fee: number; rate: number }[]>([]);
const { domRef, updateOptions } = useEcharts(() => ({
tooltip: {
@@ -33,9 +37,6 @@ const { domRef, updateOptions } = useEcharts(() => ({
align: 'right',
fontWeight: 600,
},
rate: {
align: 'left',
},
money: {
width: 80,
align: 'right',
@@ -44,16 +45,16 @@ const { domRef, updateOptions } = useEcharts(() => ({
}
},
formatter: function (name: string) {
var info = props.data?.find((item: { name: string }) => item.name === name);
var info = chartData.value?.find((item: { name: string }) => item.name === name);
if(info){
return '{name|'+name+'} {count|'+(info.value)+' 起}{rate|'+(info.rate)+' %} {money|'+(info.money)+' 万元}';
return '{name|'+name+'} {count|'+(info.value)+' 起} {money|'+(info.fee)+' 万元}';
}else{
return name;
}
}
},
title: {
text: `{a|19}\n{b|已验收}`,
text: '',
textStyle: {
rich: {
a: {
@@ -101,8 +102,24 @@ const { domRef, updateOptions } = useEcharts(() => ({
function init() {
updateOptions(opts => {
opts.series[0].data = props.data || [];
opts.series[0].color = props.colors || [];
const total = props.data?.reduce((sum:number, item:any) => sum + item.count, 0);
const result:any = props.data?.map((item:any) => {
// 1. 避免除以 0 的情况
const safeTotal = total || 0;
// 2. 计算 rate,最多保留两位小数 (乘10000四舍五入再除以100)
// 如果 total 为 0,则 rate 直接为 0
const rate = safeTotal === 0 ? 0 : Math.round((item.count / safeTotal) * 10000) / 100;
return {
name: item.label,
value: item.count,
fee: item.fee,
rate: rate
}
});
chartData.value = result;
opts.title.text = `{a|${total}}\n{b|已验收}`;
opts.series[0].data = result;
opts.series[0].color = ["#52c41a", "#faad14", "#ff4d4f"];
return opts;
});
}
@@ -111,7 +128,7 @@ init();
</script>
<template>
<div ref="domRef" class="w-full h-220px overflow-hidden"></div>
<div ref="domRef" class="w-full h-210px overflow-hidden"></div>
</template>
<style scoped></style>