Files
moc/src/views/workshopStatistics/index.vue
T
2026-10-19 10:11:16 +08:00

367 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useThemeStore } from '@/store/modules/theme';
import { useRouterPush } from '@/hooks/common/router';
import RingChart from './modules/ringChart.vue';
import { systemConfigApi } from "@/service/api/system";
import { deptStatisticsApi } from '@/service/api/statistics';
const themeStore = useThemeStore();
const { routerPushByKey } = useRouterPush();
const loading = ref<boolean>(false);
// 变更临期天数
const remindDays = ref<number>(0);
const homeData = ref<any>(null);
// 小时转换天
const hourToDay = (hour: number) => {
return Math.trunc((hour / 24) * 100) / 100;
}
// 跳转页面
const jumpTo = (key: string) => {
routerPushByKey(key as any);
}
// 获取数据
const getData = async () => {
loading.value = true;
const {data,error} = await deptStatisticsApi({
from: null,
to: null,
});
if(!error){
homeData.value = data;
}
loading.value = false;
}
// 获取系统配置
const getConfig = async () => {
const {data,error} = await systemConfigApi();
if(!error){
// 解析 临时变更临期提醒
const remindDaysGroup = data.find((group:{group_key:string}) => group.group_key === 'change_remind');
if (remindDaysGroup) {
remindDaysGroup?.items.forEach((item:any) => {
if(item.item_key === 'temp_due_remind_days'){
remindDays.value = Number(item.value);
}
})
}
}
}
onMounted(() => {
getConfig();
getData();
});
</script>
<template>
<NSpace vertical :size="16">
<!-- 卡片数据 -->
<NGrid cols="s:1 m:4 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">本车间变更总数</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.kpis?.apply_total || 0"
class="text-24px font-600 text-slate-800"
/>
</div>
<p class="text-12px">近一年 · 汇报口径</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">在办变更</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.kpis?.unclosed_total || 0"
class="text-26px font-600"
:style="{color:themeStore.themeColor}"
/>
</div>
<p class="text-12px">实时 · 三大阶段分布见下</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">超期未关闭</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.kpis?.overdue_total || 0"
class="text-26px font-600"
:style="{color:themeStore.otherColor.error}"
/>
</div>
<p class="text-12px">管理考核一票指标</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">我的审批按时率</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.kpis?.my_timely_rate || 0"
:decimals="1"
class="text-26px font-600"
:style="{color:themeStore.otherColor.success}"
/><p class="text-26px font-600" :style="{color:themeStore.otherColor.success}">%</p>
</div>
<p class="text-12px">考核:目标 ≥90% · 达标</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="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
: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 class="space-y-3">
<div class="flex items-center gap-2 rounded-lg bg-slate-50 px-3 py-2.5">
<span class="min-w-0 flex-1 text-sm text-slate-700">平均审批时长</span>
<span class="text-base font-bold" :style="{color:themeStore.themeColor}">{{homeData?.my_eff?.avg_audit_days || 0}} 天</span>
<span class="text-[11px]" :style="{color:themeStore.otherColor.success}">目标 ≤{{hourToDay(homeData?.my_eff?.sign_timely_hours || 0)}} 天</span>
</div>
<div class="flex items-center gap-2 rounded-lg bg-slate-50 px-3 py-2.5">
<span class="min-w-0 flex-1 text-sm text-slate-700">按时审批率</span>
<span class="text-base font-bold" :style="{color:themeStore.themeColor}">{{homeData?.my_eff?.timely_rate || 0}}%</span>
<span class="text-[11px]" :style="{color:themeStore.otherColor.success}">目标 ≥90%</span>
</div>
<div class="flex items-center gap-2 rounded-lg bg-slate-50 px-3 py-2.5">
<span class="min-w-0 flex-1 text-sm text-slate-700">驳回补充说明率</span>
<span class="text-base font-bold" :style="{color:themeStore.themeColor}">{{homeData?.my_eff?.reject_comment_rate || 0}}%</span>
<span class="text-[11px]" :style="{color:themeStore.otherColor.success}">驳回必附整改指引</span>
</div>
<p class="text-[11px] text-slate-400">考核数据来源:本人节点签收 → 签出时长,系统自动统计,不可修改</p></div>
</div>
</NCard>
</NGi>
<!-- 本车间变更状态构成 -->
<NGi span="24 s:24 m:24 l:8">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
: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 class="space-y-2.5">
<div v-for="item in homeData?.status_dist" :key="item.status" class="flex items-center gap-2">
<!-- 审批中 -->
<span v-if="item.status==='APPROVING'" class="h-2.5 w-2.5 shrink-0 rounded-sm" :style="{background:themeStore.otherColor.warning}"></span>
<!-- 已批准待投用 -->
<span v-if="item.status==='APPROVED'" class="h-2.5 w-2.5 shrink-0 rounded-sm" :style="{background:themeStore.themeColor}"></span>
<!-- 待验收 -->
<span v-if="item.status==='IMPLEMENTED'" class="h-2.5 w-2.5 shrink-0 rounded-sm" :style="{background:themeStore.otherColor.success}"></span>
<!-- 待关闭 -->
<span v-if="item.status==='ACCEPTED'" class="h-2.5 w-2.5 shrink-0 rounded-sm" :style="{background:themeStore.otherColor.error}"></span>
<!-- 已驳回 -->
<span v-if="item.status==='REJECTED'" class="h-2.5 w-2.5 shrink-0 rounded-sm" :style="{background:themeStore.otherColor.error}"></span>
<span class="min-w-0 flex-1 text-sm text-slate-600">{{item.label}}</span>
<span class="text-sm font-bold text-slate-800">{{item.count}}</span>
</div>
<p class="pt-1 text-[11px] text-slate-400">管理价值:一眼定位积压阶段,决定催办方向</p></div>
</div>
</NCard>
</NGi>
<!-- 临时变更监控 -->
<NGi span="24 s:24 m:24 l:8">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
: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 class="space-y-3">
<div class="rounded-lg border px-3 py-2">
<div class="flex items-center gap-2 text-xs">
<span class="h-2 w-2 rounded-full" :style="{ background: themeStore.otherColor.success }"></span>
<span class="font-medium text-slate-700">正常(到期 > {{remindDays}} 天)</span>
<span class="ml-auto font-semibold text-slate-600">{{homeData?.temp_watch?.normal || 0}}</span>
</div>
</div>
<div class="rounded-lg border px-3 py-2">
<div class="flex items-center gap-2 text-xs">
<span class="h-2 w-2 rounded-full" :style="{ background: themeStore.otherColor.warning }"></span>
<span class="font-medium text-slate-700">临期(到期 ≤ {{remindDays}} 天)</span>
<span class="ml-auto font-semibold text-slate-600">{{homeData?.temp_watch?.near || 0}}</span>
</div>
</div>
<div class="rounded-lg border px-3 py-2">
<div class="flex items-center gap-2 text-xs">
<span class="h-2 w-2 rounded-full" :style="{ background: themeStore.otherColor.error }"></span>
<span class="font-medium text-slate-700">已超期</span>
<span class="ml-auto font-semibold text-slate-600">{{homeData?.temp_watch?.overdue || 0}}</span>
</div>
</div>
<NButton size="small" ghost dashed block class="text-xs text-slate-400" @click="jumpTo('changeledger')">前往变更台账查看处置 →</NButton>
</div>
</div>
</NCard>
</NGi>
</NGrid>
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<!-- 变更类型分布 -->
<NGi span="24 s:24 m:24 l:10">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
: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>
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
</NCard>
</NGi>
<!-- 风险增量与管控 -->
<NGi span="24 s:24 m:24 l:14">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
: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 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-slate-500">新增固有高风险项</div>
<div class="text-xl font-bold" :style="{color:themeStore.otherColor.error}">{{homeData?.risk?.new_high || 0}}</div>
</div>
<div class="rounded-lg bg-amber-50 px-3 py-2">
<div class="text-[11px] text-slate-500">新增中风险项</div>
<div class="text-xl font-bold" :style="{color:themeStore.otherColor.warning}">{{homeData?.risk?.new_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" :style="{color:themeStore.otherColor.error}">{{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" :style="{color:themeStore.otherColor.warning}">{{homeData?.risk?.residual_mid || 0}}</div>
</div>
</div>
<!-- <div class="flex items-center gap-2 text-slate-500">
<span class="text-xs">管控措施落实率</span>
<div class="h-2 flex-1 rounded-full bg-slate-100">
<div class="h-2 rounded-full" :style="{ background: themeStore.otherColor.success }" style="width: 96%"></div>
</div>
<span class="font-semibold" :style="{color:themeStore.otherColor.success}">96%</span>
</div> -->
<p class="mt-1.5 text-[11px] text-slate-400">高风险场景明细见「高风险」页(按部门筛选一车间)</p>
</div>
</NCard>
</NGi>
</NGrid>
<!-- 变更投入和效果 -->
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:24 l:24">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
'--error-color': themeStore.otherColor.error,
'--success-color': themeStore.otherColor.success,
}"
>
<div class="flex items-center p-3 border-b border-slate-100">
<p class=" text-base font-semibold">变更投入和效果(近一年 · 已验收变更)</p>
<p class="text-11px text-slate-400 mt-3px">费用取验收时填报的「变更材料和实施费」;完整明细见操作台「变更投入和效果」</p>
<p class="text-[11px] text-slate-400 ml-auto">
已验收 <b class="text-slate-700">{{homeData?.invest?.total || 0}}</b> 起 ·
投入合计 <b :style="{color:themeStore.themeColor}">{{homeData?.invest?.total_cost || 0}}</b> 万元 ·
完全达到率 <b :style="{color:themeStore.otherColor.success}">{{homeData?.invest?.full_rate || 0}}%</b>
</p>
</div>
<div class="py-3 px-4">
<NTable size="small">
<thead>
<tr>
<th>变更名称 / 编号</th>
<th>申请日期</th>
<th>投用日期</th>
<th>验收日期</th>
<th>费用(万元)</th>
<th>验收结论</th>
</tr>
</thead>
<tbody>
<tr v-if="homeData?.invest?.items?.length===0">
<td colspan="6">
<div class="h-100% flex items-center justify-center py-3">
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
</div>
</td>
</tr>
<tr v-for="row in homeData?.invest?.items || []" :key="row.change_id">
<td>
<p>{{row.title}}</p>
<span class="text-11px text-slate-400">{{row.change_no}}</span>
</td>
<td class="text-xs">{{row?.apply_time?.slice(0,10)}}</td>
<td class="text-xs">{{row?.use_time?.slice(0,10)}}</td>
<td class="text-xs">{{row?.accept_time?.slice(0,10)}}</td>
<td class="text-xs"><span class="font-600">{{row?.cost || 0}}</span></td>
<td>
<NTag v-if="row.result_status===3" size="small" type="success">{{row.result_label}}</NTag>
<NTag v-if="row.result_status===2" size="small" type="warning">{{row.result_label}}</NTag>
<NTag v-if="row.result_status===1" size="small" type="error">{{row.result_label}}</NTag>
</td>
</tr>
</tbody>
</NTable>
</div>
</NCard>
</NGi>
</NGrid>
</NSpace>
</template>
<style scoped></style>