本专业变更统计-数据统计字段修改

This commit is contained in:
2026-09-30 13:32:00 +08:00
parent 253df87b31
commit 8206121d46
5 changed files with 369 additions and 722 deletions
+173 -164
View File
@@ -10,12 +10,6 @@ import { specialtyStatisticsApi } from '@/service/api/statistics';
const themeStore = useThemeStore();
const { routerPushByKey } = useRouterPush();
const profHighRisk = [
{ id: "MOC-2026-R01-0035", status: '审批中', title: 'R-201 反应釜搅拌器电机更换(55kW→75kW 防爆电机)' },
{ id: "MOC-2026-R01-0036", status: '审批中', title: '聚合反应温度控制上限调整(78℃→82℃)' },
{ id: "MOC-2026-R01-0037", status: '实施中', title: '原料环己烷供应商变更(新增 B 供应商)' },
];
const loading = ref<boolean>(false);
const homeData = ref<any>(null);
@@ -23,6 +17,10 @@ const homeData = ref<any>(null);
const jumpTo = (key: string) => {
routerPushByKey(key as any);
}
// 小时转换天
const hourToDay = (hour: number) => {
return Math.trunc((hour / 24) * 100) / 100;
}
// 获取数据
const getData = async () => {
@@ -33,7 +31,6 @@ const getData = async () => {
});
if(!error){
homeData.value = data;
console.log(data);
}
loading.value = false;
}
@@ -44,168 +41,180 @@ onMounted(() => {
</script>
<template>
<NSpace vertical :size="16">
<!-- 卡片数据 -->
<NGrid cols="s:1 m:5 l:5" responsive="screen" :x-gap="16" :y-gap="16"
:style="{
'--theme-color': themeStore.themeColor,
'--success-color': themeStore.otherColor.success,
'--warning-color': themeStore.otherColor.warning,
}"
>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">本月会签 / 审批</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="6"
class="text-24px font-600 text-[#333]"
/>
</div>
<p class="text-12px">口径:节点签出记录</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">我的会签平均耗时</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="1.8"
:decimals="1"
class="text-26px font-600 text-[var(--success-color)]"
/><p class="text-26px font-600 text-[var(--success-color)]">天</p>
</div>
<p class="text-12px">口径:签收→签出时长 · 目标 ≤2.5 天</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">按时完成率</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="95"
class="text-26px font-600 text-[var(--success-color)]"
/><p class="text-26px font-600 text-[var(--success-color)]">%</p>
</div>
<p class="text-12px">考核:目标 ≥90%</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">参与风险分析</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="2"
class="text-26px font-600 text-[var(--theme-color)]"
/><p class="text-26px font-600 text-[var(--theme-color)]">次</p>
</div>
<p class="text-12px">口径:HAZOP / FMEA 分析组成员(选择式)记录</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">当前待我审批</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="1"
class="text-26px font-600 text-[var(--warning-color)]"
/>
</div>
<p class="text-12px">实时 · 与工作台同源</p>
</div>
</NGi>
</NGrid>
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
'--theme-color': themeStore.themeColor,
'--error-color': themeStore.otherColor.error,
'--warning-color': themeStore.otherColor.warning,
'--success-color': themeStore.otherColor.success,
'--error-bg': getColorWithOpacity(themeStore.otherColor.error,0.1),
'--warning-bg': getColorWithOpacity(themeStore.otherColor.warning,0.1),
'--theme-bg': getColorWithOpacity(themeStore.themeColor,0.1),
<NSpin :show="loading" size="small">
<NSpace vertical :size="16">
<!-- 卡片数据 -->
<NGrid cols="s:1 m:5 l:5" responsive="screen" :x-gap="16" :y-gap="16"
:style="{
'--theme-color': themeStore.themeColor,
'--success-color': themeStore.otherColor.success,
'--warning-color': themeStore.otherColor.warning,
}"
>
<p class=" text-base font-semibold border-b border-slate-100 p-3">会签效率对比(考核 · 汇报)</p>
<div class="py-3 px-4">
<div class="space-y-3">
<div v-for="e in homeData?.approver_time || []" :key="e">
<div class="mb-1 flex items-center justify-between text-xs">
<span class="text-slate-600">{{ e.user_name }}{{e.step_name?'('+e.step_name+')':''}}</span>
<span :class="e.avg_days > 2.5 ? 'font-medium text-[var(--error-color)]' : 'text-slate-500'">{{ e.avg_days }} 天</span>
</div>
<div class="h-2 overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full" :class="e.avg_days > 2.5 ? 'bg-[var(--error-color)]' : 'bg-[var(--theme-color)]'" :style="{ width: `${Math.min(100, (e.avg_days / e.total) * 100)}%` }"></div>
</div>
</div>
<p v-if="homeData?.approver_time?.length===0" class="text-xs text-slate-400 text-center py-3">暂无数据</p>
<div>
<div class="mb-1 flex items-center justify-between text-xs">
<span class="text-slate-600">考核目标</span>
<span class="text-slate-500">2.5 天(标线)</span>
</div>
<div class="h-2 overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full bg-slate-300" :style="{ width: `${Math.min(100, (2.5 / 2.5) * 100)}%` }"></div>
</div>
</div>
<p class="text-[11px] text-slate-400">考核数据来源:节点签收 → 签出时长自动统计;连续两月超标将纳入绩效面谈</p>
>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">本月会签 / 审批</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.my_stats?.month_signed || 0"
class="text-24px font-600 text-[#333]"
/>
</div>
<p class="text-12px">口径:节点签出记录</p>
</div>
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">本专业变更类型分布(近一年)</p>
<div class="py-3 px-4">
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">我的会签平均耗时</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.my_stats?.avg_audit_days || 0"
:decimals="1"
class="text-26px font-600 text-[var(--success-color)]"
/><p class="text-26px font-600 text-[var(--success-color)]">天</p>
</div>
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">高风险在办(直达风险分析)</p>
<div class="py-3 px-4">
<div v-if="profHighRisk.length" class="space-y-2 pt-1">
<div v-for="c in profHighRisk" :key="c.id" @click="jumpTo('my_highrisk')" class="cursor-pointer flex w-full items-center gap-2 rounded-lg border px-3 py-2 text-left hover:border-red-300 hover:bg-red-50/30">
<span class="min-w-0 flex-1">
<span class="block truncate text-sm font-medium text-slate-800">{{ c.title }}</span>
<span class="text-[11px] text-slate-400">{{ c.id }} · {{ c.status }}</span>
</span>
<Icon icon="formkit:right" class="size-14px text-slate-300" />
</div>
<p class="text-[11px] text-slate-400">更多高风险场景与分析记录见「高风险」页</p>
</div>
<p v-else class="text-center text-xs text-slate-400">暂无高风险在办</p>
<p class="text-12px">口径:签收→签出时长 · 目标 ≤{{hourToDay(homeData?.my_stats?.sign_timely_hours || 0)}} 天</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">按时完成率</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.my_stats?.timely_rate || 0"
class="text-26px font-600 text-[var(--success-color)]"
/><p class="text-26px font-600 text-[var(--success-color)]">%</p>
</div>
</NCard>
</NGi>
</NGrid>
</NSpace>
<p class="text-12px">考核:目标 ≥90%</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">参与风险分析</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.my_stats?.risk_analysis_count || 0"
class="text-26px font-600 text-[var(--theme-color)]"
/><p class="text-26px font-600 text-[var(--theme-color)]">次</p>
</div>
<p class="text-12px">口径:HAZOP / FMEA 分析组成员(选择式)记录</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">当前待我审批</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.my_stats?.todo_total || 0"
class="text-26px font-600 text-[var(--warning-color)]"
/>
</div>
<p class="text-12px">实时 · 与工作台同源</p>
</div>
</NGi>
</NGrid>
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
'--theme-color': themeStore.themeColor,
'--error-color': themeStore.otherColor.error,
'--warning-color': themeStore.otherColor.warning,
'--success-color': themeStore.otherColor.success,
'--error-bg': getColorWithOpacity(themeStore.otherColor.error,0.1),
'--warning-bg': getColorWithOpacity(themeStore.otherColor.warning,0.1),
'--theme-bg': getColorWithOpacity(themeStore.themeColor,0.1),
}"
>
<p class=" text-base font-semibold border-b border-slate-100 p-3">会签效率对比(考核 · 汇报)</p>
<div class="py-3 px-4">
<div class="space-y-3">
<div v-for="e in homeData?.approver_time || []" :key="e">
<div class="mb-1 flex items-center justify-between text-xs">
<span class="text-slate-600">{{ e.user_name }}{{e.step_name?'('+e.step_name+')':''}}</span>
<span :class="e.avg_days > 2.5 ? 'font-medium text-[var(--error-color)]' : 'text-slate-500'">{{ e.avg_days }} 天</span>
</div>
<div class="h-2 overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full" :class="e.avg_days > hourToDay(homeData?.my_stats?.sign_timely_hours || 0) ? 'bg-[var(--error-color)]' : 'bg-[var(--theme-color)]'" :style="{ width: `${Math.min(100, (e.avg_days / e.total) * 100)}%` }"></div>
</div>
</div>
<p v-if="homeData?.approver_time?.length===0" class="text-xs text-slate-400 text-center py-3">暂无数据</p>
<div>
<div class="mb-1 flex items-center justify-between text-xs">
<span class="text-slate-600">考核目标</span>
<span class="text-slate-500">{{hourToDay(homeData?.my_stats?.sign_timely_hours || 0)}} 天(标线)</span>
</div>
<div class="h-2 overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full bg-slate-300" :style="{ width: `${Math.min(100, (hourToDay(homeData?.my_stats?.sign_timely_hours || 0) / hourToDay(homeData?.my_stats?.sign_timely_hours || 0)) * 100)}%` }"></div>
</div>
</div>
<p class="text-[11px] text-slate-400">考核数据来源:节点签收 → 签出时长自动统计;连续两月超标将纳入绩效面谈</p>
</div>
</div>
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">本专业变更类型分布(近一年)</p>
<div class="py-3 px-4">
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
</div>
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">高风险在办(直达风险分析)</p>
<div class="py-3 px-4">
<div v-if="homeData?.high_risk?.total > 0" class="space-y-2 pt-1">
<div v-for="c in homeData?.high_risk?.items || []" :key="c.id" @click="jumpTo('my_highrisk')" class="cursor-pointer flex w-full items-center gap-2 rounded-lg border px-3 py-2 text-left hover:border-red-300 hover:bg-red-50/30">
<span class="min-w-0 flex-1">
<span class="block truncate text-sm font-medium text-slate-800">{{ c.change_title }}</span>
<div class="flex items-center gap-2 text-[11px] text-slate-400 mt-1">
<span>{{ c.change_no }}</span>
<!-- 状态标签 -->
<span v-if="c.change_status==='APPROVING'">审批中</span>
<span v-if="c.change_status==='APPROVED'">已通过</span>
<span v-if="c.change_status==='IMPLEMENTED'">待验收</span>
<span v-if="c.change_status==='ACCEPTED'">待关闭</span>
<span v-if="c.change_status==='CLOSED'">已关闭</span>
<span v-if="c.change_status==='WITHDRAWN'">已撤回</span>
<span v-if="c.change_status==='REJECTED'">已驳回</span>
</div>
</span>
<Icon icon="formkit:right" class="size-14px text-slate-300" />
</div>
<p class="text-[11px] text-slate-400">更多高风险场景与分析记录见「高风险」页</p>
</div>
<p v-else class="text-center text-xs text-slate-400">暂无高风险在办</p>
</div>
</NCard>
</NGi>
</NGrid>
</NSpace>
</NSpin>
</template>
<style scoped></style>