统计报表静态页面完成

This commit is contained in:
2026-08-25 14:15:23 +08:00
parent 0dd1fb7a62
commit 517f7496c1
8 changed files with 795 additions and 1 deletions
+599
View File
@@ -0,0 +1,599 @@
<script setup lang="ts">
import { ref, computed } 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 RingChart from './modules/ringChart.vue';
import ColumnChart from './modules/columnChart.vue';
const appStore = useAppStore();
const themeStore = useThemeStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16));
const P_PERIODS = ["近一个月", "近三个月", "近半年", "近一年"];
const period = ref<(typeof P_PERIODS)[number]>("近一年");
const DEPT_ACCEPT = [
{ name: "工艺", value: 52 },
{ name: "设备", value: 49 },
{ name: "管理", value: 25 },
];
const ACCEPT_COLORS = ["#2080f0", "#7CC242", "#F5A623"];
const changeData = ref([
{
name: '一车间',
value: 42,
},
{
name: '二车间',
value: 35,
},
{
name: '三车间',
value: 28,
},
{
name: '供应部',
value: 12,
},
{
name: '质量部',
value: 9,
},
]);
const ADM_ACCEPT_DETAIL = [
{ id: "MOC-2026-R01-0028", title: "成品包装规格调整", dept: "一车间", applyDate: "2026-02-20", useTime: "2026-03-15", acceptDate: "2026-04-10", cost: 3.2, result: "完全达到" },
{ id: "MOC-2026-R01-0019", title: "R-201 搅拌密封改造", dept: "一车间", applyDate: "2026-03-18", useTime: "2026-04-12", acceptDate: "2026-05-20", cost: 6.8, result: "完全达到" },
{ id: "MOC-2026-R01-0022", title: "循环水加药泵冲程临时调整", dept: "一车间", applyDate: "2026-04-25", useTime: "2026-05-18", acceptDate: "2026-06-30", cost: 1.5, result: "基本达到" },
{ id: "MOC-2026-R02-0031", title: "冷冻盐水泵 P-103 叶轮材质变更", dept: "二车间", applyDate: "2026-06-30", useTime: "2026-07-24", acceptDate: "2026-08-02", cost: 4.6, result: "基本达到" },
{ id: "MOC-2026-R03-0040", title: "原料环己烷供应商变更", dept: "供应部", applyDate: "2026-07-10", useTime: "2026-08-01", acceptDate: "2026-08-04", cost: 2.1, result: "完全达到" },
];
const admOpenStages = ref<{ name: string, n: number }[]>([
{ name: "审批中(含会签)", n: 5 },
{ name: "已批准待投用", n: 2 },
{ name: "待验收", n: 0 },
{ name: "待关闭", n: 1 },
]);
const EFFICIENCY_DATA = [
{ d: "一车间", days: 6.2 }, { d: "二车间", days: 7.8 }, { d: "三车间", days: 9.4 }, { d: "供应部", days: 5.1 },
];
const ADM_PERSON_EFF = [
{ p: "李主任", node: "部门审核", avg: 1.2, max: 2.5, onTime: 96 },
{ p: "王仪表", node: "专业会签(仪表)", avg: 1.8, max: 3.0, onTime: 92 },
{ p: "周设备", node: "专业会签(设备)", avg: 2.6, max: 4.1, onTime: 85 },
{ p: "陈质量", node: "质量会签", avg: 1.6, max: 2.4, onTime: 95 },
{ p: "王海燕", node: "安全审查", avg: 2.1, max: 3.5, onTime: 90 },
{ p: "刘副总", node: "最终批准", avg: 1.5, max: 2.8, onTime: 94 },
];
const ADM_TEMP_DISP = [
{ name: "到期恢复", value: 8, c: "#1D4E9C", note: "恢复原状并验收关闭" },
{ name: "办理延期", value: 5, c: "#F5A623", note: "均符合「至多延期 1 次」" },
{ name: "转为永久", value: 2, c: "#7CC242", note: "补办永久变更审批" },
{ name: "超期未处理", value: 3, c: "#E15555", note: "系统已推送超期提醒至申请人" },
];
const ADM_REJECT_REASONS = [
{ reason: "变更前后对比描述不完整 / 缺少核算数据", n: 4 },
{ reason: "风险分析覆盖不足(情景缺失)", n: 3 },
{ reason: "连带变更识别遗漏", n: 3 },
{ reason: "所需材料不齐全", n: 2 },
{ reason: "变更等级判定偏低", n: 2 },
];
const ADM_ARCHIVE_GAP = [
{ item: "培训签到表", n: 3 }, { item: "PSSR 确认表", n: 2 }, { item: "变更验收单", n: 1 },
];
const ADM_ARCHIVE_TAGS = [
{ k: "资料齐全", n: 76, c: "border-emerald-200 bg-emerald-50 text-emerald-700" },
{ k: "缺培训签到", n: 3, c: "border-amber-200 bg-amber-50 text-amber-700" },
{ k: "缺PSSR表", n: 2, c: "border-amber-200 bg-amber-50 text-amber-700" },
{ k: "缺验收单", n: 1, c: "border-red-200 bg-red-50 text-red-700" },
];
</script>
<template>
<NSpace vertical :size="16">
<!-- 统计报表 -->
<NCard :bordered="false" size="small" :style="{borderRadius: themeStore.themeRadius + 'px'}">
<div class="flex flex-wrap items-center justify-between">
<div class="flex items-center gap-1.5 text-sm font-semibold text-slate-700">
<span>统计中心</span>
<span class="text-xs font-normal text-slate-400">变更归口管理视角 · 全厂口径</span>
</div>
<div class="flex flex-wrap items-center gap-1.5 min-h-28px">
<NButton size="tiny" round v-for="p in P_PERIODS" :key="p" @click="period = p"
:ghost="period === p ? false : true"
:type="period === p ? 'primary' : 'default'"
>{{ p }}</NButton>
<NButton size="small" class="text-xs ml-1">
<Icon icon="material-symbols:download" class="size-14px mr-1" />导出
</NButton>
</div>
</div>
</NCard>
<!-- 卡片数据 -->
<NGrid cols="s:1 m:4 l:4" responsive="screen" :x-gap="gap" :y-gap="16" :style="{'--theme-color': themeStore.themeColor}">
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">变更总数</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="126"
class="text-24px font-600 text-[var(--theme-color)]"
/>
</div>
<p class="text-12px">{{period}} · 全厂</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">重要变更</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="18"
class="text-26px font-600 text-[var(--theme-color)]"
/>
</div>
<p class="text-12px">占比 14%</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">变更费用合计</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="86.4"
:decimals="1"
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">验收填报口径</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">平均审批通过时长</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="58"
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">验收结论口径</p>
</div>
</NGi>
</NGrid>
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:24 l:14">
<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">各车间变更数量{{period}}</p>
<div class="py-3 px-4">
<ColumnChart :data="changeData" style="height: 220px" />
</div>
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:10">
<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">变更类型分布{{period}}</p>
<div class="py-3 px-4">
<RingChart :data="DEPT_ACCEPT" :colors="ACCEPT_COLORS" />
</div>
</NCard>
</NGi>
</NGrid>
<NGrid :x-gap="gap" :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">各审批人审批时长 · {{period}} · 系统自动采集</p>
<div class="py-3 px-4">
<table class="w-full text-xs">
<thead>
<tr class="border-b text-left text-slate-500">
<th class="py-2 font-semibold">审批人 / 节点</th>
<th class="w-[50px] py-2 text-right font-semibold">平均时长</th>
<th class="w-[60px] py-2 text-right font-semibold">最长时长</th>
<th class="w-[130px] py-2 font-semibold">及时率2</th>
</tr>
</thead>
<tbody>
<tr v-for="r in ADM_PERSON_EFF" :key="r.p" class="border-b last:border-0">
<td class="py-2">
<p class="text-xs font-medium text-slate-800">{{ r.p }}</p>
<p class="text-[11px] text-slate-400">{{ r.node }}</p>
</td>
<td class="py-2 text-right text-xs font-bold text-slate-800">{{ r.avg.toFixed(1) }}</td>
<td class="py-2 text-right text-xs text-slate-500">{{ r.max.toFixed(1) }}</td>
<td class="py-2">
<div class="flex items-center gap-2">
<div class="h-1.5 flex-1 overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full" :class="r.onTime >= 90 ? 'bg-[var(--success-color)]' : 'bg-[var(--warning-color)]'" :style="{ width: `${r.onTime}%` }" />
</div>
<span class="text-[11px] text-slate-500">{{ r.onTime }}%</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:16">
<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),
}"
>
<p class=" text-base font-semibold border-b border-slate-100 p-3">变更是否达到预期效果及费用{{period}}</p>
<div class="py-3 px-4">
<div class="space-y-2.5">
<div class="flex flex-wrap items-center gap-x-4 gap-y-1">
<div class="flex items-center gap-1 text-[11px] text-slate-500">
<span class="h-2 w-2 rounded-full bg-[var(--theme-color)]"></span>
完全达到 73
</div>
<div class="flex items-center gap-1 text-[11px] text-slate-500">
<span class="h-2 w-2 rounded-full bg-[var(--success-color)]"></span>
基本达到 44
</div>
<div class="flex items-center gap-1 text-[11px] text-slate-500">
<span class="h-2 w-2 rounded-full bg-[var(--error-color)]"></span>
未达到 9
</div>
<span class="ml-auto text-[11px] text-slate-500">费用合计 <b class="text-[var(--theme-color)]">86.4 万元</b></span>
</div>
<div class="overflow-x-auto rounded-lg border">
<div class="min-w-[680px]">
<div class="flex items-center gap-3 border-b bg-slate-50 px-3 py-1.5 text-[11px] font-semibold text-slate-500">
<span class="min-w-0 flex-1">验收明细 · 变更名称 / 编号 / 部门</span>
<span class="w-[84px] shrink-0">申请日期</span>
<span class="w-[84px] shrink-0">投用日期</span>
<span class="w-[84px] shrink-0">验收日期</span>
<span class="w-[68px] shrink-0 text-right">费用万元</span>
<span class="w-[76px] shrink-0 text-center">验收结论</span>
<span class="w-[52px] shrink-0 text-right">操作</span>
</div>
<div class="divide-y">
<div v-for="r in ADM_ACCEPT_DETAIL" :key="r.id" class="flex items-center gap-3 px-3 py-2 transition hover:bg-[#EAF0F9]/40">
<span class="min-w-0 flex-1">
<span class="block truncate text-xs font-medium text-slate-800">{{ r.title }}</span>
<span class="block font-mono text-[11px] text-slate-400">{{ r.id }} · {{ r.dept }}</span>
</span>
<span class="w-[84px] shrink-0 text-xs text-slate-600">{{ r.applyDate }}</span>
<span class="w-[84px] shrink-0 text-xs text-slate-600">{{ r.useTime }}</span>
<span class="w-[84px] shrink-0 text-xs text-slate-600">{{ r.acceptDate }}</span>
<span class="w-[68px] shrink-0 text-right text-xs font-bold text-slate-800">{{ r.cost.toFixed(1) }}</span>
<span class="w-[76px] shrink-0 text-center">
<NTag v-if="r.result==='完全达到'" size="small" type="success">{{ r.result }}</NTag>
<NTag v-if="r.result==='基本达到'" size="small" type="info">{{ r.result }}</NTag>
<NTag v-if="r.result==='未达到'" size="small" type="error">{{ r.result }}</NTag>
</span>
<span class="w-[52px] shrink-0 text-right">
<NButton size="small" class="text-[11px]" @click="message.info(`演示:打开 ${r.id} 验收详情(含费用明细与运行数据)`)">查看</NButton>
</span>
</div>
</div>
</div>
</div>
<div class="text-[11px] text-slate-400">明细为部分演示数据 · 全量见变更台账筛选已关闭</div>
</div>
</div>
</NCard>
</NGi>
</NGrid>
<NGrid :x-gap="gap" :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%',
'--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),
}"
>
<p class=" text-base font-semibold border-b border-slate-100 p-3">变更带来的风险增量及管控{{period}}</p>
<div class="py-3 px-4">
<div class="space-y-3">
<div class="grid grid-cols-2 gap-3">
<div class="rounded-lg border border-[var(--error-color)] bg-[var(--error-bg)] px-3 py-2.5">
<div class="text-[11px] text-slate-500">新增固有风险项</div>
<div class="mt-1 text-sm font-bold text-slate-800"> <b class="text-[var(--error-color)]">7</b> · <b class="text-[var(--warning-color)]">21</b></div>
</div>
<div class="rounded-lg border border-[var(--warning-color)] bg-[var(--warning-bg)] px-3 py-2.5">
<div class="text-[11px] text-slate-500">剩余风险项管控中</div>
<div class="mt-1 text-sm font-bold text-slate-800"> <b class="text-[var(--error-color)]">2</b> · <b class="text-[var(--warning-color)]">9</b></div>
</div>
</div>
<div>
<div class="flex items-center justify-between text-xs text-slate-500">
<span>管控措施落实率</span><b class="text-[var(--success-color)]">92%</b>
</div>
<div class="mt-1.5 h-1.5 w-full overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full bg-[var(--success-color)]" :style="{ width: `92%` }"></div>
</div>
</div>
<div class="text-[11px] text-slate-400">口径风险分析记录表识别的新增固有高/中风险项与验收时剩余风险项对比剩余高风险项均挂接管控措施并跟踪落实</div>
</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%',
'--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">当前未关闭变更{{period}}</p>
<div class="py-3 px-4">
<div class="space-y-3">
<div class="flex items-end gap-2">
<span class="text-3xl font-extrabold leading-none text-[var(--theme-color)]">8</span>
<span class="pb-0.5 text-[11px] text-slate-400"> · 实时台账口径</span>
</div>
<div class="flex flex-wrap gap-1.5">
<span v-for="s in admOpenStages" :key="s.name" class="rounded-md border border-[var(--theme-color)] bg-[var(--theme-bg)] px-2 py-0.5 text-[11px] text-[var(--theme-color)]">{{ s.name }} <b>{{ s.n }}</b></span>
</div>
<div class="rounded-lg border border-[var(--warning-color)] bg-[var(--warning-bg)] px-3 py-2 text-[11px] text-[var(--warning-color)]">
临时变更临期 <b>1</b> · 已到期未处置 <b class="text-[var(--error-color)]">1</b> 临期提醒天数可在通知设置调整
</div>
<NButton size="small" ghost type="primary" block class="text-xs" @click="emit('go', 'ledger')">
进入变更台账<Icon icon="formkit:right" class="size-12px" />
</NButton>
</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%',
'--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">驳回的变更申请{{period}}</p>
<div class="py-3 px-4">
<div class="space-y-3">
<div class="flex items-end gap-2">
<span class="text-3xl font-extrabold leading-none text-[var(--error-color)]">14</span>
<span class="pb-0.5 text-[11px] text-slate-400"> · 驳回率 11.1%</span>
</div>
<div class="rounded-lg border px-3 py-2.5 text-[11px] text-slate-500">
驳回后修改重报通过率 <b class="text-[var(--success-color)]">78.6%</b>
<div class="mt-0.5 text-slate-400">驳回均留痕并附带理由申请人可修改重报</div>
</div>
<NButton size="small" ghost type="primary" block class="text-xs" @click="emit('go', 'ledger')">
查看驳回单<Icon icon="formkit:right" class="size-12px" />
</NButton>
</div>
</div>
</NCard>
</NGi>
</NGrid>
<NGrid :x-gap="gap" :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%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">各部门审批时长 · {{period}}</p>
<div class="py-3 px-4">
<div class="space-y-3">
<div v-for="e in EFFICIENCY_DATA" :key="e.d" class="flex items-center gap-3">
<span class="w-14 shrink-0 text-xs text-slate-600">{{ e.d }}</span>
<NProgress
type="line"
:percentage="(e.days / 10) * 100"
:height="15"
:border-radius="4"
:fill-border-radius="4"
>
<span class="w-12 shrink-0 text-right text-xs font-bold text-slate-800">{{ e.days }} </span>
</NProgress>
</div>
</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%',
'--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">临时变更后续处置方式{{period}}</p>
<div class="py-3 px-4">
<div class="space-y-2.5">
<div v-for="t in ADM_TEMP_DISP" :key="t.name" class="rounded-lg border px-3 py-2.5">
<div class="flex items-center gap-1">
<div class="flex items-center gap-1">
<span v-if="t.name==='到期恢复'" class="h-2.5 w-2.5 rounded-sm bg-[var(--theme-color)]"></span>
<span v-if="t.name==='办理延期'" class="h-2.5 w-2.5 rounded-sm bg-[var(--warning-color)]"></span>
<span v-if="t.name==='转为永久'" class="h-2.5 w-2.5 rounded-sm bg-[var(--success-color)]"></span>
<span v-if="t.name==='超期未处理'" class="h-2.5 w-2.5 rounded-sm bg-[var(--error-color)]"></span>
<span class="text-xs font-medium text-slate-800">{{ t.name }}</span>
<p class="mt-[1px] pl-1 text-[11px] text-slate-400">{{ t.note }}</p>
</div>
<span v-if="t.name==='到期恢复'" class="ml-auto text-sm font-extrabold text-[var(--theme-color)]">{{ t.value }}</span>
<span v-if="t.name==='办理延期'" class="ml-auto text-sm font-extrabold text-[var(--warning-color)]">{{ t.value }}</span>
<span v-if="t.name==='转为永久'" class="ml-auto text-sm font-extrabold text-[var(--success-color)]">{{ t.value }}</span>
<span v-if="t.name==='超期未处理'" class="ml-auto text-sm font-extrabold text-[var(--error-color)]">{{ t.value }}</span>
</div>
</div>
<div class="text-[11px] text-slate-400">处置子单延期 / 转永久 / 恢复均走简化审批并留痕与本统计同源</div>
</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%',
'--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">主要驳回理由汇总{{period}} · 按驳回单数</p>
<div class="py-3 px-4">
<div class="space-y-2.5">
<div v-for="r in ADM_REJECT_REASONS" :key="r.reason">
<div class="flex items-center justify-between text-xs">
<span class="text-slate-600">{{ r.reason }}</span>
</div>
<NProgress
type="line"
:percentage="(r.n / 10) * 100"
>
<span class="w-12 shrink-0 text-right text-xs font-bold text-slate-800">{{ r.n }} </span>
</NProgress>
</div>
<div class="text-[11px] text-slate-400">可用于申请端填报质量改进TOP2 理由已纳入 AI 预识别检查项</div>
</div>
</div>
</NCard>
</NGi>
</NGrid>
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:24 l:24">
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
'--error-color': themeStore.otherColor.error,
'--success-color': themeStore.otherColor.success,
}"
>
<p class=" text-base font-semibold border-b border-slate-100 p-3">变更归档资料情况{{period}}</p>
<div class="py-3 px-4">
<div class="grid gap-4 grid-cols-12">
<div class="flex items-center gap-4 col-span-4">
<NProgress type="circle" :percentage="93.8" :offset-degree="180">
<div class="flex flex-col items-center mt-2">
<p class="flex text-xl font-bold leading-none text-[#1D2129]">
93.8<span class="text-sm font-semibold">%</span>
</p>
<p class="mt-1 text-[11px] text-slate-400">Good</p>
</div>
</NProgress>
<div class="space-y-1 text-xs text-slate-500">
<div>应归档 <b class="text-slate-800">81</b> · 已归档 <b class="text-[var(--success-color)]">76</b> </div>
<div>待补齐资料 <b class="text-[var(--error-color)]">5</b> 已推送申请人补齐提醒</div>
<div class="text-[11px] text-slate-400">归档包确认关闭后只读锁定不可修改删除</div>
</div>
</div>
<div class="col-span-4">
<p class="text-[11px] font-bold uppercase tracking-[0.14em] text-slate-400">资料缺项 TOP</p>
<div class="mt-2 space-y-2">
<div v-for="g in ADM_ARCHIVE_GAP" :key="g.item" class="flex items-center gap-2 text-xs">
<span class="min-w-0 flex-1 text-slate-600">{{ g.item }}缺失</span>
<div class="w-44">
<NProgress
type="line"
status="warning"
:percentage="(g.n / 5) * 100"
>
<span class="w-12 shrink-0 text-right text-xs font-bold text-slate-800">{{ g.n }}</span>
</NProgress>
</div>
</div>
</div>
</div>
<div class="col-span-4">
<p class="text-[11px] font-bold uppercase tracking-[0.14em] text-slate-400">归档构成已关闭 81 </p>
<div class="mt-2 flex flex-wrap gap-1.5">
<template v-for="x in ADM_ARCHIVE_TAGS" :key="x.k">
<NTag v-if="x.k==='资料齐全'" size="small" type="success">{{ x.k }} {{ x.n }}</NTag>
<NTag v-if="x.k==='缺培训签到' || x.k==='缺PSSR表'" size="small" type="warning">{{ x.k }} {{ x.n }}</NTag>
<NTag v-if="x.k==='缺验收单'" size="small" type="error">{{ x.k }} {{ x.n }}</NTag>
</template>
</div>
<NButton size="small" type="primary" ghost block class="mt-3 text-xs" @click="emit('go', 'knowledge')">
进入资料库 / 归档<Icon icon="formkit:right" class="size-12px" />
</NButton>
</div>
</div>
</div>
</NCard>
</NGi>
</NGrid>
</NSpace>
</template>
<style scoped></style>
+90
View File
@@ -0,0 +1,90 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useEcharts } from '@/hooks/common/echarts';
defineOptions({
name: 'RingChart'
});
const props = defineProps<{ data: { name: string; value: number; }[]; }>();
const { domRef, updateOptions } = useEcharts(() => ({
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
textStyle: {
color: "#fff",
},
},
},
legend: {
show:false,
bottom: '20%',
left: 'center',
icon: 'circle',
itemWidth: 10,
itemHeight: 10,
itemStyle: {
borderWidth: 0
},
},
grid: {
top: "5%",
left: "5%",
right: "5%",
bottom: "11%",
},
calculable: true,
xAxis: [
{
type: "category",
boundaryGap: true, // 新增,固定柱子位置
axisLine: {
show: false,
},
axisLabel: {
interval: 0,
},
splitLine: { show: false },
axisTick: { show: false },
splitArea: { show: false },
data: [] as string[],
},
],
yAxis: [
{
type: "value",
show: false,
},
],
series: [
{
type: "bar",
barWidth: 45,
barGap: "10%",
color: "#2080f0",
label: { show: true, position: 'top',color: '#333',fontWeight: 'bold' },
data: [] as number[],
},
]
}));
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;
opts.series[0].data = values;
return opts;
});
}
init();
</script>
<template>
<div ref="domRef" class="w-full h-220px overflow-hidden"></div>
</template>
<style scoped></style>
+88
View File
@@ -0,0 +1,88 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useEcharts } from '@/hooks/common/echarts';
defineOptions({
name: 'RingChart'
});
const props = defineProps<{ data?: { name: string; value: number }[]; colors?: string[] }>();
const { domRef, updateOptions } = useEcharts(() => ({
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
bottom: 0,
left: 'center',
icon: 'circle',
itemWidth: 10,
itemHeight: 10,
itemStyle: {
borderWidth: 0
},
},
title: {
text: `{a|126}\n{b|总数}`,
textStyle: {
rich: {
a: {
fontSize: 24,
color: '#333',
fontWeight: 800,
align: 'center',
verticalAlign: 'middle',
},
b: {
fontSize: 12,
color: '#999',
align: 'center',
verticalAlign: 'middle',
fontWeight: 500,
padding: [5, 0, 0, 0]
}
}
},
left: 'center',
top: '35%',
},
series: [
{
type: 'pie',
color: ['#5da8ff', '#8e9dff', '#fedc69', '#26deca'],
radius: ['45%', '65%'],
center: ['50%', '45%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'outside',
formatter: '{b}\n{c} ({d}%)',
fontSize: 12,
},
labelLine: {
show: true,
length: 10,
length2: 10
},
data: [] as { name: string; value: number }[]
}
]
}));
function init() {
updateOptions(opts => {
opts.series[0].data = props.data || [];
opts.series[0].color = props.colors || [];
return opts;
});
}
init();
</script>
<template>
<div ref="domRef" class="w-full h-220px overflow-hidden"></div>
</template>
<style scoped></style>