变更数据中心页面完成
This commit is contained in:
@@ -38,6 +38,17 @@ export function homeLeaderApi() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 变更数据中心
|
||||||
|
export function changeDataCenterApi(params: {from: string | null, to: string | null}) {
|
||||||
|
return request({
|
||||||
|
url: `/api/reports/data-center`,
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 本部门变更统计
|
// 本部门变更统计
|
||||||
export function departmentStatisticsApi(params: {from: string | null, to: string | null}) {
|
export function departmentStatisticsApi(params: {from: string | null, to: string | null}) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
rate: {
|
rate: {
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
money: {
|
fee: {
|
||||||
width: 80,
|
width: 80,
|
||||||
align: 'right',
|
align: 'right',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ const homeData = ref<any>(null);
|
|||||||
|
|
||||||
// 时间范围切换
|
// 时间范围切换
|
||||||
const timeChange = (val: string) => {
|
const timeChange = (val: string) => {
|
||||||
console.log(val);
|
|
||||||
dateRange.value = val;
|
dateRange.value = val;
|
||||||
if(val === 'custom'){
|
if(val === 'custom'){
|
||||||
from.value = null;
|
from.value = null;
|
||||||
|
|||||||
@@ -1,78 +1,93 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
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 ColumnChart from './modules/columnChart.vue';
|
||||||
import RingChart from "./modules/ringChart.vue";
|
import RingChart from "./modules/ringChart.vue";
|
||||||
import RingChart2 from "./modules/ringChart2.vue";
|
import RingChart2 from "./modules/ringChart2.vue";
|
||||||
|
import { changeDataCenterApi } from "@/service/api/statistics";
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
const { routerPushByKey } = useRouterPush();
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
||||||
|
|
||||||
const emit = defineEmits<{ go: [v: string, preset?: string]; openChange: [c: any] }>();
|
const emit = defineEmits<{ go: [v: string, preset?: string]; openChange: [c: any] }>();
|
||||||
|
|
||||||
const range = ref("近一年");
|
const DATE_RANGE_OPTIONS = [
|
||||||
const rangeOptions = [
|
{ label: "近一年", value: 'year' },
|
||||||
{label: "近一年", value: "近一年"},
|
{ label: "近半年", value: 'half_year' },
|
||||||
{label: "近半年", value: "近半年"},
|
{ label: "近三个月", value: 'quarter' },
|
||||||
{label: "近三个月", value: "近三个月"},
|
{ label: "近一个月", value: 'month' },
|
||||||
{label: "近一个月", value: "近一个月"},
|
]
|
||||||
];
|
|
||||||
|
|
||||||
const changeData = ref([
|
const loading = ref<boolean>(false);
|
||||||
{
|
const homeData = ref<any>(null);
|
||||||
name: '一车间',
|
// 日期范围
|
||||||
value: 42,
|
const dateRange = ref<string>('year');
|
||||||
},
|
// 日期
|
||||||
{
|
const from = ref<number | null>(getTimeRangeStamp(dateRange.value)?.from ?? null);
|
||||||
name: '二车间',
|
const to = ref<number | null>(getTimeRangeStamp(dateRange.value)?.to ?? null);
|
||||||
value: 35,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '三车间',
|
|
||||||
value: 28,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '供应部',
|
|
||||||
value: 12,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '质量部',
|
|
||||||
value: 9,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const DEPT_ACCEPT = [
|
// 匹配日期范围
|
||||||
{ name: "工艺", value: 58 },
|
const dateLabel = computed(() => {
|
||||||
{ name: "设备", value: 41 },
|
return DATE_RANGE_OPTIONS.find(item => item.value === dateRange.value)?.label || '';
|
||||||
{ name: "管理", value: 27 },
|
})
|
||||||
];
|
|
||||||
const ACCEPT_COLORS = ["#2080f0", "#faad14", "#52c41a"];
|
|
||||||
|
|
||||||
const DEPT_ACCEPT2 = [
|
// 时间范围切换
|
||||||
{ name: "完全达到", value: 13, money: 61.2, rate: 68 },
|
const timeChange = (val: string) => {
|
||||||
{ name: "基本达到", value: 4, money: 19.8, rate: 21 },
|
dateRange.value = val;
|
||||||
{ name: "未达到", value: 2, money: 5.4, rate: 11 },
|
from.value = getTimeRangeStamp(val)?.from ?? null;
|
||||||
];
|
to.value = getTimeRangeStamp(val)?.to ?? null;
|
||||||
const ACCEPT_COLORS2 = ["#52c41a", "#faad14", "#ff4d4f"];
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace vertical :size="16">
|
<NSpace vertical :size="16">
|
||||||
|
<NSpin :show="loading" size="small">
|
||||||
<!-- 我的工作台 -->
|
<!-- 我的工作台 -->
|
||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<p class="text-sm font-bold text-slate-800">全厂变更数据中心</p>
|
<p class="text-sm font-bold text-slate-800">全厂变更数据中心</p>
|
||||||
<p class="text-[11px] text-slate-400 mt-[1px]">时间相关数据统一在此查询 · 统计时段</p>
|
<p class="text-[11px] text-slate-400 mt-[1px]">时间相关数据统一在此查询 · 统计时段</p>
|
||||||
<NSelect v-model:value="range" :options="rangeOptions" size="tiny" class="w-28" />
|
<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">导出</NButton>
|
<NButton ghost size="small" class="ml-auto text-xs" @click="exportData">导出</NButton>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
<!-- 卡片数据 -->
|
<!-- 卡片数据 -->
|
||||||
@@ -92,7 +107,7 @@ const residualHigh = ref<{id: string, title: string}>({id:'MOC-2026-R01-0035',ti
|
|||||||
<div
|
<div
|
||||||
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
|
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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="emit('go', 'tasks')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--theme-bg)]">
|
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--theme-bg)]">
|
||||||
<Icon icon="akar-icons:file" class="size-24px text-[--theme-color]" />
|
<Icon icon="akar-icons:file" class="size-24px text-[--theme-color]" />
|
||||||
@@ -100,10 +115,10 @@ const residualHigh = ref<{id: string, title: string}>({id:'MOC-2026-R01-0035',ti
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="126"
|
:end-value="homeData?.summary?.total || 0"
|
||||||
class="text-26px font-600"
|
class="text-26px font-600"
|
||||||
/>
|
/>
|
||||||
<p class="text-[11px] font-semibold text-slate-400">变更总数 · {{ range }}</p>
|
<p class="text-[11px] font-semibold text-slate-400">变更总数 · {{ dateLabel }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -111,7 +126,7 @@ const residualHigh = ref<{id: string, title: string}>({id:'MOC-2026-R01-0035',ti
|
|||||||
<div
|
<div
|
||||||
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
|
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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="emit('go', 'tasks')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--warning-bg)]">
|
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--warning-bg)]">
|
||||||
<Icon icon="lucide:shield-check" class="size-24px text-[--warning-color]" />
|
<Icon icon="lucide:shield-check" class="size-24px text-[--warning-color]" />
|
||||||
@@ -119,10 +134,10 @@ const residualHigh = ref<{id: string, title: string}>({id:'MOC-2026-R01-0035',ti
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="18"
|
:end-value="homeData?.summary?.important_total || 0"
|
||||||
class="text-26px font-600"
|
class="text-26px font-600"
|
||||||
/>
|
/>
|
||||||
<p class="text-[11px] font-semibold text-slate-400">重要变更数量 · {{ range }} · 占比 14%</p>
|
<p class="text-[11px] font-semibold text-slate-400">重要变更数量 · {{ dateLabel }} · 占比 {{homeData?.summary?.important_pct || 0}}%</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -130,7 +145,7 @@ const residualHigh = ref<{id: string, title: string}>({id:'MOC-2026-R01-0035',ti
|
|||||||
<div
|
<div
|
||||||
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
|
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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="emit('go', 'tasks')"
|
@click="jumpTo('my_highrisk')"
|
||||||
>
|
>
|
||||||
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--warning-bg)]">
|
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--warning-bg)]">
|
||||||
<Icon icon="lucide:shield-check" class="size-24px text-[--warning-color]" />
|
<Icon icon="lucide:shield-check" class="size-24px text-[--warning-color]" />
|
||||||
@@ -139,17 +154,17 @@ const residualHigh = ref<{id: string, title: string}>({id:'MOC-2026-R01-0035',ti
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="4"
|
:end-value="homeData?.risk?.inherent_high || 0"
|
||||||
class="text-26px font-600 text-[var(--error-color)]"
|
class="text-26px font-600 text-[var(--error-color)]"
|
||||||
/>
|
/>
|
||||||
<p class="text-26px font-600 text-[var(--error-color)]"> 高 /</p>
|
<p class="text-26px font-600 text-[var(--error-color)]"> 高 /</p>
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="11"
|
:end-value="homeData?.risk?.inherent_mid || 0"
|
||||||
class="text-26px font-600 text-[var(--error-color)]"
|
class="text-26px font-600 text-[var(--error-color)]"
|
||||||
/><p class="text-26px font-600 text-[var(--error-color)]"> 中</p>
|
/><p class="text-26px font-600 text-[var(--error-color)]"> 中</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-[11px] font-semibold text-slate-400">变更新增风险 · 剩余高风险 1 · 剩余中风险 6</p>
|
<p class="text-[11px] font-semibold text-slate-400">变更新增风险 · 剩余高风险 {{homeData?.risk?.residual_high || 0}} · 剩余中风险 {{homeData?.risk?.residual_mid || 0}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -157,18 +172,21 @@ const residualHigh = ref<{id: string, title: string}>({id:'MOC-2026-R01-0035',ti
|
|||||||
<div
|
<div
|
||||||
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
|
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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="emit('go', 'tasks')"
|
@click="jumpTo('my_effect')"
|
||||||
>
|
>
|
||||||
<span class="shrink-0 rounded-lg p-2.5 bg-[var(--success-bg)]">
|
<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]" />
|
<Icon icon="material-symbols:check-circle-outline" class="size-24px text-[--success-color]" />
|
||||||
</span>
|
</span>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
<div class="flex items-center">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="86.4"
|
:end-value="homeData?.invest?.total_fee || 0"
|
||||||
|
:decimals="1"
|
||||||
class="text-26px font-600"
|
class="text-26px font-600"
|
||||||
/>
|
/><p class="text-26px font-600 ml-1">万元</p>
|
||||||
<p class="text-[11px] font-semibold text-slate-400">验收投入合计 · 已验收 19 起 · 完全达标率 68%</p>
|
</div>
|
||||||
|
<p class="text-[11px] font-semibold text-slate-400">验收投入合计 · 已验收 {{homeData?.invest?.total_count || 0}} 起 · 完全达标率 {{homeData?.invest?.full_rate || 0}}%</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -185,9 +203,9 @@ const residualHigh = ref<{id: string, title: string}>({id:'MOC-2026-R01-0035',ti
|
|||||||
<div class="flex items-center mb-3 gap-2">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
|
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
|
||||||
<p class="text-13px font-700 text-#64749a">各车间变更数量</p>
|
<p class="text-13px font-700 text-#64749a">各车间变更数量</p>
|
||||||
<p class="text-11px text-#94a3b8 mt-2px">{{range}} · 按申请部门</p>
|
<p class="text-11px text-#94a3b8 mt-2px">{{dateLabel}} · 按申请部门</p>
|
||||||
</div>
|
</div>
|
||||||
<ColumnChart :data="changeData" style="height: 100%;" />
|
<ColumnChart v-if="homeData" :data="homeData?.workshop_counts?.items" style="height: 100%;" />
|
||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:10">
|
<NGi span="24 s:24 m:24 l:10">
|
||||||
@@ -195,9 +213,9 @@ const residualHigh = ref<{id: string, title: string}>({id:'MOC-2026-R01-0035',ti
|
|||||||
<div class="flex items-center mb-3 gap-2">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
|
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
|
||||||
<p class="text-13px font-700 text-#64749a">变更类型分布</p>
|
<p class="text-13px font-700 text-#64749a">变更类型分布</p>
|
||||||
<p class="text-11px text-#94a3b8 mt-2px">{{range}} · 工艺 / 设备 / 管理</p>
|
<p class="text-11px text-#94a3b8 mt-2px">{{dateLabel}} · 工艺 / 设备 / 管理</p>
|
||||||
</div>
|
</div>
|
||||||
<RingChart :data="DEPT_ACCEPT" :colors="ACCEPT_COLORS" :range="range" />
|
<RingChart v-if="homeData" :data="homeData?.type_dist?.items" :range="dateLabel" />
|
||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
@@ -216,28 +234,28 @@ const residualHigh = ref<{id: string, title: string}>({id:'MOC-2026-R01-0035',ti
|
|||||||
<div class="flex items-center mb-3 gap-2">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="lucide:shield-alert" class="size-16px text-[--theme-color]" />
|
<Icon icon="lucide:shield-alert" class="size-16px text-[--theme-color]" />
|
||||||
<p class="text-13px font-700 text-#64749a">风险增量与管控</p>
|
<p class="text-13px font-700 text-#64749a">风险增量与管控</p>
|
||||||
<p class="text-11px text-#94a3b8 mt-2px">{{range}} · 变更新增风险与管控落实</p>
|
<p class="text-11px text-#94a3b8 mt-2px">{{dateLabel}} · 变更新增风险与管控落实</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2.5">
|
<div class="space-y-2.5">
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<div class="rounded-lg bg-red-50 px-3 py-2">
|
<div class="rounded-lg bg-red-50 px-3 py-2">
|
||||||
<div class="text-[11px] text-[var(--error-color)]">新增固有高风险项</div>
|
<div class="text-[11px] text-[var(--error-color)]">新增固有高风险项</div>
|
||||||
<div class="text-xl font-bold text-[var(--error-color)]">4</div>
|
<div class="text-xl font-bold text-[var(--error-color)]">{{homeData?.risk?.inherent_high || 0}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg bg-amber-50 px-3 py-2">
|
<div class="rounded-lg bg-amber-50 px-3 py-2">
|
||||||
<div class="text-[11px] text-[var(--warning-color)]">新增中风险项</div>
|
<div class="text-[11px] text-[var(--warning-color)]">新增中风险项</div>
|
||||||
<div class="text-xl font-bold text-[var(--warning-color)]">11</div>
|
<div class="text-xl font-bold text-[var(--warning-color)]">{{homeData?.risk?.inherent_mid || 0}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg bg-slate-50 px-3 py-2">
|
<div class="rounded-lg bg-slate-50 px-3 py-2">
|
||||||
<div class="text-[11px] text-slate-500">剩余高风险项</div>
|
<div class="text-[11px] text-slate-500">剩余高风险项</div>
|
||||||
<div class="text-xl font-bold text-slate-800">1</div>
|
<div class="text-xl font-bold text-slate-800">{{homeData?.risk?.residual_high || 0}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg bg-slate-50 px-3 py-2">
|
<div class="rounded-lg bg-slate-50 px-3 py-2">
|
||||||
<div class="text-[11px] text-slate-500">剩余中风险项</div>
|
<div class="text-[11px] text-slate-500">剩余中风险项</div>
|
||||||
<div class="text-xl font-bold text-slate-800">6</div>
|
<div class="text-xl font-bold text-slate-800">{{homeData?.risk?.residual_mid || 0}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<!-- <div>
|
||||||
<div class="flex justify-between text-[11px] text-slate-500">
|
<div class="flex justify-between text-[11px] text-slate-500">
|
||||||
<span>管控措施落实率</span>
|
<span>管控措施落实率</span>
|
||||||
<span class="font-semibold text-[var(--success-color)]">96%</span>
|
<span class="font-semibold text-[var(--success-color)]">96%</span>
|
||||||
@@ -245,12 +263,12 @@ const residualHigh = ref<{id: string, title: string}>({id:'MOC-2026-R01-0035',ti
|
|||||||
<div class="mt-1 h-2 w-full rounded-full bg-slate-100">
|
<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 class="h-2 rounded-full bg-[var(--success-color)]" style="width: 96%" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
<div v-if="residualHigh.id" @click="emit('openChange', residualHigh)"
|
<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">
|
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="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 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">{{ residualHigh.id }} · 电机吊装作业管控措施执行跟踪</span>
|
<span class="mt-0.5 block text-[11px] text-slate-400">{{ homeData?.risk?.high_item?.change_no }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
@@ -260,21 +278,22 @@ const residualHigh = ref<{id: string, title: string}>({id:'MOC-2026-R01-0035',ti
|
|||||||
<div class="flex items-center mb-3 gap-2">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="material-symbols:check-circle-outline" class="size-16px text-[--theme-color]" />
|
<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-13px font-700 text-#64749a">变更投入和效果</p>
|
||||||
<p class="text-11px text-#94a3b8 mt-2px">{{range}} · 支持查询验收明细</p>
|
<p class="text-11px text-#94a3b8 mt-2px">{{dateLabel}} · 支持查询验收明细</p>
|
||||||
</div>
|
</div>
|
||||||
<RingChart2 :data="DEPT_ACCEPT2" :colors="ACCEPT_COLORS2" style="height: 210px;" />
|
<RingChart2 v-if="homeData" :data="homeData?.invest?.items" :range="dateLabel" />
|
||||||
<div class="flex items-center justify-between border-slate-200 pt-1.5">
|
<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-[11px] text-slate-400">投入合计(变更材料和实施费) · 完全达到率 {{homeData?.invest?.full_rate || 0}}%</span>
|
||||||
<span class="text-xs font-bold text-[var(--theme-color)]">86.4 万元</span>
|
<span class="text-xs font-bold text-[var(--theme-color)]">{{homeData?.invest?.total_fee || 0}} 万元</span>
|
||||||
</div>
|
</div>
|
||||||
<div @click="emit('go', 'invest')"
|
<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]">
|
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" />
|
验收明细(名称 / 日期链 / 费用 / 结论)见左边栏「变更效果跟踪」<ChevronRight class="h-3 w-3" />
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
</NSpace>
|
</NSpin>
|
||||||
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useEcharts } from '@/hooks/common/echarts';
|
import { useEcharts } from '@/hooks/common/echarts';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -8,6 +8,10 @@ defineOptions({
|
|||||||
|
|
||||||
const props = defineProps<{ data: { name: string; value: number; }[]; }>();
|
const props = defineProps<{ data: { name: string; value: number; }[]; }>();
|
||||||
|
|
||||||
|
watch(() => props.data, (newVal:any) => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
const { domRef, updateOptions } = useEcharts(() => ({
|
const { domRef, updateOptions } = useEcharts(() => ({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "axis",
|
trigger: "axis",
|
||||||
@@ -30,7 +34,7 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
top: "5%",
|
top: "10%",
|
||||||
left: "5%",
|
left: "5%",
|
||||||
right: "5%",
|
right: "5%",
|
||||||
bottom: "5%",
|
bottom: "5%",
|
||||||
@@ -72,9 +76,13 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
updateOptions(opts => {
|
updateOptions(opts => {
|
||||||
let allMonth = props.data.map((item:any) => item.name);
|
const result:any = props.data?.map((item:any) => ({
|
||||||
let values = props.data.map((item:any) => item.value);
|
name: item.org_name,
|
||||||
opts.xAxis[0].data = allMonth;
|
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;
|
opts.series[0].data = values;
|
||||||
return opts;
|
return opts;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useEcharts } from '@/hooks/common/echarts';
|
import { useEcharts } from '@/hooks/common/echarts';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'RingChart'
|
name: 'RingChart'
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<{ data?: { name: string; value: number }[]; colors?: string[]; range?: string }>();
|
const props = defineProps<{ data?: { name: string; value: number }[]; range?: string }>();
|
||||||
const range = ref("近一年");
|
watch(() => props.data, (newVal:any) => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
const chartData = ref<{ name: string; value: number; fee: number; rate: number }[]>([]);
|
||||||
|
|
||||||
const { domRef, updateOptions } = useEcharts(() => ({
|
const { domRef, updateOptions } = useEcharts(() => ({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@@ -23,9 +26,28 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderWidth: 0
|
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: {
|
title: {
|
||||||
text: `{a|126}\n{b|${range.value}}`,
|
text: '',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
rich: {
|
rich: {
|
||||||
a: {
|
a: {
|
||||||
@@ -73,9 +95,24 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
updateOptions(opts => {
|
updateOptions(opts => {
|
||||||
range.value = props.range || "近一年";
|
const total = props.data?.reduce((sum:number, item:any) => sum + item.count, 0);
|
||||||
opts.series[0].data = props.data || [];
|
const result:any = props.data?.map((item:any) => {
|
||||||
opts.series[0].color = props.colors || [];
|
// 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;
|
return opts;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -84,7 +121,7 @@ init();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="domRef" class="w-full h-220px overflow-hidden"></div>
|
<div ref="domRef" class="w-full h-210px overflow-hidden"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useEcharts } from '@/hooks/common/echarts';
|
import { useEcharts } from '@/hooks/common/echarts';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'RingChart'
|
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(() => ({
|
const { domRef, updateOptions } = useEcharts(() => ({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@@ -33,9 +37,6 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
align: 'right',
|
align: 'right',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
},
|
},
|
||||||
rate: {
|
|
||||||
align: 'left',
|
|
||||||
},
|
|
||||||
money: {
|
money: {
|
||||||
width: 80,
|
width: 80,
|
||||||
align: 'right',
|
align: 'right',
|
||||||
@@ -44,16 +45,16 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
formatter: function (name: string) {
|
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){
|
if(info){
|
||||||
return '{name|'+name+'} {count|'+(info.value)+' 起}{rate|'+(info.rate)+' %} {money|'+(info.money)+' 万元}';
|
return '{name|'+name+'} {count|'+(info.value)+' 起} {money|'+(info.fee)+' 万元}';
|
||||||
}else{
|
}else{
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
text: `{a|19}\n{b|已验收}`,
|
text: '',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
rich: {
|
rich: {
|
||||||
a: {
|
a: {
|
||||||
@@ -101,8 +102,24 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
updateOptions(opts => {
|
updateOptions(opts => {
|
||||||
opts.series[0].data = props.data || [];
|
const total = props.data?.reduce((sum:number, item:any) => sum + item.count, 0);
|
||||||
opts.series[0].color = props.colors || [];
|
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;
|
return opts;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -111,7 +128,7 @@ init();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="domRef" class="w-full h-220px overflow-hidden"></div>
|
<div ref="domRef" class="w-full h-210px overflow-hidden"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
Reference in New Issue
Block a user