统计报表静态页面完成
This commit is contained in:
@@ -250,7 +250,8 @@ const local: App.I18n.Schema = {
|
||||
home_pendingchange: '待投用变更',
|
||||
home_effect: '变更投入和效果',
|
||||
home_datachangecenter: '变更数据中心',
|
||||
home_closeconfirm: '关闭确认'
|
||||
home_closeconfirm: '关闭确认',
|
||||
report: '统计报表'
|
||||
},
|
||||
form: {
|
||||
required: '不能为空',
|
||||
|
||||
@@ -38,6 +38,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
||||
home_processdetail: () => import("@/views/home/processDetail/index.vue"),
|
||||
home_pssr: () => import("@/views/home/pssr/index.vue"),
|
||||
login: () => import("@/views/login/index.vue"),
|
||||
report: () => import("@/views/report/index.vue"),
|
||||
riskstatistics: () => import("@/views/riskStatistics/index.vue"),
|
||||
systemmanage_role: () => import("@/views/systemManage/role/index.vue"),
|
||||
systemmanage_template: () => import("@/views/systemManage/template/index.vue"),
|
||||
|
||||
@@ -297,6 +297,17 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
hideInMenu: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'report',
|
||||
path: '/report',
|
||||
component: 'layout.base$view.report',
|
||||
meta: {
|
||||
title: 'report',
|
||||
i18nKey: 'route.report',
|
||||
icon: 'ix:barchart',
|
||||
order: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'riskstatistics',
|
||||
path: '/riskstatistics',
|
||||
|
||||
@@ -189,6 +189,7 @@ const routeMap: RouteMap = {
|
||||
"home_processdetail": "/home/processdetail",
|
||||
"home_pssr": "/home/pssr",
|
||||
"login": "/login/:module(pwd-login)?",
|
||||
"report": "/report",
|
||||
"riskstatistics": "/riskstatistics",
|
||||
"systemmanage": "/systemmanage",
|
||||
"systemmanage_role": "/systemmanage/role",
|
||||
|
||||
Vendored
+3
@@ -43,6 +43,7 @@ declare module "@elegant-router/types" {
|
||||
"home_processdetail": "/home/processdetail";
|
||||
"home_pssr": "/home/pssr";
|
||||
"login": "/login/:module(pwd-login)?";
|
||||
"report": "/report";
|
||||
"riskstatistics": "/riskstatistics";
|
||||
"systemmanage": "/systemmanage";
|
||||
"systemmanage_role": "/systemmanage/role";
|
||||
@@ -87,6 +88,7 @@ declare module "@elegant-router/types" {
|
||||
| "docs"
|
||||
| "home"
|
||||
| "login"
|
||||
| "report"
|
||||
| "riskstatistics"
|
||||
| "systemmanage"
|
||||
>;
|
||||
@@ -128,6 +130,7 @@ declare module "@elegant-router/types" {
|
||||
| "home_processdetail"
|
||||
| "home_pssr"
|
||||
| "login"
|
||||
| "report"
|
||||
| "riskstatistics"
|
||||
| "systemmanage_role"
|
||||
| "systemmanage_template"
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user