全工厂统计概览页面完成
This commit is contained in:
@@ -96,7 +96,7 @@ export function departmentStatisticsApi(params: {from: string | null, to: string
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 本专业变更统计
|
// 本专业变更统计(质量会签统计)
|
||||||
export function specialtyStatisticsApi(params: {from: string | null, to: string | null}) {
|
export function specialtyStatisticsApi(params: {from: string | null, to: string | null}) {
|
||||||
return request({
|
return request({
|
||||||
url: `/api/reports/dept-stats/specialty-sign`,
|
url: `/api/reports/dept-stats/specialty-sign`,
|
||||||
@@ -113,3 +113,12 @@ export function deptStatisticsApi(params: {from: string | null, to: string | nul
|
|||||||
params
|
params
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 全工厂统计概览
|
||||||
|
export function factoryStatisticsApi(params: {from: string | null, to: string | null}) {
|
||||||
|
return request({
|
||||||
|
url: `/api/reports/plant-overview`,
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,63 +1,48 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
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 { getColorWithOpacity } from '@/utils/common';
|
||||||
import RingChart from './modules/ringChart.vue';
|
import RingChart from './modules/ringChart.vue';
|
||||||
import ColumnChart from './modules/columnChart.vue';
|
import ColumnChart from './modules/columnChart.vue';
|
||||||
|
import ColumnChart2 from './modules/columnChart2.vue';
|
||||||
|
import { factoryStatisticsApi } from '@/service/api/statistics';
|
||||||
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
|
||||||
|
|
||||||
const DEPT_ACCEPT = [
|
|
||||||
{ name: "工艺", value: 52 },
|
|
||||||
{ name: "设备", value: 49 },
|
|
||||||
{ name: "管理", value: 25 },
|
|
||||||
];
|
|
||||||
const ACCEPT_COLORS = ["#2080f0", "#7CC242", "#F5A623"];
|
|
||||||
|
|
||||||
const changeData = ref([
|
|
||||||
{
|
|
||||||
name: '2月',
|
|
||||||
value: 42,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '3月',
|
|
||||||
value: 35,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '4月',
|
|
||||||
value: 28,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '5月',
|
|
||||||
value: 12,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '6月',
|
|
||||||
value: 9,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '7月',
|
|
||||||
value: 33,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const DEFAULT_EFF = [
|
|
||||||
{ pct: 85, label: "按期投用率", c: "#1D4E9C" },
|
|
||||||
{ pct: 64, label: "培训按期完成率", c: "#F5A623" },
|
|
||||||
{ pct: 25, label: "超期变更占比", c: "#E15555" },
|
|
||||||
];
|
|
||||||
const DEFAULT_EXPORTS = ["变更数量统计", "变更周期分析", "超期预警报表", "风险分析执行", "PSSR 与验收", "关闭及时率"];
|
const DEFAULT_EXPORTS = ["变更数量统计", "变更周期分析", "超期预警报表", "风险分析执行", "PSSR 与验收", "关闭及时率"];
|
||||||
|
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
const homeData = ref<any>(null);
|
||||||
|
|
||||||
|
// 导出数据
|
||||||
|
const exportData = (type: string) => {
|
||||||
|
window.$message?.info('还差接口')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取数据
|
||||||
|
const getData = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const {data,error} = await factoryStatisticsApi({
|
||||||
|
from: null,
|
||||||
|
to: null,
|
||||||
|
});
|
||||||
|
if(!error){
|
||||||
|
homeData.value = data;
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace vertical :size="16">
|
<NSpace vertical :size="16">
|
||||||
<!-- 卡片数据 -->
|
<!-- 卡片数据 -->
|
||||||
<NGrid cols="s:1 m:4 l:4" responsive="screen" :x-gap="gap" :y-gap="16" :style="{'--theme-color': themeStore.themeColor,'--theme-bg': getColorWithOpacity(themeStore.themeColor, 0.1)}">
|
<NGrid cols="s:1 m:4 l:4" responsive="screen" :x-gap="16" :y-gap="16" :style="{'--theme-color': themeStore.themeColor,'--theme-bg': getColorWithOpacity(themeStore.themeColor, 0.1)}">
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<div
|
||||||
class="bg-white px-16px py-12px text-[#94a3b8]"
|
class="bg-white px-16px py-12px text-[#94a3b8]"
|
||||||
@@ -65,12 +50,11 @@ const DEFAULT_EXPORTS = ["变更数量统计", "变更周期分析", "超期预
|
|||||||
>
|
>
|
||||||
<p class="text-12px font-600 text-center">按期关闭率</p>
|
<p class="text-12px font-600 text-center">按期关闭率</p>
|
||||||
<div class="flex justify-center py-2.5">
|
<div class="flex justify-center py-2.5">
|
||||||
<NProgress type="circle" :percentage="93.8" :offset-degree="180">
|
<NProgress type="circle" :percentage="homeData?.gauges?.ontime_close_rate || 0" :offset-degree="180">
|
||||||
<div class="flex flex-col items-center mt-2">
|
<div class="flex flex-col items-center mt-2">
|
||||||
<p class="flex items-end text-2xl font-bold leading-none text-[#1D2129]">
|
<p class="flex items-end text-2xl font-bold leading-none text-[#1D2129]">
|
||||||
93.8<span class="text-sm font-semibold">%</span>
|
{{homeData?.gauges?.ontime_close_rate || 0}}<span class="text-sm font-semibold">%</span>
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-[11px] text-slate-400">Good</p>
|
|
||||||
</div>
|
</div>
|
||||||
</NProgress>
|
</NProgress>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,68 +68,57 @@ const DEFAULT_EXPORTS = ["变更数量统计", "变更周期分析", "超期预
|
|||||||
>
|
>
|
||||||
<p class="text-12px font-600 text-center">PSSR 完成率</p>
|
<p class="text-12px font-600 text-center">PSSR 完成率</p>
|
||||||
<div class="flex justify-center py-2.5">
|
<div class="flex justify-center py-2.5">
|
||||||
<NProgress type="circle" :percentage="96" :offset-degree="180" status="success">
|
<NProgress type="circle" :percentage="homeData?.gauges?.pssr_rate || 0" :offset-degree="180" status="success">
|
||||||
<div class="flex flex-col items-center mt-2">
|
<div class="flex flex-col items-center mt-2">
|
||||||
<p class="flex items-end text-2xl font-bold leading-none text-[#1D2129]">
|
<p class="flex items-end text-2xl font-bold leading-none text-[#1D2129]">
|
||||||
96<span class="text-sm font-semibold">%</span>
|
{{homeData?.gauges?.pssr_rate || 0}}<span class="text-sm font-semibold">%</span>
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-[11px] text-slate-400">Excellent</p>
|
|
||||||
</div>
|
|
||||||
</NProgress>
|
|
||||||
</div>
|
|
||||||
<p class="text-12px text-center">近一年 · 全厂</p>
|
|
||||||
</div>
|
|
||||||
</NGi>
|
|
||||||
<NGi>
|
|
||||||
<div
|
|
||||||
class="bg-white px-16px py-12px text-[#94a3b8]"
|
|
||||||
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
|
||||||
>
|
|
||||||
<p class="text-12px font-600 text-center">验收一次通过率</p>
|
|
||||||
<div class="flex justify-center py-2.5">
|
|
||||||
<NProgress type="circle" :percentage="88" :offset-degree="180" status="warning">
|
|
||||||
<div class="flex flex-col items-center mt-2">
|
|
||||||
<p class="flex items-end text-2xl font-bold leading-none text-[#1D2129]">
|
|
||||||
88<span class="text-sm font-semibold">%</span>
|
|
||||||
</p>
|
|
||||||
<p class="mt-1 text-[11px] text-slate-400">Fair</p>
|
|
||||||
</div>
|
</div>
|
||||||
</NProgress>
|
</NProgress>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-12px text-center">近一年 · 全厂</p>
|
<p class="text-12px text-center">近一年 · 全厂</p>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
|
<NGi>
|
||||||
|
<div
|
||||||
|
class="bg-white px-16px py-12px text-[#94a3b8] flex flex-col justify-center items-center"
|
||||||
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
|
>
|
||||||
|
<p class="text-12px font-600 text-center">当月临时变更数量</p>
|
||||||
|
<p class="mt-2 text-[40px] font-bold leading-none text-[#1D2129] text-center">{{homeData?.gauges?.month_created || 0}}</p>
|
||||||
|
<p class="text-12px text-center mt-1">近一年 · 全厂</p>
|
||||||
|
</div>
|
||||||
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<div
|
||||||
class="bg-white px-16px py-12px text-[#94a3b8] flex flex-col justify-center items-center"
|
class="bg-white px-16px py-12px text-[#94a3b8] flex flex-col justify-center items-center"
|
||||||
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600 text-center">平均关闭时长</p>
|
<p class="text-12px font-600 text-center">平均关闭时长</p>
|
||||||
<p class="mt-2 text-[40px] font-bold leading-none text-[#1D2129] text-center">8.2</p>
|
<p class="mt-2 text-[40px] font-bold leading-none text-[#1D2129] text-center">{{homeData?.gauges?.avg_close_days || 0}}</p>
|
||||||
<p class="text-12px text-center mt-1">天 · 优于行业均值</p>
|
<p class="text-12px text-center mt-1">天 · 优于行业均值</p>
|
||||||
<span class="mt-2 rounded-full bg-[var(--theme-bg)] px-2 py-0.5 text-[11px] font-medium text-[var(--theme-color)]">环比 ↓ 0.6 天</span>
|
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
|
|
||||||
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:12">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
|
<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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">月度变更数量</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<ColumnChart :data="changeData" style="height: 220px" />
|
<ColumnChart v-if="homeData" :data="homeData?.monthly_counts || []" />
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:6">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
|
<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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">变更类型分布</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<RingChart :data="DEPT_ACCEPT" :colors="ACCEPT_COLORS" />
|
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:6">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
||||||
:style="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
@@ -158,15 +131,31 @@ const DEFAULT_EXPORTS = ["变更数量统计", "变更周期分析", "超期预
|
|||||||
<p class=" text-base font-semibold border-b border-slate-100 p-3">变更效率指标</p>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">变更效率指标</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<div class="space-y-5">
|
<div class="space-y-5">
|
||||||
<div v-for="r in DEFAULT_EFF" :key="r.label">
|
<div>
|
||||||
<div class="flex items-baseline gap-2">
|
<div class="flex items-baseline gap-2">
|
||||||
<span class="text-2xl font-bold text-[#1D2129]">{{ r.pct }}<span class="text-sm font-semibold">%</span></span>
|
<span class="text-2xl font-bold text-[#1D2129]">{{homeData?.eff?.ontime_use_rate || 0}}<span class="text-sm font-semibold">%</span></span>
|
||||||
<span class="text-xs text-slate-400">{{ r.label }}</span>
|
<span class="text-xs text-slate-400">按期投用率</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1.5 h-1 w-full overflow-hidden rounded-full bg-slate-100">
|
<div class="mt-1.5 h-1 w-full overflow-hidden rounded-full bg-slate-100">
|
||||||
<div v-if="r.label==='按期投用率'" class="h-full rounded-full bg-[var(--theme-color)]" :style="{ width: `${r.pct}%` }"></div>
|
<div class="h-full rounded-full bg-[var(--theme-color)]" :style="{ width: `${homeData?.eff?.ontime_use_rate || 0}%` }"></div>
|
||||||
<div v-if="r.label==='培训按期完成率'" class="h-full rounded-full bg-[var(--warning-color)]" :style="{ width: `${r.pct}%` }"></div>
|
</div>
|
||||||
<div v-if="r.label==='超期变更占比'" class="h-full rounded-full bg-[var(--error-color)]" :style="{ width: `${r.pct}%` }"></div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-baseline gap-2">
|
||||||
|
<span class="text-2xl font-bold text-[#1D2129]">{{homeData?.eff?.train_upload_rate || 0}}<span class="text-sm font-semibold">%</span></span>
|
||||||
|
<span class="text-xs text-slate-400">培训按期完成率</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1.5 h-1 w-full overflow-hidden rounded-full bg-slate-100">
|
||||||
|
<div class="h-full rounded-full bg-[var(--warning-color)]" :style="{ width: `${homeData?.eff?.train_upload_rate || 0}%` }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-baseline gap-2">
|
||||||
|
<span class="text-2xl font-bold text-[#1D2129]">{{homeData?.eff?.overdue_rate || 0}}<span class="text-sm font-semibold">%</span></span>
|
||||||
|
<span class="text-xs text-slate-400">超期变更占比</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1.5 h-1 w-full overflow-hidden rounded-full bg-slate-100">
|
||||||
|
<div class="h-full rounded-full bg-[var(--error-color)]" :style="{ width: `${homeData?.eff?.overdue_rate || 0}%` }"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-[11px] text-slate-400">近一年 · 全厂口径 · 超期占比越低越好</div>
|
<div class="text-[11px] text-slate-400">近一年 · 全厂口径 · 超期占比越低越好</div>
|
||||||
@@ -176,12 +165,12 @@ const DEFAULT_EXPORTS = ["变更数量统计", "变更周期分析", "超期预
|
|||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
|
|
||||||
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:24 l:16">
|
<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%' }">
|
<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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">平均审批周期趋势(天)</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<ColumnChart :data="changeData" style="height: 220px" />
|
<ColumnChart2 v-if="homeData" :data="homeData?.cycle_trend || []" />
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -197,7 +186,7 @@ const DEFAULT_EXPORTS = ["变更数量统计", "变更周期分析", "超期预
|
|||||||
<p class=" text-base font-semibold border-b border-slate-100 p-3">报表导出</p>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">报表导出</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<div v-for="r in DEFAULT_EXPORTS" :key="r" @click="message.success(`演示:${r}已导出 Excel`)"
|
<div v-for="r in DEFAULT_EXPORTS" :key="r" @click="exportData(r)"
|
||||||
class="cursor-pointer flex items-center gap-2 rounded-lg border px-3 py-2.5 text-sm hover:border-[var(--theme-color)] hover:bg-[var(--theme-bg)]">
|
class="cursor-pointer flex items-center gap-2 rounded-lg border px-3 py-2.5 text-sm hover:border-[var(--theme-color)] hover:bg-[var(--theme-bg)]">
|
||||||
<Icon icon="akar-icons:file" class="size-16px text-slate-400" />
|
<Icon icon="akar-icons:file" class="size-16px text-slate-400" />
|
||||||
{{ r }}
|
{{ r }}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { ref } from 'vue';
|
|||||||
import { useEcharts } from '@/hooks/common/echarts';
|
import { useEcharts } from '@/hooks/common/echarts';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'RingChart'
|
name: 'ColumnChart'
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<{ data: { name: string; value: number; }[]; }>();
|
const props = defineProps<{ data: { month: string; count: number; }[]; }>();
|
||||||
|
|
||||||
const { domRef, updateOptions } = useEcharts(() => ({
|
const { domRef, updateOptions } = useEcharts(() => ({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@@ -31,8 +31,8 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
top: "5%",
|
top: "5%",
|
||||||
left: "5%",
|
left: "0",
|
||||||
right: "5%",
|
right: "0",
|
||||||
bottom: "11%",
|
bottom: "11%",
|
||||||
},
|
},
|
||||||
calculable: true,
|
calculable: true,
|
||||||
@@ -44,7 +44,8 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
interval: 0,
|
interval: 'auto',
|
||||||
|
hideOverlap: true
|
||||||
},
|
},
|
||||||
splitLine: { show: false },
|
splitLine: { show: false },
|
||||||
axisTick: { show: false },
|
axisTick: { show: false },
|
||||||
@@ -61,7 +62,7 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: "bar",
|
type: "bar",
|
||||||
barWidth: 45,
|
barWidth: 30,
|
||||||
barGap: "10%",
|
barGap: "10%",
|
||||||
color: "#2080f0",
|
color: "#2080f0",
|
||||||
label: { show: true, position: 'top',color: '#333',fontWeight: 'bold' },
|
label: { show: true, position: 'top',color: '#333',fontWeight: 'bold' },
|
||||||
@@ -72,8 +73,8 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
updateOptions(opts => {
|
updateOptions(opts => {
|
||||||
let allMonth = props.data.map((item:any) => item.name);
|
let allMonth = props.data.map((item:any) => item.month);
|
||||||
let values = props.data.map((item:any) => item.value);
|
let values = props.data.map((item:any) => item.count);
|
||||||
opts.xAxis[0].data = allMonth;
|
opts.xAxis[0].data = allMonth;
|
||||||
opts.series[0].data = values;
|
opts.series[0].data = values;
|
||||||
return opts;
|
return opts;
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useEcharts } from '@/hooks/common/echarts';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'ColumnChart2'
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps<{ data: { month: string; days: 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: "10%",
|
||||||
|
left: "0",
|
||||||
|
right: "0",
|
||||||
|
bottom: "11%",
|
||||||
|
},
|
||||||
|
calculable: true,
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
boundaryGap: true, // 新增,固定柱子位置
|
||||||
|
axisLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
interval: 'auto',
|
||||||
|
hideOverlap: true
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
axisTick: { show: false },
|
||||||
|
splitArea: { show: false },
|
||||||
|
data: [] as string[],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: "value",
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: "bar",
|
||||||
|
barWidth: 30,
|
||||||
|
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.month);
|
||||||
|
let values = props.data.map((item:any) => item.days);
|
||||||
|
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>
|
||||||
@@ -6,7 +6,8 @@ defineOptions({
|
|||||||
name: 'RingChart'
|
name: 'RingChart'
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<{ data?: { name: string; value: number }[]; colors?: string[] }>();
|
const props = defineProps<{ data?: { name: string; value: number }[]; }>();
|
||||||
|
const chartData = ref<{ name: string; value: number; }[]>([]);
|
||||||
|
|
||||||
const { domRef, updateOptions } = useEcharts(() => ({
|
const { domRef, updateOptions } = useEcharts(() => ({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@@ -22,9 +23,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|总数}`,
|
text: '',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
rich: {
|
rich: {
|
||||||
a: {
|
a: {
|
||||||
@@ -50,7 +70,7 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
color: ['#5da8ff', '#8e9dff', '#fedc69', '#26deca'],
|
color: ["#2080f0", "#7CC242", "#F5A623"],
|
||||||
radius: ['45%', '65%'],
|
radius: ['45%', '65%'],
|
||||||
center: ['50%', '45%'],
|
center: ['50%', '45%'],
|
||||||
avoidLabelOverlap: false,
|
avoidLabelOverlap: false,
|
||||||
@@ -72,8 +92,14 @@ 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) => ({
|
||||||
|
name: item.label,
|
||||||
|
value: item.count,
|
||||||
|
}));
|
||||||
|
chartData.value = result || [];
|
||||||
|
opts.title.text = `{a|${total}}\n{b|总数}`;
|
||||||
|
opts.series[0].data = result || [];
|
||||||
return opts;
|
return opts;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,9 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
interval: 0,
|
interval: 'auto',
|
||||||
rotate: 50,
|
hideOverlap: true
|
||||||
|
// rotate: 50,
|
||||||
},
|
},
|
||||||
splitLine: { show: false },
|
splitLine: { show: false },
|
||||||
axisTick: { show: false },
|
axisTick: { show: false },
|
||||||
|
|||||||
@@ -1,41 +1,50 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
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 { useRouterPush } from '@/hooks/common/router';
|
||||||
import { getColorWithOpacity } from '@/utils/common';
|
import { getColorWithOpacity } from '@/utils/common';
|
||||||
import RingChart from './modules/ringChart.vue';
|
import RingChart from './modules/ringChart.vue';
|
||||||
|
import { specialtyStatisticsApi } from '@/service/api/statistics';
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
const { routerPushByKey } = useRouterPush();
|
||||||
|
|
||||||
const profEff = ref<{d: string, days: number, me?: boolean, target?: boolean}[]>([
|
const loading = ref<boolean>(false);
|
||||||
{ d: `陈质量(本人)`, days: 1.8, me: true },
|
const homeData = ref<any>(null);
|
||||||
{ d: "仪表专业均值", days: 2.1, me: false },
|
|
||||||
{ d: "设备专业均值", days: 2.2, me: false },
|
|
||||||
{ d: "工艺专业均值", days: 2.6, me: false },
|
|
||||||
{ d: "考核目标", days: 2.5, me: false, target: true },
|
|
||||||
]);
|
|
||||||
const DEPT_ACCEPT = [
|
|
||||||
{ name: "工艺", value: 52 },
|
|
||||||
{ name: "设备", value: 49 },
|
|
||||||
{ name: "管理", value: 25 },
|
|
||||||
];
|
|
||||||
const ACCEPT_COLORS = ["#2080f0", "#7CC242", "#F5A623"];
|
|
||||||
|
|
||||||
const profHighRisk = [
|
// 跳转页面
|
||||||
{ id: "MOC-2026-R01-0035", status: '审批中', title: 'R-201 反应釜搅拌器电机更换(55kW→75kW 防爆电机)' },
|
const jumpTo = (key: string) => {
|
||||||
{ id: "MOC-2026-R01-0036", status: '审批中', title: '聚合反应温度控制上限调整(78℃→82℃)' },
|
routerPushByKey(key as any);
|
||||||
{ id: "MOC-2026-R01-0037", status: '实施中', title: '原料环己烷供应商变更(新增 B 供应商)' },
|
}
|
||||||
];
|
// 小时转换天
|
||||||
|
const hourToDay = (hour: number) => {
|
||||||
|
return Math.trunc((hour / 24) * 100) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取数据
|
||||||
|
const getData = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const {data,error} = await specialtyStatisticsApi({
|
||||||
|
from: null,
|
||||||
|
to: null,
|
||||||
|
});
|
||||||
|
if(!error){
|
||||||
|
homeData.value = data;
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getData();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<NSpin :show="loading" size="small">
|
||||||
<NSpace vertical :size="16">
|
<NSpace vertical :size="16">
|
||||||
<!-- 卡片数据 -->
|
<!-- 卡片数据 -->
|
||||||
<NGrid cols="s:1 m:5 l:5" responsive="screen" :x-gap="gap" :y-gap="16"
|
<NGrid cols="s:1 m:5 l:5" responsive="screen" :x-gap="16" :y-gap="16"
|
||||||
:style="{
|
:style="{
|
||||||
'--theme-color': themeStore.themeColor,
|
'--theme-color': themeStore.themeColor,
|
||||||
'--success-color': themeStore.otherColor.success,
|
'--success-color': themeStore.otherColor.success,
|
||||||
@@ -51,7 +60,7 @@ const profHighRisk = [
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="6"
|
:end-value="homeData?.my_stats?.month_signed || 0"
|
||||||
class="text-24px font-600 text-[#333]"
|
class="text-24px font-600 text-[#333]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,12 +76,12 @@ const profHighRisk = [
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="1.8"
|
:end-value="homeData?.my_stats?.avg_audit_days || 0"
|
||||||
:decimals="1"
|
:decimals="1"
|
||||||
class="text-26px font-600 text-[var(--success-color)]"
|
class="text-26px font-600 text-[var(--success-color)]"
|
||||||
/><p class="text-26px font-600 text-[var(--success-color)]">天</p>
|
/><p class="text-26px font-600 text-[var(--success-color)]">天</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-12px">口径:签收→签出时长 · 目标 ≤2.5 天</p>
|
<p class="text-12px">口径:签收→签出时长 · 目标 ≤{{hourToDay(homeData?.my_stats?.sign_timely_hours || 0)}} 天</p>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
@@ -84,7 +93,7 @@ const profHighRisk = [
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="95"
|
:end-value="homeData?.my_stats?.timely_rate || 0"
|
||||||
class="text-26px font-600 text-[var(--success-color)]"
|
class="text-26px font-600 text-[var(--success-color)]"
|
||||||
/><p class="text-26px font-600 text-[var(--success-color)]">%</p>
|
/><p class="text-26px font-600 text-[var(--success-color)]">%</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,11 +109,11 @@ const profHighRisk = [
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="2"
|
:end-value="homeData?.my_stats?.risk_analysis_count || 0"
|
||||||
class="text-26px font-600 text-[var(--theme-color)]"
|
class="text-26px font-600 text-[var(--theme-color)]"
|
||||||
/><p class="text-26px font-600 text-[var(--theme-color)]">次</p>
|
/><p class="text-26px font-600 text-[var(--theme-color)]">次</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-12px">口径:HAZOP / JSA 分析组成员(选择式)记录</p>
|
<p class="text-12px">口径:HAZOP / FMEA 分析组成员(选择式)记录</p>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
@@ -116,7 +125,7 @@ const profHighRisk = [
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="1"
|
:end-value="homeData?.my_stats?.todo_total || 0"
|
||||||
class="text-26px font-600 text-[var(--warning-color)]"
|
class="text-26px font-600 text-[var(--warning-color)]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,7 +134,7 @@ const profHighRisk = [
|
|||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
|
|
||||||
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
||||||
:style="{
|
:style="{
|
||||||
@@ -143,13 +152,23 @@ const profHighRisk = [
|
|||||||
<p class=" text-base font-semibold border-b border-slate-100 p-3">会签效率对比(考核 · 汇报)</p>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">会签效率对比(考核 · 汇报)</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div v-for="e in profEff" :key="e.d">
|
<div v-for="e in homeData?.approver_time || []" :key="e">
|
||||||
<div class="mb-1 flex items-center justify-between text-xs">
|
<div class="mb-1 flex items-center justify-between text-xs">
|
||||||
<span :class="e.me ? 'font-semibold text-[var(--theme-color)]' : 'text-slate-600'">{{ e.d }}</span>
|
<span class="text-slate-600">{{ e.user_name }}{{e.step_name?'('+e.step_name+')':''}}</span>
|
||||||
<span :class="e.target ? 'text-slate-400' : e.days > 2.5 ? 'font-medium text-[var(--error-color)]' : 'text-slate-500'">{{ e.days }} 天{{ e.target ? "(标线)" : "" }}</span>
|
<span :class="e.avg_days > 2.5 ? 'font-medium text-[var(--error-color)]' : 'text-slate-500'">{{ e.avg_days }} 天</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-2 overflow-hidden rounded-full bg-slate-100">
|
<div class="h-2 overflow-hidden rounded-full bg-slate-100">
|
||||||
<div class="h-full rounded-full" :class="e.target ? 'bg-slate-300' : e.me ? 'bg-[var(--theme-color)]' : e.days > 2.5 ? 'bg-[var(--error-color)]' : 'bg-[#6E93C6]'" :style="{ width: `${Math.min(100, (e.days / 4) * 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>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-[11px] text-slate-400">考核数据来源:节点签收 → 签出时长自动统计;连续两月超标将纳入绩效面谈</p>
|
<p class="text-[11px] text-slate-400">考核数据来源:节点签收 → 签出时长自动统计;连续两月超标将纳入绩效面谈</p>
|
||||||
@@ -161,7 +180,7 @@ const profHighRisk = [
|
|||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
|
<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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">本专业变更类型分布(近一年)</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<RingChart :data="DEPT_ACCEPT" :colors="ACCEPT_COLORS" />
|
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -169,21 +188,33 @@ const profHighRisk = [
|
|||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
|
<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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">高风险在办(直达风险分析)</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<div class="space-y-2 pt-1">
|
<div v-if="homeData?.high_risk?.total > 0" class="space-y-2 pt-1">
|
||||||
<div v-for="c in profHighRisk" :key="c.id" @click="emit('go', 'risk')" 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">
|
<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="min-w-0 flex-1">
|
||||||
<span class="block truncate text-sm font-medium text-slate-800">{{ c.title }}</span>
|
<span class="block truncate text-sm font-medium text-slate-800">{{ c.change_title }}</span>
|
||||||
<span class="text-[11px] text-slate-400">{{ c.id }} · {{ c.status }}</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>
|
</span>
|
||||||
<ChevronRight class="h-3.5 w-3.5 shrink-0 text-slate-300" />
|
<Icon icon="formkit:right" class="size-14px text-slate-300" />
|
||||||
</div>
|
</div>
|
||||||
<p class="text-[11px] text-slate-400">更多高风险场景与分析记录见「高风险」页</p>
|
<p class="text-[11px] text-slate-400">更多高风险场景与分析记录见「高风险」页</p>
|
||||||
</div>
|
</div>
|
||||||
|
<p v-else class="text-center text-xs text-slate-400">暂无高风险在办</p>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
|
</NSpin>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ defineOptions({
|
|||||||
name: 'RingChart'
|
name: 'RingChart'
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<{ data?: { name: string; value: number }[]; colors?: string[] }>();
|
const props = defineProps<{ data?: { name: string; value: number }[] }>();
|
||||||
|
const chartData = ref<{ name: string; value: number; }[]>([]);
|
||||||
|
|
||||||
const { domRef, updateOptions } = useEcharts(() => ({
|
const { domRef, updateOptions } = useEcharts(() => ({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@@ -22,9 +23,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|39}\n{b|本专业变更}`,
|
text: '',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
rich: {
|
rich: {
|
||||||
a: {
|
a: {
|
||||||
@@ -50,7 +70,7 @@ const { domRef, updateOptions } = useEcharts(() => ({
|
|||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
color: ['#5da8ff', '#8e9dff', '#fedc69', '#26deca'],
|
color: ["#2080f0", "#7CC242", "#F5A623"],
|
||||||
radius: ['45%', '65%'],
|
radius: ['45%', '65%'],
|
||||||
center: ['50%', '45%'],
|
center: ['50%', '45%'],
|
||||||
avoidLabelOverlap: false,
|
avoidLabelOverlap: false,
|
||||||
@@ -72,8 +92,14 @@ 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) => ({
|
||||||
|
name: item.label,
|
||||||
|
value: item.count,
|
||||||
|
}));
|
||||||
|
chartData.value = result || [];
|
||||||
|
opts.title.text = `{a|${total}}\n{b|本专业变更}`;
|
||||||
|
opts.series[0].data = result || [];
|
||||||
return opts;
|
return opts;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
import RingChart from './modules/ringChart.vue';
|
import RingChart from './modules/ringChart.vue';
|
||||||
import { systemConfigApi } from "@/service/api/system";
|
import { systemConfigApi } from "@/service/api/system";
|
||||||
import { deptStatisticsApi } from '@/service/api/statistics';
|
import { deptStatisticsApi } from '@/service/api/statistics';
|
||||||
|
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
|
const { routerPushByKey } = useRouterPush();
|
||||||
const DEPT_ACCEPT = [
|
|
||||||
{ name: "工艺", value: 52 },
|
|
||||||
{ name: "设备", value: 49 },
|
|
||||||
{ name: "管理", value: 25 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
// 变更临期天数
|
// 变更临期天数
|
||||||
const remindDays = ref<number>(0);
|
const remindDays = ref<number>(0);
|
||||||
const homeData = ref<any>(null);
|
const homeData = ref<any>(null);
|
||||||
|
|
||||||
|
// 跳转页面
|
||||||
|
const jumpTo = (key: string) => {
|
||||||
|
routerPushByKey(key as any);
|
||||||
|
}
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -27,7 +28,6 @@ const getData = async () => {
|
|||||||
});
|
});
|
||||||
if(!error){
|
if(!error){
|
||||||
homeData.value = data;
|
homeData.value = data;
|
||||||
console.log(data);
|
|
||||||
}
|
}
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
@@ -204,7 +204,7 @@ onMounted(() => {
|
|||||||
<span class="ml-auto font-semibold text-slate-600">{{homeData?.kpis?.overdue_total || 0}}</span>
|
<span class="ml-auto font-semibold text-slate-600">{{homeData?.kpis?.overdue_total || 0}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NButton size="small" ghost dashed block class="text-xs text-slate-400">前往变更台账查看处置 →</NButton>
|
<NButton size="small" ghost dashed block class="text-xs text-slate-400" @click="jumpTo('changeledger')">前往变更台账查看处置 →</NButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
@@ -221,7 +221,7 @@ onMounted(() => {
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<p class=" text-base font-semibold border-b border-slate-100 p-3">变更类型分布(近一年)</p>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">变更类型分布(近一年)</p>
|
||||||
<RingChart :data="DEPT_ACCEPT" />
|
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
|
||||||
</NCard>
|
</NCard>
|
||||||
|
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -281,9 +281,10 @@ onMounted(() => {
|
|||||||
'--success-color': themeStore.otherColor.success,
|
'--success-color': themeStore.otherColor.success,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between p-3">
|
<div class="flex items-center p-3 border-b border-slate-100">
|
||||||
<p class=" text-base font-semibold border-b border-slate-100">变更投入和效果(近一年 · 已验收变更)</p>
|
<p class=" text-base font-semibold">变更投入和效果(近一年 · 已验收变更)</p>
|
||||||
<p class="text-[11px] text-slate-400">
|
<p class="text-11px text-slate-400 mt-3px">费用取验收时填报的「变更材料和实施费」;完整明细见操作台「变更投入和效果」</p>
|
||||||
|
<p class="text-[11px] text-slate-400 ml-auto">
|
||||||
已验收 <b class="text-slate-700">3</b> 起 ·
|
已验收 <b class="text-slate-700">3</b> 起 ·
|
||||||
投入合计 <b :style="{color:themeStore.themeColor}">11.5</b> 万元 ·
|
投入合计 <b :style="{color:themeStore.themeColor}">11.5</b> 万元 ·
|
||||||
完全达到率 <b :style="{color:themeStore.otherColor.success}">67%</b>
|
完全达到率 <b :style="{color:themeStore.otherColor.success}">67%</b>
|
||||||
@@ -293,22 +294,42 @@ onMounted(() => {
|
|||||||
<NTable size="small">
|
<NTable size="small">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>变更名称 / 编号</td>
|
<th>变更名称 / 编号</th>
|
||||||
<td>申请日期</td>
|
<th>申请日期</th>
|
||||||
<td>投用日期</td>
|
<th>投用日期</th>
|
||||||
<td>验收日期</td>
|
<th>验收日期</th>
|
||||||
<td>费用(万元)</td>
|
<th>费用(万元)</th>
|
||||||
<td>验收结论</td>
|
<th>验收结论</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
<td>
|
<td>
|
||||||
|
<p>氮气管网压力分级调整</p>
|
||||||
|
<span class="text-11px text-slate-400">MOC-2026-R01-0024</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-xs">2026-07-08</td>
|
||||||
|
<td class="text-xs">2026-07-08</td>
|
||||||
|
<td class="text-xs">2026-07-08</td>
|
||||||
|
<td class="text-xs"><span class="font-600">11.5</span></td>
|
||||||
|
<td>
|
||||||
|
<NTag size="small" type="success" class="text-xs">完全达到</NTag>
|
||||||
|
<!-- <NTag v-if="r.result_status===3" size="small" type="success">{{r.result_label}}</NTag>
|
||||||
|
<NTag v-if="r.result_status===2" size="small" type="warning">{{r.result_label}}</NTag>
|
||||||
|
<NTag v-if="r.result_status===1" size="small" type="error">{{r.result_label}}</NTag> -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p>氮气管网压力分级调整</p>
|
||||||
|
<span class="text-11px text-slate-400">MOC-2026-R01-0024</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-xs">2026-07-08</td>
|
||||||
|
<td class="text-xs">2026-07-08</td>
|
||||||
|
<td class="text-xs">2026-07-08</td>
|
||||||
|
<td class="text-xs"><span class="font-600">11.5</span></td>
|
||||||
|
<td>
|
||||||
|
<NTag size="small" type="success" class="text-xs">完全达到</NTag>
|
||||||
<!-- <NTag v-if="r.result_status===3" size="small" type="success">{{r.result_label}}</NTag>
|
<!-- <NTag v-if="r.result_status===3" size="small" type="success">{{r.result_label}}</NTag>
|
||||||
<NTag v-if="r.result_status===2" size="small" type="warning">{{r.result_label}}</NTag>
|
<NTag v-if="r.result_status===2" size="small" type="warning">{{r.result_label}}</NTag>
|
||||||
<NTag v-if="r.result_status===1" size="small" type="error">{{r.result_label}}</NTag> -->
|
<NTag v-if="r.result_status===1" size="small" type="error">{{r.result_label}}</NTag> -->
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ defineOptions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<{ data?: { name: string; value: number }[]; }>();
|
const props = defineProps<{ data?: { name: string; value: number }[]; }>();
|
||||||
|
const chartData = ref<{ name: string; value: number; }[]>([]);
|
||||||
|
|
||||||
const { domRef, updateOptions } = useEcharts(() => ({
|
const { domRef, updateOptions } = useEcharts(() => ({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@@ -22,9 +23,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|近一年}`,
|
text: '',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
rich: {
|
rich: {
|
||||||
a: {
|
a: {
|
||||||
@@ -72,7 +92,14 @@ 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);
|
||||||
|
const result:any = props.data?.map((item:any) => ({
|
||||||
|
name: item.label,
|
||||||
|
value: item.count,
|
||||||
|
}));
|
||||||
|
chartData.value = result || [];
|
||||||
|
opts.title.text = `{a|${total}}\n{b|近一年}`;
|
||||||
|
opts.series[0].data = result || [];
|
||||||
return opts;
|
return opts;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user