Files
moc/src/views/professionalStatistics/index.vue
T

225 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { useRouterPush } from '@/hooks/common/router';
import { getColorWithOpacity } from '@/utils/common';
import RingChart from './modules/ringChart.vue';
import { specialtyStatisticsApi } from '@/service/api/statistics';
const themeStore = useThemeStore();
const { routerPushByKey } = useRouterPush();
const loading = ref<boolean>(false);
const homeData = ref<any>(null);
// 跳转页面
const jumpTo = (key: string) => {
routerPushByKey(key as any);
}
// 小时转换天
const hourToDay = (hour: number) => {
return Math.trunc((hour / 24) * 100) / 100;
}
// 获取数据
const getData = async () => {
loading.value = true;
const {data,error} = await specialtyStatisticsApi({
from: null,
to: null,
});
if(!error){
homeData.value = data;
}
loading.value = false;
}
onMounted(() => {
getData();
});
</script>
<template>
<NSpin :show="loading" size="small">
<NSpace vertical :size="16">
<!-- 卡片数据 -->
<NGrid cols="s:1 m:5 l:5" responsive="screen" :x-gap="16" :y-gap="16"
:style="{
'--theme-color': themeStore.themeColor,
'--success-color': themeStore.otherColor.success,
'--warning-color': themeStore.otherColor.warning,
}"
>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">本月会签 / 审批</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.my_stats?.month_signed || 0"
class="text-24px font-600 text-[#333]"
/>
</div>
<p class="text-12px">口径:节点签出记录</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">我的会签平均耗时</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.my_stats?.avg_audit_days || 0"
:decimals="1"
class="text-26px font-600 text-[var(--success-color)]"
/><p class="text-26px font-600 text-[var(--success-color)]">天</p>
</div>
<p class="text-12px">口径:签收→签出时长 · 目标 ≤{{hourToDay(homeData?.my_stats?.sign_timely_hours || 0)}} 天</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">按时完成率</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.my_stats?.timely_rate || 0"
class="text-26px font-600 text-[var(--success-color)]"
/><p class="text-26px font-600 text-[var(--success-color)]">%</p>
</div>
<p class="text-12px">考核:目标 ≥90%</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">参与风险分析</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.my_stats?.risk_analysis_count || 0"
class="text-26px font-600 text-[var(--theme-color)]"
/><p class="text-26px font-600 text-[var(--theme-color)]">次</p>
</div>
<p class="text-12px">口径:HAZOP / FMEA 分析组成员(选择式)记录</p>
</div>
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">当前待我审批</p>
<div class="flex items-center">
<CountTo
:start-value="0"
:end-value="homeData?.my_stats?.todo_total || 0"
class="text-26px font-600 text-[var(--warning-color)]"
/>
</div>
<p class="text-12px">实时 · 与工作台同源</p>
</div>
</NGi>
</NGrid>
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
'--theme-color': themeStore.themeColor,
'--error-color': themeStore.otherColor.error,
'--warning-color': themeStore.otherColor.warning,
'--success-color': themeStore.otherColor.success,
'--error-bg': getColorWithOpacity(themeStore.otherColor.error,0.1),
'--warning-bg': getColorWithOpacity(themeStore.otherColor.warning,0.1),
'--theme-bg': getColorWithOpacity(themeStore.themeColor,0.1),
}"
>
<p class=" text-base font-semibold border-b border-slate-100 p-3">会签效率对比(考核 · 汇报)</p>
<div class="py-3 px-4">
<div class="space-y-3">
<div v-for="e in homeData?.approver_time || []" :key="e">
<div class="mb-1 flex items-center justify-between text-xs">
<span class="text-slate-600">{{ e.user_name }}{{e.step_name?'('+e.step_name+')':''}}</span>
<span :class="e.avg_days > 2.5 ? 'font-medium text-[var(--error-color)]' : 'text-slate-500'">{{ e.avg_days }} 天</span>
</div>
<div class="h-2 overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full" :class="e.avg_days > hourToDay(homeData?.my_stats?.sign_timely_hours || 0) ? 'bg-[var(--error-color)]' : 'bg-[var(--theme-color)]'" :style="{ width: `${Math.min(100, (e.avg_days / e.total) * 100)}%` }"></div>
</div>
</div>
<div v-if="homeData?.approver_time?.length === 0" class="h-100% flex items-center justify-center">
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
</div>
<div>
<div class="mb-1 flex items-center justify-between text-xs">
<span class="text-slate-600">考核目标</span>
<span class="text-slate-500">{{hourToDay(homeData?.my_stats?.sign_timely_hours || 0)}} 天(标线)</span>
</div>
<div class="h-2 overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full bg-slate-300" :style="{ width: `${Math.min(100, (hourToDay(homeData?.my_stats?.sign_timely_hours || 0) / hourToDay(homeData?.my_stats?.sign_timely_hours || 0)) * 100)}%` }"></div>
</div>
</div>
<p class="text-[11px] text-slate-400">考核数据来源:节点签收 → 签出时长自动统计;连续两月超标将纳入绩效面谈</p>
</div>
</div>
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">本专业变更类型分布(近一年)</p>
<div class="py-3 px-4">
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
</div>
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">高风险在办(直达风险分析)</p>
<div class="py-3 px-4">
<div v-if="homeData?.high_risk?.total > 0" class="space-y-2 pt-1">
<div v-for="c in homeData?.high_risk?.items || []" :key="c.id" @click="jumpTo('my_highrisk')" class="cursor-pointer flex w-full items-center gap-2 rounded-lg border px-3 py-2 text-left hover:border-red-300 hover:bg-red-50/30">
<span class="min-w-0 flex-1">
<span class="block truncate text-sm font-medium text-slate-800">{{ c.change_title }}</span>
<div class="flex items-center gap-2 text-[11px] text-slate-400 mt-1">
<span>{{ c.change_no }}</span>
<!-- 状态标签 -->
<span v-if="c.change_status==='APPROVING'">审批中</span>
<span v-if="c.change_status==='APPROVED'">已通过</span>
<span v-if="c.change_status==='IMPLEMENTED'">待验收</span>
<span v-if="c.change_status==='ACCEPTED'">待关闭</span>
<span v-if="c.change_status==='CLOSED'">已关闭</span>
<span v-if="c.change_status==='WITHDRAWN'">已撤回</span>
<span v-if="c.change_status==='REJECTED'">已驳回</span>
</div>
</span>
<Icon icon="formkit:right" class="size-14px text-slate-300" />
</div>
<p class="text-[11px] text-slate-400">更多高风险场景与分析记录见「高风险」页</p>
</div>
<div v-else class="h-100% flex items-center justify-center">
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
</div>
</div>
</NCard>
</NGi>
</NGrid>
</NSpace>
</NSpin>
</template>
<style scoped></style>