Compare commits

...
20 Commits
Author SHA1 Message Date
bestlee f97f2b4a87 系统管理员首页修改 2026-10-26 09:29:38 +08:00
bestlee eb76e7aeae 发起变更样式调整 2026-10-19 10:37:13 +08:00
bestlee 43b2fc309c 字体大小修改 2026-10-19 10:25:24 +08:00
bestlee 4f5100552c 样式调整 2026-10-19 10:20:53 +08:00
bestlee b1bd09293d 部分样式修改 2026-10-19 10:11:16 +08:00
bestlee b4b0c1bb42 文字大小修改 2026-10-19 09:21:38 +08:00
bestlee 89271e545b 我的任务-按钮显示修改 2026-10-16 16:55:12 +08:00
bestlee dadffd5bde 发起变更样式文字调整 2026-10-16 14:28:11 +08:00
bestlee ba8f61d752 审批流程设置-专业会签勾选问题修改 2026-10-16 10:12:23 +08:00
bestlee 453d747ce2 变更申请表-上传资料回显问题修改 2026-10-13 14:14:50 +08:00
bestlee 735d64c869 变更归档查询右侧数据为空显示问题 2026-10-13 13:11:10 +08:00
bestlee 0b4f8f3404 预览文件统一修改 2026-10-13 12:04:41 +08:00
bestlee 3d765d2243 公用文件修改 2026-10-13 11:47:49 +08:00
bestlee b6ee63299e 下载文件名称统一修改 2026-10-13 11:45:45 +08:00
bestlee 7797b4479d 预览文件添加地址前缀 2026-10-13 11:39:21 +08:00
bestlee 91536b193c 资料档案库添加删除文件功能 2026-10-13 10:48:05 +08:00
bestlee 842f7f0941 详情页-风险评估方法显示修改 2026-10-13 10:00:18 +08:00
bestlee f355f6fe11 发起变更-变更预识别文字描述修改 2026-10-12 14:43:08 +08:00
bestlee 161354c584 Merge branch 'dev' of https://git.djxinxi.net/bestlee/MOC into dev 2026-10-12 13:30:14 +08:00
bestlee 2d6973147b 发起变更-等级变更判断修改 2026-10-12 13:30:03 +08:00
36 changed files with 4136 additions and 3815 deletions
+10 -5
View File
@@ -5,6 +5,7 @@ import 'vue3-office-preview/lib/style.css'
const props = defineProps(['previewFile',])
const currentOrigin = window.location.origin;
// 判断文件类型
const handleFileType = (type:string) => {
if(type){
@@ -37,25 +38,29 @@ const handleFileType = (type:string) => {
<!-- 图片预览 -->
<template v-if="handleFileType(previewFile?.file_type) === 'image'">
<div class="flex items-center justify-center py-2">
<NImage :src="previewFile?.file_url" />
<NImage :src="currentOrigin + previewFile?.file_url" />
</div>
</template>
<!-- Word 预览 -->
<template v-else-if="handleFileType(previewFile?.file_type) === 'word'">
<VueOfficeDocx :src="previewFile?.file_url" class="w-full items-center" />
<div class="h-[68vh]">
<VueOfficeDocx :src="currentOrigin + previewFile?.file_url" class="w-full items-center" />
</div>
</template>
<!-- Excel 预览 -->
<template v-else-if="handleFileType(previewFile?.file_type) === 'excel'">
<VueOfficeExcel :src="previewFile?.file_url" class="w-full items-center" />
<div class="h-[68vh]">
<VueOfficeExcel :src="currentOrigin + previewFile?.file_url" class="w-full items-center" />
</div>
</template>
<!-- Ppt 预览 -->
<template v-else-if="handleFileType(previewFile?.file_type) === 'ppt'">
<VueOfficePptx :src="previewFile?.file_url" class="w-full items-center" />
<VueOfficePptx :src="currentOrigin + previewFile?.file_url" class="w-full items-center" />
</template>
<!-- PDF 预览 -->
<template v-else-if="handleFileType(previewFile?.file_type) === 'pdf'">
<div class="h-[68vh]">
<PDF :src="previewFile?.file_url" class="w-full h-full items-center" />
<PDF :src="currentOrigin + previewFile?.file_url" class="w-full h-full items-center" />
</div>
</template>
<!-- 其他类型文件预览 -->
+8
View File
@@ -66,6 +66,14 @@ export function docHistoryListApi(doc_id:number) {
});
}
// 删除资料
export function deleteDocApi(id: number) {
return request({
url: `/api/knowledge/${id}`,
method: 'delete',
});
}
// 归档目录树
export function docArchiveTreeListApi(params: {from:string | number | null,to:string | number | null,org_id: number,type: number | string,change_no?:string}) {
return request({
+27 -112
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, computed, onMounted, h } from 'vue';
import { ref, onMounted, h } from 'vue';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { useRouterPush } from '@/hooks/common/router';
@@ -79,11 +79,6 @@ const isAll = ref<boolean>(true);
const currentRow = ref<any>(null);
// 进度抽屉
const progressDrawer = ref<boolean>(false);
// 撤回/未实施情况说明弹框
const revokeModal = ref<boolean>(false);
const modalType = ref<string>('');
const reason = ref<string>('');
const revokeSaving = ref<boolean>(false);
// 列表
const list = ref<any[]>([]);
@@ -235,40 +230,6 @@ const openProgress = (r: any) => {
currentRow.value = r;
progressDrawer.value = true;
};
// 撤回/未实施情况说明
const openModal = (r: any,type:string) => {
currentRow.value = r;
modalType.value = type;
revokeModal.value = true;
}
// 撤回/未实施情况说明提交
const revokeSave = async () => {
if (!reason.value) {
return window.$message?.warning(modalType.value === "withdraw" ? "请输入撤回理由" : "请输入未实施情况说明");
}
revokeSaving.value = true;
setTimeout(() => {
revokeSaving.value = false;
}, 1000);
}
// 下载文件
const downloadFiles = (r:any,type:string) => {
// 下载申请表
if(type==='apply'){
}
// 下载已验收待关闭
if(type==='accepted'){
}
// 下载归档包
if(type==='closed'){
}
window.$message?.info('还差接口');
}
// 分页改变
const pageChange = (page: number) => {
pagination.value.page = page;
@@ -437,17 +398,17 @@ onMounted(async () => {
'--theme-color': themeStore.themeColor
}"
>
<div class="flex flex-wrap items-start gap-3">
<div class="flex flex-wrap items-center gap-3">
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<span class="text-xs text-slate-300">{{ Number(i)+1 }}</span>
<span class="text-xs text-slate-500">{{ Number(i)+1 }}</span>
<p class="cursor-pointer text-left text-sm font-semibold text-slate-800 hover:text-[var(--theme-color)] hover:underline" @click="jumpTo(r)">
{{ r.title }}
</p>
<span class="text-xs text-slate-400">{{ r.change_no }}</span>
<div class="flex items-center gap-2">
<NTag size="small" v-if="r.urgent" type="error">紧急</NTag>
<span class="font-mono text-[11px] text-slate-400">{{ r.change_no }}</span>
</div>
<div class="mt-1.5 flex flex-wrap items-center gap-1.5">
<div class="flex flex-wrap items-center gap-1.5">
<!-- 类型 -->
<NTag
size="small"
@@ -474,7 +435,22 @@ onMounted(async () => {
<NTag v-if="r.duration_type===1" size="small" type="info">永久</NTag>
<NTag v-if="r.extend_count" size="small" type="warning">延期</NTag>
</div>
<div class="mt-1.5 flex flex-wrap gap-x-4 gap-y-0.5 text-[11px] text-slate-500">
<div class="flex flex-wrap items-center gap-2">
<!-- 状态标签 -->
<NTag v-if="r.status==='APPROVING'" size="small" type="warning">审批中</NTag>
<NTag v-if="r.status==='APPROVED'" size="small" type="success">已通过</NTag>
<NTag v-if="r.status==='IMPLEMENTED'" size="small" type="success">待验收</NTag>
<NTag v-if="r.status==='ACCEPTED'" size="small" type="error">待关闭</NTag>
<NTag v-if="r.status==='CLOSED'" size="small" type="success">已关闭</NTag>
<NTag v-if="r.status==='WITHDRAWN'" size="small" type="error">已撤回</NTag>
<NTag v-if="r.status==='REJECTED'" size="small" type="error">已驳回</NTag>
</div>
<!-- 超期 -->
<span v-if="r.overdue_status===2" class="text-xs font-medium text-red-500">超期 {{ r.overdue_days }} 天</span>
</div>
</div>
<div class="mt-1.5 flex flex-wrap gap-x-4 gap-y-0.5 text-xs text-slate-500">
<span v-if="r.apply_time">申请 {{ r.apply_time?.slice(0,16) }}</span>
<span>{{ r.org_name }} · {{ r.applicant_name }}</span>
<span v-if="r.plan_use_date">计划投用 {{ r.plan_use_date?.slice(0,16) }}</span>
@@ -485,35 +461,11 @@ onMounted(async () => {
</div>
</div>
<div class="flex shrink-0 flex-col items-end gap-1.5">
<div class="flex flex-wrap items-center justify-end gap-2">
<!-- 状态标签 -->
<NTag v-if="r.status==='APPROVING'" size="small" type="warning">审批中</NTag>
<NTag v-if="r.status==='APPROVED'" size="small" type="success">已通过</NTag>
<NTag v-if="r.status==='IMPLEMENTED'" size="small" type="success">待验收</NTag>
<NTag v-if="r.status==='ACCEPTED'" size="small" type="error">待关闭</NTag>
<NTag v-if="r.status==='CLOSED'" size="small" type="success">已关闭</NTag>
<NTag v-if="r.status==='WITHDRAWN'" size="small" type="error">已撤回</NTag>
<NTag v-if="r.status==='REJECTED'" size="small" type="error">已驳回</NTag>
<!-- 超期 -->
<span v-if="r.overdue_status===2" class="text-[11px] font-medium text-red-500">超期 {{ r.overdue_days }} 天</span>
</div>
<!-- 操作按钮 -->
<div class="flex flex-wrap items-center justify-end gap-1 pt-0.5">
<!-- 审批中 -->
<!-- <NButton v-if="r.status==='APPROVING'" size="small" ghost type="error" class="text-xs" @click="openModal(r,'withdraw')">撤回</NButton> -->
<!-- 已批准待实施 -->
<!-- <NButton v-if="r.status==='APPROVED'" size="small" ghost type="primary" class="text-xs" @click="openModal(r,'unimplemented')">未实施情况说明</NButton> -->
<!-- <NButton v-if="r.status==='APPROVED'" size="small" ghost type="success" class="text-xs" @click="downloadFiles(r,'apply')">下载申请表</NButton> -->
<!-- 已验收待关闭 -->
<!-- <NButton v-if="r.status==='ACCEPTED'" size="small" ghost type="primary" class="text-xs" @click="openModal(r,'unuse')">未投用情况说明</NButton> -->
<!-- <NButton v-if="r.status==='ACCEPTED'" size="small" ghost type="success" class="text-xs" @click="downloadFiles(r,'accepted')">下载</NButton> -->
<!-- 已关闭 -->
<!-- <NButton v-if="r.status==='CLOSED'" size="small" ghost type="success" class="text-xs" @click="downloadFiles(r,'closed')">下载归档包</NButton> -->
<NButton size="small" type="primary" class="text-xs" @click="jumpTo(r)">详情</NButton>
<NButton size="small" type="default" class="text-xs" @click="openProgress(r)">进度</NButton>
</div>
<!-- 临时变更 -->
<div v-if="r.duration_type === 2" class="text-right text-[10px] leading-4 text-slate-400">临时变更恢复 / 延期 / 转永久请在详情页中操作</div>
</div>
</div>
</div>
@@ -533,49 +485,12 @@ onMounted(async () => {
</template>
</NPagination>
</div>
<!-- 撤回/未实施情况说明/未投用情况说明弹框 -->
<NModal
v-model:show="revokeModal"
preset="card"
:auto-focus="false"
:style="{ width: '450px', height: 'auto' }"
:segmented="{ content: true, footer: true }"
>
<template #header>
<h3 class="text-sm font-bold text-slate-800">
<span v-if="modalType === 'withdraw'">撤回变更申请</span>
<span v-else-if="modalType === 'unuse'">未投用情况说明</span>
<span v-else>未实施情况说明</span>
{{currentRow?.change_no}}
</h3>
</template>
<template #default>
<div class="rounded-lg bg-slate-50 px-3 py-2 text-xs leading-5 text-slate-500">
{{ modalType === 'withdraw'
? '撤回后申请退回草稿状态,已产生的审批记录保留可查;修改内容后可重新提交。仅本人发起的审批中变更可撤回。'
: '变更审批通过 / 投用后未实施或未投用的,由申请人填写情况说明,提交后变更转入关闭流程,由变更主管部门专责确认关闭并记录关闭时间。' }}
</div>
<NInput
class="mt-3"
v-model:value="reason"
type="textarea"
:rows="3"
:placeholder="modalType === 'withdraw' ? '撤回理由(必填)…' : '情况说明(必填):原因、现场当前状态、后续处置安排…'"
/>
</template>
<template #footer>
<div class="flex justify-end gap-[10px]">
<NButton size="small" @click="revokeModal = false">取消</NButton>
<NButton size="small" type="primary" :loading="revokeSaving" @click="revokeSave">提交</NButton>
</div>
</template>
</NModal>
<!-- 进度弹框 -->
<NDrawer v-model:show="progressDrawer" :width="502" placement="right" :auto-focus="false">
<NDrawerContent title="变更进度">
<div class="rounded-lg border bg-slate-50/60 px-3 py-2.5">
<div class="text-sm font-semibold text-slate-800">{{ currentRow?.title }}</div>
<div class="mt-1.5 flex flex-wrap items-center gap-1.5 text-[11px] text-slate-400">
<div class="mt-1.5 flex flex-wrap items-center gap-1.5 text-xs text-slate-400">
<span class="font-mono">{{ currentRow?.change_no }}</span>
<NTag v-if="currentRow.status==='APPROVING'" size="small" type="warning">审批中</NTag>
<NTag v-if="currentRow.status==='APPROVED'" size="small" type="success">已通过</NTag>
@@ -586,8 +501,8 @@ onMounted(async () => {
<NTag v-if="currentRow.status==='REJECTED'" size="small" type="error">已驳回</NTag>
<span v-if="currentRow?.overdue_status===2" class="font-medium text-red-500">超期 {{ currentRow?.overdue_days }} 天</span>
</div>
<div v-if="currentRow?.dept || currentRow?.applicant" class="mt-1 text-[11px] text-slate-400">{{ [currentRow?.dept, currentRow?.applicant].filter(Boolean).join(' · ') }}</div>
<div v-if="currentRow?.meta" class="mt-1 text-[11px] text-slate-400">{{ currentRow?.meta }}</div>
<div v-if="currentRow?.dept || currentRow?.applicant" class="mt-1 text-xs text-slate-400">{{ [currentRow?.dept, currentRow?.applicant].filter(Boolean).join(' · ') }}</div>
<div v-if="currentRow?.meta" class="mt-1 text-xs text-slate-400">{{ currentRow?.meta }}</div>
</div>
<div class="mt-4">
<div v-for="(s, i) in currentRow?.approval_records" :key="s.id" class="flex gap-3">
@@ -603,11 +518,11 @@ onMounted(async () => {
</div>
<div class="pb-5">
<p class="text-sm">{{ s.action }}</p>
<span class="mt-0.5 text-[11px] text-slate-400">{{s.finished_at?.slice(0, 16)}}</span>
<span class="mt-0.5 text-xs text-slate-400">{{s.finished_at?.slice(0, 16)}}</span>
</div>
</div>
</div>
<div v-if="currentRow?.duration_type === 2" class="rounded-lg border border-amber-200 bg-amber-50/50 px-3 py-2 text-[11px] leading-5 text-amber-700">
<div v-if="currentRow?.duration_type === 2" class="rounded-lg border border-amber-200 bg-amber-50/50 px-3 py-2 text-xs leading-5 text-amber-700">
临时变更:计划恢复 {{ currentRow?.restore_deadline ?? '—' }} · 恢复 / 延期 / 转永久请在详情页中操作
</div>
</NDrawerContent>
+10 -10
View File
@@ -120,7 +120,7 @@ onMounted(() => {
<NSpace vertical :size="16">
<NSpin :show="loading" size="small">
<div class="space-y-3.5">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex flex-wrap items-center justify-between">
<div class="flex-y-center gap-3">
<span class="flex items-center gap-1.5 text-sm font-semibold text-slate-700">
@@ -147,7 +147,7 @@ onMounted(() => {
<NGrid cols="s:1 m:5 l:5" responsive="screen" :x-gap="16" :y-gap="16">
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">申请变更数量</p>
@@ -163,7 +163,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">重要变更数量</p>
@@ -179,7 +179,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">未关闭变更数量</p>
@@ -195,7 +195,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">平均审批通过时长</p>
@@ -213,7 +213,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">申请驳回率</p>
@@ -232,7 +232,7 @@ onMounted(() => {
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:24 l:6">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 pb-3 mb-4">尚未关闭的变更所处的状态数量(共 {{openTotal}} 项)</p>
<div class="space-y-3">
<div v-for="s in deptStatistics?.open_status_counts" :key="s.status">
@@ -258,7 +258,7 @@ onMounted(() => {
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:18">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 pb-3 mb-4">验收达标 · 变更类型 · 变更时限分布</p>
<div class="grid gap-4 grid-cols-3">
<div>
@@ -280,7 +280,7 @@ onMounted(() => {
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:24 l:12">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="border-b border-slate-100 pb-3 mb-4 flex justify-between items-center">
<p class=" text-base font-semibold">各人员审批时间对比(平均处理时长)</p>
<p class="text-[11px] text-slate-400 mt-5px">超过 2 天红色标识 · 用于会签催办与流程优化参考</p>
@@ -302,7 +302,7 @@ onMounted(() => {
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:12">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 pb-3 mb-4">临时变更到期提醒情况(共 {{tempTotal}} 项)</p>
<div class="space-y-3">
<div class="rounded-lg border px-3 py-2">
+41 -19
View File
@@ -8,7 +8,7 @@ import { getColorWithOpacity, tagTextColor, tagBgColor, tagBorderColor, download
import { AiError, aiTypeJudge } from "@/service/api/ai";
import { systemConfigApi } from '@/service/api/system';
import { getChangeDetailApi } from '@/service/api/change';
import previewComponent from '@/components/previewFile/index.vue'
import PreviewComponent from '@/components/previewFile/index.vue'
import { rejectApi,agreeApi } from '@/service/api/myTasks';
const themeStore = useThemeStore();
@@ -185,7 +185,7 @@ const opinion = ref<string>("");
// 已阅 AI 建议
const readSuggest = () => {
aiRead.value = true;
window.$message?.success(`${roleName} 已阅 AI 建议,时间与操作已写入审批留痕`);
window.$message?.success(`${roleName.value} 已阅 AI 建议,时间与操作已写入审批留痕`);
}
// 采纳为行动项
const acceptSuggest = (s: any) => {
@@ -456,7 +456,9 @@ onMounted(() => {
<p
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
:style="{'--theme-color': themeStore.themeColor}"
>变更申请表</p>
>
变更申请表
</p>
<NButton ghost size="small" class="text-xs" @click="exportFile('change')"><Icon icon="material-symbols:download" class="size-16px" />导出变更申请表</NButton>
</div>
<div
@@ -527,7 +529,12 @@ onMounted(() => {
<div class="grid border-b grid-cols-[100px_1fr]">
<p class="flex items-center justify-end px-3 py-1.6 text-xs text-slate-500 bg-[#fafafc]">风险评估方法</p>
<div class="px-3 py-1.6 text-sm leading-relaxed text-slate-800">
{{currentInfo?.apply_form?.risk_tools?.join('、')}}
<template v-for="(item,index) in currentInfo?.apply_form?.risk_tools" :key="index">
<span v-if="index!==0">、</span>
<span v-if="item==='RISK'">风险预分析</span>
<span v-else-if="item==='RISK_CHECK'">风险检查表</span>
<span v-else>{{ item }}</span>
</template>
</div>
</div>
<div class="grid border-b grid-cols-[100px_1fr]">
@@ -554,7 +561,9 @@ onMounted(() => {
<p
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
:style="{'--theme-color': themeStore.themeColor}"
>变更附带资料</p>
>
变更附带资料
</p>
<NButton ghost size="small" class="text-xs" @click="downloadFile(null)"><Icon icon="material-symbols:download" class="size-16px" />一键下载</NButton>
</div>
<NTable size="small">
@@ -594,7 +603,9 @@ onMounted(() => {
<p
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
:style="{'--theme-color': themeStore.themeColor}"
>风险分析记录表</p>
>
风险分析记录表
</p>
<NButton ghost size="small" class="text-xs" @click="exportFile('risk')"><Icon icon="material-symbols:download" class="size-16px" />导出风险分析记录表</NButton>
</div>
<NTable size="small">
@@ -643,7 +654,7 @@ onMounted(() => {
<span class="text-sm font-semibold text-slate-700">PSSR 检查内容</span>
</div>
<div class="space-y-2.5 border p-3" :style="{borderRadius: themeStore.themeRadius + 'px'}">
<div v-for="(pssr,index) in pssrlist" class="border" :key="index" :style="{borderRadius: themeStore.themeRadius + 'px'}">
<div v-for="(pssr,index) in pssrlist" :key="index" class="border" :style="{borderRadius: themeStore.themeRadius + 'px'}">
<div class="bg-slate-100 px-3 py-1.6 font-medium text-slate-600">{{ pssr.g }}</div>
<div v-for="item in pssr.items" :key="item.id" class="flex items-center border-t px-3 py-1.6">
<p class="flex-1">{{ item.content }}</p>
@@ -685,7 +696,9 @@ onMounted(() => {
<p
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
:style="{'--theme-color': themeStore.themeColor}"
>审批记录</p>
>
审批记录
</p>
</div>
<NTable size="small">
<thead>
@@ -713,8 +726,10 @@ onMounted(() => {
<template v-for="(n, i) in currentInfo?.approval_flow?.steps" :key="n.name">
<div class="flex items-center gap-1">
<Icon v-if="i !== 0" icon="formkit:right" class="size-14px text-slate-300" />
<div class="rounded-lg border px-3 py-2 text-center"
:class="n.status === 'PASSED' ? 'border-emerald-300 bg-emerald-50' : n.status === 'PENDING' ? 'border-amber-400 bg-amber-50' : n.status === 'REJECTED' ? 'border-red-300 bg-red-50' :'border-amber-400 bg-amber-50'">
<div
class="rounded-lg border px-3 py-2 text-center"
:class="n.status === 'PASSED' ? 'border-emerald-300 bg-emerald-50' : n.status === 'PENDING' ? 'border-amber-400 bg-amber-50' : n.status === 'REJECTED' ? 'border-red-300 bg-red-50' :'border-amber-400 bg-amber-50'"
>
<p class="text-xs font-medium">
{{ n.name }}
</p>
@@ -776,7 +791,9 @@ onMounted(() => {
textColor: tagTextColor(matchLabel(levelList,level)?.color),
borderColor: tagBorderColor(matchLabel(levelList,level)?.color),
}"
>{{ matchLabel(levelList,level)?.label? matchLabel(levelList,level)?.label+'变更':'' }}</NTag>
>
{{ matchLabel(levelList,level)?.label? matchLabel(levelList,level)?.label+'变更':'' }}
</NTag>
<span class="text-xs text-slate-400">合计得分 <b class="text-[#17407F]">{{ totalScore }}</b> 分(&lt;20 分为一般,≥20 分为重要)</span>
</div>
<NTable size="small">
@@ -906,7 +923,9 @@ onMounted(() => {
<div
class="mt-0.5 text-[11px] leading-snug"
:class="d.over ? 'text-[var(--error-color)]' : 'text-slate-400'"
>{{ d.note }}</div>
>
{{ d.note }}
</div>
</div>
</div>
<!-- AI 审批建议(版本冻结 · 建议分级 · 已阅留痕 · 无感审计) -->
@@ -947,12 +966,16 @@ onMounted(() => {
<div class="leading-relaxed text-slate-700">{{ s.text }}</div>
<div class="mt-1.5">
<div v-if="s.st === '待定'" class="flex gap-3">
<NButton text type="success" class="text-xs hover:underline"
@click="acceptSuggest(s)">
<NButton
text type="success" class="text-xs hover:underline"
@click="acceptSuggest(s)"
>
采纳为行动项
</NButton>
<NButton text class="text-xs hover:underline"
@click="rejectSuggest(s)">
<NButton
text class="text-xs hover:underline"
@click="rejectSuggest(s)"
>
已知悉,不采纳
</NButton>
</div>
@@ -980,7 +1003,6 @@ onMounted(() => {
</div>
</div>
</div>
</div>
</NDrawerContent>
</NDrawer>
@@ -1004,7 +1026,7 @@ onMounted(() => {
<span>上传时间:{{ previewFile?.created_at }}</span>
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
</div>
<previewComponent v-if="previewFile" :previewFile="previewFile" />
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
</div>
</template>
</NModal>
@@ -1027,8 +1049,8 @@ onMounted(() => {
<NTag v-for="item in rejectQuick" :key="item" class="cursor-pointer" size="small" type="error" @click="rejectReason += item + ';'">{{ item }}</NTag>
</div>
<NInput
class="mt-3"
v-model:value="rejectReason"
class="mt-3"
type="textarea"
:autosize="{ minRows: 3 }"
placeholder="驳回意见(必填),将退回至申请人/指定节点…"
+27 -23
View File
@@ -9,7 +9,7 @@ import { uploadFileApi, deleteFileApi, fileListApi } from "@/service/api/file";
import { systemConfigApi, peopleListApi, templateListApi } from '@/service/api/system';
import { getChangeDetailApi } from '@/service/api/change';
import { myTasksCloseMaterialApi } from '@/service/api/myTasks';
import previewComponent from '@/components/previewFile/index.vue'
import PreviewComponent from '@/components/previewFile/index.vue'
const themeStore = useThemeStore();
const { routerPushByKey } = useRouterPush();
@@ -170,7 +170,7 @@ const chooseFiles = () => {
const ids = new Set(checkedFiles.value);
const result = fileList.value.filter((item:any) => ids.has(item.id));
// 合并文件列表
let files = list.value[currentRowIndex.value]?.attachments.concat(result);
const files = list.value[currentRowIndex.value]?.attachments.concat(result);
// 按id去重
const uniqueList = [
...new Map(files.map((item:any) => [item.id, item])).values(),
@@ -349,7 +349,7 @@ const togglePicker = (s: any) => {
// 添加审批步骤人员
const addMember = (s: any, v: string | null) => {
if (v && !s.members.some((m:any) => m.user_name === v)){
let info = findUserInTree(memberList.value, v);
const info = findUserInTree(memberList.value, v);
s.members.push({ user_id: Number(v), user_name: info?.user_name || '', org_id: info?.org_id || '', org_name: info?.org_name || '', specialty: "", is_sign: 0 });
pick.value = null;
pickerStepId.value = null;
@@ -439,8 +439,8 @@ onMounted(async () => {
<NButton text type="primary" class="text-11px" @click="downloadFile(file,'train')">下载</NButton>
<NPopconfirm
positive-text="确定"
:positiveButtonProps="{ size: 'tiny' }"
:negativeButtonProps="{ size: 'tiny' }"
:positive-button-props="{ size: 'tiny' }"
:negative-button-props="{ size: 'tiny' }"
@positive-click="removeFileReal('3', Number(fileIndex))"
>
<template #trigger>
@@ -456,8 +456,8 @@ onMounted(async () => {
下载培训签到表
</NButton>
<NUpload
class="w-auto"
:key="trainFiles.length?'train_'+trainFiles.length+1:'train'"
class="w-auto"
accept="image/*,.pdf,.docx,.xlsx,.pptx"
:default-upload="false"
:show-file-list="false"
@@ -486,8 +486,8 @@ onMounted(async () => {
<NButton text type="primary" class="text-11px" @click="downloadFile(file,'pssr')">下载</NButton>
<NPopconfirm
positive-text="确定"
:positiveButtonProps="{ size: 'tiny' }"
:negativeButtonProps="{ size: 'tiny' }"
:positive-button-props="{ size: 'tiny' }"
:negative-button-props="{ size: 'tiny' }"
@positive-click="removeFileReal('4', Number(fileIndex))"
>
<template #trigger>
@@ -503,8 +503,8 @@ onMounted(async () => {
下载PSSR检查表
</NButton>
<NUpload
class="w-auto"
:key="pssrFiles.length?'pssr_'+pssrFiles.length+1:'pssr'"
class="w-auto"
accept="image/*,.pdf,.docx,.xlsx,.pptx"
:default-upload="false"
:show-file-list="false"
@@ -556,8 +556,8 @@ onMounted(async () => {
<NButton text type="primary" class="text-11px" @click="downloadFile(file,'accept')">下载</NButton>
<NPopconfirm
positive-text="确定"
:positiveButtonProps="{ size: 'tiny' }"
:negativeButtonProps="{ size: 'tiny' }"
:positive-button-props="{ size: 'tiny' }"
:negative-button-props="{ size: 'tiny' }"
@positive-click="removeFileReal('5', Number(fileIndex))"
>
<template #trigger>
@@ -573,8 +573,8 @@ onMounted(async () => {
下载验收表
</NButton>
<NUpload
class="w-auto"
:key="acceptFiles.length?'accept_'+acceptFiles.length+1:'accept'"
class="w-auto"
accept="image/*,.pdf,.docx,.xlsx,.pptx"
:default-upload="false"
:show-file-list="false"
@@ -635,8 +635,8 @@ onMounted(async () => {
<NButton text type="primary" class="text-11px" @click="downloadFile(file,'close')">下载</NButton>
<NPopconfirm
positive-text="确定"
:positiveButtonProps="{ size: 'tiny' }"
:negativeButtonProps="{ size: 'tiny' }"
:positive-button-props="{ size: 'tiny' }"
:negative-button-props="{ size: 'tiny' }"
@positive-click="removeFileReal(item.doc_name, Number(fileIndex), Number(index))"
>
<template #trigger>
@@ -657,8 +657,8 @@ onMounted(async () => {
<div v-if="item.status === 1" class="flex justify-center gap-2">
<NButton size="small" class="text-xs" @click="openFileList(Number(index))">从变更附件选择</NButton>
<NUpload
class="w-auto"
:key="item.id"
class="w-auto"
accept="image/*,.pdf,.docx,.xlsx,.pptx"
:default-upload="false"
:show-file-list="false"
@@ -699,10 +699,10 @@ onMounted(async () => {
<div class="flex flex-wrap items-center gap-2">
<NTag type="info" size="small">{{ Number(idx)+1 }}</NTag>
<p class="text-sm">{{ s.name }}</p>
<NCheckbox :checked="s.countersign" size="small" @update:checked="(v) => setStepCountersign(s, v)">
<NCheckbox :checked="s.countersign" :disabled="true" size="small" @update:checked="(v) => setStepCountersign(s, v)">
<span class="text-xs text-slate-500">专业会签步骤</span>
</NCheckbox>
<NButton ghost type="primary" size="tiny" @click="togglePicker(s)" title="添加预设人员" class="ml-auto">
<NButton ghost type="primary" size="tiny" title="添加预设人员" class="ml-auto" @click="togglePicker(s)">
<Icon icon="ic:round-plus" class="size-16px" />
</NButton>
</div>
@@ -714,11 +714,13 @@ onMounted(async () => {
<span class="shrink-0 truncate text-xs font-medium text-slate-700">{{ m.user_name }}</span>
<template v-if="s.countersign">
<span class="text-xs text-slate-400"><span class="text-red-500">*</span>专业:</span>
<NSelect :value="m.specialty || null" clearable size="small" placeholder="请选择专业" class="w-28"
<NSelect
:value="m.specialty || null" clearable size="small" placeholder="请选择专业" class="w-28"
:options="SPECIALTIES"
@update:value="(v) => selectSpecialty(Number(idx), Number(mIdx), v)"
/>
<NCheckbox :checked="m.is_sign" size="small"
<NCheckbox
:checked="m.is_sign" size="small"
:checked-value="1"
:unchecked-value="0"
@update:checked="(v) => selectSign(Number(idx), Number(mIdx), v)"
@@ -726,16 +728,18 @@ onMounted(async () => {
<span class="text-xs text-slate-500">必须参与会签</span>
</NCheckbox>
</template>
<NButton text type="error" @click="delMember(s, m.user_id)" title="删除该人员">
<NButton text type="error" title="删除该人员" @click="delMember(s, m.user_id)">
<Icon icon="iconamoon:trash" class="size-16px" />
</NButton>
</div>
<p v-if="s.countersign && s.members.length" class="text-[11px] leading-5 text-slate-400">
必须参与会签:勾选的人员必须参与专业会签,未勾选的人员为可选会签(按需参加)
</p>
<NCascader v-if="pickerStepId === s.id" :value="pick" :options="memberList"
<NCascader
v-if="pickerStepId === s.id" :value="pick" :options="memberList"
placeholder="请选择人员" size="small" check-strategy="child" :show-path="false" expand-trigger="click"
class="w-56" :on-update:value="(v) => addMember(s, v)" />
class="w-56" :on-update:value="(v) => addMember(s, v)"
/>
<span v-if="s.members.length === 0" class="text-xs leading-7 text-slate-300">未预设</span>
</div>
@@ -795,7 +799,7 @@ onMounted(async () => {
<span>上传时间:{{ previewFile?.created_at }}</span>
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
</div>
<previewComponent v-if="previewFile" :previewFile="previewFile" />
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
</div>
</template>
</NModal>
+46 -25
View File
@@ -9,7 +9,7 @@ import { AiError, aiTypeJudge } from "@/service/api/ai";
import { systemConfigApi, peopleListApi, templateListApi } from '@/service/api/system';
import { getChangeDetailApi, urgeApi } from '@/service/api/change';
import { myTasksDisposalSubmitApi, myTasksAddItemApi } from '@/service/api/myTasks';
import previewComponent from '@/components/previewFile/index.vue'
import PreviewComponent from '@/components/previewFile/index.vue'
const themeStore = useThemeStore();
const { routerPushByKey } = useRouterPush();
@@ -376,7 +376,7 @@ const getDetail = async () => {
flowList.value = data?.disposal_approval_flow?.steps || [];
steps.value = data?.disposal_approval_flow?.steps || [];
}else{
let template = templateList.value.find((item: any) =>
const template = templateList.value.find((item: any) =>
item.level === data?.apply_form?.change_level &&
item.category === data?.apply_form?.change_type &&
item.duration === data?.apply_form?.duration_type
@@ -553,7 +553,7 @@ const togglePicker = (s: any) => {
// 添加审批步骤人员
const addMember = (s: any, v: string | null) => {
if (v && !s.members.some((m:any) => m.user_name === v)){
let info = findUserInTree(memberList.value, v);
const info = findUserInTree(memberList.value, v);
s.members.push({ user_id: Number(v), user_name: info?.user_name || '', org_id: info?.org_id || '', org_name: info?.org_name || '', specialty: "", is_sign: 0 });
pick.value = null;
pickerStepId.value = null;
@@ -665,7 +665,9 @@ onMounted(async () => {
<p
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
:style="{'--theme-color': themeStore.themeColor}"
>变更申请表</p>
>
变更申请表
</p>
<NButton ghost size="small" class="text-xs" @click="exportFile('change')"><Icon icon="material-symbols:download" class="size-16px" />导出变更申请表</NButton>
</div>
<div
@@ -736,7 +738,12 @@ onMounted(async () => {
<div class="grid border-b grid-cols-[100px_1fr]">
<p class="flex items-center justify-end px-3 py-1.6 text-xs text-slate-500 bg-[#fafafc]">风险评估方法</p>
<div class="px-3 py-1.6 text-sm leading-relaxed text-slate-800">
{{currentInfo?.apply_form?.risk_tools?.join('、')}}
<template v-for="(item,index) in currentInfo?.apply_form?.risk_tools" :key="index">
<span v-if="index!==0">、</span>
<span v-if="item==='RISK'">风险预分析</span>
<span v-else-if="item==='RISK_CHECK'">风险检查表</span>
<span v-else>{{ item }}</span>
</template>
</div>
</div>
<div class="grid border-b grid-cols-[100px_1fr]">
@@ -763,7 +770,9 @@ onMounted(async () => {
<p
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
:style="{'--theme-color': themeStore.themeColor}"
>变更附带资料</p>
>
变更附带资料
</p>
<NButton ghost size="small" class="text-xs" @click="downloadFile(null)"><Icon icon="material-symbols:download" class="size-16px" />一键下载</NButton>
</div>
<NTable size="small">
@@ -803,7 +812,9 @@ onMounted(async () => {
<p
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
:style="{'--theme-color': themeStore.themeColor}"
>风险分析记录表</p>
>
风险分析记录表
</p>
<NButton ghost size="small" class="text-xs" @click="exportFile('risk')"><Icon icon="material-symbols:download" class="size-16px" />导出风险分析记录表</NButton>
</div>
<NTable size="small">
@@ -852,7 +863,7 @@ onMounted(async () => {
<span class="text-sm font-semibold text-slate-700">PSSR 检查内容</span>
</div>
<div class="space-y-2.5 border p-3" :style="{borderRadius: themeStore.themeRadius + 'px'}">
<div v-for="(pssr,index) in pssrList" class="border" :key="index" :style="{borderRadius: themeStore.themeRadius + 'px'}">
<div v-for="(pssr,index) in pssrList" :key="index" class="border" :style="{borderRadius: themeStore.themeRadius + 'px'}">
<div class="bg-slate-100 px-3 py-1.6 font-medium text-slate-600">{{ pssr.g }}</div>
<div v-for="item in pssr.items" :key="item.id" class="flex items-center border-t px-3 py-1.6">
<p class="flex-1">{{ item.content }}</p>
@@ -894,7 +905,9 @@ onMounted(async () => {
<p
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
:style="{'--theme-color': themeStore.themeColor}"
>审批记录</p>
>
审批记录
</p>
</div>
<NTable size="small">
<thead>
@@ -922,8 +935,10 @@ onMounted(async () => {
<template v-for="(n, i) in currentInfo?.approval_flow?.steps" :key="n.name">
<div class="flex items-center gap-1">
<Icon v-if="i !== 0" icon="formkit:right" class="size-14px text-slate-300" />
<div class="rounded-lg border px-3 py-2 text-center"
:class="n.status === 'PASSED' ? 'border-emerald-300 bg-emerald-50' : n.status === 'PENDING' ? 'border-amber-400 bg-amber-50' : n.status === 'REJECTED' ? 'border-red-300 bg-red-50' :'border-amber-400 bg-amber-50'">
<div
class="rounded-lg border px-3 py-2 text-center"
:class="n.status === 'PASSED' ? 'border-emerald-300 bg-emerald-50' : n.status === 'PENDING' ? 'border-amber-400 bg-amber-50' : n.status === 'REJECTED' ? 'border-red-300 bg-red-50' :'border-amber-400 bg-amber-50'"
>
<p class="text-xs font-medium">
{{ n.name }}
</p>
@@ -951,7 +966,8 @@ onMounted(async () => {
</template>
{{ m.user_name }}
</NTag>
<div v-if="m.status === 'PENDING'"
<div
v-if="m.status === 'PENDING'"
class="cursor-pointer inline-flex h-6 items-center gap-1 rounded-md border border-amber-300 bg-white px-2 text-[11px] text-amber-700 transition hover:bg-amber-100"
@click="handleReminder(m)"
>
@@ -961,7 +977,6 @@ onMounted(async () => {
</div>
</div>
</div>
</div>
</NCard>
</NGi>
@@ -1055,7 +1070,7 @@ onMounted(async () => {
<p class="text-xs text-slate-400">{{ confirmedPssr }}/{{ totalPssr }} 已确认</p>
</div>
<div class="p-2 space-y-2">
<div v-for="(pssr,index) in pssrList" class="border" :key="index" :style="{borderRadius: themeStore.themeRadius + 'px'}">
<div v-for="(pssr,index) in pssrList" :key="index" class="border" :style="{borderRadius: themeStore.themeRadius + 'px'}">
<div class="bg-slate-100 px-2 py-1.5 font-medium text-xs text-slate-600 flex items-center justify-between">
{{ pssr.g }}
<NButton text size="small" type="primary" class="text-xs" @click="addItem(index)">
@@ -1064,10 +1079,10 @@ onMounted(async () => {
</div>
<div v-for="(item,subIndex) in pssr.items" :key="subIndex" class="flex items-center border-t px-2 py-1 gap-2">
<NInput v-if="!item.id" v-model:value="item.content" size="small" type="textarea" :autosize="{minRows: 1}" placeholder="填写检查项内容" class="w-full text-xs"></NInput>
<NButton v-if="!item.id" text type="success" @click="saveItem(index, subIndex)" title="保存检查项">
<NButton v-if="!item.id" text type="success" title="保存检查项" @click="saveItem(index, subIndex)">
<Icon icon="boxicons:save" class="size-16px" />
</NButton>
<NButton v-if="!item.id" text type="error" @click="pssrList[index].items.splice(subIndex, 1)" title="删除">
<NButton v-if="!item.id" text type="error" title="删除" @click="pssrList[index].items.splice(subIndex, 1)">
<Icon icon="ep:delete" class="size-14px" />
</NButton>
<p v-if="item.id" class="flex-1 text-xs">{{ item.content }}</p>
@@ -1143,7 +1158,9 @@ onMounted(async () => {
textColor: tagTextColor(matchLabel(levelList,level)?.color),
borderColor: tagBorderColor(matchLabel(levelList,level)?.color),
}"
>{{ matchLabel(levelList,level)?.label? matchLabel(levelList,level)?.label+'变更':'' }}</NTag>
>
{{ matchLabel(levelList,level)?.label? matchLabel(levelList,level)?.label+'变更':'' }}
</NTag>
<span class="text-xs text-slate-400">合计得分 <b class="text-[#17407F]">{{ totalScore }}</b> 分(&lt;20 分为一般,≥20 分为重要)</span>
</div>
<NTable size="small">
@@ -1177,10 +1194,10 @@ onMounted(async () => {
<div class="flex flex-wrap items-center gap-2">
<NTag type="info" size="small">{{ Number(idx)+1 }}</NTag>
<p class="text-sm">{{ s.name }}</p>
<NCheckbox :checked="s.countersign" size="small" @update:checked="(v) => setStepCountersign(s, v)">
<NCheckbox :checked="s.countersign" :disabled="true" size="small" @update:checked="(v) => setStepCountersign(s, v)">
<span class="text-xs text-slate-500">专业会签步骤</span>
</NCheckbox>
<NButton ghost type="primary" size="tiny" @click="togglePicker(s)" title="添加预设人员" class="ml-auto">
<NButton ghost type="primary" size="tiny" title="添加预设人员" class="ml-auto" @click="togglePicker(s)">
<Icon icon="ic:round-plus" class="size-16px" />
</NButton>
</div>
@@ -1192,11 +1209,13 @@ onMounted(async () => {
<span class="shrink-0 truncate text-xs font-medium text-slate-700">{{ m.user_name }}</span>
<template v-if="s.countersign">
<span class="text-xs text-slate-400"><span class="text-red-500">*</span>专业:</span>
<NSelect :value="m.specialty || null" clearable size="small" placeholder="请选择专业" class="w-28"
<NSelect
:value="m.specialty || null" clearable size="small" placeholder="请选择专业" class="w-28"
:options="SPECIALTIES"
@update:value="(v) => selectSpecialty(Number(idx), Number(mIdx), v)"
/>
<NCheckbox :checked="m.is_sign" size="small"
<NCheckbox
:checked="m.is_sign" size="small"
:checked-value="1"
:unchecked-value="0"
@update:checked="(v) => selectSign(Number(idx), Number(mIdx), v)"
@@ -1204,16 +1223,18 @@ onMounted(async () => {
<span class="text-xs text-slate-500">必须参与会签</span>
</NCheckbox>
</template>
<NButton text type="error" @click="delMember(s, m.user_id)" title="删除该人员">
<NButton text type="error" title="删除该人员" @click="delMember(s, m.user_id)">
<Icon icon="iconamoon:trash" class="size-16px" />
</NButton>
</div>
<p v-if="s.countersign && s.members.length" class="text-[11px] leading-5 text-slate-400">
必须参与会签:勾选的人员必须参与专业会签,未勾选的人员为可选会签(按需参加)
</p>
<NCascader v-if="pickerStepId === s.id" :value="pick" :options="memberList"
<NCascader
v-if="pickerStepId === s.id" :value="pick" :options="memberList"
placeholder="请选择人员" size="small" check-strategy="child" :show-path="false" expand-trigger="click"
class="w-56" :on-update:value="(v) => addMember(s, v)" />
class="w-56" :on-update:value="(v) => addMember(s, v)"
/>
<span v-if="s.members.length === 0" class="text-xs leading-7 text-slate-300">未预设</span>
</div>
@@ -1245,7 +1266,7 @@ onMounted(async () => {
<span>上传时间:{{ previewFile?.created_at }}</span>
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
</div>
<previewComponent v-if="previewFile" :previewFile="previewFile" />
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
</div>
</template>
</NModal>
+30 -13
View File
@@ -8,7 +8,7 @@ import { tagTextColor, tagBgColor, tagBorderColor, downloadFileFun } from '@/uti
import { AiError, aiTypeJudge } from "@/service/api/ai";
import { systemConfigApi } from '@/service/api/system';
import { getChangeDetailApi, urgeApi } from '@/service/api/change';
import previewComponent from '@/components/previewFile/index.vue'
import PreviewComponent from '@/components/previewFile/index.vue'
const themeStore = useThemeStore();
const { routerPushByKey } = useRouterPush();
@@ -342,7 +342,9 @@ onMounted(() => {
<p
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
:style="{'--theme-color': themeStore.themeColor}"
>变更申请表</p>
>
变更申请表
</p>
<NButton ghost size="small" class="text-xs" @click="exportFile('change')"><Icon icon="material-symbols:download" class="size-16px" />导出变更申请表</NButton>
</div>
<div
@@ -413,7 +415,12 @@ onMounted(() => {
<div class="grid border-b grid-cols-[100px_1fr]">
<p class="flex items-center justify-end px-3 py-1.6 text-xs text-slate-500 bg-[#fafafc]">风险评估方法</p>
<div class="px-3 py-1.6 text-sm leading-relaxed text-slate-800">
{{currentInfo?.apply_form?.risk_tools?.join('、')}}
<template v-for="(item,index) in currentInfo?.apply_form?.risk_tools" :key="index">
<span v-if="index!==0">、</span>
<span v-if="item==='RISK'">风险预分析</span>
<span v-else-if="item==='RISK_CHECK'">风险检查表</span>
<span v-else>{{ item }}</span>
</template>
</div>
</div>
<div class="grid border-b grid-cols-[100px_1fr]">
@@ -440,7 +447,9 @@ onMounted(() => {
<p
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
:style="{'--theme-color': themeStore.themeColor}"
>变更附带资料</p>
>
变更附带资料
</p>
<NButton ghost size="small" class="text-xs" @click="downloadFile(null)"><Icon icon="material-symbols:download" class="size-16px" />一键下载</NButton>
</div>
<NTable size="small">
@@ -480,7 +489,9 @@ onMounted(() => {
<p
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
:style="{'--theme-color': themeStore.themeColor}"
>风险分析记录表</p>
>
风险分析记录表
</p>
<NButton ghost size="small" class="text-xs" @click="exportFile('risk')"><Icon icon="material-symbols:download" class="size-16px" />导出风险分析记录表</NButton>
</div>
<NTable size="small">
@@ -529,7 +540,7 @@ onMounted(() => {
<span class="text-sm font-semibold text-slate-700">PSSR 检查内容</span>
</div>
<div class="space-y-2.5 border p-3" :style="{borderRadius: themeStore.themeRadius + 'px'}">
<div v-for="(pssr,index) in pssrlist" class="border" :key="index" :style="{borderRadius: themeStore.themeRadius + 'px'}">
<div v-for="(pssr,index) in pssrlist" :key="index" class="border" :style="{borderRadius: themeStore.themeRadius + 'px'}">
<div class="bg-slate-100 px-3 py-1.6 font-medium text-slate-600">{{ pssr.g }}</div>
<div v-for="item in pssr.items" :key="item.id" class="flex items-center border-t px-3 py-1.6">
<p class="flex-1">{{ item.content }}</p>
@@ -571,7 +582,9 @@ onMounted(() => {
<p
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
:style="{'--theme-color': themeStore.themeColor}"
>审批记录</p>
>
审批记录
</p>
</div>
<NTable size="small">
<thead>
@@ -599,8 +612,10 @@ onMounted(() => {
<template v-for="(n, i) in currentInfo?.approval_flow?.steps" :key="n.name">
<div class="flex items-center gap-1">
<Icon v-if="i !== 0" icon="formkit:right" class="size-14px text-slate-300" />
<div class="rounded-lg border px-3 py-2 text-center"
:class="n.status === 'PASSED' ? 'border-emerald-300 bg-emerald-50' : n.status === 'PENDING' ? 'border-amber-400 bg-amber-50' : n.status === 'REJECTED' ? 'border-red-300 bg-red-50' :'border-amber-400 bg-amber-50'">
<div
class="rounded-lg border px-3 py-2 text-center"
:class="n.status === 'PASSED' ? 'border-emerald-300 bg-emerald-50' : n.status === 'PENDING' ? 'border-amber-400 bg-amber-50' : n.status === 'REJECTED' ? 'border-red-300 bg-red-50' :'border-amber-400 bg-amber-50'"
>
<p class="text-xs font-medium">
{{ n.name }}
</p>
@@ -628,7 +643,8 @@ onMounted(() => {
</template>
{{ m.user_name }}
</NTag>
<div v-if="m.status === 'PENDING'"
<div
v-if="m.status === 'PENDING'"
class="cursor-pointer inline-flex h-6 items-center gap-1 rounded-md border border-amber-300 bg-white px-2 text-[11px] text-amber-700 transition hover:bg-amber-100"
@click="handleReminder(m)"
>
@@ -638,7 +654,6 @@ onMounted(() => {
</div>
</div>
</div>
</div>
</NCard>
<!-- 变更类型判定说明(右侧抽屉) -->
@@ -681,7 +696,9 @@ onMounted(() => {
textColor: tagTextColor(matchLabel(levelList,level)?.color),
borderColor: tagBorderColor(matchLabel(levelList,level)?.color),
}"
>{{ matchLabel(levelList,level)?.label? matchLabel(levelList,level)?.label+'变更':'' }}</NTag>
>
{{ matchLabel(levelList,level)?.label? matchLabel(levelList,level)?.label+'变更':'' }}
</NTag>
<span class="text-xs text-slate-400">合计得分 <b class="text-[#17407F]">{{ totalScore }}</b> 分(&lt;20 分为一般,≥20 分为重要)</span>
</div>
<NTable size="small">
@@ -723,7 +740,7 @@ onMounted(() => {
<span>上传时间:{{ previewFile?.created_at }}</span>
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
</div>
<previewComponent v-if="previewFile" :previewFile="previewFile" />
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
</div>
</template>
</NModal>
+43 -4
View File
@@ -3,7 +3,7 @@ import { ref, onMounted, h } from 'vue';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { NButton, NTag, NDropdown, useDialog } from 'naive-ui';
import previewComponent from '@/components/previewFile/index.vue'
import PreviewComponent from '@/components/previewFile/index.vue'
import { downloadFileFun } from '@/utils/common';
import {
docTreeListApi,
@@ -13,7 +13,8 @@ import {
docListApi,
uploadDocApi,
updateDocApi,
docHistoryListApi
docHistoryListApi,
deleteDocApi
} from '@/service/api/doc';
const themeStore = useThemeStore();
@@ -315,6 +316,7 @@ const searchHandle = () => {
if(!keyword.value) {
// 搜索框为空时,显示树状列表
showTree.value = true;
getFileList(currentNode.value.id);
}else{
// 搜索框不为空时,显示搜索结果
showTree.value = false;
@@ -409,7 +411,7 @@ const preview = (d:any) => {
}
// 下载资料
const download = (file:any) => {
downloadFileFun(file.file_url, file.file_name);
downloadFileFun(file?.file_url, file?.file_name || file?.doc_name);
}
// 上传资料
const uploadFiles = async (e:any) => {
@@ -464,6 +466,20 @@ const viewHistory = async (d:any) => {
}
historyLoading.value = false;
}
// 删除资料
const deleteFile = async (d:any) => {
loading.value = true;
const {error} = await deleteDocApi(d.id);
if(!error){
window.$message?.success('删除成功');
if(showTree.value){
getFileList(currentNode.value.id);
}else{
getFileList(0);
}
}
loading.value = false;
}
onMounted(() => {
getDocList();
@@ -519,6 +535,17 @@ onMounted(() => {
<NButton type="primary" text class="hover:underline text-xs" @click="updateFile(d)">更新</NButton>
<NButton type="primary" text class="hover:underline text-xs" @click="preview(d)">预览</NButton>
<NButton type="primary" text class="hover:underline text-xs" @click="download(d)">下载</NButton>
<NPopconfirm
positive-text="确定"
:positive-button-props="{ size: 'tiny' }"
:negative-button-props="{ size: 'tiny' }"
@positive-click="deleteFile(d)"
>
<template #trigger>
<NButton type="error" text class="hover:underline text-xs">删除</NButton>
</template>
确定删除吗?
</NPopconfirm>
</div>
</div>
<div v-if="queryList.length === 0" class="h-100% flex items-center justify-center">
@@ -598,6 +625,17 @@ onMounted(() => {
<NButton type="primary" text class="hover:underline text-xs" @click="updateFile(d)">更新</NButton>
<NButton type="primary" text class="hover:underline text-xs" @click="preview(d)">预览</NButton>
<NButton type="primary" text class="hover:underline text-xs" @click="download(d)">下载</NButton>
<NPopconfirm
positive-text="确定"
:positive-button-props="{ size: 'tiny' }"
:negative-button-props="{ size: 'tiny' }"
@positive-click="deleteFile(d)"
>
<template #trigger>
<NButton type="error" text class="hover:underline text-xs">删除</NButton>
</template>
确定删除吗?
</NPopconfirm>
</div>
</div>
<div v-if="fileList.length===0" class="h-100% flex items-center justify-center">
@@ -681,7 +719,7 @@ onMounted(() => {
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
</div>
<previewComponent v-if="previewFile" :previewFile="previewFile" />
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
</div>
</template>
</NModal>
@@ -692,6 +730,7 @@ onMounted(() => {
<style scoped>
</style>
<style scoped lang="scss">
.scroll-left {
height: calc(100vh - 276px);
+13 -7
View File
@@ -3,7 +3,7 @@ import { ref, onMounted, h } from 'vue';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { formatTimestamp, getTimeRangeStamp, downloadFileFun } from "@/utils/common";
import previewComponent from '@/components/previewFile/index.vue'
import PreviewComponent from '@/components/previewFile/index.vue'
import { systemConfigApi } from '@/service/api/system';
import { orgListApi } from '@/service/api/user';
import { docArchiveTreeListApi, docArchiveDetailsApi } from "@/service/api/doc";
@@ -236,7 +236,7 @@ const handleSelectKeys = (keys: string[],option:any) => {
// 下载
const downloadFile = (file:any) => {
downloadFileFun(file.file_url, file.file_name);
downloadFileFun(file?.file_url, file?.file_name || file?.doc_name);
}
// 预览
@@ -442,10 +442,12 @@ onMounted(async () => {
</div>
<div class="divide-y scroll-right">
<div v-for="f in rightDetails?.folders" :key="f.step_no">
<button @click="openFolders[f.step_no] = !openFolders[f.step_no]"
class="flex w-full items-center gap-2 px-3 py-2 text-left text-xs font-medium hover:bg-slate-50 bg-transparent">
<button
class="flex w-full items-center gap-2 px-3 py-2 text-left text-xs font-medium hover:bg-slate-50 bg-transparent"
@click="openFolders[f.step_no] = !openFolders[f.step_no]"
>
<Icon icon="formkit:right" class="size-14px text-slate-400 transition-transform" :class="openFolders[f.step_no] ? 'rotate-90' : ''" />
<Icon icon="quill:folder-open" v-if="openFolders[f.step_no]" class="size-16px" :style="{color:themeStore.themeColor}" />
<Icon v-if="openFolders[f.step_no]" icon="quill:folder-open" class="size-16px" :style="{color:themeStore.themeColor}" />
<Icon v-else icon="material-symbols:folder-outline" class="size-16px text-slate-400" />
<span v-if="f.step_no===1">1-申请与审批</span>
<span v-else-if="f.step_no===2">2-风险分析</span>
@@ -469,6 +471,9 @@ onMounted(async () => {
</div>
</template>
</div>
<div v-if="rightDetails?.folders?.length==0" class="h-100% flex items-center justify-center">
<NEmpty description="暂无归档包文件数据" class="text-xs"></NEmpty>
</div>
</div>
</div>
</template>
@@ -501,7 +506,7 @@ onMounted(async () => {
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
</div>
<previewComponent v-if="previewFile" :previewFile="previewFile" />
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
</div>
</template>
</NModal>
@@ -512,13 +517,14 @@ onMounted(async () => {
<style scoped>
</style>
<style scoped lang="scss">
.scroll-left {
height: calc(100vh - 303px);
overflow-y: auto;
}
.scroll-right {
height: calc(100vh - 416px);
height: calc(100vh - 375px);
overflow-y: auto;
}
.scroll-empty {
+9 -9
View File
@@ -45,7 +45,7 @@ onMounted(() => {
<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>
<div
class="bg-white px-16px py-12px text-[#94a3b8]"
class="bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600 text-center">按期关闭率</p>
@@ -63,7 +63,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="bg-white px-16px py-12px text-[#94a3b8]"
class="bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600 text-center">PSSR 完成率</p>
@@ -81,7 +81,7 @@ onMounted(() => {
</NGi>
<NGi>
<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 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600 text-center">当月临时变更数量</p>
@@ -91,7 +91,7 @@ onMounted(() => {
</NGi>
<NGi>
<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 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600 text-center">平均关闭时长</p>
@@ -103,7 +103,7 @@ onMounted(() => {
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<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="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">月度变更数量</p>
<div class="py-3 px-4">
<ColumnChart v-if="homeData" :data="homeData?.monthly_counts || []" />
@@ -111,7 +111,7 @@ onMounted(() => {
</NCard>
</NGi>
<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="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">变更类型分布</p>
<div class="py-3 px-4">
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
@@ -119,7 +119,7 @@ onMounted(() => {
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:6">
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
@@ -167,7 +167,7 @@ onMounted(() => {
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<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="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">平均审批周期趋势(天)</p>
<div class="py-3 px-4">
<ColumnChart2 v-if="homeData" :data="homeData?.cycle_trend || []" />
@@ -175,7 +175,7 @@ onMounted(() => {
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
+32 -17
View File
@@ -74,7 +74,11 @@ onUnmounted(() => {
<template>
<NSpin :show="loading" size="small">
<div class="space-y-3.5">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:16">
<div class="flex-y-center">
@@ -99,7 +103,7 @@ onUnmounted(() => {
<NGrid cols="s:1 m:2 l:2" responsive="screen" :x-gap="16" :y-gap="16">
<NGi>
<div
class="flex-1 bg-[#fff2f0] px-16px py-12px text-[#e15555]"
class="flex-1 bg-[#fff2f0] px-16px py-12px text-[#e15555] transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">待关闭确认</p>
@@ -115,7 +119,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -136,7 +140,11 @@ onUnmounted(() => {
<!-- 左侧 -->
<NGi span="24 s:24 m:24 l:16">
<!-- 待关闭确认 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<Icon icon="ix:success" class="size-16px" :style="{ color: themeStore.themeColor }" />
@@ -155,11 +163,10 @@ onUnmounted(() => {
<!-- 状态标签 -->
<NTag size="small" type="error">待关闭</NTag>
<p class="flex-1 truncate text-sm font-medium text-slate-800 dark:text-white">{{ row.title }}</p>
<span v-if="row.status==='CLOSED'" class="text-[11px]" :style="{color: themeStore.otherColor.success}">关闭时间 2026/9/28 14:29:57</span>
<NButton size="small" type="success" class="text-xs" @click="jumpTo('details_approval', row.change_id)">确认关闭</NButton>
<NButton size="small" ghost class="text-xs" @click="jumpTo('details_index', row.change_id)">打开变更单</NButton>
</div>
<div class="mt-0.5 truncate text-[11px] text-slate-400 flex items-center gap-2">
<div class="mt-0.5 truncate text-xs text-slate-400 flex items-center gap-2">
<span>{{ row.change_no }}</span>
<span>{{row.org_name}}</span>
<span>{{row.applicant_name}}</span>
@@ -175,7 +182,11 @@ onUnmounted(() => {
</div>
</NCard>
<!-- 在途变更跟踪 -->
<NCard :bordered="false" size="small" class="mt-16px" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="mt-16px shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
@@ -234,7 +245,11 @@ onUnmounted(() => {
<!-- 右侧 -->
<NGi span="24 s:24 m:24 l:8">
<!-- 系统设置 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center flex-wrap gap-2">
<Icon icon="keyline-icons:sliders-horizontal" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">系统设置</p>
@@ -246,7 +261,7 @@ onUnmounted(() => {
<div class="flex items-center gap-2">
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">审批流程设置</span>
</div>
<div class="mt-1 text-[11px] text-slate-400">按等级路由 · 会签 · 最终批准</div>
<div class="mt-1 text-xs text-slate-400">按等级路由 · 会签 · 最终批准</div>
</div>
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
</div>
@@ -255,7 +270,7 @@ onUnmounted(() => {
<div class="flex items-center gap-2">
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">变更类型管理</span>
</div>
<div class="mt-1 text-[11px] text-slate-400">工艺 / 设备 / 管理 · 支持自定义</div>
<div class="mt-1 text-xs text-slate-400">工艺 / 设备 / 管理 · 支持自定义</div>
</div>
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
</div>
@@ -264,7 +279,7 @@ onUnmounted(() => {
<div class="flex items-center gap-2">
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">变更等级规则</span>
</div>
<div class="mt-1 text-[11px] text-slate-400">等级评分表 · 重要 / 一般判定</div>
<div class="mt-1 text-xs text-slate-400">等级评分表 · 重要 / 一般判定</div>
</div>
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
</div>
@@ -273,7 +288,7 @@ onUnmounted(() => {
<div class="flex items-center gap-2">
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">账号设定</span>
</div>
<div class="mt-1 text-[11px] text-slate-400">用户账号 · 组织 · 启停用</div>
<div class="mt-1 text-xs text-slate-400">用户账号 · 组织 · 启停用</div>
</div>
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
</div>
@@ -282,7 +297,7 @@ onUnmounted(() => {
<div class="flex items-center gap-2">
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">角色和权限分配</span>
</div>
<div class="mt-1 text-[11px] text-slate-400">角色 · 菜单 · 数据权限</div>
<div class="mt-1 text-xs text-slate-400">角色 · 菜单 · 数据权限</div>
</div>
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
</div>
@@ -291,7 +306,7 @@ onUnmounted(() => {
<div class="flex items-center gap-2">
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">通知提醒设置</span>
</div>
<div class="mt-1 text-[11px] text-slate-400">超期 · 临期 · 关闭提醒</div>
<div class="mt-1 text-xs text-slate-400">超期 · 临期 · 关闭提醒</div>
</div>
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
</div>
@@ -300,7 +315,7 @@ onUnmounted(() => {
<div class="flex items-center gap-2">
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">AI 开关与维护</span>
</div>
<div class="mt-1 text-[11px] text-slate-400">AI 辅助开关 · 模型与提示词</div>
<div class="mt-1 text-xs text-slate-400">AI 辅助开关 · 模型与提示词</div>
</div>
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
</div>
@@ -313,11 +328,11 @@ onUnmounted(() => {
'--success-bg': getColorWithOpacity(themeStore.otherColor.success, 0.1),
}"
>
<div v-if="homeData?.ai_assist_enabled" class="mt-3 flex items-center gap-2 rounded-lg bg-[var(--success-bg)] px-3 py-2 text-[11px] text-[var(--success-color)]">
<div v-if="homeData?.ai_assist_enabled" class="mt-3 flex items-center gap-2 rounded-lg bg-[var(--success-bg)] px-3 py-2 text-xs text-[var(--success-color)]">
<Icon icon="lucide:sparkles" class="size-12px" />
AI 辅助当前已启用 · 可在「AI 开关与维护」调整
</div>
<div v-else class="mt-3 flex items-center gap-2 rounded-lg bg-[var(--warning-bg)] px-3 py-2 text-[11px] text-[var(--warning-color)]">
<div v-else class="mt-3 flex items-center gap-2 rounded-lg bg-[var(--warning-bg)] px-3 py-2 text-xs text-[var(--warning-color)]">
<Icon icon="lucide:sparkles" class="size-12px" />
AI 辅助当前未启用 · 可在「AI 开关与维护」调整
</div>
+28 -8
View File
@@ -102,7 +102,11 @@ onUnmounted(() => {
<template>
<NSpin :show="loading" size="small">
<div class="space-y-3.5">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:12">
<div class="flex-y-center">
@@ -130,7 +134,7 @@ onUnmounted(() => {
<NGrid cols="s:1 m:3 l:3" responsive="screen" :x-gap="16" :y-gap="16">
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -150,7 +154,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -170,7 +174,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -194,7 +198,11 @@ onUnmounted(() => {
<!-- 左侧 -->
<NGi span="24 s:24 m:24 l:16">
<!-- 变更动态 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
@@ -247,7 +255,11 @@ onUnmounted(() => {
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="mt-[16px]">
<!-- 全厂变更阶段分布 -->
<NGi span="24 s:24 m:24 l:24">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">全厂变更阶段分布</p>
@@ -263,7 +275,11 @@ onUnmounted(() => {
<!-- 右侧 -->
<NGi span="24 s:24 m:24 l:8">
<!-- 资料快捷查阅 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">资料快捷查阅</p>
@@ -300,7 +316,11 @@ onUnmounted(() => {
</div>
</NCard>
<!-- 全厂统计概览 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">全厂统计概览</p>
+34 -10
View File
@@ -125,7 +125,11 @@ onUnmounted(() => {
<template>
<NSpin :show="loading" size="small">
<div class="space-y-3.5">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:16">
<div class="flex-y-center">
@@ -151,7 +155,7 @@ onUnmounted(() => {
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<NGi>
<div
class="flex-1 bg-[#fff2f0] px-16px py-12px text-[#e15555] cursor-pointer"
class="flex-1 bg-[#fff2f0] px-16px py-12px text-[#e15555] cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_mytask')"
>
@@ -168,7 +172,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_mytask')"
>
@@ -185,7 +189,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_mytask')"
>
@@ -202,7 +206,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff2f0] px-16px py-12px text-[#e15555] cursor-pointer"
class="flex-1 bg-[#fff2f0] px-16px py-12px text-[#e15555] cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_mytask')"
>
@@ -223,7 +227,11 @@ onUnmounted(() => {
<!-- 左侧 -->
<NGi span="24 s:24 m:24 l:16">
<!-- 我的变更进度 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
@@ -305,7 +313,11 @@ onUnmounted(() => {
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="mt-[16px]">
<!-- 临时变更到期提醒情况 -->
<NGi span="12 s:12 m:12 l:12">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">临时变更到期提醒情况</p>
@@ -342,7 +354,11 @@ onUnmounted(() => {
</NGi>
<!-- 尚未关闭的变更所处状态 -->
<NGi span="12 s:12 m:12 l:12">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px',height:'100%' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px',height:'100%' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">尚未关闭的变更所处状态</p>
@@ -379,7 +395,11 @@ onUnmounted(() => {
<!-- 右侧 -->
<NGi span="24 s:24 m:24 l:8">
<!-- 被驳回信息处理 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="system-uicons:reset" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">被驳回信息处理</p>
@@ -417,7 +437,11 @@ onUnmounted(() => {
</div>
</NCard>
<!-- 资料归档待办 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="quill:folder-open" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">资料归档待办</p>
+29 -9
View File
@@ -114,7 +114,11 @@ onUnmounted(() => {
<template>
<NSpin :show="loading" size="small">
<div class="space-y-3.5">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:12">
<div class="flex-y-center">
@@ -143,7 +147,7 @@ onUnmounted(() => {
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_mytask')"
>
@@ -164,7 +168,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('professionalstatistics')"
>
@@ -184,7 +188,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_highrisk')"
>
@@ -204,7 +208,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('professionalstatistics')"
>
@@ -228,7 +232,11 @@ onUnmounted(() => {
<!-- 左侧 -->
<NGi span="24 s:24 m:24 l:16">
<!-- 待我会签 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<Icon icon="lucide:clipboard-check" class="size-16px" :style="{ color: themeStore.themeColor }" />
@@ -306,7 +314,11 @@ onUnmounted(() => {
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="mt-[16px]">
<!-- 最近已会签 -->
<NGi span="24 s:24 m:24 l:24">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="ix:success" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">最近已会签</p>
@@ -336,7 +348,11 @@ onUnmounted(() => {
<!-- 右侧 -->
<NGi span="24 s:24 m:24 l:8">
<!-- 高风险变更 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">高风险变更</p>
@@ -360,7 +376,11 @@ onUnmounted(() => {
</template>
</NCard>
<!-- 我的会签效率 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">我的会签效率</p>
+39 -18
View File
@@ -6,7 +6,7 @@ import { useRouterPush } from '@/hooks/common/router';
import { getColorWithOpacity, tagTextColor, tagBgColor, tagBorderColor, downloadFileFun } from '@/utils/common';
import { systemConfigApi } from "@/service/api/system";
import { homeSafeApi } from "@/service/api/statistics";
import previewComponent from '@/components/previewFile/index.vue'
import PreviewComponent from '@/components/previewFile/index.vue'
const themeStore = useThemeStore();
const { routerPushByKey } = useRouterPush();
@@ -64,7 +64,7 @@ const viewFile = (file: any) => {
};
// 下载
const downloadFile = (file: any) => {
downloadFileFun(file.file_url, file.file_name);
downloadFileFun(file?.file_url, file?.file_name || file?.doc_name);
};
// 下载模板
const downloadTemplate = (record: any) => {
@@ -131,7 +131,11 @@ onUnmounted(() => {
<template>
<NSpin :show="loading" size="small">
<div class="space-y-3.5">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:10">
<div class="flex-y-center">
@@ -163,7 +167,7 @@ onUnmounted(() => {
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_mytask')"
>
@@ -184,7 +188,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_pssr')"
>
@@ -204,7 +208,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_highrisk')"
>
@@ -224,7 +228,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -249,7 +253,11 @@ onUnmounted(() => {
<!-- 左侧 -->
<NGi span="24 s:24 m:24 l:16">
<!-- 审批待办 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<Icon icon="lucide:clipboard-check" class="size-16px" :style="{ color: themeStore.themeColor }" />
@@ -263,18 +271,19 @@ onUnmounted(() => {
</div>
<div class="flex flex-col gap-3">
<template v-if="homeData?.todo_sign?.total > 0">
<div v-for="row in homeData?.todo_sign?.items?.slice(0, 3) || []" :key="row.change_id" @click="jumpTo('details_approval',row.change_id)"
class="cursor-pointer flex w-full items-center gap-3 border px-3.5 py-2.5 text-left transition hover:border-[var(--hover-border)] hover:bg-[var(--hover-bg)] hover:bg-[#EAF0F9]/40"
<div
v-for="row in homeData?.todo_sign?.items?.slice(0, 3) || []" :key="row.change_id" class="cursor-pointer flex w-full items-center gap-3 border px-3.5 py-2.5 text-left transition hover:border-[var(--hover-border)] hover:bg-[var(--hover-bg)] hover:bg-[#EAF0F9]/40"
:style="{
borderRadius: themeStore.themeRadius + 'px',
'--hover-border': themeStore.themeColor,
'--hover-bg': getColorWithOpacity(themeStore.themeColor, 0.05)
}"
@click="jumpTo('details_approval',row.change_id)"
>
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<span class="text-[11px] text-slate-400">{{ row.change_no }}</span>
<NTag class="text-[11px]" v-if="row.urgent===1" type="error" size="small">紧急</NTag>
<NTag v-if="row.urgent===1" class="text-[11px]" type="error" size="small">紧急</NTag>
<!-- 变更等级 -->
<NTag
size="small"
@@ -283,7 +292,9 @@ onUnmounted(() => {
textColor: tagTextColor(matchObj(levelList,row.change_level)?.color),
borderColor: tagBorderColor(matchObj(levelList,row.change_level)?.color),
}"
>{{matchObj(levelList,row.change_level)?.label}}</NTag>
>
{{ matchObj(levelList,row.change_level)?.label }}
</NTag>
<!-- 变更类型 -->
<NTag
size="small"
@@ -292,10 +303,12 @@ onUnmounted(() => {
textColor: tagTextColor(matchObj(typeList,row.change_type)?.color),
borderColor: tagBorderColor(matchObj(typeList,row.change_type)?.color),
}"
>{{matchObj(typeList,row.change_type)?.label}}</NTag>
>
{{ matchObj(typeList,row.change_type)?.label }}
</NTag>
<NTag class="text-[11px]" v-if="row.duration_type===2" type="warning" size="small">临时</NTag>
<NTag class="text-[11px]" v-else type="info" size="small">永久</NTag>
<NTag v-if="row.duration_type===2" class="text-[11px]" type="warning" size="small">临时</NTag>
<NTag v-else class="text-[11px]" type="info" size="small">永久</NTag>
<span v-if="row.overdue_status===2" class="text-xs" :style="{ color: themeStore.otherColor.error }">已超期 {{ row.overdue_days || 0 }} 天</span>
</div>
<div class="mt-1 flex items-center gap-2">
@@ -327,7 +340,11 @@ onUnmounted(() => {
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="mt-[16px]">
<!-- 通过审批的变更(未关闭) -->
<NGi span="24 s:24 m:24 l:24">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="lucide:list-checks" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">通过审批的变更(未关闭)</p>
@@ -372,7 +389,11 @@ onUnmounted(() => {
<!-- 右侧 -->
<NGi span="24 s:24 m:24 l:8">
<!-- 高风险变更跟踪 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">高风险变更跟踪</p>
@@ -429,7 +450,7 @@ onUnmounted(() => {
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
</div>
<previewComponent v-if="previewFile" :previewFile="previewFile" />
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
</div>
</template>
</NModal>
+67 -25
View File
@@ -172,7 +172,11 @@ onUnmounted(() => {
<template>
<NSpin :show="loading" size="small">
<div class="space-y-3.5">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:8">
<div class="flex-y-center">
@@ -201,7 +205,7 @@ onUnmounted(() => {
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_mytask')"
>
@@ -219,7 +223,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff2f0] px-16px py-12px text-[#e15555] cursor-pointer"
class="flex-1 bg-[#fff2f0] px-16px py-12px text-[#e15555] cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -236,7 +240,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_mytask')"
>
@@ -253,7 +257,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff2f0] px-16px py-12px text-[#e15555] cursor-pointer"
class="flex-1 bg-[#fff2f0] px-16px py-12px text-[#e15555] cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -274,7 +278,11 @@ onUnmounted(() => {
<!-- 左侧 -->
<NGi span="24 s:24 m:24 l:16">
<!-- 审批待办 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<Icon icon="lucide:clipboard-check" class="size-16px" :style="{ color: themeStore.themeColor }" />
@@ -288,18 +296,19 @@ onUnmounted(() => {
</div>
<div class="flex flex-col gap-3">
<template v-if="homeData?.todo_approvals?.total > 0">
<div v-for="row in homeData?.todo_approvals?.items || []" :key="row.change_id" @click="jumpTo('details_approval',row.change_id)"
class="cursor-pointer flex w-full items-center gap-3 border px-3.5 py-2.5 text-left transition"
<div
v-for="row in homeData?.todo_approvals?.items || []" :key="row.change_id" class="cursor-pointer flex w-full items-center gap-3 border px-3.5 py-2.5 text-left transition"
:style="{
borderRadius: themeStore.themeRadius + 'px',
borderColor: row.overdue_status===2?getColorWithOpacity(themeStore.otherColor.error, 0.25):getColorWithOpacity(themeStore.themeColor, 0.25),
backgroundColor: row.overdue_status===2?getColorWithOpacity(themeStore.otherColor.error, 0.05):getColorWithOpacity(themeStore.themeColor, 0.05),
}"
@click="jumpTo('details_approval',row.change_id)"
>
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<span class="font-mono text-[11px] text-slate-400">{{ row.change_no }}</span>
<NTag class="text-[11px]" v-if="row.urgent===1" type="error" size="small">紧急</NTag>
<NTag v-if="row.urgent===1" class="text-[11px]" type="error" size="small">紧急</NTag>
<!-- 变更等级 -->
<NTag
size="small"
@@ -308,7 +317,9 @@ onUnmounted(() => {
textColor: tagTextColor(matchObj(levelList,row.change_level)?.color),
borderColor: tagBorderColor(matchObj(levelList,row.change_level)?.color),
}"
>{{matchObj(levelList,row.change_level)?.label}}</NTag>
>
{{ matchObj(levelList,row.change_level)?.label }}
</NTag>
<!-- 变更类型 -->
<NTag
size="small"
@@ -317,10 +328,12 @@ onUnmounted(() => {
textColor: tagTextColor(matchObj(typeList,row.change_type)?.color),
borderColor: tagBorderColor(matchObj(typeList,row.change_type)?.color),
}"
>{{matchObj(typeList,row.change_type)?.label}}</NTag>
>
{{ matchObj(typeList,row.change_type)?.label }}
</NTag>
<NTag class="text-[11px]" v-if="row.duration_type===2" type="warning" size="small">临时</NTag>
<NTag class="text-[11px]" v-else type="info" size="small">永久</NTag>
<NTag v-if="row.duration_type===2" class="text-[11px]" type="warning" size="small">临时</NTag>
<NTag v-else class="text-[11px]" type="info" size="small">永久</NTag>
<span v-if="row.overdue_status===2" class="text-xs" :style="{ color: themeStore.otherColor.error }">已超期 {{ row.overdue_days }} 天</span>
</div>
<div class="mt-1 flex items-center gap-2">
@@ -346,7 +359,11 @@ onUnmounted(() => {
</div>
</NCard>
<!-- 未关闭变更进度一览 -->
<NCard :bordered="false" size="small" class="mt-16px" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="mt-16px shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
@@ -360,13 +377,14 @@ onUnmounted(() => {
</div>
<div class="flex flex-col gap-3">
<template v-if="homeData?.unclosed_progress?.total > 0">
<div v-for="row in homeData?.unclosed_progress?.items || []" :key="row.change_id" @click="jumpTo('details_index',row.change_id)"
class="cursor-pointer flex w-full items-center gap-3 border px-3.5 py-2.5 text-left transition hover:border-[var(--hover-border)] hover:bg-[var(--hover-bg)]"
<div
v-for="row in homeData?.unclosed_progress?.items || []" :key="row.change_id" class="cursor-pointer flex w-full items-center gap-3 border px-3.5 py-2.5 text-left transition hover:border-[var(--hover-border)] hover:bg-[var(--hover-bg)]"
:style="{
borderRadius: themeStore.themeRadius + 'px',
'--hover-border': getColorWithOpacity(themeStore.themeColor, 0.25),
'--hover-bg': getColorWithOpacity(themeStore.themeColor, 0.05)
}"
@click="jumpTo('details_index',row.change_id)"
>
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
@@ -387,7 +405,9 @@ onUnmounted(() => {
textColor: tagTextColor(matchObj(levelList,row.change_level)?.color),
borderColor: tagBorderColor(matchObj(levelList,row.change_level)?.color),
}"
>{{matchObj(levelList,row.change_level)?.label}}</NTag>
>
{{ matchObj(levelList,row.change_level)?.label }}
</NTag>
</div>
<div class="mt-1 flex items-center gap-2 font-mono text-[11px] text-slate-400">
<span>{{ row.change_no }}</span>
@@ -423,7 +443,11 @@ onUnmounted(() => {
</div>
</NCard>
<!-- 临时变更投用后情况 -->
<NCard :bordered="false" size="small" class="mt-16px" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="mt-16px shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
@@ -433,7 +457,8 @@ onUnmounted(() => {
</div>
<div class="flex flex-col gap-3">
<template v-if="homeData?.temp_after_use?.total > 0">
<div v-for="row in homeData?.temp_after_use?.items || []" :key="row.change_id"
<div
v-for="row in homeData?.temp_after_use?.items || []" :key="row.change_id"
class="flex w-full items-center gap-3 border px-3.5 py-2.5 text-left transition"
:style="{
borderRadius: themeStore.themeRadius + 'px',
@@ -474,7 +499,11 @@ onUnmounted(() => {
</div>
</NCard>
<!-- 重点关注 -->
<NCard :bordered="false" size="small" class="mt-16px" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="mt-16px shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
@@ -484,15 +513,16 @@ onUnmounted(() => {
</div>
<div class="flex flex-col gap-3">
<template v-if="homeData?.key_attention?.total > 0">
<div v-for="row in homeData?.key_attention?.items || []" :key="row.change_id"
<div
v-for="row in homeData?.key_attention?.items || []" :key="row.change_id"
class="cursor-pointer flex w-full items-center gap-3 border px-3.5 py-2.5 text-left transition !hover:border-[var(--hover-border)]"
@click="jumpTo('details_index',row.change_id)"
:style="{
borderRadius: themeStore.themeRadius + 'px',
'--hover-border': getColorWithOpacity(themeStore.otherColor.error, 0.3),
backgroundColor: getColorWithOpacity(themeStore.otherColor.error, 0.05),
borderColor: getColorWithOpacity(themeStore.otherColor.error, 0.05),
}"
@click="jumpTo('details_index',row.change_id)"
>
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
@@ -526,7 +556,11 @@ onUnmounted(() => {
<!-- 右侧 -->
<NGi span="24 s:24 m:24 l:8">
<!-- 本车间统计 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center flex-wrap gap-2">
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">本车间统计</p>
@@ -577,7 +611,11 @@ onUnmounted(() => {
</div>
</NCard>
<!-- 资料管理 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="material-symbols:folder-open-outline" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">资料管理</p>
@@ -614,7 +652,11 @@ onUnmounted(() => {
</div>
</NCard>
<!-- 近一年验收结论占比 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">近一年验收结论占比</p>
+29 -9
View File
@@ -113,7 +113,11 @@ onUnmounted(() => {
<template>
<NSpin :show="loading" size="small">
<div class="space-y-3.5">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:8">
<div class="flex-y-center">
@@ -153,7 +157,7 @@ onUnmounted(() => {
>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_mytask')"
>
@@ -172,7 +176,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -194,7 +198,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -216,7 +220,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -241,7 +245,11 @@ onUnmounted(() => {
<!-- 左侧 -->
<NGi span="24 s:24 m:24 l:16">
<!-- 重要变更审批 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<Icon icon="lucide:clipboard-check" class="size-16px" :style="{ color: themeStore.themeColor }" />
@@ -302,7 +310,11 @@ onUnmounted(() => {
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="mt-[16px]">
<!-- 各部门 / 人员审批时长 -->
<NGi span="24 s:24 m:24 l:24">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px',height:'100%' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px',height:'100%' }">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
@@ -364,7 +376,11 @@ onUnmounted(() => {
<!-- 右侧 -->
<NGi span="24 s:24 m:24 l:8">
<!-- 超期未关闭变更 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="proicons:alert-triangle" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">超期未关闭变更</p>
@@ -408,7 +424,11 @@ onUnmounted(() => {
</div>
</NCard>
<!-- 临时变更临期或到期 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<div class="flex items-center justify-between">
<div class="flex-1">
<div class="flex items-center gap-2">
+29 -9
View File
@@ -114,7 +114,11 @@ onUnmounted(() => {
<template>
<NSpin :show="loading" size="small">
<div class="space-y-3.5">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:12">
<div class="flex-y-center">
@@ -143,7 +147,7 @@ onUnmounted(() => {
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_mytask')"
>
@@ -164,7 +168,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('signstatistics')"
>
@@ -183,7 +187,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -203,7 +207,7 @@ onUnmounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3"
class="flex-1 bg-[#fff] px-16px py-12px cursor-pointer flex items-center gap-3 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('signstatistics')"
>
@@ -226,7 +230,11 @@ onUnmounted(() => {
<!-- 左侧 -->
<NGi span="24 s:24 m:24 l:16">
<!-- 待我会签 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<Icon icon="lucide:clipboard-check" class="size-16px" :style="{ color: themeStore.themeColor }" />
@@ -301,7 +309,11 @@ onUnmounted(() => {
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="mt-[16px]">
<!-- 最近已会签 -->
<NGi span="24 s:24 m:24 l:24">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="ix:success" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">最近已会签</p>
@@ -331,7 +343,11 @@ onUnmounted(() => {
<!-- 右侧 -->
<NGi span="24 s:24 m:24 l:8">
<!-- 质量关注变更 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">质量关注变更</p>
@@ -355,7 +371,11 @@ onUnmounted(() => {
</template>
</NCard>
<!-- 我的会签效率 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px',marginTop:'16px' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-13px font-700 text-#64749a">我的会签效率</p>
+15 -15
View File
@@ -105,7 +105,7 @@ onMounted(() => {
<NSpin :show="loading" size="small">
<NSpace vertical :size="16">
<!-- 我的工作台 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex flex-wrap items-center gap-2">
<p class="text-sm font-bold text-slate-800">全厂变更数据中心</p>
<p class="text-[11px] text-slate-400 mt-[1px]">时间相关数据统一在此查询 · 统计时段</p>
@@ -128,7 +128,7 @@ onMounted(() => {
>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -147,7 +147,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -166,7 +166,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_highrisk')"
>
@@ -193,7 +193,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1"
class="h-100% flex-1 flex items-center gap-3 bg-white px-16px py-12px cursor-pointer transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_effect')"
>
@@ -222,7 +222,7 @@ onMounted(() => {
}"
>
<NGi span="24 s:24 m:24 l:14">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
<p class="text-13px font-700 text-#64749a">各车间变更数量</p>
@@ -232,7 +232,7 @@ onMounted(() => {
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:10">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
<p class="text-13px font-700 text-#64749a">变更类型分布</p>
@@ -253,7 +253,7 @@ onMounted(() => {
}"
>
<NGi span="24 s:24 m:24 l:10">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="lucide:shield-alert" class="size-16px text-[--theme-color]" />
<p class="text-13px font-700 text-#64749a">风险增量与管控</p>
@@ -262,19 +262,19 @@ onMounted(() => {
<div class="space-y-2.5">
<div class="grid grid-cols-2 gap-2">
<div class="rounded-lg bg-red-50 px-3 py-2">
<div class="text-[11px] text-[var(--error-color)]">新增固有高风险项</div>
<div class="text-xs text-[var(--error-color)]">新增固有高风险项</div>
<div class="text-xl font-bold text-[var(--error-color)]">{{homeData?.risk?.inherent_high || 0}}</div>
</div>
<div class="rounded-lg bg-amber-50 px-3 py-2">
<div class="text-[11px] text-[var(--warning-color)]">新增中风险项</div>
<div class="text-xs text-[var(--warning-color)]">新增中风险项</div>
<div class="text-xl font-bold text-[var(--warning-color)]">{{homeData?.risk?.inherent_mid || 0}}</div>
</div>
<div class="rounded-lg bg-slate-50 px-3 py-2">
<div class="text-[11px] text-slate-500">剩余高风险项</div>
<div class="text-xs text-slate-500">剩余高风险项</div>
<div class="text-xl font-bold text-slate-800">{{homeData?.risk?.residual_high || 0}}</div>
</div>
<div class="rounded-lg bg-slate-50 px-3 py-2">
<div class="text-[11px] text-slate-500">剩余中风险项</div>
<div class="text-xs text-slate-500">剩余中风险项</div>
<div class="text-xl font-bold text-slate-800">{{homeData?.risk?.residual_mid || 0}}</div>
</div>
</div>
@@ -297,7 +297,7 @@ onMounted(() => {
</NCard>
</NGi>
<NGi span="24 s:24 m:24 l:14">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="flex items-center mb-3 gap-2">
<Icon icon="material-symbols:check-circle-outline" class="size-16px text-[--theme-color]" />
<p class="text-13px font-700 text-#64749a">变更投入和效果</p>
@@ -305,11 +305,11 @@ onMounted(() => {
</div>
<RingChart2 v-if="homeData" :data="homeData?.invest?.items" :range="dateLabel" />
<div class="flex items-center justify-between border-slate-200 pt-1.5">
<span class="text-[11px] text-slate-400">投入合计(变更材料和实施费) · 完全达到率 {{homeData?.invest?.full_rate || 0}}%</span>
<span class="text-xs text-slate-400">投入合计(变更材料和实施费) · 完全达到率 {{homeData?.invest?.full_rate || 0}}%</span>
<span class="text-xs font-bold text-[var(--theme-color)]">{{homeData?.invest?.total_fee || 0}} 万元</span>
</div>
<div @click="jumpTo('my_effect')"
class="cursor-pointer mt-3 flex w-full items-center justify-center gap-1 rounded-lg border border-dashed py-2 text-[11px] text-slate-400 transition hover:border-[#6E93C6] hover:text-[#1D4E9C]">
class="cursor-pointer mt-3 flex w-full items-center justify-center gap-1 rounded-lg border border-dashed py-2 text-xs text-slate-400 transition hover:border-[#6E93C6] hover:text-[#1D4E9C]">
验收明细(名称 / 日期链 / 费用 / 结论)见左边栏「变更投入和效果」<ChevronRight class="h-3 w-3" />
</div>
</NCard>
+2 -2
View File
@@ -150,7 +150,7 @@ onMounted(() => {
</NButton>
</div>
<div class="rounded-lg bg-white shadow-[0_2px_8px_rgba(0,0,0,0.06)]">
<div class="flex items-center gap-3 border-b bg-slate-50 px-4 py-2 text-[11px] font-semibold text-slate-500">
<div class="flex items-center gap-3 border-b bg-slate-50 px-4 py-2 text-xs font-semibold text-slate-500">
<span class="min-w-0 flex-1">变更名称 / 编号</span>
<span class="w-[92px] shrink-0">申请日期</span>
<span class="w-[92px] shrink-0">投用日期</span>
@@ -190,7 +190,7 @@ onMounted(() => {
<NTag v-if="r.duration_type===2" size="small" type="warning">临时</NTag>
<NTag v-if="r.duration_type===1" size="small" type="info">永久</NTag>
</span>
<span class="mt-0.5 block font-mono text-[11px] text-slate-400">{{ r.change_no }} · {{ r.applicant_name }} · 费用取验收时填报的「变更材料和实施费」</span>
<span class="mt-1 block text-xs text-slate-400">{{ r.change_no }} · {{ r.applicant_name }} · 费用取验收时填报的「变更材料和实施费」</span>
</span>
<span class="w-[92px] shrink-0 text-xs text-slate-600">{{ r.apply_time?.slice(0,10) }}</span>
<span class="w-[92px] shrink-0 text-xs text-slate-600">{{ r.use_time?.slice(0,10) }}</span>
+1 -1
View File
@@ -388,7 +388,7 @@ onMounted(() => {
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<!-- 顶部 -->
<div class="border-b bg-[var(--bg-color)]" :style="{'--bg-color': getColorWithOpacity(themeStore.themeColor, 0.1)}">
<div class="border-b bg-[var(--bg-color)]" :style="{'--bg-color': getColorWithOpacity(themeStore.themeColor, 0.15)}">
<div
class="flex flex-wrap items-center gap-x-3 gap-y-2 border-b px-4 py-2 border-[var(--border-color)]"
:style="{'--border-color': getColorWithOpacity(themeStore.themeColor, 0.2)}"
@@ -8,7 +8,7 @@ import { localStg } from '@/utils/storage';
import { AiError, aiTypeJudge, aiLevelScore, aiApplication } from "@/service/api/ai";
import { uploadFileApi, deleteFileApi } from "@/service/api/file";
import Footer from './footer.vue'
import previewComponent from '@/components/previewFile/index.vue'
import PreviewComponent from '@/components/previewFile/index.vue'
const themeStore = useThemeStore();
@@ -60,7 +60,7 @@ const form = ref<{
level_score: any[],
}>({
change_type: null,
change_level: null,
change_level: 1,
duration_type: null,
restore_deadline: null,
org_id: null,
@@ -172,6 +172,8 @@ const currentSteps = ref<any>(null);
watch(() => props.currentForm, (newVal:any) => {
if (!newVal) return;
isSyncingFromParent.value = true;
uploadFile.value = newVal?.attachments || [];
// 过滤出需要的字段
// 过滤出需要的字段
const {
approval_flow,
@@ -232,7 +234,7 @@ const filterMembersBySpecialties = (steps: any, specialties: string[]) => {
}
// 更新当前模板步骤
const updateCurrentTemplate = () => {
let template = props.templateList.find((item: any) =>
const template = props.templateList.find((item: any) =>
item.level === form.value?.change_level &&
item.category === form.value?.change_type &&
item.duration === form.value?.duration_type
@@ -266,7 +268,7 @@ const typeAi = ref<{ type: string; confidence: number; conclusion: string; hit_r
const typeAiLoading = ref<boolean>(false);
// 打开变更类型抽屉
const openTypeSheet = async () => {
let form = props.formInfo;
const form = props.formInfo;
if (!form?.description) {
return window.$message?.warning("缺少变更描述,请先在 [变更预识别] 里面输入变更描述");
}
@@ -296,7 +298,7 @@ const levelAiBasis = ref<string[]>([]);
const levelAiLoading = ref<boolean>(false);
// 打开等级判定表抽屉
const openLevelSheet = async () => {
let form = props.formInfo;
const form = props.formInfo;
if (!form?.description && props.isAiOpen) {
return window.$message?.warning("缺少变更描述,请先在 [变更预识别] 里面输入变更描述");
}
@@ -305,7 +307,7 @@ const openLevelSheet = async () => {
};
// 使用AI 评分
const useAiLevel = async () => {
let form = props.formInfo;
const form = props.formInfo;
if (levelAiLoading.value) return;
levelAiLoading.value = true;
try {
@@ -482,7 +484,7 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
<!-- 预期效果 -->
<div class="flex gap-3">
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">预期效果</div>
<NInput type="textarea" v-model:value="form.effect" rows="2" placeholder="" />
<NInput v-model:value="form.effect" type="textarea" rows="2" placeholder="" />
</div>
<!-- 主要变更内容 -->
<div class="flex gap-3">
@@ -494,8 +496,8 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
<NPopconfirm
v-if="form.main_changes.length > 1"
positive-text="确定"
:positiveButtonProps="{ size: 'tiny' }"
:negativeButtonProps="{ size: 'tiny' }"
:positive-button-props="{ size: 'tiny' }"
:negative-button-props="{ size: 'tiny' }"
@positive-click="form.main_changes.splice(i, 1)"
>
<template #trigger>
@@ -521,8 +523,8 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
<NPopconfirm
v-if="form.related_changes.length > 1"
positive-text="确定"
:positiveButtonProps="{ size: 'tiny' }"
:negativeButtonProps="{ size: 'tiny' }"
:positive-button-props="{ size: 'tiny' }"
:negative-button-props="{ size: 'tiny' }"
@positive-click="form.related_changes.splice(i, 1)"
>
<template #trigger>
@@ -588,14 +590,15 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
<div class="flex gap-3">
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">风险分析</div>
<div class="flex flex-1 flex-wrap items-center gap-2 rounded-lg bg-slate-50 px-3 py-2">
<NButton size="small" round v-for="t in RISK_TOOLS_FORM" :key="t.key" @click="toggleTool(t)" title="点击选用 / 取消;AI 辅助分析在各工具插件内使用"
class="text-sm"
<NButton
v-for="t in RISK_TOOLS_FORM" :key="t.key" size="small" round title="点击选用 / 取消;AI 辅助分析在各工具插件内使用" class="text-sm"
:ghost="form.risk_tools?.includes(t.key) ? false : true"
:type="form.risk_tools?.includes(t.key) ? 'primary' : 'default'"
@click="toggleTool(t)"
>
{{ t.label }}
</NButton>
<NButton type="primary" ghost dashed size="small" round @click="recommendTools" class="text-xs">
<NButton type="primary" ghost dashed size="small" round class="text-xs" @click="recommendTools">
按等级重新推荐
</NButton>
</div>
@@ -620,10 +623,10 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
<!-- 所需材料 -->
<div class="flex gap-3">
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">所需材料</div>
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">所需施工材料</div>
<div class="flex-1 space-y-2">
<div class="text-[11px] text-slate-400">施工所需材料(生成变更申请表时系统自动生成,支持手动修改)</div>
<NInput type="textarea" v-model:value="form.materials" rows="4" placeholder="点击「生成变更申请单」后由系统按变更内容自动生成,也可直接填写" />
<NInput v-model:value="form.materials" type="textarea" rows="4" placeholder="点击「生成变更申请单」后由系统按变更内容自动生成,也可直接填写" />
</div>
</div>
<!-- 资料上传 -->
@@ -631,9 +634,9 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">资料上传</div>
<div class="flex flex-1 flex-wrap items-center gap-2 rounded-lg border border-dashed p-3">
<div
class="relative"
:style="{'--theme-color':themeStore.themeColor}"
v-for="(f,index) in uploadFile" :key="f.id"
v-for="(f,index) in uploadFile"
:key="f.id"
class="relative" :style="{'--theme-color':themeStore.themeColor}"
>
<div
class="text-xs cursor-pointer flex items-center border py-1.5 px-2 rounded-md hover:border-[var(--theme-color)] hover:text-[var(--theme-color)]"
@@ -644,8 +647,8 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
</div>
<NPopconfirm
positive-text="确定"
:positiveButtonProps="{ size: 'tiny' }"
:negativeButtonProps="{ size: 'tiny' }"
:positive-button-props="{ size: 'tiny' }"
:negative-button-props="{ size: 'tiny' }"
@positive-click="removeFile(index)"
>
<template #trigger>
@@ -691,7 +694,7 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
</div>
<div v-if="signers && signers.length" class="mt-1.5 flex flex-wrap items-center gap-1.5 text-xs text-slate-500">
<Icon icon="lucide:users" class="size-12px" />已联动专业会签:
<NTag size="small" type="info" v-for="(p, d) in signers" :key="d">{{ p?.member?.specialty }} · {{ p?.member?.user_name }}{{p?.stepName ? ` (${p.stepName})` : ''}}</NTag>
<NTag v-for="(p, d) in signers" :key="d" size="small" type="info">{{ p?.member?.specialty }} · {{ p?.member?.user_name }}{{ p?.stepName ? ` (${p.stepName})` : '' }}</NTag>
</div>
</div>
</div>
@@ -700,21 +703,21 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
<Footer
ref="footerRef"
:type="props.type"
:currentId="props.currentId"
:current-id="props.currentId"
:title="props.title"
:isAiOpen="props.isAiOpen"
:currentForm="form"
:is-ai-open="props.isAiOpen"
:current-form="form"
:confirmed="props.confirmed"
:members="props.members"
:steps="currentSteps"
@save="saveDraft"
@submit="submitConfirm"
@confirmTab="confirmTab"
@confirm-tab="confirmTab"
@regenerate="onRegenerate"
@update:steps="updateSteps"
/>
<!-- 侧边抽屉:AI 类型判定 / 等级判定表 -->
<NDrawer :show="sheet !== ''" @update:show="(v) => !v && (sheet = '')" placement="right" :width="sheet === 'level' ? '45rem' : '28rem'" :auto-focus="false">
<NDrawer :show="sheet !== ''" placement="right" :width="sheet === 'level' ? '45rem' : '28rem'" :auto-focus="false" @update:show="(v) => !v && (sheet = '')">
<NDrawerContent>
<template #header>
<div v-if="sheet === 'type'" class="text-base font-semibold text-slate-800">AI 变更类型判定说明</div>
@@ -836,7 +839,7 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
<span>上传时间:{{ previewFile?.created_at }}</span>
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
</div>
<previewComponent v-if="previewFile" :previewFile="previewFile" />
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
</div>
</template>
<template #footer>
@@ -849,6 +852,7 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
</template>
</NModal>
</template>
<style scoped lang="scss">
:deep(.n-radio) {
display: flex;
@@ -3,6 +3,7 @@
import { inject, watch, ref, computed } from 'vue';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { getColorWithOpacity } from "@/utils/common";
import { AiError, aiApplication, aiRecognize, aiRiskPreAnalysis } from "@/service/api/ai";
import ChangeCompareTable from "./ChangeCompareTable.vue";
import PreRisk from "./preRisk.vue";
@@ -158,17 +159,17 @@ watch(() => props.currentForm, (newVal:any) => {
</template>
</div>
</div>
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 bg-[#EAF0F9] px-4 py-2.5 shadow-[0_-2px_8px_rgba(0,0,0,0.05)]">
<span class="text-xs text-slate-400">预识别阶段不设审批人与提交:完成识别与预分析后,请切换至「变更申请表」选择审批人并提交</span>
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 px-4 py-2.5" :style="{ backgroundColor: getColorWithOpacity(themeStore.themeColor, 0.15)}">
<span class="text-xs text-slate-500">预识别阶段不设审批人与提交:完成识别与预分析后,请切换至「变更申请表」选择审批人并提交</span>
<div class="ml-auto flex shrink-0 gap-2">
<NButton ghost type="success" size="small" :loading="generateLoading" :disabled="btnDisabled" @click="genForm">
<Icon icon="akar-icons:file" class="size-14px" /> 生成变更申请单
</NButton>
<NButton ghost type="warning" size="small" @click="runRiskAnalysis" :disabled="btnDisabled">
<Icon v-if="analyzing" icon="ri:loader-4-fill" class="size-14px animate-spin" />
<Icon v-else icon="mdi:shield-alert-outline" class="size-14px" />风险预分析
</NButton>
<NButton type="primary" size="small" :disabled="btnDisabled" :loading="saveLoading" @click="saveDraft">保存</NButton>
<NButton ghost type="success" size="small" :loading="generateLoading" :disabled="btnDisabled" @click="genForm">
<Icon icon="akar-icons:file" class="size-14px" /> 生成变更申请单
</NButton>
</div>
</div>
</template>
@@ -2,8 +2,11 @@
<script setup lang="ts">
import { computed, inject, ref, watch } from 'vue';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { getColorWithOpacity } from "@/utils/common";
import { useAiAnalysisApi } from '@/service/api/change';
const themeStore = useThemeStore();
const props = defineProps([
'type',
'currentId',
@@ -255,7 +258,7 @@ defineExpose({setRegenerateLoading})
</NButton>
</div>
</div>
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 bg-[#EAF0F9] px-4 py-2.5 shadow-[0_-2px_8px_rgba(0,0,0,0.05)]">
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 px-4 py-2.5" :style="{ backgroundColor: getColorWithOpacity(themeStore.themeColor, 0.15)}">
<div class="flex min-w-0 flex-1 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-slate-500">
<Icon icon="lucide:users" class="size-14px" />
<span class="min-w-0 truncate">
@@ -286,7 +289,7 @@ defineExpose({setRegenerateLoading})
<div class="flex flex-wrap items-center gap-2">
<NTag type="info" size="small">{{Number(idx)+1}}</NTag>
<p class="text-sm">{{s.name}}</p>
<NCheckbox :checked="s.countersign" size="small" @update:checked="(v) => setStepCountersign(s, v)">
<NCheckbox :checked="s.countersign" :disabled="true" size="small" @update:checked="(v) => setStepCountersign(s, v)">
<span class="text-xs text-slate-500">专业会签步骤</span>
</NCheckbox>
<NButton ghost type="primary" size="tiny" @click="togglePicker(s)" title="添加预设人员" class="ml-auto">
@@ -45,7 +45,7 @@ const onCellChange = (key:string,val:string) => {
<RichTextInput :disabled="!preRiskEdit" :modelValue="form[s.key]" :minRows="2" :bordered="false" @update:modelValue="(v) => onCellChange(s.key,v)" />
</div>
<div class="rounded-lg bg-amber-50 px-3 py-2 text-xs leading-5 text-amber-700">
AI 风险预分析仅供参考,用于申请阶段的快速研判;正式风险识别请前往「风险分析记录表」标签页,使用 HAZOP / JSA / 检查表法开展并留存记录。
AI 风险预分析仅供参考,用于申请阶段的快速研判;正式风险识别请前往「风险分析记录表」标签页,使用 风险预分析 / HAZOP / FMEA / 风险检查表开展并留存记录。
</div>
</div>
</div>
+1 -1
View File
@@ -89,7 +89,7 @@ onMounted(() => {
<div class="flex flex-wrap items-center gap-3">
<div class="min-w-0 flex-1">
<div class="text-sm font-semibold text-slate-800 dark:text-white">{{ d.title }}</div>
<div class="mt-1 flex flex-wrap items-center gap-2 text-[11px] text-slate-400">
<div class="mt-1 flex flex-wrap items-center gap-2 text-xs text-slate-400">
<span class="font-mono">{{ d.change_no }}</span>
<span>保存于 {{ d.updated_at }}</span>
</div>
+10 -9
View File
@@ -349,7 +349,7 @@ onMounted(() => {
<div v-if="item.status!=='REJECTED'" class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<!-- 变更编号 -->
<span class="font-mono text-[11px] text-slate-400">{{ c.change_no }}</span>
<span class="text-xs text-slate-400">{{ c.change_no }}</span>
<!-- 变更等级 -->
<NTag
size="small"
@@ -378,10 +378,10 @@ onMounted(() => {
</div>
<div class="mt-1 flex flex-wrap items-baseline gap-x-2">
<span class="text-sm font-medium text-slate-800">{{ c.title }}</span>
<span v-if="c.apply_time" class="text-[11px] text-slate-400">申请 {{ c.apply_time.slice(0, 16) }}</span>
<span v-if="c.apply_time" class="text-xs text-slate-400">申请 {{ c.apply_time.slice(0, 16) }}</span>
</div>
<!-- 当前节点与会签成员进度;状态更新时间跟在审核人员名字后面 -->
<div class="mt-1 flex flex-wrap items-center gap-1.5 text-[11px]">
<div class="mt-1 flex flex-wrap items-center gap-1.5 text-xs">
<span class="text-slate-400">当前节点</span>
<span class="font-medium text-slate-600">{{ c.step_name }}</span>
<div v-if="c.current_step_members && c.current_step_members.length" class="flex items-center gap-1">
@@ -439,6 +439,7 @@ onMounted(() => {
<div v-if="c.status === 'IMPLEMENTED' || c.status === 'ACCEPTED' || c.status === 'CLOSED' || c.status === 'WITHDRAWN'" class="flex gap-3">
<NButton class="text-xs" text type="primary" @click="openDetail(c,'detail')">详情</NButton>
<NButton class="text-xs" text type="warning" @click="openProgress(c)">进度</NButton>
<NButton v-if="c.status === 'IMPLEMENTED' || c.status === 'ACCEPTED'" class="text-xs" text type="error" @click="openDocClose(c)">资料关闭</NButton>
</div>
<!-- 驳回处理 -->
<div v-if="c.status === 'REJECTED'" class="flex gap-3">
@@ -470,8 +471,8 @@ onMounted(() => {
<NDrawerContent title="变更进度">
<div class="rounded-lg border bg-slate-50/60 px-3 py-2.5">
<div class="text-sm font-semibold text-slate-800">{{ currentRow?.title }}</div>
<div class="mt-1.5 flex flex-wrap items-center gap-1.5 text-[11px] text-slate-400">
<span class="font-mono">{{ currentRow?.change_no }}</span>
<div class="mt-1.5 flex flex-wrap items-center gap-1.5 text-xs text-slate-400">
<span>{{ currentRow?.change_no }}</span>
<NTag v-if="currentRow.status==='APPROVING'" size="small" type="warning">审批中</NTag>
<NTag v-if="currentRow.status==='APPROVED'" size="small" type="success">已通过</NTag>
<NTag v-if="currentRow.status==='IMPLEMENTED'" size="small" type="success">待验收</NTag>
@@ -481,8 +482,8 @@ onMounted(() => {
<NTag v-if="currentRow.status==='REJECTED'" size="small" type="error">已驳回</NTag>
<span v-if="currentRow?.overdue_status===2" class="font-medium text-red-500">超期 {{ currentRow?.overdue_days }} 天</span>
</div>
<div v-if="currentRow?.dept || currentRow?.applicant" class="mt-1 text-[11px] text-slate-400">{{ [currentRow?.dept, currentRow?.applicant].filter(Boolean).join(' · ') }}</div>
<div v-if="currentRow?.meta" class="mt-1 text-[11px] text-slate-400">{{ currentRow?.meta }}</div>
<div v-if="currentRow?.dept || currentRow?.applicant" class="mt-1 text-xs text-slate-400">{{ [currentRow?.dept, currentRow?.applicant].filter(Boolean).join(' · ') }}</div>
<div v-if="currentRow?.meta" class="mt-1 text-xs text-slate-400">{{ currentRow?.meta }}</div>
</div>
<div class="mt-4">
<div v-for="(s, i) in currentRow?.approval_records" :key="s.id" class="flex gap-3">
@@ -498,11 +499,11 @@ onMounted(() => {
</div>
<div class="pb-5">
<p class="text-sm">{{ s.action }}</p>
<span class="mt-0.5 text-[11px] text-slate-400">{{s.finished_at?.slice(0, 16)}}</span>
<span class="mt-0.5 text-xs text-slate-400">{{s.finished_at?.slice(0, 16)}}</span>
</div>
</div>
</div>
<div v-if="currentRow?.duration_type === 2" class="rounded-lg border border-amber-200 bg-amber-50/50 px-3 py-2 text-[11px] leading-5 text-amber-700">
<div v-if="currentRow?.duration_type === 2" class="rounded-lg border border-amber-200 bg-amber-50/50 px-3 py-2 text-xs leading-5 text-amber-700">
临时变更:计划恢复 {{ currentRow?.restore_deadline ?? '—' }} · 恢复 / 延期 / 转永久请在详情页中操作
</div>
</NDrawerContent>
+7 -7
View File
@@ -40,7 +40,7 @@ const viewFile = (file: any) => {
};
// 下载
const downloadFile = (file: any) => {
downloadFileFun(file.file_url, file.file_name);
downloadFileFun(file?.file_url, file?.file_name || file?.doc_name);
};
// 打开变更单
const openChange = (record: any) => {
@@ -153,19 +153,19 @@ onMounted(() => {
<NTag v-if="s.duration_type===2" size="small" type="warning">临时</NTag>
<NTag v-if="s.duration_type===1" size="small" type="info">永久</NTag>
</div>
<p class="mt-0.5 text-[11px] text-slate-400">{{ s.change_no }} · {{ s.applicant_name }}</p>
<p class="text-slate-400 text-[11px]">审批通过时间:<span class="font-medium text-slate-600">{{ s.approve_time?.slice(0,16) }}</span></p>
<p class="text-slate-400 text-[11px]">变更投用时间:<span class="font-medium text-[var(--warning-color)]">{{ s.use_time ? s.use_time?.slice(0,10) : `待投用(计划 ${s.plan_use_date?.slice(0,10)})` }}</span></p>
<p class="mt-0.5 text-xs text-slate-400">{{ s.change_no }} · {{ s.applicant_name }}</p>
<p class="text-slate-400 text-xs">审批通过时间:<span class="font-medium text-slate-600">{{ s.approve_time?.slice(0,16) }}</span></p>
<p class="text-slate-400 text-xs">变更投用时间:<span class="font-medium text-[var(--warning-color)]">{{ s.use_time ? s.use_time?.slice(0,10) : `待投用(计划 ${s.plan_use_date?.slice(0,10)})` }}</span></p>
</div>
<div class="flex items-center gap-1.5 rounded-md border bg-slate-50/60 px-2 py-1">
<Icon icon="akar-icons:file" class="size-14px text-[var(--theme-color)]" />
<span class="text-[11px] font-medium text-slate-600">PSSR 确认表</span>
<span class="text-xs font-medium text-slate-600">PSSR 确认表</span>
<template v-if="s.pssr_attachment">
<NButton ghost size="tiny" class="text-11px" @click="viewFile(s)">查看</NButton>
<NButton ghost size="tiny" class="text-11px" @click="downloadFile(s)">下载</NButton>
</template>
<template v-else>
<span class="text-[11px] text-[var(--warning-color)]">待传</span>
<span class="text-xs text-[var(--warning-color)]">待传</span>
</template>
</div>
<NButton size="small" ghost class="text-xs" @click="openChange(s)">打开变更单</NButton>
@@ -208,7 +208,7 @@ onMounted(() => {
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
</div>
<previewComponent v-if="previewFile" :previewFile="previewFile" />
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
</div>
</template>
</NModal>
+18 -14
View File
@@ -5,7 +5,7 @@ import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { useRouterPush } from '@/hooks/common/router';
import { formatTimestamp, getTimeRangeStamp, downloadFileFun } from '@/utils/common';
import previewComponent from '@/components/previewFile/index.vue'
import PreviewComponent from '@/components/previewFile/index.vue'
import { pssrListApi } from '@/service/api/pssr';
const themeStore = useThemeStore();
@@ -71,7 +71,7 @@ const viewFile = (file: any) => {
};
// 下载
const downloadFile = (file: any) => {
downloadFileFun(file.file_url, file.file_name);
downloadFileFun(file?.file_url, file?.file_name || file?.doc_name);
};
// 下载模板
const downloadTemplate = (record: any) => {
@@ -123,12 +123,16 @@ onMounted(() => {
:style="{ '--theme-color': themeStore.themeColor}"
>
<div class="flex overflow-hidden rounded-full border">
<div @click="tabChange(0)" class="px-3.5 py-1.5 text-xs cursor-pointer select-none"
:class="formInfo.is_close === 0 ? 'bg-[var(--theme-color)] font-medium text-white' : 'bg-white text-slate-600 hover:bg-slate-50'">
<div
class="px-3.5 py-1.5 text-xs cursor-pointer select-none" :class="formInfo.is_close === 0 ? 'bg-[var(--theme-color)] font-medium text-white' : 'bg-white text-slate-600 hover:bg-slate-50'"
@click="tabChange(0)"
>
未关闭的变更 <b>{{ openCount }}</b>
</div>
<div @click="tabChange(1)" class="border-l px-3.5 py-1.5 text-xs cursor-pointer select-none"
:class="formInfo.is_close === 1 ? 'bg-[var(--theme-color)] font-medium text-white' : 'bg-white text-slate-600 hover:bg-slate-50'">
<div
class="border-l px-3.5 py-1.5 text-xs cursor-pointer select-none" :class="formInfo.is_close === 1 ? 'bg-[var(--theme-color)] font-medium text-white' : 'bg-white text-slate-600 hover:bg-slate-50'"
@click="tabChange(1)"
>
已关闭的变更 <b>{{ closedCount }}</b>
</div>
</div>
@@ -139,10 +143,10 @@ onMounted(() => {
</NInput>
<div class="flex items-center gap-1.5">
<NButton
size="tiny"
round
v-for="t in timeRange"
:key="t.value"
size="tiny"
round
class="text-[11px]"
:type="range === t.value ? 'primary' : 'default'"
@click="timeRangeChange(t.value)"
@@ -152,7 +156,7 @@ onMounted(() => {
</div>
<span class="ml-auto text-[11px] text-slate-400">培训 / PSSR 为线下确认 + 资料上传制 · PSSR 检查表同行查看 / 下载</span>
</div>
<div class="text-[11px] leading-4 text-slate-400 pb-2">
<div class="text-xs leading-4 text-slate-400 pb-2">
培训 / PSSR 为线下组织并签字,拍照或扫描上传即视为完成确认并随单归档;系统不做内容识别,本页为 PSSR 检查表的查看 / 下载入口。
</div>
<!-- 列表 -->
@@ -176,18 +180,18 @@ onMounted(() => {
<NTag v-if="s.status==='REJECTED'" size="small" type="error">已驳回</NTag>
<div class="min-w-0 flex-1">
<p class="block truncate text-sm font-medium text-slate-800">{{ s.title }}</p>
<span class="mt-0.5 block text-[11px] text-slate-400">{{ s.change_no }} · {{ s.org_name }} · {{ s.applicant_name }} · 计划投用 {{ s.plan_use_date }}</span>
<span class="mt-0.5 block text-xs text-slate-400">{{ s.change_no }} · {{ s.org_name }} · {{ s.applicant_name }} · 计划投用 {{ s.plan_use_date }}</span>
</div>
<div class="flex items-center gap-1.5 rounded-md border bg-slate-50/60 px-2 py-1">
<Icon icon="akar-icons:file" class="size-14px text-[var(--theme-color)]" />
<span class="text-[11px] font-medium text-slate-600">PSSR 检查确认表</span>
<span class="text-xs font-medium text-slate-600">PSSR 检查确认表</span>
<template v-if="s.pssr_attachment">
<span class="max-w-[150px] truncate text-[11px] text-slate-400">{{ s.pssr_attachment?.file_name }}</span>
<span class="max-w-[150px] truncate text-xs text-slate-400">{{ s.pssr_attachment?.file_name }}</span>
<NButton ghost size="tiny" class="text-11px" @click="viewFile(s.pssr_attachment)">查看</NButton>
<NButton ghost size="tiny" class="text-11px" @click="downloadFile(s.pssr_attachment)">下载</NButton>
</template>
<template v-else>
<span class="text-[11px] text-[var(--warning-color)]">待传</span>
<span class="text-xs text-[var(--warning-color)]">待传</span>
<NButton ghost size="tiny" class="text-11px" @click="downloadTemplate(s)">下载模板</NButton>
</template>
</div>
@@ -231,7 +235,7 @@ onMounted(() => {
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
</div>
<previewComponent v-if="previewFile" :previewFile="previewFile" />
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
</div>
</template>
</NModal>
+8 -8
View File
@@ -53,7 +53,7 @@ onMounted(() => {
>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">本月会签 / 审批</p>
@@ -69,7 +69,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">我的会签平均耗时</p>
@@ -86,7 +86,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">按时完成率</p>
@@ -102,7 +102,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">参与风险分析</p>
@@ -118,7 +118,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">当前待我审批</p>
@@ -136,7 +136,7 @@ onMounted(() => {
<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}"
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
@@ -179,7 +179,7 @@ onMounted(() => {
</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%' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">本专业变更类型分布(近一年)</p>
<div class="py-3 px-4">
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
@@ -187,7 +187,7 @@ onMounted(() => {
</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%' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">高风险在办(直达风险分析)</p>
<div class="py-3 px-4">
<div v-if="homeData?.high_risk?.total > 0" class="space-y-2 pt-1">
+42 -16
View File
@@ -78,7 +78,7 @@ onMounted(() => {
<NSpin :show="loading" size="small">
<NSpace vertical :size="16">
<!-- 统计报表 -->
<NCard :bordered="false" size="small" :style="{borderRadius: themeStore.themeRadius + 'px'}">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :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>
@@ -99,7 +99,7 @@ onMounted(() => {
<NGrid cols="s:1 m:4 l:4" responsive="screen" :x-gap="16" :y-gap="16" :style="{'--theme-color': themeStore.themeColor}">
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">变更总数</p>
@@ -115,7 +115,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">重要变更</p>
@@ -131,7 +131,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">变更费用合计</p>
@@ -148,7 +148,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">完全达到率</p>
@@ -167,7 +167,7 @@ onMounted(() => {
<NGrid :x-gap="16" :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%' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">各车间变更数量({{dateLabel}})</p>
<div class="py-3 px-4">
<ColumnChart v-if="homeData" :data="homeData?.workshop_counts?.items || []" />
@@ -176,7 +176,7 @@ onMounted(() => {
</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%' }">
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">变更类型分布({{dateLabel}})</p>
<div class="py-3 px-4">
<RingChart v-if="homeData" :data="homeData?.type_dist?.items || []" />
@@ -188,7 +188,7 @@ onMounted(() => {
<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}"
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
@@ -244,8 +244,9 @@ onMounted(() => {
<!-- 变更是否达到预期效果及费用 -->
<NGi span="24 s:24 m:24 l:16">
<NCard
:bordered="true"
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
@@ -317,8 +318,9 @@ onMounted(() => {
<!-- 变更带来的风险增量及管控 -->
<NGi span="24 s:24 m:24 l:8">
<NCard
:bordered="true"
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
@@ -358,7 +360,11 @@ onMounted(() => {
</NGi>
<!-- 当前未关闭变更 -->
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
@@ -395,7 +401,11 @@ onMounted(() => {
</NGi>
<!-- 驳回的变更申请 -->
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
@@ -431,7 +441,11 @@ onMounted(() => {
<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%' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">各部门审批时长(天 · {{dateLabel}})</p>
<div class="py-3 px-4">
<div class="space-y-3">
@@ -456,7 +470,11 @@ onMounted(() => {
</NGi>
<!-- 临时变更后续处置方式 -->
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
@@ -519,7 +537,11 @@ onMounted(() => {
</NGi>
<!-- 主要驳回理由汇总 -->
<NGi span="24 s:24 m:24 l:8">
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
@@ -557,7 +579,11 @@ onMounted(() => {
<!-- 变更归档资料情况 -->
<NGrid :x-gap="16" :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}"
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
+34 -9
View File
@@ -63,7 +63,7 @@ onMounted(() => {
>
<NGi>
<div
class="flex-1 flex items-center gap-3 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer"
class="flex-1 flex items-center gap-3 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('my_highrisk')"
>
@@ -82,7 +82,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 flex items-center gap-3 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 flex items-center gap-3 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<div class="flex items-center">
@@ -100,7 +100,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 flex items-center gap-3 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 flex items-center gap-3 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<div class="flex items-center">
@@ -118,7 +118,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 flex items-center gap-3 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer"
class="flex-1 flex items-center gap-3 bg-white px-16px py-12px text-[#94a3b8] cursor-pointer shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
@click="jumpTo('changeledger')"
>
@@ -146,7 +146,12 @@ onMounted(() => {
}"
>
<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%' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{ padding: '0' }"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">月度新增固有风险项(管控前)</p>
<ColumnChart v-if="homeData" :data="homeData?.monthly_inherent || []" />
</NCard>
@@ -170,7 +175,12 @@ onMounted(() => {
</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%' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{ padding: '0' }"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">PSSR 执行情况</p>
<div class="space-y-5 px-3 pt-5 pb-3">
<!-- 投用前完成率 -->
@@ -201,7 +211,12 @@ onMounted(() => {
</NGrid>
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive :style="{'--theme-color':themeStore.themeColor}">
<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%' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{ padding: '0' }"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">验收与投用质量</p>
<div class="flex justify-center pt-5 pb-3">
<div class="w-50% flex flex-col items-center gap-1.5">
@@ -218,7 +233,12 @@ onMounted(() => {
</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%' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{ padding: '0' }"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">超期未关闭清单</p>
<div class="space-y-2 p-3">
<div v-for="o in homeData?.overdue_list || []" :key="o.change_id" class="flex items-center gap-2 rounded-lg border border-red-200 bg-red-50/40 px-3 py-2">
@@ -246,7 +266,12 @@ onMounted(() => {
</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%' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{ padding: '0' }"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">报表导出</p>
<div class="grid grid-cols-2 gap-3 p-3">
<div v-for="r in SAFETY_EXPORTS" :key="r" @click="handleExport(r)"
+22 -8
View File
@@ -53,7 +53,7 @@ onMounted(() => {
>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">本月会签 / 审批</p>
@@ -69,7 +69,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">我的会签平均耗时</p>
@@ -86,7 +86,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">按时完成率</p>
@@ -102,7 +102,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">参与风险分析</p>
@@ -118,7 +118,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
>
<p class="text-12px font-600">当前待我审批</p>
@@ -136,7 +136,11 @@ onMounted(() => {
<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}"
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
@@ -179,7 +183,12 @@ onMounted(() => {
</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%' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">本专业变更类型分布(近一年)</p>
<div class="py-3 px-4">
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
@@ -187,7 +196,12 @@ onMounted(() => {
</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%' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">高风险在办(直达风险分析)</p>
<div class="py-3 px-4">
<div v-if="homeData?.high_risk?.total > 0" class="space-y-2 pt-1">
+34 -10
View File
@@ -65,7 +65,7 @@ onMounted(() => {
<NGrid cols="s:1 m:4 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">本车间变更总数</p>
@@ -81,7 +81,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">在办变更</p>
@@ -98,7 +98,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">超期未关闭</p>
@@ -115,7 +115,7 @@ onMounted(() => {
</NGi>
<NGi>
<div
class="flex-1 bg-white px-16px py-12px text-[#94a3b8]"
class="flex-1 bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
>
<p class="text-12px font-600">我的审批按时率</p>
@@ -136,7 +136,12 @@ onMounted(() => {
<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%' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">我的审批效率(考核口径)</p>
<div class="py-3 px-4">
<div class="space-y-3">
@@ -161,7 +166,12 @@ onMounted(() => {
</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%' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">本车间变更状态构成(实时)</p>
<div class="py-3 px-4">
<div class="space-y-2.5">
@@ -185,7 +195,12 @@ onMounted(() => {
</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%' }">
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<p class=" text-base font-semibold border-b border-slate-100 p-3">临时变更监控(管理抓手)</p>
<div class="py-3 px-4">
<div class="space-y-3">
@@ -220,7 +235,11 @@ onMounted(() => {
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
<!-- 变更类型分布 -->
<NGi span="24 s:24 m:24 l:10">
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',
@@ -234,8 +253,9 @@ onMounted(() => {
<!-- 风险增量与管控 -->
<NGi span="24 s:24 m:24 l:14">
<NCard
:bordered="true"
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
@@ -277,7 +297,11 @@ onMounted(() => {
<!-- 变更投入和效果 -->
<NGrid :x-gap="16" :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}"
<NCard
:bordered="false"
size="small"
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
:content-style="{padding: 0}"
:style="{
borderRadius: themeStore.themeRadius + 'px',
height: '100%',