Compare commits
20
Commits
7b71038949
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f97f2b4a87 | ||
|
|
eb76e7aeae | ||
|
|
43b2fc309c | ||
|
|
4f5100552c | ||
|
|
b1bd09293d | ||
|
|
b4b0c1bb42 | ||
|
|
89271e545b | ||
|
|
dadffd5bde | ||
|
|
ba8f61d752 | ||
|
|
453d747ce2 | ||
|
|
735d64c869 | ||
|
|
0b4f8f3404 | ||
|
|
3d765d2243 | ||
|
|
b6ee63299e | ||
|
|
7797b4479d | ||
|
|
91536b193c | ||
|
|
842f7f0941 | ||
|
|
f355f6fe11 | ||
|
|
161354c584 | ||
|
|
2d6973147b |
@@ -5,6 +5,7 @@ import 'vue3-office-preview/lib/style.css'
|
|||||||
|
|
||||||
const props = defineProps(['previewFile',])
|
const props = defineProps(['previewFile',])
|
||||||
|
|
||||||
|
const currentOrigin = window.location.origin;
|
||||||
// 判断文件类型
|
// 判断文件类型
|
||||||
const handleFileType = (type:string) => {
|
const handleFileType = (type:string) => {
|
||||||
if(type){
|
if(type){
|
||||||
@@ -33,37 +34,41 @@ const handleFileType = (type:string) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="max-h-[70vh] overflow-x-auto rounded-lg border">
|
<div class="max-h-[70vh] overflow-x-auto rounded-lg border">
|
||||||
<!-- 图片预览 -->
|
<!-- 图片预览 -->
|
||||||
<template v-if="handleFileType(previewFile?.file_type) === 'image'">
|
<template v-if="handleFileType(previewFile?.file_type) === 'image'">
|
||||||
<div class="flex items-center justify-center py-2">
|
<div class="flex items-center justify-center py-2">
|
||||||
<NImage :src="previewFile?.file_url" />
|
<NImage :src="currentOrigin + previewFile?.file_url" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- Word 预览 -->
|
<!-- Word 预览 -->
|
||||||
<template v-else-if="handleFileType(previewFile?.file_type) === '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>
|
</template>
|
||||||
<!-- Excel 预览 -->
|
<!-- Excel 预览 -->
|
||||||
<template v-else-if="handleFileType(previewFile?.file_type) === '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>
|
</template>
|
||||||
<!-- Ppt 预览 -->
|
<!-- Ppt 预览 -->
|
||||||
<template v-else-if="handleFileType(previewFile?.file_type) === '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>
|
</template>
|
||||||
<!-- PDF 预览 -->
|
<!-- PDF 预览 -->
|
||||||
<template v-else-if="handleFileType(previewFile?.file_type) === 'pdf'">
|
<template v-else-if="handleFileType(previewFile?.file_type) === 'pdf'">
|
||||||
<div class="h-[68vh]">
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- 其他类型文件预览 -->
|
<!-- 其他类型文件预览 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="flex items-center justify-center py-2">
|
<div class="flex items-center justify-center py-2">
|
||||||
<p>暂不支持预览该文件类型</p>
|
<p>暂不支持预览该文件类型</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -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}) {
|
export function docArchiveTreeListApi(params: {from:string | number | null,to:string | number | null,org_id: number,type: number | string,change_no?:string}) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
+2
-2
@@ -181,7 +181,7 @@ export function generateSecurePassword(passwordLength:number) {
|
|||||||
|
|
||||||
// 随机选择三种字符类型(保证至少三类)
|
// 随机选择三种字符类型(保证至少三类)
|
||||||
const requiredTypes = shuffle(Object.keys(charSets)).slice(0, 3);
|
const requiredTypes = shuffle(Object.keys(charSets)).slice(0, 3);
|
||||||
|
|
||||||
// 生成必选字符(每类至少1个)
|
// 生成必选字符(每类至少1个)
|
||||||
let password = requiredTypes
|
let password = requiredTypes
|
||||||
.map((type:any) => getRandomChar(charSets[type]))
|
.map((type:any) => getRandomChar(charSets[type]))
|
||||||
@@ -269,4 +269,4 @@ export const tagBgColor = (color: string) => {
|
|||||||
// 边框颜色
|
// 边框颜色
|
||||||
export const tagBorderColor = (color: string) => {
|
export const tagBorderColor = (color: string) => {
|
||||||
return getColorWithOpacity((color || '#333'), 0.3);
|
return getColorWithOpacity((color || '#333'), 0.3);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, h } from 'vue';
|
import { ref, onMounted, h } from 'vue';
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
@@ -79,11 +79,6 @@ const isAll = ref<boolean>(true);
|
|||||||
const currentRow = ref<any>(null);
|
const currentRow = ref<any>(null);
|
||||||
// 进度抽屉
|
// 进度抽屉
|
||||||
const progressDrawer = ref<boolean>(false);
|
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[]>([]);
|
const list = ref<any[]>([]);
|
||||||
|
|
||||||
@@ -235,40 +230,6 @@ const openProgress = (r: any) => {
|
|||||||
currentRow.value = r;
|
currentRow.value = r;
|
||||||
progressDrawer.value = true;
|
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) => {
|
const pageChange = (page: number) => {
|
||||||
pagination.value.page = page;
|
pagination.value.page = page;
|
||||||
@@ -437,44 +398,59 @@ onMounted(async () => {
|
|||||||
'--theme-color': themeStore.themeColor
|
'--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="min-w-0 flex-1">
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<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)">
|
<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 }}
|
{{ r.title }}
|
||||||
</p>
|
</p>
|
||||||
<NTag size="small" v-if="r.urgent" type="error">紧急</NTag>
|
<span class="text-xs text-slate-400">{{ r.change_no }}</span>
|
||||||
<span class="font-mono text-[11px] text-slate-400">{{ r.change_no }}</span>
|
<div class="flex items-center gap-2">
|
||||||
|
<NTag size="small" v-if="r.urgent" type="error">紧急</NTag>
|
||||||
|
<div class="flex flex-wrap items-center gap-1.5">
|
||||||
|
<!-- 类型 -->
|
||||||
|
<NTag
|
||||||
|
size="small"
|
||||||
|
:color="{
|
||||||
|
color: tagBgColor(matchObj(typeList,r.change_type)?.color),
|
||||||
|
textColor: tagTextColor(matchObj(typeList,r.change_type)?.color),
|
||||||
|
borderColor: tagBorderColor(matchObj(typeList,r.change_type)?.color),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ matchObj(typeList,r.change_type)?.label }}
|
||||||
|
</NTag>
|
||||||
|
<!-- 等级 -->
|
||||||
|
<NTag
|
||||||
|
size="small"
|
||||||
|
:color="{
|
||||||
|
color: tagBgColor(matchObj(levelList,r.change_level)?.color),
|
||||||
|
textColor: tagTextColor(matchObj(levelList,r.change_level)?.color),
|
||||||
|
borderColor: tagBorderColor(matchObj(levelList,r.change_level)?.color),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ matchObj(levelList,r.change_level)?.label }}
|
||||||
|
</NTag>
|
||||||
|
<NTag v-if="r.duration_type===2" size="small" type="warning">临时</NTag>
|
||||||
|
<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="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>
|
||||||
<div class="mt-1.5 flex flex-wrap items-center gap-1.5">
|
|
||||||
<!-- 类型 -->
|
<div class="mt-1.5 flex flex-wrap gap-x-4 gap-y-0.5 text-xs text-slate-500">
|
||||||
<NTag
|
|
||||||
size="small"
|
|
||||||
:color="{
|
|
||||||
color: tagBgColor(matchObj(typeList,r.change_type)?.color),
|
|
||||||
textColor: tagTextColor(matchObj(typeList,r.change_type)?.color),
|
|
||||||
borderColor: tagBorderColor(matchObj(typeList,r.change_type)?.color),
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ matchObj(typeList,r.change_type)?.label }}
|
|
||||||
</NTag>
|
|
||||||
<!-- 等级 -->
|
|
||||||
<NTag
|
|
||||||
size="small"
|
|
||||||
:color="{
|
|
||||||
color: tagBgColor(matchObj(levelList,r.change_level)?.color),
|
|
||||||
textColor: tagTextColor(matchObj(levelList,r.change_level)?.color),
|
|
||||||
borderColor: tagBorderColor(matchObj(levelList,r.change_level)?.color),
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ matchObj(levelList,r.change_level)?.label }}
|
|
||||||
</NTag>
|
|
||||||
<NTag v-if="r.duration_type===2" size="small" type="warning">临时</NTag>
|
|
||||||
<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">
|
|
||||||
<span v-if="r.apply_time">申请 {{ r.apply_time?.slice(0,16) }}</span>
|
<span v-if="r.apply_time">申请 {{ r.apply_time?.slice(0,16) }}</span>
|
||||||
<span>{{ r.org_name }} · {{ r.applicant_name }}</span>
|
<span>{{ r.org_name }} · {{ r.applicant_name }}</span>
|
||||||
<span v-if="r.plan_use_date">计划投用 {{ r.plan_use_date?.slice(0,16) }}</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>
|
</div>
|
||||||
<div class="flex shrink-0 flex-col items-end gap-1.5">
|
<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">
|
<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="primary" class="text-xs" @click="jumpTo(r)">详情</NButton>
|
||||||
<NButton size="small" type="default" class="text-xs" @click="openProgress(r)">进度</NButton>
|
<NButton size="small" type="default" class="text-xs" @click="openProgress(r)">进度</NButton>
|
||||||
</div>
|
</div>
|
||||||
<!-- 临时变更 -->
|
|
||||||
<div v-if="r.duration_type === 2" class="text-right text-[10px] leading-4 text-slate-400">临时变更恢复 / 延期 / 转永久请在详情页中操作</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -533,49 +485,12 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
</NPagination>
|
</NPagination>
|
||||||
</div>
|
</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">
|
<NDrawer v-model:show="progressDrawer" :width="502" placement="right" :auto-focus="false">
|
||||||
<NDrawerContent title="变更进度">
|
<NDrawerContent title="变更进度">
|
||||||
<div class="rounded-lg border bg-slate-50/60 px-3 py-2.5">
|
<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="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>
|
<span class="font-mono">{{ currentRow?.change_no }}</span>
|
||||||
<NTag v-if="currentRow.status==='APPROVING'" size="small" type="warning">审批中</NTag>
|
<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==='APPROVED'" size="small" type="success">已通过</NTag>
|
||||||
@@ -586,8 +501,8 @@ onMounted(async () => {
|
|||||||
<NTag v-if="currentRow.status==='REJECTED'" size="small" type="error">已驳回</NTag>
|
<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>
|
<span v-if="currentRow?.overdue_status===2" class="font-medium text-red-500">超期 {{ currentRow?.overdue_days }} 天</span>
|
||||||
</div>
|
</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?.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-[11px] text-slate-400">{{ currentRow?.meta }}</div>
|
<div v-if="currentRow?.meta" class="mt-1 text-xs text-slate-400">{{ currentRow?.meta }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div v-for="(s, i) in currentRow?.approval_records" :key="s.id" class="flex gap-3">
|
<div v-for="(s, i) in currentRow?.approval_records" :key="s.id" class="flex gap-3">
|
||||||
@@ -603,11 +518,11 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="pb-5">
|
<div class="pb-5">
|
||||||
<p class="text-sm">{{ s.action }}</p>
|
<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>
|
||||||
</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 ?? '—' }} · 恢复 / 延期 / 转永久请在详情页中操作
|
临时变更:计划恢复 {{ currentRow?.restore_deadline ?? '—' }} · 恢复 / 延期 / 转永久请在详情页中操作
|
||||||
</div>
|
</div>
|
||||||
</NDrawerContent>
|
</NDrawerContent>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ onMounted(() => {
|
|||||||
<NSpace vertical :size="16">
|
<NSpace vertical :size="16">
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<div class="space-y-3.5">
|
<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 flex-wrap items-center justify-between">
|
||||||
<div class="flex-y-center gap-3">
|
<div class="flex-y-center gap-3">
|
||||||
<span class="flex items-center gap-1.5 text-sm font-semibold text-slate-700">
|
<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">
|
<NGrid cols="s:1 m:5 l:5" responsive="screen" :x-gap="16" :y-gap="16">
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">申请变更数量</p>
|
<p class="text-12px font-600">申请变更数量</p>
|
||||||
@@ -163,7 +163,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">重要变更数量</p>
|
<p class="text-12px font-600">重要变更数量</p>
|
||||||
@@ -179,7 +179,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">未关闭变更数量</p>
|
<p class="text-12px font-600">未关闭变更数量</p>
|
||||||
@@ -195,7 +195,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">平均审批通过时长</p>
|
<p class="text-12px font-600">平均审批通过时长</p>
|
||||||
@@ -213,7 +213,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">申请驳回率</p>
|
<p class="text-12px font-600">申请驳回率</p>
|
||||||
@@ -232,7 +232,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:24 l:6">
|
<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>
|
<p class=" text-base font-semibold border-b border-slate-100 pb-3 mb-4">尚未关闭的变更所处的状态数量(共 {{openTotal}} 项)</p>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div v-for="s in deptStatistics?.open_status_counts" :key="s.status">
|
<div v-for="s in deptStatistics?.open_status_counts" :key="s.status">
|
||||||
@@ -258,7 +258,7 @@ onMounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:18">
|
<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>
|
<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 class="grid gap-4 grid-cols-3">
|
||||||
<div>
|
<div>
|
||||||
@@ -280,7 +280,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:24 l:12">
|
<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">
|
<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-base font-semibold">各人员审批时间对比(平均处理时长)</p>
|
||||||
<p class="text-[11px] text-slate-400 mt-5px">超过 2 天红色标识 · 用于会签催办与流程优化参考</p>
|
<p class="text-[11px] text-slate-400 mt-5px">超过 2 天红色标识 · 用于会签催办与流程优化参考</p>
|
||||||
@@ -302,7 +302,7 @@ onMounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:12">
|
<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>
|
<p class=" text-base font-semibold border-b border-slate-100 pb-3 mb-4">临时变更到期提醒情况(共 {{tempTotal}} 项)</p>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div class="rounded-lg border px-3 py-2">
|
<div class="rounded-lg border px-3 py-2">
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@ import { uploadFileApi, deleteFileApi, fileListApi } from "@/service/api/file";
|
|||||||
import { systemConfigApi, peopleListApi, templateListApi } from '@/service/api/system';
|
import { systemConfigApi, peopleListApi, templateListApi } from '@/service/api/system';
|
||||||
import { getChangeDetailApi } from '@/service/api/change';
|
import { getChangeDetailApi } from '@/service/api/change';
|
||||||
import { myTasksCloseMaterialApi } from '@/service/api/myTasks';
|
import { myTasksCloseMaterialApi } from '@/service/api/myTasks';
|
||||||
import previewComponent from '@/components/previewFile/index.vue'
|
import PreviewComponent from '@/components/previewFile/index.vue'
|
||||||
|
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const { routerPushByKey } = useRouterPush();
|
const { routerPushByKey } = useRouterPush();
|
||||||
@@ -170,7 +170,7 @@ const chooseFiles = () => {
|
|||||||
const ids = new Set(checkedFiles.value);
|
const ids = new Set(checkedFiles.value);
|
||||||
const result = fileList.value.filter((item:any) => ids.has(item.id));
|
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去重
|
// 按id去重
|
||||||
const uniqueList = [
|
const uniqueList = [
|
||||||
...new Map(files.map((item:any) => [item.id, item])).values(),
|
...new Map(files.map((item:any) => [item.id, item])).values(),
|
||||||
@@ -349,11 +349,11 @@ const togglePicker = (s: any) => {
|
|||||||
// 添加审批步骤人员
|
// 添加审批步骤人员
|
||||||
const addMember = (s: any, v: string | null) => {
|
const addMember = (s: any, v: string | null) => {
|
||||||
if (v && !s.members.some((m:any) => m.user_name === v)){
|
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 });
|
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;
|
pick.value = null;
|
||||||
pickerStepId.value = null;
|
pickerStepId.value = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 删除审批步骤人员
|
// 删除审批步骤人员
|
||||||
const delMember = (s: any, p: number) => {
|
const delMember = (s: any, p: number) => {
|
||||||
@@ -397,411 +397,415 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace vertical :size="16">
|
<NSpace vertical :size="16">
|
||||||
<!-- 资料关闭详情-->
|
<!-- 资料关闭详情-->
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<div
|
<div
|
||||||
class="flex flex-wrap items-center gap-3 border border-slate-200 px-3 py-2 mb-3"
|
class="flex flex-wrap items-center gap-3 border border-slate-200 px-3 py-2 mb-3"
|
||||||
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<NButton ghost type="primary" @click="back">
|
<NButton ghost type="primary" @click="back">
|
||||||
<Icon icon="ep:back" class="size-16px mr-1" />返回
|
<Icon icon="ep:back" class="size-16px mr-1" />返回
|
||||||
|
</NButton>
|
||||||
|
<div class="min-w-0 flex-1 truncate text-base font-bold text-slate-800">{{ currentInfo?.title }}</div>
|
||||||
|
<NTag size="small" type="info" class="font-mono text-xs">编号:{{ currentInfo?.change_no }}</NTag>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-3 scroll">
|
||||||
|
<!-- 投用前确认 -->
|
||||||
|
<div class="border rounded-md">
|
||||||
|
<div class="flex items-center gap-x-3 px-4 py-2.5 border-b">
|
||||||
|
<p class="font-600 text-slate-800 text-sm">投用前确认(含变更培训、投用前检查)</p>
|
||||||
|
<span class="text-slate-400 text-xs">上传线下纸质表照片 / 扫描件即视为完成确认,上传后可查阅、下载;空白模板可下载</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 space-y-3">
|
||||||
|
<div class="border rounded-lg py-3 px-4 flex items-center gap-3">
|
||||||
|
<Icon icon="bi:upload" class="size-18px" :style="{ color: trainFiles.length?themeStore.otherColor.success:themeStore.themeColor }" />
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-slate-800 text-sm font-600">上传培训签到</span>
|
||||||
|
<p v-if="trainFiles.length" class="py-0.5 px-1.5 text-11px bg-emerald-100 rounded-md text-emerald-700">已上传 · 视为完成确认</p>
|
||||||
|
</div>
|
||||||
|
<p class="text-slate-400 text-xs mt-1 leading-4">线下培训签到表照片或扫描件;上传后视为培训完成确认,签到记录随单归档</p>
|
||||||
|
<div v-if="trainFiles.length" class="flex flex-wrap gap-2 mt-1">
|
||||||
|
<div
|
||||||
|
v-for="(file, fileIndex) in trainFiles"
|
||||||
|
:key="fileIndex"
|
||||||
|
class="text-11px flex items-center gap-2 border py-0.5 px-1.5 rounded-md text-[#17407F] border-[#ACC6E5] bg-[#EAF0F9]"
|
||||||
|
>
|
||||||
|
<Icon icon="akar-icons:file" class="size-12px" />
|
||||||
|
{{ file.file_name }}
|
||||||
|
<NButton text type="primary" class="text-11px" @click="preview(file)">预览</NButton>
|
||||||
|
<NButton text type="primary" class="text-11px" @click="downloadFile(file,'train')">下载</NButton>
|
||||||
|
<NPopconfirm
|
||||||
|
positive-text="确定"
|
||||||
|
:positive-button-props="{ size: 'tiny' }"
|
||||||
|
:negative-button-props="{ size: 'tiny' }"
|
||||||
|
@positive-click="removeFileReal('3', Number(fileIndex))"
|
||||||
|
>
|
||||||
|
<template #trigger>
|
||||||
|
<NButton text type="error" class="text-11px">删除</NButton>
|
||||||
|
</template>
|
||||||
|
确定删除吗?
|
||||||
|
</NPopconfirm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NButton ghost type="primary" size="small" class="text-xs" @click="downloadFile(null,'train')">
|
||||||
|
<Icon icon="akar-icons:download" class="size-14px mr-1" />
|
||||||
|
下载培训签到表
|
||||||
</NButton>
|
</NButton>
|
||||||
<div class="min-w-0 flex-1 truncate text-base font-bold text-slate-800">{{ currentInfo?.title }}</div>
|
<NUpload
|
||||||
<NTag size="small" type="info" class="font-mono text-xs">编号:{{ currentInfo?.change_no }}</NTag>
|
:key="trainFiles.length?'train_'+trainFiles.length+1:'train'"
|
||||||
|
class="w-auto"
|
||||||
|
accept="image/*,.pdf,.docx,.xlsx,.pptx"
|
||||||
|
:default-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
@change="(files) => onUploadFile('3', files)"
|
||||||
|
>
|
||||||
|
<NButton type="primary" size="small" class="text-xs" :disabled="loading">上传</NButton>
|
||||||
|
</NUpload>
|
||||||
|
</div>
|
||||||
|
<div class="border rounded-lg py-3 px-4 flex items-center gap-3">
|
||||||
|
<Icon icon="bi:upload" class="size-18px" :style="{ color: pssrFiles.length?themeStore.otherColor.success:themeStore.themeColor }" />
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-slate-800 text-sm font-600">上传 PSSR 检查</span>
|
||||||
|
<p v-if="pssrFiles.length" class="py-0.5 px-1.5 text-11px bg-emerald-100 rounded-md text-emerald-700">已上传 · 视为完成确认</p>
|
||||||
|
</div>
|
||||||
|
<p class="text-slate-400 text-xs mt-1 leading-4">PSSR 纸质确认表照片或扫描件;上传后视为 PSSR 全项确认完成,具备投用条件</p>
|
||||||
|
<div v-if="pssrFiles.length" class="flex flex-wrap gap-2 mt-1">
|
||||||
|
<div
|
||||||
|
v-for="(file, fileIndex) in pssrFiles"
|
||||||
|
:key="fileIndex"
|
||||||
|
class="text-11px flex items-center gap-2 border py-0.5 px-1.5 rounded-md text-[#17407F] border-[#ACC6E5] bg-[#EAF0F9]"
|
||||||
|
>
|
||||||
|
<Icon icon="akar-icons:file" class="size-12px" />
|
||||||
|
{{ file.file_name }}
|
||||||
|
<NButton text type="primary" class="text-11px" @click="preview(file)">预览</NButton>
|
||||||
|
<NButton text type="primary" class="text-11px" @click="downloadFile(file,'pssr')">下载</NButton>
|
||||||
|
<NPopconfirm
|
||||||
|
positive-text="确定"
|
||||||
|
:positive-button-props="{ size: 'tiny' }"
|
||||||
|
:negative-button-props="{ size: 'tiny' }"
|
||||||
|
@positive-click="removeFileReal('4', Number(fileIndex))"
|
||||||
|
>
|
||||||
|
<template #trigger>
|
||||||
|
<NButton text type="error" class="text-11px">删除</NButton>
|
||||||
|
</template>
|
||||||
|
确定删除吗?
|
||||||
|
</NPopconfirm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NButton ghost type="primary" size="small" class="text-xs" @click="downloadFile(null,'pssr')">
|
||||||
|
<Icon icon="akar-icons:download" class="size-14px mr-1" />
|
||||||
|
下载PSSR检查表
|
||||||
|
</NButton>
|
||||||
|
<NUpload
|
||||||
|
:key="pssrFiles.length?'pssr_'+pssrFiles.length+1:'pssr'"
|
||||||
|
class="w-auto"
|
||||||
|
accept="image/*,.pdf,.docx,.xlsx,.pptx"
|
||||||
|
:default-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
@change="(files) => onUploadFile('4', files)"
|
||||||
|
>
|
||||||
|
<NButton type="primary" size="small" class="text-xs" :disabled="loading">上传</NButton>
|
||||||
|
</NUpload>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-3 scroll">
|
</div>
|
||||||
<!-- 投用前确认 -->
|
<!-- 变更投用时间 -->
|
||||||
<div class="border rounded-md">
|
<div class="border rounded-md">
|
||||||
<div class="flex items-center gap-x-3 px-4 py-2.5 border-b">
|
<div class="flex items-center gap-x-3 px-4 py-2.5 border-b">
|
||||||
<p class="font-600 text-slate-800 text-sm">投用前确认(含变更培训、投用前检查)</p>
|
<p class="font-600 text-slate-800 text-sm">变更投用时间</p>
|
||||||
<span class="text-slate-400 text-xs">上传线下纸质表照片 / 扫描件即视为完成确认,上传后可查阅、下载;空白模板可下载</span>
|
|
||||||
</div>
|
|
||||||
<div class="p-3 space-y-3">
|
|
||||||
<div class="border rounded-lg py-3 px-4 flex items-center gap-3">
|
|
||||||
<Icon icon="bi:upload" class="size-18px" :style="{ color: trainFiles.length?themeStore.otherColor.success:themeStore.themeColor }" />
|
|
||||||
<div class="flex-1">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="text-slate-800 text-sm font-600">上传培训签到</span>
|
|
||||||
<p v-if="trainFiles.length" class="py-0.5 px-1.5 text-11px bg-emerald-100 rounded-md text-emerald-700">已上传 · 视为完成确认</p>
|
|
||||||
</div>
|
|
||||||
<p class="text-slate-400 text-xs mt-1 leading-4">线下培训签到表照片或扫描件;上传后视为培训完成确认,签到记录随单归档</p>
|
|
||||||
<div v-if="trainFiles.length" class="flex flex-wrap gap-2 mt-1">
|
|
||||||
<div
|
|
||||||
v-for="(file, fileIndex) in trainFiles"
|
|
||||||
:key="fileIndex"
|
|
||||||
class="text-11px flex items-center gap-2 border py-0.5 px-1.5 rounded-md text-[#17407F] border-[#ACC6E5] bg-[#EAF0F9]"
|
|
||||||
>
|
|
||||||
<Icon icon="akar-icons:file" class="size-12px" />
|
|
||||||
{{file.file_name}}
|
|
||||||
<NButton text type="primary" class="text-11px" @click="preview(file)">预览</NButton>
|
|
||||||
<NButton text type="primary" class="text-11px" @click="downloadFile(file,'train')">下载</NButton>
|
|
||||||
<NPopconfirm
|
|
||||||
positive-text="确定"
|
|
||||||
:positiveButtonProps="{ size: 'tiny' }"
|
|
||||||
:negativeButtonProps="{ size: 'tiny' }"
|
|
||||||
@positive-click="removeFileReal('3', Number(fileIndex))"
|
|
||||||
>
|
|
||||||
<template #trigger>
|
|
||||||
<NButton text type="error" class="text-11px">删除</NButton>
|
|
||||||
</template>
|
|
||||||
确定删除吗?
|
|
||||||
</NPopconfirm>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<NButton ghost type="primary" size="small" class="text-xs" @click="downloadFile(null,'train')">
|
|
||||||
<Icon icon="akar-icons:download" class="size-14px mr-1" />
|
|
||||||
下载培训签到表
|
|
||||||
</NButton>
|
|
||||||
<NUpload
|
|
||||||
class="w-auto"
|
|
||||||
:key="trainFiles.length?'train_'+trainFiles.length+1:'train'"
|
|
||||||
accept="image/*,.pdf,.docx,.xlsx,.pptx"
|
|
||||||
:default-upload="false"
|
|
||||||
:show-file-list="false"
|
|
||||||
@change="(files) => onUploadFile('3', files)"
|
|
||||||
>
|
|
||||||
<NButton type="primary" size="small" class="text-xs" :disabled="loading">上传</NButton>
|
|
||||||
</NUpload>
|
|
||||||
</div>
|
|
||||||
<div class="border rounded-lg py-3 px-4 flex items-center gap-3">
|
|
||||||
<Icon icon="bi:upload" class="size-18px" :style="{ color: pssrFiles.length?themeStore.otherColor.success:themeStore.themeColor }" />
|
|
||||||
<div class="flex-1">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="text-slate-800 text-sm font-600">上传 PSSR 检查</span>
|
|
||||||
<p v-if="pssrFiles.length" class="py-0.5 px-1.5 text-11px bg-emerald-100 rounded-md text-emerald-700">已上传 · 视为完成确认</p>
|
|
||||||
</div>
|
|
||||||
<p class="text-slate-400 text-xs mt-1 leading-4">PSSR 纸质确认表照片或扫描件;上传后视为 PSSR 全项确认完成,具备投用条件</p>
|
|
||||||
<div v-if="pssrFiles.length" class="flex flex-wrap gap-2 mt-1">
|
|
||||||
<div
|
|
||||||
v-for="(file, fileIndex) in pssrFiles"
|
|
||||||
:key="fileIndex"
|
|
||||||
class="text-11px flex items-center gap-2 border py-0.5 px-1.5 rounded-md text-[#17407F] border-[#ACC6E5] bg-[#EAF0F9]"
|
|
||||||
>
|
|
||||||
<Icon icon="akar-icons:file" class="size-12px" />
|
|
||||||
{{file.file_name}}
|
|
||||||
<NButton text type="primary" class="text-11px" @click="preview(file)">预览</NButton>
|
|
||||||
<NButton text type="primary" class="text-11px" @click="downloadFile(file,'pssr')">下载</NButton>
|
|
||||||
<NPopconfirm
|
|
||||||
positive-text="确定"
|
|
||||||
:positiveButtonProps="{ size: 'tiny' }"
|
|
||||||
:negativeButtonProps="{ size: 'tiny' }"
|
|
||||||
@positive-click="removeFileReal('4', Number(fileIndex))"
|
|
||||||
>
|
|
||||||
<template #trigger>
|
|
||||||
<NButton text type="error" class="text-11px">删除</NButton>
|
|
||||||
</template>
|
|
||||||
确定删除吗?
|
|
||||||
</NPopconfirm>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<NButton ghost type="primary" size="small" class="text-xs" @click="downloadFile(null,'pssr')">
|
|
||||||
<Icon icon="akar-icons:download" class="size-14px mr-1" />
|
|
||||||
下载PSSR检查表
|
|
||||||
</NButton>
|
|
||||||
<NUpload
|
|
||||||
class="w-auto"
|
|
||||||
:key="pssrFiles.length?'pssr_'+pssrFiles.length+1:'pssr'"
|
|
||||||
accept="image/*,.pdf,.docx,.xlsx,.pptx"
|
|
||||||
:default-upload="false"
|
|
||||||
:show-file-list="false"
|
|
||||||
@change="(files) => onUploadFile('4', files)"
|
|
||||||
>
|
|
||||||
<NButton type="primary" size="small" class="text-xs" :disabled="loading">上传</NButton>
|
|
||||||
</NUpload>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 变更投用时间 -->
|
|
||||||
<div class="border rounded-md">
|
|
||||||
<div class="flex items-center gap-x-3 px-4 py-2.5 border-b">
|
|
||||||
<p class="font-600 text-slate-800 text-sm">变更投用时间</p>
|
|
||||||
</div>
|
|
||||||
<div class="p-3">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<p class="text-sm text-slate-600">实际投用时间</p>
|
|
||||||
<NDatePicker v-model:value="useDate" type="date" size="small" />
|
|
||||||
<p class="text-xs text-slate-400 mt-1px">默认为当天日期,可按实际投用日期调整</p>
|
|
||||||
</div>
|
|
||||||
<div class="mt-2.5 rounded-lg border px-3 py-2 text-[11px] leading-5 border-amber-200 bg-amber-50/60 text-amber-700"> 投用提示:投用前应完成 PSSR 检查。 未完成的变更不得投用。</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 变更验收 -->
|
|
||||||
<div class="border rounded-md">
|
|
||||||
<div class="flex items-center gap-x-3 px-4 py-2.5 border-b">
|
|
||||||
<p class="font-600 text-slate-800 text-sm">变更验收</p>
|
|
||||||
<span class="text-slate-400 text-xs">投用后组织验收:上传验收资料并选择验收结论;上传后可查阅、下载;验收表可下载</span>
|
|
||||||
</div>
|
|
||||||
<div class="p-3 space-y-2.5">
|
|
||||||
<div class="border rounded-lg py-3 px-4 flex items-center gap-3">
|
|
||||||
<Icon icon="bi:upload" class="size-18px" :style="{ color: acceptFiles.length?themeStore.otherColor.success:themeStore.themeColor }" />
|
|
||||||
<div class="flex-1">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="text-slate-800 text-sm font-600">上传验收资料</span>
|
|
||||||
<p v-if="pssrFiles.length" class="py-0.5 px-1.5 text-11px bg-emerald-100 rounded-md text-emerald-700">已上传 · 视为完成确认</p>
|
|
||||||
</div>
|
|
||||||
<p class="text-slate-400 text-xs mt-0.5 leading-4">验收报告、运行数据记录等验收证明材料;上传后纳入归档清单</p>
|
|
||||||
<div v-if="acceptFiles.length" class="flex flex-wrap gap-2 mt-1">
|
|
||||||
<div
|
|
||||||
v-for="(file, fileIndex) in acceptFiles"
|
|
||||||
:key="fileIndex"
|
|
||||||
class="text-11px flex items-center gap-2 border py-0.5 px-1.5 rounded-md text-[#17407F] border-[#ACC6E5] bg-[#EAF0F9]"
|
|
||||||
>
|
|
||||||
<Icon icon="akar-icons:file" class="size-12px" />
|
|
||||||
{{file.file_name}}
|
|
||||||
<NButton text type="primary" class="text-11px" @click="preview(file)">预览</NButton>
|
|
||||||
<NButton text type="primary" class="text-11px" @click="downloadFile(file,'accept')">下载</NButton>
|
|
||||||
<NPopconfirm
|
|
||||||
positive-text="确定"
|
|
||||||
:positiveButtonProps="{ size: 'tiny' }"
|
|
||||||
:negativeButtonProps="{ size: 'tiny' }"
|
|
||||||
@positive-click="removeFileReal('5', Number(fileIndex))"
|
|
||||||
>
|
|
||||||
<template #trigger>
|
|
||||||
<NButton text type="error" class="text-11px">删除</NButton>
|
|
||||||
</template>
|
|
||||||
确定删除吗?
|
|
||||||
</NPopconfirm>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<NButton ghost type="primary" size="small" class="text-xs" @click="downloadFile(null,'accept')">
|
|
||||||
<Icon icon="akar-icons:download" class="size-14px mr-1" />
|
|
||||||
下载验收表
|
|
||||||
</NButton>
|
|
||||||
<NUpload
|
|
||||||
class="w-auto"
|
|
||||||
:key="acceptFiles.length?'accept_'+acceptFiles.length+1:'accept'"
|
|
||||||
accept="image/*,.pdf,.docx,.xlsx,.pptx"
|
|
||||||
:default-upload="false"
|
|
||||||
:show-file-list="false"
|
|
||||||
@change="(files) => onUploadFile('5', files)"
|
|
||||||
>
|
|
||||||
<NButton type="primary" size="small" class="text-xs" :disabled="loading">上传</NButton>
|
|
||||||
</NUpload>
|
|
||||||
</div>
|
|
||||||
<div class="border rounded-lg py-3 px-4 flex flex-col gap-2">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<p class="text-xs font-600" :style="{ color: themeStore.themeColor }">验收结论(互斥选择)</p>
|
|
||||||
<NButton size="small" :type="resultStatus === 3?'primary':'default'" class="text-xs" @click="resultStatus = 3">完全达到</NButton>
|
|
||||||
<NButton size="small" :type="resultStatus === 2?'primary':'default'" class="text-xs" @click="resultStatus = 2">基本达到</NButton>
|
|
||||||
<NButton size="small" :type="resultStatus === 1?'primary':'default'" class="text-xs" @click="resultStatus = 1">未达到</NButton>
|
|
||||||
</div>
|
|
||||||
<NInput v-model:value="acceptDesc" type="textarea" size="small" :autosize="{ minRows: 2 }" placeholder="请输入验收结论" />
|
|
||||||
</div>
|
|
||||||
<div class="border rounded-lg py-3 px-4 flex items-center gap-2">
|
|
||||||
<p class="text-xs font-600" :style="{ color: themeStore.themeColor }">变更材料和实施费(万元)</p>
|
|
||||||
<NInputNumber v-model:value="price" size="small" :min="0" :show-button="false" placeholder="如 12.6" />
|
|
||||||
<p class="text-xs text-slate-400 mt-1px">非必填 · 填报本次变更材料与实施费用合计,用于变更投入与效果统计</p>
|
|
||||||
</div>
|
|
||||||
<div class="border rounded-lg py-3 px-4 flex items-center gap-2">
|
|
||||||
<p class="text-xs font-600" :style="{ color: themeStore.themeColor }">验收时间</p>
|
|
||||||
<NDatePicker v-model:value="acceptDate" type="date" size="small" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 变更关闭资料归档确认 -->
|
|
||||||
<div class="border rounded-md">
|
|
||||||
<div class="flex items-center gap-x-3 px-4 py-2.5 border-b">
|
|
||||||
<p class="font-600 text-slate-800 text-sm">变更关闭资料归档确认</p>
|
|
||||||
<span class="text-slate-400 text-xs">涉及(本地上传或从变更附件选择)· 不涉及(无需处理)</span>
|
|
||||||
<NButton v-if="isAiOn" size="small" type="primary" ghost class="ml-auto text-xs">AI辅助判定</NButton>
|
|
||||||
</div>
|
|
||||||
<div class="p-3">
|
|
||||||
<NTable size="small">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>资料名称</th>
|
|
||||||
<th class="!text-center">是否涉及</th>
|
|
||||||
<th class="w-200px !text-center">操作</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="(item, index) in list" :key="index">
|
|
||||||
<td>
|
|
||||||
<p>{{item.doc_name}}</p>
|
|
||||||
<div v-if="item.attachments.length" class="flex flex-wrap gap-2">
|
|
||||||
<div
|
|
||||||
v-for="(file, fileIndex) in item.attachments"
|
|
||||||
:key="fileIndex"
|
|
||||||
class="text-11px flex items-center gap-2 border py-0.5 px-1.5 rounded-md text-[#17407F] border-[#ACC6E5] bg-[#EAF0F9]"
|
|
||||||
>
|
|
||||||
<Icon icon="akar-icons:file" class="size-12px" />
|
|
||||||
{{file.file_name}}
|
|
||||||
<NButton text type="primary" class="text-11px" @click="preview(file)">预览</NButton>
|
|
||||||
<NButton text type="primary" class="text-11px" @click="downloadFile(file,'close')">下载</NButton>
|
|
||||||
<NPopconfirm
|
|
||||||
positive-text="确定"
|
|
||||||
:positiveButtonProps="{ size: 'tiny' }"
|
|
||||||
:negativeButtonProps="{ size: 'tiny' }"
|
|
||||||
@positive-click="removeFileReal(item.doc_name, Number(fileIndex), Number(index))"
|
|
||||||
>
|
|
||||||
<template #trigger>
|
|
||||||
<NButton text type="error" class="text-11px">删除</NButton>
|
|
||||||
</template>
|
|
||||||
确定删除吗?
|
|
||||||
</NPopconfirm>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="flex justify-center gap-2">
|
|
||||||
<NButton size="small" :type="item.status === 1 ? 'primary' : 'default'" class="text-xs" @click="item.status = 1">涉及</NButton>
|
|
||||||
<NButton size="small" :type="item.status === 2 ? 'primary' : 'default'" class="text-xs" @click="item.status = 2">不涉及</NButton>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<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"
|
|
||||||
accept="image/*,.pdf,.docx,.xlsx,.pptx"
|
|
||||||
:default-upload="false"
|
|
||||||
:show-file-list="false"
|
|
||||||
@change="(files) => onUploadFile(item.doc_name, files, Number(index))"
|
|
||||||
>
|
|
||||||
<NButton type="primary" size="small" ghost class="text-xs" :disabled="loading">
|
|
||||||
本地上传
|
|
||||||
</NButton>
|
|
||||||
</NUpload>
|
|
||||||
</div>
|
|
||||||
<div v-if="item.status === 2" class="flex justify-end gap-2">
|
|
||||||
<span class="text-xs text-slate-400">无需处理</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</NTable>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-end border-t pt-3 mt-3">
|
<div class="p-3">
|
||||||
<div class="flex gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<NButton size="small" @click="approvalDrawer = true">审批流程设置</NButton>
|
<p class="text-sm text-slate-600">实际投用时间</p>
|
||||||
<NButton ghost size="small" @click="back">取消</NButton>
|
<NDatePicker v-model:value="useDate" type="date" size="small" />
|
||||||
<NButton ghost type="primary" size="small" @click="submitCloseDoc('save')">保存</NButton>
|
<p class="text-xs text-slate-400 mt-1px">默认为当天日期,可按实际投用日期调整</p>
|
||||||
<NButton type="primary" size="small" @click="submitCloseDoc('close')">申请关闭</NButton>
|
</div>
|
||||||
</div>
|
<div class="mt-2.5 rounded-lg border px-3 py-2 text-[11px] leading-5 border-amber-200 bg-amber-50/60 text-amber-700"> 投用提示:投用前应完成 PSSR 检查。 未完成的变更不得投用。</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 审批流程设置 -->
|
</div>
|
||||||
<NDrawer v-model:show="approvalDrawer" placement="right" width="28rem" :auto-focus="false">
|
<!-- 变更验收 -->
|
||||||
<NDrawerContent>
|
<div class="border rounded-md">
|
||||||
<template #header>
|
<div class="flex items-center gap-x-3 px-4 py-2.5 border-b">
|
||||||
<div class="text-base font-semibold text-slate-800">审批流程设置</div>
|
<p class="font-600 text-slate-800 text-sm">变更验收</p>
|
||||||
</template>
|
<span class="text-slate-400 text-xs">投用后组织验收:上传验收资料并选择验收结论;上传后可查阅、下载;验收表可下载</span>
|
||||||
<div class="h-full space-y-4 overflow-y-auto">
|
</div>
|
||||||
<p class="text-xs leading-relaxed text-slate-400">审批链:{{ approvalFlowText }}。专业会签人由申请表「涉及专业」勾选自动增减,可在此调整人选;重要变更自动追加安全审查与分管领导终批。</p>
|
<div class="p-3 space-y-2.5">
|
||||||
<div v-for="(s, idx) in flowList" :key="s.id" class="rounded-lg border border-slate-100 bg-slate-50/40 p-3">
|
<div class="border rounded-lg py-3 px-4 flex items-center gap-3">
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<Icon icon="bi:upload" class="size-18px" :style="{ color: acceptFiles.length?themeStore.otherColor.success:themeStore.themeColor }" />
|
||||||
<NTag type="info" size="small">{{Number(idx)+1}}</NTag>
|
<div class="flex-1">
|
||||||
<p class="text-sm">{{s.name}}</p>
|
<div class="flex items-center gap-2">
|
||||||
<NCheckbox :checked="s.countersign" size="small" @update:checked="(v) => setStepCountersign(s, v)">
|
<span class="text-slate-800 text-sm font-600">上传验收资料</span>
|
||||||
<span class="text-xs text-slate-500">专业会签步骤</span>
|
<p v-if="pssrFiles.length" class="py-0.5 px-1.5 text-11px bg-emerald-100 rounded-md text-emerald-700">已上传 · 视为完成确认</p>
|
||||||
</NCheckbox>
|
</div>
|
||||||
<NButton ghost type="primary" size="tiny" @click="togglePicker(s)" title="添加预设人员" class="ml-auto">
|
<p class="text-slate-400 text-xs mt-0.5 leading-4">验收报告、运行数据记录等验收证明材料;上传后纳入归档清单</p>
|
||||||
<Icon icon="ic:round-plus" class="size-16px" />
|
<div v-if="acceptFiles.length" class="flex flex-wrap gap-2 mt-1">
|
||||||
</NButton>
|
<div
|
||||||
</div>
|
v-for="(file, fileIndex) in acceptFiles"
|
||||||
<!-- 预设人员:人员必选;仅会签步骤可选择专业(必选),且人员可勾选「必须进行专业会签」 -->
|
:key="fileIndex"
|
||||||
<div class="mt-2 flex gap-2 pl-8">
|
class="text-11px flex items-center gap-2 border py-0.5 px-1.5 rounded-md text-[#17407F] border-[#ACC6E5] bg-[#EAF0F9]"
|
||||||
<span class="shrink-0 text-xs leading-7 text-slate-400">预设人员:</span>
|
>
|
||||||
<div class="min-w-0 flex-1 space-y-1.5">
|
<Icon icon="akar-icons:file" class="size-12px" />
|
||||||
<div v-for="(m, mIdx) in s.members" :key="m.id" class="flex min-h-7 flex-wrap items-center gap-2">
|
{{ file.file_name }}
|
||||||
<span class="shrink-0 truncate text-xs font-medium text-slate-700">{{ m.user_name }}</span>
|
<NButton text type="primary" class="text-11px" @click="preview(file)">预览</NButton>
|
||||||
<template v-if="s.countersign">
|
<NButton text type="primary" class="text-11px" @click="downloadFile(file,'accept')">下载</NButton>
|
||||||
<span class="text-xs text-slate-400"><span class="text-red-500">*</span>专业:</span>
|
<NPopconfirm
|
||||||
<NSelect :value="m.specialty || null" clearable size="small" placeholder="请选择专业" class="w-28"
|
positive-text="确定"
|
||||||
:options="SPECIALTIES"
|
:positive-button-props="{ size: 'tiny' }"
|
||||||
@update:value="(v) => selectSpecialty(Number(idx), Number(mIdx), v)"
|
:negative-button-props="{ size: 'tiny' }"
|
||||||
/>
|
@positive-click="removeFileReal('5', Number(fileIndex))"
|
||||||
<NCheckbox :checked="m.is_sign" size="small"
|
>
|
||||||
:checked-value="1"
|
<template #trigger>
|
||||||
:unchecked-value="0"
|
<NButton text type="error" class="text-11px">删除</NButton>
|
||||||
@update:checked="(v) => selectSign(Number(idx), Number(mIdx), v)"
|
</template>
|
||||||
>
|
确定删除吗?
|
||||||
<span class="text-xs text-slate-500">必须参与会签</span>
|
</NPopconfirm>
|
||||||
</NCheckbox>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<NButton text type="error" @click="delMember(s, m.user_id)" title="删除该人员">
|
</div>
|
||||||
<Icon icon="iconamoon:trash" class="size-16px" />
|
<NButton ghost type="primary" size="small" class="text-xs" @click="downloadFile(null,'accept')">
|
||||||
</NButton>
|
<Icon icon="akar-icons:download" class="size-14px mr-1" />
|
||||||
</div>
|
下载验收表
|
||||||
<p v-if="s.countersign && s.members.length" class="text-[11px] leading-5 text-slate-400">
|
</NButton>
|
||||||
必须参与会签:勾选的人员必须参与专业会签,未勾选的人员为可选会签(按需参加)
|
<NUpload
|
||||||
</p>
|
:key="acceptFiles.length?'accept_'+acceptFiles.length+1:'accept'"
|
||||||
<NCascader v-if="pickerStepId === s.id" :value="pick" :options="memberList"
|
class="w-auto"
|
||||||
placeholder="请选择人员" size="small" check-strategy="child" :show-path="false" expand-trigger="click"
|
accept="image/*,.pdf,.docx,.xlsx,.pptx"
|
||||||
class="w-56" :on-update:value="(v) => addMember(s, v)" />
|
:default-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
@change="(files) => onUploadFile('5', files)"
|
||||||
|
>
|
||||||
|
<NButton type="primary" size="small" class="text-xs" :disabled="loading">上传</NButton>
|
||||||
|
</NUpload>
|
||||||
|
</div>
|
||||||
|
<div class="border rounded-lg py-3 px-4 flex flex-col gap-2">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<p class="text-xs font-600" :style="{ color: themeStore.themeColor }">验收结论(互斥选择)</p>
|
||||||
|
<NButton size="small" :type="resultStatus === 3?'primary':'default'" class="text-xs" @click="resultStatus = 3">完全达到</NButton>
|
||||||
|
<NButton size="small" :type="resultStatus === 2?'primary':'default'" class="text-xs" @click="resultStatus = 2">基本达到</NButton>
|
||||||
|
<NButton size="small" :type="resultStatus === 1?'primary':'default'" class="text-xs" @click="resultStatus = 1">未达到</NButton>
|
||||||
|
</div>
|
||||||
|
<NInput v-model:value="acceptDesc" type="textarea" size="small" :autosize="{ minRows: 2 }" placeholder="请输入验收结论" />
|
||||||
|
</div>
|
||||||
|
<div class="border rounded-lg py-3 px-4 flex items-center gap-2">
|
||||||
|
<p class="text-xs font-600" :style="{ color: themeStore.themeColor }">变更材料和实施费(万元)</p>
|
||||||
|
<NInputNumber v-model:value="price" size="small" :min="0" :show-button="false" placeholder="如 12.6" />
|
||||||
|
<p class="text-xs text-slate-400 mt-1px">非必填 · 填报本次变更材料与实施费用合计,用于变更投入与效果统计</p>
|
||||||
|
</div>
|
||||||
|
<div class="border rounded-lg py-3 px-4 flex items-center gap-2">
|
||||||
|
<p class="text-xs font-600" :style="{ color: themeStore.themeColor }">验收时间</p>
|
||||||
|
<NDatePicker v-model:value="acceptDate" type="date" size="small" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 变更关闭资料归档确认 -->
|
||||||
|
<div class="border rounded-md">
|
||||||
|
<div class="flex items-center gap-x-3 px-4 py-2.5 border-b">
|
||||||
|
<p class="font-600 text-slate-800 text-sm">变更关闭资料归档确认</p>
|
||||||
|
<span class="text-slate-400 text-xs">涉及(本地上传或从变更附件选择)· 不涉及(无需处理)</span>
|
||||||
|
<NButton v-if="isAiOn" size="small" type="primary" ghost class="ml-auto text-xs">AI辅助判定</NButton>
|
||||||
|
</div>
|
||||||
|
<div class="p-3">
|
||||||
|
<NTable size="small">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>资料名称</th>
|
||||||
|
<th class="!text-center">是否涉及</th>
|
||||||
|
<th class="w-200px !text-center">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(item, index) in list" :key="index">
|
||||||
|
<td>
|
||||||
|
<p>{{ item.doc_name }}</p>
|
||||||
|
<div v-if="item.attachments.length" class="flex flex-wrap gap-2">
|
||||||
|
<div
|
||||||
|
v-for="(file, fileIndex) in item.attachments"
|
||||||
|
:key="fileIndex"
|
||||||
|
class="text-11px flex items-center gap-2 border py-0.5 px-1.5 rounded-md text-[#17407F] border-[#ACC6E5] bg-[#EAF0F9]"
|
||||||
|
>
|
||||||
|
<Icon icon="akar-icons:file" class="size-12px" />
|
||||||
|
{{ file.file_name }}
|
||||||
|
<NButton text type="primary" class="text-11px" @click="preview(file)">预览</NButton>
|
||||||
|
<NButton text type="primary" class="text-11px" @click="downloadFile(file,'close')">下载</NButton>
|
||||||
|
<NPopconfirm
|
||||||
|
positive-text="确定"
|
||||||
|
:positive-button-props="{ size: 'tiny' }"
|
||||||
|
:negative-button-props="{ size: 'tiny' }"
|
||||||
|
@positive-click="removeFileReal(item.doc_name, Number(fileIndex), Number(index))"
|
||||||
|
>
|
||||||
|
<template #trigger>
|
||||||
|
<NButton text type="error" class="text-11px">删除</NButton>
|
||||||
|
</template>
|
||||||
|
确定删除吗?
|
||||||
|
</NPopconfirm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center gap-2">
|
||||||
|
<NButton size="small" :type="item.status === 1 ? 'primary' : 'default'" class="text-xs" @click="item.status = 1">涉及</NButton>
|
||||||
|
<NButton size="small" :type="item.status === 2 ? 'primary' : 'default'" class="text-xs" @click="item.status = 2">不涉及</NButton>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div v-if="item.status === 1" class="flex justify-center gap-2">
|
||||||
|
<NButton size="small" class="text-xs" @click="openFileList(Number(index))">从变更附件选择</NButton>
|
||||||
|
<NUpload
|
||||||
|
:key="item.id"
|
||||||
|
class="w-auto"
|
||||||
|
accept="image/*,.pdf,.docx,.xlsx,.pptx"
|
||||||
|
:default-upload="false"
|
||||||
|
:show-file-list="false"
|
||||||
|
@change="(files) => onUploadFile(item.doc_name, files, Number(index))"
|
||||||
|
>
|
||||||
|
<NButton type="primary" size="small" ghost class="text-xs" :disabled="loading">
|
||||||
|
本地上传
|
||||||
|
</NButton>
|
||||||
|
</NUpload>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.status === 2" class="flex justify-end gap-2">
|
||||||
|
<span class="text-xs text-slate-400">无需处理</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</NTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-end border-t pt-3 mt-3">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<NButton size="small" @click="approvalDrawer = true">审批流程设置</NButton>
|
||||||
|
<NButton ghost size="small" @click="back">取消</NButton>
|
||||||
|
<NButton ghost type="primary" size="small" @click="submitCloseDoc('save')">保存</NButton>
|
||||||
|
<NButton type="primary" size="small" @click="submitCloseDoc('close')">申请关闭</NButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 审批流程设置 -->
|
||||||
|
<NDrawer v-model:show="approvalDrawer" placement="right" width="28rem" :auto-focus="false">
|
||||||
|
<NDrawerContent>
|
||||||
|
<template #header>
|
||||||
|
<div class="text-base font-semibold text-slate-800">审批流程设置</div>
|
||||||
|
</template>
|
||||||
|
<div class="h-full space-y-4 overflow-y-auto">
|
||||||
|
<p class="text-xs leading-relaxed text-slate-400">审批链:{{ approvalFlowText }}。专业会签人由申请表「涉及专业」勾选自动增减,可在此调整人选;重要变更自动追加安全审查与分管领导终批。</p>
|
||||||
|
<div v-for="(s, idx) in flowList" :key="s.id" class="rounded-lg border border-slate-100 bg-slate-50/40 p-3">
|
||||||
|
<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" :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" title="添加预设人员" class="ml-auto" @click="togglePicker(s)">
|
||||||
|
<Icon icon="ic:round-plus" class="size-16px" />
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
<!-- 预设人员:人员必选;仅会签步骤可选择专业(必选),且人员可勾选「必须进行专业会签」 -->
|
||||||
|
<div class="mt-2 flex gap-2 pl-8">
|
||||||
|
<span class="shrink-0 text-xs leading-7 text-slate-400">预设人员:</span>
|
||||||
|
<div class="min-w-0 flex-1 space-y-1.5">
|
||||||
|
<div v-for="(m, mIdx) in s.members" :key="m.id" class="flex min-h-7 flex-wrap items-center gap-2">
|
||||||
|
<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"
|
||||||
|
:options="SPECIALTIES"
|
||||||
|
@update:value="(v) => selectSpecialty(Number(idx), Number(mIdx), v)"
|
||||||
|
/>
|
||||||
|
<NCheckbox
|
||||||
|
:checked="m.is_sign" size="small"
|
||||||
|
:checked-value="1"
|
||||||
|
:unchecked-value="0"
|
||||||
|
@update:checked="(v) => selectSign(Number(idx), Number(mIdx), v)"
|
||||||
|
>
|
||||||
|
<span class="text-xs text-slate-500">必须参与会签</span>
|
||||||
|
</NCheckbox>
|
||||||
|
</template>
|
||||||
|
<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"
|
||||||
|
placeholder="请选择人员" size="small" check-strategy="child" :show-path="false" expand-trigger="click"
|
||||||
|
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>
|
<span v-if="s.members.length === 0" class="text-xs leading-7 text-slate-300">未预设</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
|
||||||
<NButton v-if="flowList.length" type="primary" class="w-full" @click="finishApprover">完成设置</NButton>
|
|
||||||
</template>
|
|
||||||
</NDrawerContent>
|
|
||||||
</NDrawer>
|
|
||||||
<!-- 附件选择弹框 -->
|
|
||||||
<NModal
|
|
||||||
v-model:show="filesModal"
|
|
||||||
preset="card"
|
|
||||||
title="从变更附件选择"
|
|
||||||
:auto-focus="false"
|
|
||||||
:style="{ width: '450px', height: 'auto' }"
|
|
||||||
:segmented="{ content: true, footer: true }"
|
|
||||||
>
|
|
||||||
<div v-if="fileList.length" class="max-h-60vh overflow-y-auto">
|
|
||||||
<NCheckboxGroup v-model:value="checkedFiles">
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div v-for="item in fileList" :key="item.id">
|
|
||||||
<NCheckbox :value="item.id" :label="item.file_name" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</NCheckboxGroup>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
</div>
|
||||||
<NEmpty description="暂无附件"></NEmpty>
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<NButton v-if="flowList.length" type="primary" class="w-full" @click="finishApprover">完成设置</NButton>
|
||||||
|
</template>
|
||||||
|
</NDrawerContent>
|
||||||
|
</NDrawer>
|
||||||
|
<!-- 附件选择弹框 -->
|
||||||
|
<NModal
|
||||||
|
v-model:show="filesModal"
|
||||||
|
preset="card"
|
||||||
|
title="从变更附件选择"
|
||||||
|
:auto-focus="false"
|
||||||
|
:style="{ width: '450px', height: 'auto' }"
|
||||||
|
:segmented="{ content: true, footer: true }"
|
||||||
|
>
|
||||||
|
<div v-if="fileList.length" class="max-h-60vh overflow-y-auto">
|
||||||
|
<NCheckboxGroup v-model:value="checkedFiles">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div v-for="item in fileList" :key="item.id">
|
||||||
|
<NCheckbox :value="item.id" :label="item.file_name" />
|
||||||
</div>
|
</div>
|
||||||
<template v-if="fileList.length" #footer>
|
</div>
|
||||||
<div class="flex justify-end gap-[10px]">
|
</NCheckboxGroup>
|
||||||
<NButton size="small" @click="filesModal = false">取消</NButton>
|
</div>
|
||||||
<NButton size="small" type="primary" @click="chooseFiles">确定选择</NButton>
|
<div v-else>
|
||||||
</div>
|
<NEmpty description="暂无附件"></NEmpty>
|
||||||
</template>
|
</div>
|
||||||
</NModal>
|
<template v-if="fileList.length" #footer>
|
||||||
<!-- 附件在线预览弹窗 -->
|
<div class="flex justify-end gap-[10px]">
|
||||||
<NModal
|
<NButton size="small" @click="filesModal = false">取消</NButton>
|
||||||
v-model:show="previewModal"
|
<NButton size="small" type="primary" @click="chooseFiles">确定选择</NButton>
|
||||||
preset="card"
|
</div>
|
||||||
:auto-focus="false"
|
</template>
|
||||||
:style="{ width: '80%', height: 'auto' }"
|
</NModal>
|
||||||
:segmented="{ content: true, footer: true }"
|
<!-- 附件在线预览弹窗 -->
|
||||||
>
|
<NModal
|
||||||
<template #header>
|
v-model:show="previewModal"
|
||||||
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
preset="card"
|
||||||
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
:auto-focus="false"
|
||||||
</div>
|
:style="{ width: '80%', height: 'auto' }"
|
||||||
</template>
|
:segmented="{ content: true, footer: true }"
|
||||||
<template #default>
|
>
|
||||||
<div class="space-y-3">
|
<template #header>
|
||||||
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
||||||
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
||||||
<span>上传时间:{{ previewFile?.created_at }}</span>
|
</div>
|
||||||
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
</template>
|
||||||
</div>
|
<template #default>
|
||||||
<previewComponent v-if="previewFile" :previewFile="previewFile" />
|
<div class="space-y-3">
|
||||||
</div>
|
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
||||||
</template>
|
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
||||||
</NModal>
|
<span>上传时间:{{ previewFile?.created_at }}</span>
|
||||||
</NCard>
|
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
||||||
|
</div>
|
||||||
|
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NModal>
|
||||||
|
</NCard>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+423
-406
@@ -8,7 +8,7 @@ import { tagTextColor, tagBgColor, tagBorderColor, downloadFileFun } from '@/uti
|
|||||||
import { AiError, aiTypeJudge } from "@/service/api/ai";
|
import { AiError, aiTypeJudge } from "@/service/api/ai";
|
||||||
import { systemConfigApi } from '@/service/api/system';
|
import { systemConfigApi } from '@/service/api/system';
|
||||||
import { getChangeDetailApi, urgeApi } from '@/service/api/change';
|
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 themeStore = useThemeStore();
|
||||||
const { routerPushByKey } = useRouterPush();
|
const { routerPushByKey } = useRouterPush();
|
||||||
@@ -62,7 +62,7 @@ const previewModal = ref<boolean>(false);
|
|||||||
const questions = ref<any>([
|
const questions = ref<any>([
|
||||||
{
|
{
|
||||||
question_id: 1,
|
question_id: 1,
|
||||||
name: "1. 设备影响",
|
name: "1. 设备影响",
|
||||||
note: "不合适的材质将会导致",
|
note: "不合适的材质将会导致",
|
||||||
score: 0,
|
score: 0,
|
||||||
options: [
|
options: [
|
||||||
@@ -74,7 +74,7 @@ const questions = ref<any>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
question_id: 2,
|
question_id: 2,
|
||||||
name: "2. 财务影响",
|
name: "2. 财务影响",
|
||||||
note: "工艺设计错误将会导致",
|
note: "工艺设计错误将会导致",
|
||||||
score: 0,
|
score: 0,
|
||||||
options: [
|
options: [
|
||||||
@@ -86,7 +86,7 @@ const questions = ref<any>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
question_id: 3,
|
question_id: 3,
|
||||||
name: "3. 质量影响",
|
name: "3. 质量影响",
|
||||||
note: "",
|
note: "",
|
||||||
score: 0,
|
score: 0,
|
||||||
options: [
|
options: [
|
||||||
@@ -98,7 +98,7 @@ const questions = ref<any>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
question_id: 4,
|
question_id: 4,
|
||||||
name: "4. 设计的复杂程度",
|
name: "4. 设计的复杂程度",
|
||||||
note: "工艺设计包含了",
|
note: "工艺设计包含了",
|
||||||
score: 0,
|
score: 0,
|
||||||
options: [
|
options: [
|
||||||
@@ -110,7 +110,7 @@ const questions = ref<any>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
question_id: 5,
|
question_id: 5,
|
||||||
name: "5. 工艺设计的成熟度",
|
name: "5. 工艺设计的成熟度",
|
||||||
note: "",
|
note: "",
|
||||||
score: 0,
|
score: 0,
|
||||||
options: [
|
options: [
|
||||||
@@ -122,7 +122,7 @@ const questions = ref<any>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
question_id: 6,
|
question_id: 6,
|
||||||
name: "6. 工艺危害",
|
name: "6. 工艺危害",
|
||||||
note: "物料性质和工艺条件",
|
note: "物料性质和工艺条件",
|
||||||
score: 0,
|
score: 0,
|
||||||
options: [
|
options: [
|
||||||
@@ -322,413 +322,430 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace vertical :size="16">
|
<NSpace vertical :size="16">
|
||||||
<!-- 高风险 -->
|
<!-- 高风险 -->
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<div
|
<div
|
||||||
class="flex flex-wrap items-center gap-3 border border-slate-200 px-3 py-2 mb-3"
|
class="flex flex-wrap items-center gap-3 border border-slate-200 px-3 py-2 mb-3"
|
||||||
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
|
||||||
<NButton ghost type="primary" @click="back">
|
|
||||||
<Icon icon="ep:back" class="size-16px mr-1" />返回
|
|
||||||
</NButton>
|
|
||||||
<div class="min-w-0 flex-1 truncate text-base font-bold text-slate-800">{{ currentInfo?.title }}</div>
|
|
||||||
<NTag size="small" type="info" class="font-mono text-xs">编号:{{ currentInfo?.change_no }}</NTag>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-3 scroll">
|
|
||||||
<!-- 变更申请表 -->
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<p
|
|
||||||
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
|
|
||||||
:style="{'--theme-color': themeStore.themeColor}"
|
|
||||||
>变更申请表</p>
|
|
||||||
<NButton ghost size="small" class="text-xs" @click="exportFile('change')"><Icon icon="material-symbols:download" class="size-16px" />导出变更申请表</NButton>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="border border-slate-200"
|
|
||||||
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
|
||||||
>
|
|
||||||
<div class="grid border-b grid-cols-[100px_1fr_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?.applicant_name }}</div>
|
|
||||||
<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?.org_name }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid border-b grid-cols-[100px_1fr_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?.created_at?.slice(0,10) }}</div>
|
|
||||||
<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?.plan_use_date?.slice(0,10) }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid border-b grid-cols-[100px_1fr_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?.duration_type === 1 ? '永久变更' : '临时变更' }}</div>
|
|
||||||
<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?.urgent === 1 ? '是' : '否' }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid border-b grid-cols-[100px_1fr_100px_1fr]">
|
|
||||||
<p class="flex items-center justify-end px-3 py-1.6 text-xs text-slate-500 bg-[#fafafc]">变更等级</p>
|
|
||||||
<div class="flex items-center flex-wrap gap-2 px-3 py-1.6 text-sm leading-relaxed text-slate-800">
|
|
||||||
<NRadioGroup v-model:value="level">
|
|
||||||
<NRadio v-for="item in levelList" :key="item.value" :value="item.value" :disabled="true">{{item.label}}</NRadio>
|
|
||||||
</NRadioGroup>
|
|
||||||
<NTag type="error" size="small">合计 {{totalScore}} 分</NTag>
|
|
||||||
<NButton ghost type="warning" size="small" round class="text-xs" @click="levelDrawer = true">
|
|
||||||
<Icon icon="lucide:eye" class="size-12px mr-1" />查看等级判定
|
|
||||||
</NButton>
|
|
||||||
</div>
|
|
||||||
<p class="flex items-center justify-end px-3 py-1.6 text-xs text-slate-500 bg-[#fafafc]">变更类别</p>
|
|
||||||
<div class="flex items-center gap-2 px-3 py-1.6 text-sm leading-relaxed text-slate-800">
|
|
||||||
{{matchLabel(typeList,currentInfo?.apply_form?.change_type)?.label}}
|
|
||||||
<NButton v-if="isAiOn" ghost type="primary" size="small" round class="text-xs" @click="openTypeSheet">
|
|
||||||
<Icon icon="lucide:sparkles" class="size-12px mr-1" />判定说明
|
|
||||||
</NButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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?.purposes?.join('、')}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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?.effect}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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">
|
|
||||||
<p v-for="(main,mIndex) in currentInfo?.apply_form?.main_changes" :key="mIndex">{{Number(mIndex)+1}}.{{main}}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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">
|
|
||||||
<p v-for="(related,rIndex) in currentInfo?.apply_form?.related_changes" :key="rIndex">{{Number(rIndex)+1}}.{{related}}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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('、')}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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?.materials}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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?.update_docs?.join('、')}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid 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?.disciplines?.join('、')}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 变更附带资料 -->
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<p
|
|
||||||
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
|
|
||||||
:style="{'--theme-color': themeStore.themeColor}"
|
|
||||||
>变更附带资料</p>
|
|
||||||
<NButton ghost size="small" class="text-xs" @click="downloadFile(null)"><Icon icon="material-symbols:download" class="size-16px" />一键下载</NButton>
|
|
||||||
</div>
|
|
||||||
<NTable size="small">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="w-60px !text-center">序号</th>
|
|
||||||
<th>文件名</th>
|
|
||||||
<th class="w-100px !text-center">类型</th>
|
|
||||||
<th class="w-100px !text-center">文件大小</th>
|
|
||||||
<th class="w-120px !text-center">上传时间</th>
|
|
||||||
<th class="w-120px !text-center">上传阶段</th>
|
|
||||||
<th class="w-90px !text-center">操作</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="(doc,dIndex) in currentInfo?.apply_form?.attachments" :key="dIndex">
|
|
||||||
<td class="text-center">1</td>
|
|
||||||
<td>{{doc.file_name}}</td>
|
|
||||||
<td class="text-center text-xs">{{doc?.file_type}}</td>
|
|
||||||
<td class="text-center text-xs">{{doc?.file_size}}</td>
|
|
||||||
<td class="text-center text-xs">{{doc?.created_at?.slice(0,16)}}</td>
|
|
||||||
<td class="text-center text-xs">
|
|
||||||
<span v-if="doc?.step_no===1">变更申请表</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="flex items-center justify-center gap-2">
|
|
||||||
<NButton text type="primary" class="text-xs" @click="openPreview(doc)">预览</NButton>
|
|
||||||
<NButton text type="primary" class="text-xs" @click="downloadFile(doc)">下载</NButton>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</NTable>
|
|
||||||
|
|
||||||
<!-- 风险分析记录表 -->
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<p
|
|
||||||
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
|
|
||||||
:style="{'--theme-color': themeStore.themeColor}"
|
|
||||||
>风险分析记录表</p>
|
|
||||||
<NButton ghost size="small" class="text-xs" @click="exportFile('risk')"><Icon icon="material-symbols:download" class="size-16px" />导出风险分析记录表</NButton>
|
|
||||||
</div>
|
|
||||||
<NTable size="small">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="w-60px !text-center">序号</th>
|
|
||||||
<th>风险项</th>
|
|
||||||
<th>场景描述</th>
|
|
||||||
<th class="w-80px !text-center">固有风险</th>
|
|
||||||
<th>现有措施</th>
|
|
||||||
<th>建议措施</th>
|
|
||||||
<th class="w-80px !text-center">剩余风险</th>
|
|
||||||
<th class="w-100px !text-center">来源</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="(risk,index) in currentInfo?.risk_records?.records" :key="index">
|
|
||||||
<td class="text-center">{{Number(index)+1}}</td>
|
|
||||||
<td>{{risk.item}}</td>
|
|
||||||
<td>{{risk.scene}}</td>
|
|
||||||
<td class="text-center">{{risk.inherent_level}}</td>
|
|
||||||
<td>{{risk.safeguards}}</td>
|
|
||||||
<td>{{risk.suggestions}}</td>
|
|
||||||
<td class="text-center">{{risk.residual_level}}</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<NTag size="small" type="info" class="text-11px">{{ risk.source==='RISK'?'风险预分析':risk.source==='RISK_CHECK'?'风险检查': risk.source }}</NTag>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</NTable>
|
|
||||||
|
|
||||||
<!-- 变更培训内容 -->
|
|
||||||
<div>
|
|
||||||
<div class="flex items-center gap-2 mb-1.5">
|
|
||||||
<Icon icon="lucide:graduation-cap" class="size-16px text-violet-500" />
|
|
||||||
<span class="text-sm font-semibold text-slate-700">变更培训内容</span>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-2.5 border p-3" :style="{borderRadius: themeStore.themeRadius + 'px'}">
|
|
||||||
<p>{{currentInfo?.train?.content}}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- PSSR 检查内容 -->
|
|
||||||
<div>
|
|
||||||
<div class="flex items-center gap-2 mb-1.5">
|
|
||||||
<Icon icon="lucide:list-checks" class="size-16px text-violet-500" />
|
|
||||||
<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 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>
|
|
||||||
<NTag v-if="item.phase===1" size="small" type="info">投用前完成</NTag>
|
|
||||||
<NTag v-else size="small" type="info">关闭前完成</NTag>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 验收评价 -->
|
|
||||||
<div>
|
|
||||||
<div class="flex items-center gap-2 mb-1.5">
|
|
||||||
<Icon icon="ix:success" class="size-16px text-emerald-500" />
|
|
||||||
<span class="text-sm font-semibold text-slate-700">验收评价</span>
|
|
||||||
</div>
|
|
||||||
<div class="border p-3" :style="{borderRadius: themeStore.themeRadius + 'px'}">
|
|
||||||
<NTable size="small">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="w-56">验收项目</th>
|
|
||||||
<th>量化验收标准</th>
|
|
||||||
<th>验收方式</th>
|
|
||||||
<th class="w-46 !text-center">组织层级</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="(accept,index) in acceptList" :key="index">
|
|
||||||
<td class="text-xs">{{accept.item}}</td>
|
|
||||||
<td class="text-xs">{{accept.standard}}</td>
|
|
||||||
<td class="text-xs">{{accept.method}}</td>
|
|
||||||
<td class="text-xs text-center">{{accept.org_name}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</NTable>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 审批记录 -->
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<p
|
|
||||||
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
|
|
||||||
:style="{'--theme-color': themeStore.themeColor}"
|
|
||||||
>审批记录</p>
|
|
||||||
</div>
|
|
||||||
<NTable size="small">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="w-120px">流程阶段</th>
|
|
||||||
<th class="w-120px">操作人</th>
|
|
||||||
<th class="w-140px !text-center">操作时间</th>
|
|
||||||
<th>操作内容和意见</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="(record,index) in currentInfo?.approval_records" :key="index">
|
|
||||||
<td class="text-xs">{{record.action}}</td>
|
|
||||||
<td class="text-xs">{{record.user_name}}</td>
|
|
||||||
<td class="text-center text-xs">{{record.updated_at?.slice(0,16)}}</td>
|
|
||||||
<td class="text-xs">{{record.remark}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</NTable>
|
|
||||||
|
|
||||||
<!-- 审批流程进度 -->
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="text-sm font-semibold">审批流程{{ matchLabel(levelList,level)?.label? '('+matchLabel(levelList,level)?.label+'变更)':'' }}</div>
|
|
||||||
<div class="flex flex-wrap items-center gap-1">
|
|
||||||
<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'">
|
|
||||||
<p class="text-xs font-medium">
|
|
||||||
{{ n.name }}
|
|
||||||
</p>
|
|
||||||
<div class="flex items-center justify-center gap-1">
|
|
||||||
<span v-for="member in n.members" :key="member.id" class="text-xs text-slate-500">{{ member.user_name }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div v-if="allMembers?.length" class="rounded-lg border border-amber-200 bg-amber-50/60 p-3">
|
|
||||||
<div class="mb-2 flex flex-wrap items-center gap-2 text-xs font-semibold text-amber-700">
|
|
||||||
会签进度(全体通过方可流转)
|
|
||||||
<span class="font-normal text-amber-600">
|
|
||||||
{{ allMembers.filter((m) => m.status === 'PASSED').length }}/{{ allMembers.length }} 已签
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
|
||||||
<span v-for="m in allMembers" :key="m.id" class="flex items-center gap-1">
|
|
||||||
<NTag size="small" :type="m.status === 'PASSED' ? 'success' : m.status === 'REJECTED' ? 'error' : 'default'">
|
|
||||||
<template #icon>
|
|
||||||
<Icon v-if="m.status === 'PASSED'" icon="ix:success" class="size-14px" />
|
|
||||||
<Icon v-if="m.status === 'PENDING'" icon="ant-design:clock-circle-outlined" class="size-12px text-slate-400" />
|
|
||||||
<Icon v-if="m.status === 'REJECTED'" icon="ix:close" class="size-12px" />
|
|
||||||
</template>
|
|
||||||
{{ m.user_name }}
|
|
||||||
</NTag>
|
|
||||||
<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)"
|
|
||||||
>
|
|
||||||
<Icon icon="lucide:bell" class="size-12px" /> 催办
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</NCard>
|
|
||||||
<!-- 变更类型判定说明(右侧抽屉) -->
|
|
||||||
<NDrawer v-model:show="describeDrawer" :width="576" placement="right" :auto-focus="false">
|
|
||||||
<NDrawerContent title="变更类型判定说明" closable>
|
|
||||||
<NTag v-if="typeAiLoading" type="info" size="small">
|
|
||||||
<template #icon>
|
|
||||||
<Icon icon="ri:loader-4-fill" class="size-12px animate-spin" />
|
|
||||||
</template>
|
|
||||||
AI 判定中…
|
|
||||||
</NTag>
|
|
||||||
<div v-else class="flex items-center gap-2 max-h-[22px] mb-3">
|
|
||||||
<span class="inline-flex items-center gap-1 rounded-full border border-violet-200 bg-violet-100 px-2 py-0.5 text-xs font-medium text-violet-700">
|
|
||||||
✨ AI 生成 · 需人工确认
|
|
||||||
</span>
|
|
||||||
<NTag v-if="typeAi?.type" type="info" size="small">判定:{{ typeAi?.type }}</NTag>
|
|
||||||
<NTag v-if="typeAi" size="small">置信度:{{ `${Math.round(typeAi.confidence * 100)}%` }}</NTag>
|
|
||||||
</div>
|
|
||||||
<div v-if="typeAi" class="space-y-3 text-sm leading-relaxed text-slate-700">
|
|
||||||
<p><b>判定结论:</b>{{ typeAi.conclusion }}</p>
|
|
||||||
<p><b>判定依据:</b></p>
|
|
||||||
<ul class="list-disc space-y-1 pl-5">
|
|
||||||
<li v-for="r in typeAi.hit_rules" :key="r">命中规则:{{ r }}</li>
|
|
||||||
<li v-for="r in typeAi.exclude_rules" :key="r">排除规则:{{ r }}</li>
|
|
||||||
</ul>
|
|
||||||
<p v-if="typeAi.knowledge_refs.length"><b>知识库引用:</b>{{ typeAi.knowledge_refs.join('、') }}。</p>
|
|
||||||
<p class="rounded-lg bg-amber-50 p-3 text-xs text-amber-700">AI 判定结果仅供参考,最终类型以人工确认为准;如调整为其他类型,系统将记录修改留痕。</p>
|
|
||||||
</div>
|
|
||||||
</NDrawerContent>
|
|
||||||
</NDrawer>
|
|
||||||
<!-- 变更等级判定表(右侧抽屉查看) -->
|
|
||||||
<NDrawer v-model:show="levelDrawer" :width="576" placement="right" :auto-focus="false">
|
|
||||||
<NDrawerContent title="变更等级判定表" closable>
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<NTag
|
|
||||||
size="small"
|
|
||||||
:color="{
|
|
||||||
color: tagBgColor(matchLabel(levelList,level)?.color),
|
|
||||||
textColor: tagTextColor(matchLabel(levelList,level)?.color),
|
|
||||||
borderColor: tagBorderColor(matchLabel(levelList,level)?.color),
|
|
||||||
}"
|
|
||||||
>{{ matchLabel(levelList,level)?.label? matchLabel(levelList,level)?.label+'变更':'' }}</NTag>
|
|
||||||
<span class="text-xs text-slate-400">合计得分 <b class="text-[#17407F]">{{ totalScore }}</b> 分(<20 分为一般,≥20 分为重要)</span>
|
|
||||||
</div>
|
|
||||||
<NTable size="small">
|
|
||||||
<thead>
|
|
||||||
<tr class="border-b bg-slate-50">
|
|
||||||
<th class="w-40">评估维度</th>
|
|
||||||
<th class="w-20 !text-center">得分</th>
|
|
||||||
<th>评分依据</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="s in questions" :key="s.dim" class="border-b last:border-b-0">
|
|
||||||
<td class="font-medium">{{ s.name }}</td>
|
|
||||||
<td class="font-bold text-[#17407F] text-center">{{ s.score }}</td>
|
|
||||||
<td class="text-xs leading-relaxed text-slate-600">{{ matchBasis(s.options,s.score) }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</NTable>
|
|
||||||
</div>
|
|
||||||
</NDrawerContent>
|
|
||||||
</NDrawer>
|
|
||||||
<!-- 附件在线预览弹窗 -->
|
|
||||||
<NModal
|
|
||||||
v-model:show="previewModal"
|
|
||||||
preset="card"
|
|
||||||
:auto-focus="false"
|
|
||||||
:style="{ width: '80%', height: 'auto' }"
|
|
||||||
:segmented="{ content: true, footer: true }"
|
|
||||||
>
|
>
|
||||||
<template #header>
|
<NButton ghost type="primary" @click="back">
|
||||||
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
<Icon icon="ep:back" class="size-16px mr-1" />返回
|
||||||
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
</NButton>
|
||||||
|
<div class="min-w-0 flex-1 truncate text-base font-bold text-slate-800">{{ currentInfo?.title }}</div>
|
||||||
|
<NTag size="small" type="info" class="font-mono text-xs">编号:{{ currentInfo?.change_no }}</NTag>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-3 scroll">
|
||||||
|
<!-- 变更申请表 -->
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<p
|
||||||
|
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
|
||||||
|
:style="{'--theme-color': themeStore.themeColor}"
|
||||||
|
>
|
||||||
|
变更申请表
|
||||||
|
</p>
|
||||||
|
<NButton ghost size="small" class="text-xs" @click="exportFile('change')"><Icon icon="material-symbols:download" class="size-16px" />导出变更申请表</NButton>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="border border-slate-200"
|
||||||
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
|
>
|
||||||
|
<div class="grid border-b grid-cols-[100px_1fr_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?.applicant_name }}</div>
|
||||||
|
<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?.org_name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid border-b grid-cols-[100px_1fr_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?.created_at?.slice(0,10) }}</div>
|
||||||
|
<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?.plan_use_date?.slice(0,10) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid border-b grid-cols-[100px_1fr_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?.duration_type === 1 ? '永久变更' : '临时变更' }}</div>
|
||||||
|
<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?.urgent === 1 ? '是' : '否' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid border-b grid-cols-[100px_1fr_100px_1fr]">
|
||||||
|
<p class="flex items-center justify-end px-3 py-1.6 text-xs text-slate-500 bg-[#fafafc]">变更等级</p>
|
||||||
|
<div class="flex items-center flex-wrap gap-2 px-3 py-1.6 text-sm leading-relaxed text-slate-800">
|
||||||
|
<NRadioGroup v-model:value="level">
|
||||||
|
<NRadio v-for="item in levelList" :key="item.value" :value="item.value" :disabled="true">{{ item.label }}</NRadio>
|
||||||
|
</NRadioGroup>
|
||||||
|
<NTag type="error" size="small">合计 {{ totalScore }} 分</NTag>
|
||||||
|
<NButton ghost type="warning" size="small" round class="text-xs" @click="levelDrawer = true">
|
||||||
|
<Icon icon="lucide:eye" class="size-12px mr-1" />查看等级判定
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
<p class="flex items-center justify-end px-3 py-1.6 text-xs text-slate-500 bg-[#fafafc]">变更类别</p>
|
||||||
|
<div class="flex items-center gap-2 px-3 py-1.6 text-sm leading-relaxed text-slate-800">
|
||||||
|
{{ matchLabel(typeList,currentInfo?.apply_form?.change_type)?.label }}
|
||||||
|
<NButton v-if="isAiOn" ghost type="primary" size="small" round class="text-xs" @click="openTypeSheet">
|
||||||
|
<Icon icon="lucide:sparkles" class="size-12px mr-1" />判定说明
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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?.purposes?.join('、') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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?.effect }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<p v-for="(main,mIndex) in currentInfo?.apply_form?.main_changes" :key="mIndex">{{ Number(mIndex)+1 }}.{{ main }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<p v-for="(related,rIndex) in currentInfo?.apply_form?.related_changes" :key="rIndex">{{ Number(rIndex)+1 }}.{{ related }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<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]">
|
||||||
|
<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?.materials }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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?.update_docs?.join('、') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid 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?.disciplines?.join('、') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 变更附带资料 -->
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<p
|
||||||
|
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
|
||||||
|
:style="{'--theme-color': themeStore.themeColor}"
|
||||||
|
>
|
||||||
|
变更附带资料
|
||||||
|
</p>
|
||||||
|
<NButton ghost size="small" class="text-xs" @click="downloadFile(null)"><Icon icon="material-symbols:download" class="size-16px" />一键下载</NButton>
|
||||||
|
</div>
|
||||||
|
<NTable size="small">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="w-60px !text-center">序号</th>
|
||||||
|
<th>文件名</th>
|
||||||
|
<th class="w-100px !text-center">类型</th>
|
||||||
|
<th class="w-100px !text-center">文件大小</th>
|
||||||
|
<th class="w-120px !text-center">上传时间</th>
|
||||||
|
<th class="w-120px !text-center">上传阶段</th>
|
||||||
|
<th class="w-90px !text-center">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(doc,dIndex) in currentInfo?.apply_form?.attachments" :key="dIndex">
|
||||||
|
<td class="text-center">1</td>
|
||||||
|
<td>{{ doc.file_name }}</td>
|
||||||
|
<td class="text-center text-xs">{{ doc?.file_type }}</td>
|
||||||
|
<td class="text-center text-xs">{{ doc?.file_size }}</td>
|
||||||
|
<td class="text-center text-xs">{{ doc?.created_at?.slice(0,16) }}</td>
|
||||||
|
<td class="text-center text-xs">
|
||||||
|
<span v-if="doc?.step_no===1">变更申请表</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex items-center justify-center gap-2">
|
||||||
|
<NButton text type="primary" class="text-xs" @click="openPreview(doc)">预览</NButton>
|
||||||
|
<NButton text type="primary" class="text-xs" @click="downloadFile(doc)">下载</NButton>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</NTable>
|
||||||
|
|
||||||
|
<!-- 风险分析记录表 -->
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<p
|
||||||
|
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
|
||||||
|
:style="{'--theme-color': themeStore.themeColor}"
|
||||||
|
>
|
||||||
|
风险分析记录表
|
||||||
|
</p>
|
||||||
|
<NButton ghost size="small" class="text-xs" @click="exportFile('risk')"><Icon icon="material-symbols:download" class="size-16px" />导出风险分析记录表</NButton>
|
||||||
|
</div>
|
||||||
|
<NTable size="small">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="w-60px !text-center">序号</th>
|
||||||
|
<th>风险项</th>
|
||||||
|
<th>场景描述</th>
|
||||||
|
<th class="w-80px !text-center">固有风险</th>
|
||||||
|
<th>现有措施</th>
|
||||||
|
<th>建议措施</th>
|
||||||
|
<th class="w-80px !text-center">剩余风险</th>
|
||||||
|
<th class="w-100px !text-center">来源</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(risk,index) in currentInfo?.risk_records?.records" :key="index">
|
||||||
|
<td class="text-center">{{ Number(index)+1 }}</td>
|
||||||
|
<td>{{ risk.item }}</td>
|
||||||
|
<td>{{ risk.scene }}</td>
|
||||||
|
<td class="text-center">{{ risk.inherent_level }}</td>
|
||||||
|
<td>{{ risk.safeguards }}</td>
|
||||||
|
<td>{{ risk.suggestions }}</td>
|
||||||
|
<td class="text-center">{{ risk.residual_level }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<NTag size="small" type="info" class="text-11px">{{ risk.source==='RISK'?'风险预分析':risk.source==='RISK_CHECK'?'风险检查': risk.source }}</NTag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</NTable>
|
||||||
|
|
||||||
|
<!-- 变更培训内容 -->
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-2 mb-1.5">
|
||||||
|
<Icon icon="lucide:graduation-cap" class="size-16px text-violet-500" />
|
||||||
|
<span class="text-sm font-semibold text-slate-700">变更培训内容</span>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2.5 border p-3" :style="{borderRadius: themeStore.themeRadius + 'px'}">
|
||||||
|
<p>{{ currentInfo?.train?.content }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- PSSR 检查内容 -->
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-2 mb-1.5">
|
||||||
|
<Icon icon="lucide:list-checks" class="size-16px text-violet-500" />
|
||||||
|
<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" :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>
|
||||||
|
<NTag v-if="item.phase===1" size="small" type="info">投用前完成</NTag>
|
||||||
|
<NTag v-else size="small" type="info">关闭前完成</NTag>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<template #default>
|
</div>
|
||||||
<div class="space-y-3">
|
</div>
|
||||||
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
<!-- 验收评价 -->
|
||||||
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
<div>
|
||||||
<span>上传时间:{{ previewFile?.created_at }}</span>
|
<div class="flex items-center gap-2 mb-1.5">
|
||||||
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
<Icon icon="ix:success" class="size-16px text-emerald-500" />
|
||||||
|
<span class="text-sm font-semibold text-slate-700">验收评价</span>
|
||||||
|
</div>
|
||||||
|
<div class="border p-3" :style="{borderRadius: themeStore.themeRadius + 'px'}">
|
||||||
|
<NTable size="small">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="w-56">验收项目</th>
|
||||||
|
<th>量化验收标准</th>
|
||||||
|
<th>验收方式</th>
|
||||||
|
<th class="w-46 !text-center">组织层级</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(accept,index) in acceptList" :key="index">
|
||||||
|
<td class="text-xs">{{ accept.item }}</td>
|
||||||
|
<td class="text-xs">{{ accept.standard }}</td>
|
||||||
|
<td class="text-xs">{{ accept.method }}</td>
|
||||||
|
<td class="text-xs text-center">{{ accept.org_name }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</NTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 审批记录 -->
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<p
|
||||||
|
class="rounded-sm bg-[var(--theme-color)] px-3 py-1 text-xs font-semibold text-white"
|
||||||
|
:style="{'--theme-color': themeStore.themeColor}"
|
||||||
|
>
|
||||||
|
审批记录
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<NTable size="small">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="w-120px">流程阶段</th>
|
||||||
|
<th class="w-120px">操作人</th>
|
||||||
|
<th class="w-140px !text-center">操作时间</th>
|
||||||
|
<th>操作内容和意见</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(record,index) in currentInfo?.approval_records" :key="index">
|
||||||
|
<td class="text-xs">{{ record.action }}</td>
|
||||||
|
<td class="text-xs">{{ record.user_name }}</td>
|
||||||
|
<td class="text-center text-xs">{{ record.updated_at?.slice(0,16) }}</td>
|
||||||
|
<td class="text-xs">{{ record.remark }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</NTable>
|
||||||
|
|
||||||
|
<!-- 审批流程进度 -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="text-sm font-semibold">审批流程{{ matchLabel(levelList,level)?.label? '('+matchLabel(levelList,level)?.label+'变更)':'' }}</div>
|
||||||
|
<div class="flex flex-wrap items-center gap-1">
|
||||||
|
<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'"
|
||||||
|
>
|
||||||
|
<p class="text-xs font-medium">
|
||||||
|
{{ n.name }}
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center justify-center gap-1">
|
||||||
|
<span v-for="member in n.members" :key="member.id" class="text-xs text-slate-500">{{ member.user_name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<previewComponent v-if="previewFile" :previewFile="previewFile" />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div v-if="allMembers?.length" class="rounded-lg border border-amber-200 bg-amber-50/60 p-3">
|
||||||
|
<div class="mb-2 flex flex-wrap items-center gap-2 text-xs font-semibold text-amber-700">
|
||||||
|
会签进度(全体通过方可流转)
|
||||||
|
<span class="font-normal text-amber-600">
|
||||||
|
{{ allMembers.filter((m) => m.status === 'PASSED').length }}/{{ allMembers.length }} 已签
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<span v-for="m in allMembers" :key="m.id" class="flex items-center gap-1">
|
||||||
|
<NTag size="small" :type="m.status === 'PASSED' ? 'success' : m.status === 'REJECTED' ? 'error' : 'default'">
|
||||||
|
<template #icon>
|
||||||
|
<Icon v-if="m.status === 'PASSED'" icon="ix:success" class="size-14px" />
|
||||||
|
<Icon v-if="m.status === 'PENDING'" icon="ant-design:clock-circle-outlined" class="size-12px text-slate-400" />
|
||||||
|
<Icon v-if="m.status === 'REJECTED'" icon="ix:close" class="size-12px" />
|
||||||
|
</template>
|
||||||
|
{{ m.user_name }}
|
||||||
|
</NTag>
|
||||||
|
<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)"
|
||||||
|
>
|
||||||
|
<Icon icon="lucide:bell" class="size-12px" /> 催办
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NCard>
|
||||||
|
<!-- 变更类型判定说明(右侧抽屉) -->
|
||||||
|
<NDrawer v-model:show="describeDrawer" :width="576" placement="right" :auto-focus="false">
|
||||||
|
<NDrawerContent title="变更类型判定说明" closable>
|
||||||
|
<NTag v-if="typeAiLoading" type="info" size="small">
|
||||||
|
<template #icon>
|
||||||
|
<Icon icon="ri:loader-4-fill" class="size-12px animate-spin" />
|
||||||
</template>
|
</template>
|
||||||
</NModal>
|
AI 判定中…
|
||||||
|
</NTag>
|
||||||
|
<div v-else class="flex items-center gap-2 max-h-[22px] mb-3">
|
||||||
|
<span class="inline-flex items-center gap-1 rounded-full border border-violet-200 bg-violet-100 px-2 py-0.5 text-xs font-medium text-violet-700">
|
||||||
|
✨ AI 生成 · 需人工确认
|
||||||
|
</span>
|
||||||
|
<NTag v-if="typeAi?.type" type="info" size="small">判定:{{ typeAi?.type }}</NTag>
|
||||||
|
<NTag v-if="typeAi" size="small">置信度:{{ `${Math.round(typeAi.confidence * 100)}%` }}</NTag>
|
||||||
|
</div>
|
||||||
|
<div v-if="typeAi" class="space-y-3 text-sm leading-relaxed text-slate-700">
|
||||||
|
<p><b>判定结论:</b>{{ typeAi.conclusion }}</p>
|
||||||
|
<p><b>判定依据:</b></p>
|
||||||
|
<ul class="list-disc space-y-1 pl-5">
|
||||||
|
<li v-for="r in typeAi.hit_rules" :key="r">命中规则:{{ r }}</li>
|
||||||
|
<li v-for="r in typeAi.exclude_rules" :key="r">排除规则:{{ r }}</li>
|
||||||
|
</ul>
|
||||||
|
<p v-if="typeAi.knowledge_refs.length"><b>知识库引用:</b>{{ typeAi.knowledge_refs.join('、') }}。</p>
|
||||||
|
<p class="rounded-lg bg-amber-50 p-3 text-xs text-amber-700">AI 判定结果仅供参考,最终类型以人工确认为准;如调整为其他类型,系统将记录修改留痕。</p>
|
||||||
|
</div>
|
||||||
|
</NDrawerContent>
|
||||||
|
</NDrawer>
|
||||||
|
<!-- 变更等级判定表(右侧抽屉查看) -->
|
||||||
|
<NDrawer v-model:show="levelDrawer" :width="576" placement="right" :auto-focus="false">
|
||||||
|
<NDrawerContent title="变更等级判定表" closable>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<NTag
|
||||||
|
size="small"
|
||||||
|
:color="{
|
||||||
|
color: tagBgColor(matchLabel(levelList,level)?.color),
|
||||||
|
textColor: tagTextColor(matchLabel(levelList,level)?.color),
|
||||||
|
borderColor: tagBorderColor(matchLabel(levelList,level)?.color),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ matchLabel(levelList,level)?.label? matchLabel(levelList,level)?.label+'变更':'' }}
|
||||||
|
</NTag>
|
||||||
|
<span class="text-xs text-slate-400">合计得分 <b class="text-[#17407F]">{{ totalScore }}</b> 分(<20 分为一般,≥20 分为重要)</span>
|
||||||
|
</div>
|
||||||
|
<NTable size="small">
|
||||||
|
<thead>
|
||||||
|
<tr class="border-b bg-slate-50">
|
||||||
|
<th class="w-40">评估维度</th>
|
||||||
|
<th class="w-20 !text-center">得分</th>
|
||||||
|
<th>评分依据</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="s in questions" :key="s.dim" class="border-b last:border-b-0">
|
||||||
|
<td class="font-medium">{{ s.name }}</td>
|
||||||
|
<td class="font-bold text-[#17407F] text-center">{{ s.score }}</td>
|
||||||
|
<td class="text-xs leading-relaxed text-slate-600">{{ matchBasis(s.options,s.score) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</NTable>
|
||||||
|
</div>
|
||||||
|
</NDrawerContent>
|
||||||
|
</NDrawer>
|
||||||
|
<!-- 附件在线预览弹窗 -->
|
||||||
|
<NModal
|
||||||
|
v-model:show="previewModal"
|
||||||
|
preset="card"
|
||||||
|
:auto-focus="false"
|
||||||
|
:style="{ width: '80%', height: 'auto' }"
|
||||||
|
:segmented="{ content: true, footer: true }"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
||||||
|
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
||||||
|
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
||||||
|
<span>上传时间:{{ previewFile?.created_at }}</span>
|
||||||
|
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
||||||
|
</div>
|
||||||
|
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NModal>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
+223
-184
@@ -3,17 +3,18 @@ import { ref, onMounted, h } from 'vue';
|
|||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { NButton, NTag, NDropdown, useDialog } from 'naive-ui';
|
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 { downloadFileFun } from '@/utils/common';
|
||||||
import {
|
import {
|
||||||
docTreeListApi,
|
docTreeListApi,
|
||||||
addFolderApi,
|
addFolderApi,
|
||||||
editFolderApi,
|
editFolderApi,
|
||||||
deleteFolderApi,
|
deleteFolderApi,
|
||||||
docListApi,
|
docListApi,
|
||||||
uploadDocApi,
|
uploadDocApi,
|
||||||
updateDocApi,
|
updateDocApi,
|
||||||
docHistoryListApi
|
docHistoryListApi,
|
||||||
|
deleteDocApi
|
||||||
} from '@/service/api/doc';
|
} from '@/service/api/doc';
|
||||||
|
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
@@ -279,15 +280,15 @@ const handleMenuSelect = (key: string) => {
|
|||||||
|
|
||||||
// 新增/编辑文件夹确认
|
// 新增/编辑文件夹确认
|
||||||
const modalSave = async () => {
|
const modalSave = async () => {
|
||||||
if(!name.value){
|
if(!name.value){
|
||||||
window.$message?.warning('请输入文件夹名称')
|
window.$message?.warning('请输入文件夹名称')
|
||||||
}
|
}
|
||||||
saveLoading.value = true;
|
saveLoading.value = true;
|
||||||
// 新增文件夹
|
// 新增文件夹
|
||||||
if(modalType.value === 'add'){
|
if(modalType.value === 'add'){
|
||||||
const {error} = await addFolderApi({
|
const {error} = await addFolderApi({
|
||||||
name: name.value,
|
name: name.value,
|
||||||
parent_id: currentNodeRightClick.value.id,
|
parent_id: currentNodeRightClick.value.id,
|
||||||
org_id: currentOrgId.value
|
org_id: currentOrgId.value
|
||||||
});
|
});
|
||||||
if(!error){
|
if(!error){
|
||||||
@@ -315,6 +316,7 @@ const searchHandle = () => {
|
|||||||
if(!keyword.value) {
|
if(!keyword.value) {
|
||||||
// 搜索框为空时,显示树状列表
|
// 搜索框为空时,显示树状列表
|
||||||
showTree.value = true;
|
showTree.value = true;
|
||||||
|
getFileList(currentNode.value.id);
|
||||||
}else{
|
}else{
|
||||||
// 搜索框不为空时,显示搜索结果
|
// 搜索框不为空时,显示搜索结果
|
||||||
showTree.value = false;
|
showTree.value = false;
|
||||||
@@ -409,7 +411,7 @@ const preview = (d:any) => {
|
|||||||
}
|
}
|
||||||
// 下载资料
|
// 下载资料
|
||||||
const download = (file: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) => {
|
const uploadFiles = async (e:any) => {
|
||||||
@@ -447,7 +449,7 @@ const updateDoc = async (e:any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
// 更新资料
|
// 更新资料
|
||||||
const updateFile = (d:any) => {
|
const updateFile = (d:any) => {
|
||||||
currentDocId.value = d.id;
|
currentDocId.value = d.id;
|
||||||
@@ -464,6 +466,20 @@ const viewHistory = async (d:any) => {
|
|||||||
}
|
}
|
||||||
historyLoading.value = false;
|
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(() => {
|
onMounted(() => {
|
||||||
getDocList();
|
getDocList();
|
||||||
@@ -471,227 +487,250 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace vertical :size="16">
|
<NSpace vertical :size="16">
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<!-- 上传资料 -->
|
<!-- 上传资料 -->
|
||||||
<input ref="uploadDocRef" type="file" accept="image/*,.pdf,.docx,.xlsx" class="hidden" @change="uploadFiles" />
|
<input ref="uploadDocRef" type="file" accept="image/*,.pdf,.docx,.xlsx" class="hidden" @change="uploadFiles" />
|
||||||
<!-- 更新资料 -->
|
<!-- 更新资料 -->
|
||||||
<input ref="updateDocRef" type="file" accept="image/*,.pdf,.docx,.xlsx" class="hidden" @change="updateDoc" />
|
<input ref="updateDocRef" type="file" accept="image/*,.pdf,.docx,.xlsx" class="hidden" @change="updateDoc" />
|
||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="pb-[12px] border-b">
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="pb-[12px] border-b">
|
||||||
<NGi span="24 s:24 m:16">
|
<NGi span="24 s:24 m:16">
|
||||||
<div class="h-100% flex items-center gap-2">
|
<div class="h-100% flex items-center gap-2">
|
||||||
<Icon icon="mage:book" class="size-20px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="mage:book" class="size-20px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-base font-semibold">资料档案库(按组织 / 专业类别建立文件夹)</p>
|
<p class="text-base font-semibold">资料档案库(按组织 / 专业类别建立文件夹)</p>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:8" class="flex items-center justify-end gap-12px">
|
<NGi span="24 s:24 m:8" class="flex items-center justify-end gap-12px">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<NInput v-model:value="keyword" size="small" type="text" class="w-40 text-xs" clearable placeholder="全文检索…" @clear="keyword = ''; searchHandle();">
|
<NInput v-model:value="keyword" size="small" type="text" class="w-40 text-xs" clearable placeholder="全文检索…" @clear="keyword = ''; searchHandle();">
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" @click="searchHandle" />
|
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" @click="searchHandle" />
|
||||||
</template>
|
</template>
|
||||||
</NInput>
|
</NInput>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
<div class="mt-4 mb-2 rounded-lg bg-slate-50 px-3 py-1.5 text-[11px] text-slate-400">
|
<div class="mt-4 mb-2 rounded-lg bg-slate-50 px-3 py-1.5 text-[11px] text-slate-400">
|
||||||
按「组织 → 专业类别 → 资料文件夹」归档具体资料(操作规程 / PID 图纸 / 工艺卡片 / 设备台账 / 安全阀台账 / 报警台账 / 联锁台账 / 总图 / 现场气体报警仪布置图 / 防爆分区图等);资料由指定维护人(资料管理员 / 各专业专责)上传维护,版本受控;「调用」可将资料引用为当前变更的 AI 知识来源,调用记录留痕。
|
按「组织 → 专业类别 → 资料文件夹」归档具体资料(操作规程 / PID 图纸 / 工艺卡片 / 设备台账 / 安全阀台账 / 报警台账 / 联锁台账 / 总图 / 现场气体报警仪布置图 / 防爆分区图等);资料由指定维护人(资料管理员 / 各专业专责)上传维护,版本受控;「调用」可将资料引用为当前变更的 AI 知识来源,调用记录留痕。
|
||||||
</div>
|
</div>
|
||||||
<!-- 全文检索:跨文件夹平铺结果 -->
|
<!-- 全文检索:跨文件夹平铺结果 -->
|
||||||
<div v-if="!showTree">
|
<div v-if="!showTree">
|
||||||
<NSpin :show="fileLoading" size="small">
|
<NSpin :show="fileLoading" size="small">
|
||||||
<div class="mb-1.5 text-xs text-slate-400">全文检索「{{ keyword }}」:跨全部文件夹命中 {{ queryList.length }} 份</div>
|
<div class="mb-1.5 text-xs text-slate-400">全文检索「{{ keyword }}」:跨全部文件夹命中 {{ queryList.length }} 份</div>
|
||||||
<div class="top-scroll">
|
<div class="top-scroll">
|
||||||
<div class="divide-y rounded-md border bg-white h-100%">
|
<div class="divide-y rounded-md border bg-white h-100%">
|
||||||
<div v-for="d in queryList" :key="d.name" class="flex flex-wrap items-center gap-3 px-2 py-2.5">
|
<div v-for="d in queryList" :key="d.name" class="flex flex-wrap items-center gap-3 px-2 py-2.5">
|
||||||
<Icon icon="akar-icons:file" class="size-16px" />
|
<Icon icon="akar-icons:file" class="size-16px" />
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<div class="truncate text-sm">{{ d.doc_name }}</div>
|
<div class="truncate text-sm">{{ d.doc_name }}</div>
|
||||||
<div class="mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-slate-500">
|
<div class="mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-slate-500">
|
||||||
<NTag size="small" type="info" class="text-[10px]">版本 {{ d.version }}</NTag>
|
<NTag size="small" type="info" class="text-[10px]">版本 {{ d.version }}</NTag>
|
||||||
<span>{{ d.path.join(' · ') }}</span>
|
<span>{{ d.path.join(' · ') }}</span>
|
||||||
<span>更新 {{ d?.updated_at?.slice(0, 10) }}</span>
|
<span>更新 {{ d?.updated_at?.slice(0, 10) }}</span>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex shrink-0 gap-3 text-xs">
|
|
||||||
<NButton type="primary" text class="hover:underline text-xs" @click="viewHistory(d)">查看历史</NButton>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="queryList.length === 0" class="h-100% flex items-center justify-center">
|
|
||||||
<NEmpty description="未匹配到相关资料" class="text-xs"></NEmpty>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex shrink-0 gap-3 text-xs">
|
||||||
|
<NButton type="primary" text class="hover:underline text-xs" @click="viewHistory(d)">查看历史</NButton>
|
||||||
|
<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>
|
||||||
</NSpin>
|
<div v-if="queryList.length === 0" class="h-100% flex items-center justify-center">
|
||||||
|
<NEmpty description="未匹配到相关资料" class="text-xs"></NEmpty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NSpin>
|
||||||
</div>
|
</div>
|
||||||
<!-- 文件夹树 + 当前文件夹资料 -->
|
<!-- 文件夹树 + 当前文件夹资料 -->
|
||||||
<div v-else class="flex gap-3">
|
<div v-else class="flex gap-3">
|
||||||
<!-- 左侧 -->
|
<!-- 左侧 -->
|
||||||
<div class="w-300px border rounded-md p-2">
|
<div class="w-300px border rounded-md p-2">
|
||||||
<div class="scroll-left">
|
<div class="scroll-left">
|
||||||
<NTree
|
<NTree
|
||||||
class="h-100% flex flex-col"
|
class="h-100% flex flex-col"
|
||||||
block-line
|
block-line
|
||||||
:data="docList"
|
:data="docList"
|
||||||
label-field="label"
|
label-field="label"
|
||||||
key-field="key"
|
key-field="key"
|
||||||
:expanded-keys="expandedKeys"
|
:expanded-keys="expandedKeys"
|
||||||
:selected-keys="selectedKeys"
|
:selected-keys="selectedKeys"
|
||||||
:render-switcher-icon="renderSwitcherIcon"
|
:render-switcher-icon="renderSwitcherIcon"
|
||||||
selectable
|
selectable
|
||||||
:cancelable="false"
|
:cancelable="false"
|
||||||
:on-update:expanded-keys="handleExpandKeys"
|
:on-update:expanded-keys="handleExpandKeys"
|
||||||
:on-update:selected-keys="handleSelectKeys"
|
:on-update:selected-keys="handleSelectKeys"
|
||||||
:node-props="treeProps"
|
:node-props="treeProps"
|
||||||
>
|
>
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="h-100% flex items-center justify-center">
|
<div class="h-100% flex items-center justify-center">
|
||||||
<NEmpty description="暂无数据" class="text-xs"></NEmpty>
|
<NEmpty description="暂无数据" class="text-xs"></NEmpty>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NTree>
|
</NTree>
|
||||||
<NDropdown
|
<NDropdown
|
||||||
:show="menuVisible"
|
:show="menuVisible"
|
||||||
:options="menuOptions"
|
:options="menuOptions"
|
||||||
size="small"
|
size="small"
|
||||||
:x="menuX"
|
:x="menuX"
|
||||||
:y="menuY"
|
:y="menuY"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
trigger="manual"
|
trigger="manual"
|
||||||
@clickoutside="closeMenu"
|
@clickoutside="closeMenu"
|
||||||
@select="handleMenuSelect"
|
@select="handleMenuSelect"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 右侧 -->
|
</div>
|
||||||
<div class="min-w-0 flex-1">
|
<!-- 右侧 -->
|
||||||
<div class="mb-1.5 flex flex-wrap items-center gap-1 text-xs text-slate-400">
|
<div class="min-w-0 flex-1">
|
||||||
当前位置:
|
<div class="mb-1.5 flex flex-wrap items-center gap-1 text-xs text-slate-400">
|
||||||
<div class="flex items-center gap-1">
|
当前位置:
|
||||||
<template v-for="(item,index) in currentPath" :key="index">
|
<div class="flex items-center gap-1">
|
||||||
<span v-if="index !== (currentPath.length - 1)">{{ item }} </span>
|
<template v-for="(item,index) in currentPath" :key="index">
|
||||||
<span v-else class="font-medium text-slate-600">{{ item }}</span>
|
<span v-if="index !== (currentPath.length - 1)">{{ item }} </span>
|
||||||
<Icon v-if="index !== (currentPath.length - 1)" icon="formkit:right" class="size-12px" />
|
<span v-else class="font-medium text-slate-600">{{ item }}</span>
|
||||||
</template>
|
<Icon v-if="index !== (currentPath.length - 1)" icon="formkit:right" class="size-12px" />
|
||||||
</div>
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span class="ml-auto">共 {{ fileList.length }} 份 · 版本受控</span>
|
<span class="ml-auto">共 {{ fileList.length }} 份 · 版本受控</span>
|
||||||
</div>
|
|
||||||
<NSpin :show="fileLoading" size="small">
|
|
||||||
<div class="scroll-right">
|
|
||||||
<div class="divide-y rounded-md border bg-white h-100%">
|
|
||||||
<div v-for="d in fileList" :key="d.name" class="flex flex-wrap items-center gap-3 px-2 py-2.5">
|
|
||||||
<Icon icon="akar-icons:file" class="size-16px" />
|
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<div class="truncate text-sm">{{ d.doc_name }}</div>
|
|
||||||
<div class="mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-slate-500">
|
|
||||||
<NTag size="small" type="info" class="text-[10px]">版本 {{ d.version }}</NTag>
|
|
||||||
<span>更新 {{ d?.updated_at?.slice(0, 10) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex shrink-0 gap-3 text-xs">
|
|
||||||
<NButton type="primary" text class="hover:underline text-xs" @click="viewHistory(d)">查看历史</NButton>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="fileList.length===0" class="h-100% flex items-center justify-center">
|
|
||||||
<NEmpty description="暂无资料数据" class="text-xs"></NEmpty>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</NSpin>
|
|
||||||
</div>
|
</div>
|
||||||
|
<NSpin :show="fileLoading" size="small">
|
||||||
|
<div class="scroll-right">
|
||||||
|
<div class="divide-y rounded-md border bg-white h-100%">
|
||||||
|
<div v-for="d in fileList" :key="d.name" class="flex flex-wrap items-center gap-3 px-2 py-2.5">
|
||||||
|
<Icon icon="akar-icons:file" class="size-16px" />
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<div class="truncate text-sm">{{ d.doc_name }}</div>
|
||||||
|
<div class="mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-slate-500">
|
||||||
|
<NTag size="small" type="info" class="text-[10px]">版本 {{ d.version }}</NTag>
|
||||||
|
<span>更新 {{ d?.updated_at?.slice(0, 10) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex shrink-0 gap-3 text-xs">
|
||||||
|
<NButton type="primary" text class="hover:underline text-xs" @click="viewHistory(d)">查看历史</NButton>
|
||||||
|
<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">
|
||||||
|
<NEmpty description="暂无资料数据" class="text-xs"></NEmpty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NSpin>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
<!-- 新增/编辑弹窗 -->
|
<!-- 新增/编辑弹窗 -->
|
||||||
<NModal
|
<NModal
|
||||||
v-model:show="showModal"
|
v-model:show="showModal"
|
||||||
preset="card"
|
preset="card"
|
||||||
:title="modalType==='add'?'新增文件夹':'编辑文件夹'"
|
:title="modalType==='add'?'新增文件夹':'编辑文件夹'"
|
||||||
:auto-focus="true"
|
:auto-focus="true"
|
||||||
:style="{ width: '450px', height: 'auto' }"
|
:style="{ width: '450px', height: 'auto' }"
|
||||||
:segmented="{ content: true, footer: true }"
|
:segmented="{ content: true, footer: true }"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<NInput v-model:value="name" placeholder="请输入文件夹名称" />
|
<NInput v-model:value="name" placeholder="请输入文件夹名称" />
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex justify-end gap-[10px]">
|
<div class="flex justify-end gap-[10px]">
|
||||||
<NButton size="small" @click="showModal = false">取消</NButton>
|
<NButton size="small" @click="showModal = false">取消</NButton>
|
||||||
<NButton size="small" type="primary" :loading="saveLoading" @click="modalSave">确定</NButton>
|
<NButton size="small" type="primary" :loading="saveLoading" @click="modalSave">确定</NButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NModal>
|
</NModal>
|
||||||
<!-- 历史版本查看 -->
|
<!-- 历史版本查看 -->
|
||||||
<NDrawer v-model:show="historyDrawer" :width="600" placement="right" :auto-focus="false">
|
<NDrawer v-model:show="historyDrawer" :width="600" placement="right" :auto-focus="false">
|
||||||
<NDrawerContent title="历史版本" closable>
|
<NDrawerContent title="历史版本" closable>
|
||||||
<NSpin :show="historyLoading" size="small">
|
<NSpin :show="historyLoading" size="small">
|
||||||
<NTable size="small">
|
<NTable size="small">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>名称</th>
|
<th>名称</th>
|
||||||
<th class="!text-center w-80px">版本</th>
|
<th class="!text-center w-80px">版本</th>
|
||||||
<th class="!text-center w-120px">更新时间</th>
|
<th class="!text-center w-120px">更新时间</th>
|
||||||
<th class="!text-center w-80px">操作</th>
|
<th class="!text-center w-80px">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-if="historyList.length===0">
|
<tr v-if="historyList.length===0">
|
||||||
<td colspan="4" class="text-center !text-slate-400 !py-20px">暂无历史版本数据</td>
|
<td colspan="4" class="text-center !text-slate-400 !py-20px">暂无历史版本数据</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="item in historyList" :key="item.id">
|
<tr v-for="item in historyList" :key="item.id">
|
||||||
<td>{{ item.doc_name }}</td>
|
<td>{{ item.doc_name }}</td>
|
||||||
<td class="text-center">{{ item.version }}</td>
|
<td class="text-center">{{ item.version }}</td>
|
||||||
<td class="text-center">{{ item.updated_at?.slice(0, 10) }}</td>
|
<td class="text-center">{{ item.updated_at?.slice(0, 10) }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex items-center justify-center gap-2">
|
<div class="flex items-center justify-center gap-2">
|
||||||
<NButton type="primary" text class="hover:underline text-xs" @click="preview(item)">预览</NButton>
|
<NButton type="primary" text class="hover:underline text-xs" @click="preview(item)">预览</NButton>
|
||||||
<NButton type="primary" text class="hover:underline text-xs" @click="download(item)">下载</NButton>
|
<NButton type="primary" text class="hover:underline text-xs" @click="download(item)">下载</NButton>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</NTable>
|
</NTable>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</NDrawerContent>
|
</NDrawerContent>
|
||||||
</NDrawer>
|
</NDrawer>
|
||||||
<!-- 附件在线预览弹窗 -->
|
<!-- 附件在线预览弹窗 -->
|
||||||
<NModal
|
<NModal
|
||||||
v-model:show="previewModal"
|
v-model:show="previewModal"
|
||||||
preset="card"
|
preset="card"
|
||||||
:auto-focus="false"
|
:auto-focus="false"
|
||||||
:style="{ width: '80%', height: 'auto' }"
|
:style="{ width: '80%', height: 'auto' }"
|
||||||
:segmented="{ content: true, footer: true }"
|
:segmented="{ content: true, footer: true }"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
||||||
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.doc_name }}
|
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.doc_name }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
||||||
<span>上传人:{{ previewFile?.user_name }}</span>
|
<span>上传人:{{ previewFile?.user_name }}</span>
|
||||||
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
|
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
|
||||||
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
||||||
</div>
|
|
||||||
<previewComponent v-if="previewFile" :previewFile="previewFile" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NModal>
|
</NModal>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.scroll-left {
|
.scroll-left {
|
||||||
height: calc(100vh - 276px);
|
height: calc(100vh - 276px);
|
||||||
|
|||||||
+145
-139
@@ -3,7 +3,7 @@ import { ref, onMounted, h } from 'vue';
|
|||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { formatTimestamp, getTimeRangeStamp, downloadFileFun } from "@/utils/common";
|
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 { systemConfigApi } from '@/service/api/system';
|
||||||
import { orgListApi } from '@/service/api/user';
|
import { orgListApi } from '@/service/api/user';
|
||||||
import { docArchiveTreeListApi, docArchiveDetailsApi } from "@/service/api/doc";
|
import { docArchiveTreeListApi, docArchiveDetailsApi } from "@/service/api/doc";
|
||||||
@@ -236,7 +236,7 @@ const handleSelectKeys = (keys: string[],option:any) => {
|
|||||||
|
|
||||||
// 下载
|
// 下载
|
||||||
const downloadFile = (file:any) => {
|
const downloadFile = (file:any) => {
|
||||||
downloadFileFun(file.file_url, file.file_name);
|
downloadFileFun(file?.file_url, file?.file_name || file?.doc_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预览
|
// 预览
|
||||||
@@ -354,171 +354,177 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace vertical :size="16">
|
<NSpace vertical :size="16">
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="pb-[12px] border-b">
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="pb-[12px] border-b">
|
||||||
<NGi span="24 s:24 m:24">
|
<NGi span="24 s:24 m:24">
|
||||||
<div class="h-100% flex items-center gap-2">
|
<div class="h-100% flex items-center gap-2">
|
||||||
<Icon icon="mage:book" class="size-20px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="mage:book" class="size-20px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-base font-semibold">变更归档(一次变更一个归档包 · 关闭即自动归档 · 只读锁定)</p>
|
<p class="text-base font-semibold">变更归档(一次变更一个归档包 · 关闭即自动归档 · 只读锁定)</p>
|
||||||
|
</div>
|
||||||
|
</NGi>
|
||||||
|
</NGrid>
|
||||||
|
<div class="flex items-center gap-2 mt-3">
|
||||||
|
<NInput v-model:value="formInfo.change_no" type="text" class="w-full text-xs" clearable placeholder="按变更单号精确查找,或按名称关键词检索(如 MOC-2026-R01-0039)" @clear="formInfo.change_no = ''; getList()">
|
||||||
|
<template #suffix>
|
||||||
|
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" @click="getList" />
|
||||||
|
</template>
|
||||||
|
</NInput>
|
||||||
|
<NButton class="text-sm" @click="reset">
|
||||||
|
<Icon icon="system-uicons:reset" class="size-14px" />重置
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
<!-- 筛选:时间 / 组织 / 专业 -->
|
||||||
|
<div class="flex flex-wrap items-center gap-x-4 gap-y-1.5 mt-3">
|
||||||
|
<NTreeSelect
|
||||||
|
class="!w-200px"
|
||||||
|
:value="formInfo.org_id"
|
||||||
|
:options="orgTree"
|
||||||
|
size="small"
|
||||||
|
default-expand-all
|
||||||
|
label-field="label"
|
||||||
|
key-field="id"
|
||||||
|
:render-switcher-icon="renderSwitcherIcon"
|
||||||
|
placeholder="请选择组织层级"
|
||||||
|
@update:value="orgChange"
|
||||||
|
/>
|
||||||
|
<div class="flex flex-wrap items-center gap-1">
|
||||||
|
<span class="text-[11px] text-slate-400">时间</span>
|
||||||
|
<NButton v-for="time in timeOptions" :key="time.value" size="tiny" round class="text-xs" :type="formInfo.time === time.value ? 'primary' : 'default'" @click="timeChange(time.value)">{{ time.label }}</NButton>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap items-center gap-1">
|
||||||
|
<span class="text-[11px] text-slate-400">类型</span>
|
||||||
|
<NButton v-for="type in typeList" :key="type.value" size="tiny" round class="text-xs" :type="formInfo.type === type.value ? 'primary' : 'default'" @click="typeChange(type.value)">{{ type.label }}</NButton>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
|
||||||
</NGrid>
|
|
||||||
<div class="flex items-center gap-2 mt-3">
|
|
||||||
<NInput v-model:value="formInfo.change_no" type="text" class="w-full text-xs" clearable placeholder="按变更单号精确查找,或按名称关键词检索(如 MOC-2026-R01-0039)" @clear="formInfo.change_no = ''; getList()">
|
|
||||||
<template #suffix>
|
|
||||||
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" @click="getList" />
|
|
||||||
</template>
|
|
||||||
</NInput>
|
|
||||||
<NButton class="text-sm" @click="reset">
|
|
||||||
<Icon icon="system-uicons:reset" class="size-14px" />重置
|
|
||||||
</NButton>
|
|
||||||
</div>
|
|
||||||
<!-- 筛选:时间 / 组织 / 专业 -->
|
|
||||||
<div class="flex flex-wrap items-center gap-x-4 gap-y-1.5 mt-3">
|
|
||||||
<NTreeSelect
|
|
||||||
class="!w-200px"
|
|
||||||
:value="formInfo.org_id"
|
|
||||||
:options="orgTree"
|
|
||||||
size="small"
|
|
||||||
default-expand-all
|
|
||||||
label-field="label"
|
|
||||||
key-field="id"
|
|
||||||
:render-switcher-icon="renderSwitcherIcon"
|
|
||||||
placeholder="请选择组织层级"
|
|
||||||
@update:value="orgChange"
|
|
||||||
/>
|
|
||||||
<div class="flex flex-wrap items-center gap-1">
|
|
||||||
<span class="text-[11px] text-slate-400">时间</span>
|
|
||||||
<NButton v-for="time in timeOptions" :key="time.value" size="tiny" round class="text-xs" :type="formInfo.time === time.value ? 'primary' : 'default'" @click="timeChange(time.value)">{{ time.label }}</NButton>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap items-center gap-1">
|
<div class="flex gap-3 mt-3">
|
||||||
<span class="text-[11px] text-slate-400">类型</span>
|
<!-- 左侧 -->
|
||||||
<NButton v-for="type in typeList" :key="type.value" size="tiny" round class="text-xs" :type="formInfo.type === type.value ? 'primary' : 'default'" @click="typeChange(type.value)">{{ type.label }}</NButton>
|
<div class="w-300px border rounded-md p-2">
|
||||||
</div>
|
<div class="scroll-left">
|
||||||
</div>
|
<NTree
|
||||||
<div class="flex gap-3 mt-3">
|
class="h-100% flex flex-col"
|
||||||
<!-- 左侧 -->
|
block-line
|
||||||
<div class="w-300px border rounded-md p-2">
|
:data="treeList"
|
||||||
<div class="scroll-left">
|
label-field="label"
|
||||||
<NTree
|
key-field="key"
|
||||||
class="h-100% flex flex-col"
|
:expanded-keys="expandedKeys"
|
||||||
block-line
|
:selected-keys="selectedKeys"
|
||||||
:data="treeList"
|
:render-switcher-icon="renderSwitcherIcon"
|
||||||
label-field="label"
|
selectable
|
||||||
key-field="key"
|
:cancelable="false"
|
||||||
:expanded-keys="expandedKeys"
|
:on-update:expanded-keys="handleExpandKeys"
|
||||||
:selected-keys="selectedKeys"
|
:on-update:selected-keys="handleSelectKeys"
|
||||||
:render-switcher-icon="renderSwitcherIcon"
|
>
|
||||||
selectable
|
<template #empty>
|
||||||
:cancelable="false"
|
<div class="h-100% flex items-center justify-center">
|
||||||
:on-update:expanded-keys="handleExpandKeys"
|
<NEmpty description="暂无数据" class="text-xs"></NEmpty>
|
||||||
:on-update:selected-keys="handleSelectKeys"
|
</div>
|
||||||
>
|
</template>
|
||||||
<template #empty>
|
</NTree>
|
||||||
<div class="h-100% flex items-center justify-center">
|
</div>
|
||||||
<NEmpty description="暂无数据" class="text-xs"></NEmpty>
|
</div>
|
||||||
|
<!-- 右:归档包详情 -->
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<NSpin :show="rightLoading" size="small">
|
||||||
|
<template v-if="rightDetails">
|
||||||
|
<div class="rounded-md border col-span-2">
|
||||||
|
<div class="border-b p-3">
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<span class="text-sm font-semibold">{{ rightDetails.change_no }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-xs text-slate-600">{{ rightDetails.title }}</div>
|
||||||
|
<div class="mt-2 flex flex-wrap justify-between gap-x-4 gap-y-1 text-[11px] text-slate-500">
|
||||||
|
<span>归档路径:{{ currentPath.join('/') }}</span>
|
||||||
|
<span>关闭时间:{{ rightDetails?.close_time?.slice(0,10) }}</span>
|
||||||
|
<span>共 {{ extractFiles(rightDetails.folders)?.total }} 个文件 · {{ extractFiles(rightDetails.folders)?.formattedSize }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="divide-y scroll-right">
|
||||||
|
<div v-for="f in rightDetails?.folders" :key="f.step_no">
|
||||||
|
<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 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>
|
||||||
|
<span v-else-if="f.step_no===3">3-培训与考核</span>
|
||||||
|
<span v-else-if="f.step_no===4">4-PSSR检查</span>
|
||||||
|
<span v-else-if="f.step_no===5">5-验收记录</span>
|
||||||
|
<span v-else>6-关闭资料</span>
|
||||||
|
<span class="ml-auto text-[11px] font-normal text-slate-400">{{ f.files.length }} 个文件</span>
|
||||||
|
</button>
|
||||||
|
<template v-if="openFolders[f.step_no]">
|
||||||
|
<div v-for="file in f.files" :key="file.name" class="flex items-center gap-2 py-1.5 pl-10 pr-3 text-xs hover:bg-slate-50">
|
||||||
|
<Icon icon="akar-icons:file" class="size-14px text-slate-400" />
|
||||||
|
<span class="min-w-0 flex-1 truncate">{{ file.file_name }}</span>
|
||||||
|
<span class="text-[11px] text-slate-400">{{ formatSize(file.file_size) }}</span>
|
||||||
|
<NButton text type="primary" title="预览" @click="preview(file)">
|
||||||
|
<Icon icon="lucide:eye" class="size-14px" />
|
||||||
|
</NButton>
|
||||||
|
<NButton text type="primary" title="下载" @click="downloadFile(file)">
|
||||||
|
<Icon icon="material-symbols:download" class="size-16px" />
|
||||||
|
</NButton>
|
||||||
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NTree>
|
<div v-else class="h-100% rounded-md border scroll-empty">
|
||||||
|
<div class="h-100% flex items-center justify-center">
|
||||||
|
<NEmpty description="暂无归档包详情数据" class="text-xs"></NEmpty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NSpin>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 右:归档包详情 -->
|
</NCard>
|
||||||
<div class="min-w-0 flex-1">
|
<!-- 附件在线预览弹窗 -->
|
||||||
<NSpin :show="rightLoading" size="small">
|
<NModal
|
||||||
<template v-if="rightDetails">
|
|
||||||
<div class="rounded-md border col-span-2">
|
|
||||||
<div class="border-b p-3">
|
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
|
||||||
<span class="text-sm font-semibold">{{ rightDetails.change_no }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mt-1 text-xs text-slate-600">{{ rightDetails.title }}</div>
|
|
||||||
<div class="mt-2 flex flex-wrap justify-between gap-x-4 gap-y-1 text-[11px] text-slate-500">
|
|
||||||
<span>归档路径:{{ currentPath.join('/') }}</span>
|
|
||||||
<span>关闭时间:{{ rightDetails?.close_time?.slice(0,10) }}</span>
|
|
||||||
<span>共 {{ extractFiles(rightDetails.folders)?.total }} 个文件 · {{ extractFiles(rightDetails.folders)?.formattedSize }}</span>
|
|
||||||
</div>
|
|
||||||
</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">
|
|
||||||
<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-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>
|
|
||||||
<span v-else-if="f.step_no===3">3-培训与考核</span>
|
|
||||||
<span v-else-if="f.step_no===4">4-PSSR检查</span>
|
|
||||||
<span v-else-if="f.step_no===5">5-验收记录</span>
|
|
||||||
<span v-else>6-关闭资料</span>
|
|
||||||
<span class="ml-auto text-[11px] font-normal text-slate-400">{{ f.files.length }} 个文件</span>
|
|
||||||
</button>
|
|
||||||
<template v-if="openFolders[f.step_no]">
|
|
||||||
<div v-for="file in f.files" :key="file.name" class="flex items-center gap-2 py-1.5 pl-10 pr-3 text-xs hover:bg-slate-50">
|
|
||||||
<Icon icon="akar-icons:file" class="size-14px text-slate-400" />
|
|
||||||
<span class="min-w-0 flex-1 truncate">{{ file.file_name }}</span>
|
|
||||||
<span class="text-[11px] text-slate-400">{{ formatSize(file.file_size) }}</span>
|
|
||||||
<NButton text type="primary" title="预览" @click="preview(file)">
|
|
||||||
<Icon icon="lucide:eye" class="size-14px" />
|
|
||||||
</NButton>
|
|
||||||
<NButton text type="primary" title="下载" @click="downloadFile(file)">
|
|
||||||
<Icon icon="material-symbols:download" class="size-16px" />
|
|
||||||
</NButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div v-else class="h-100% rounded-md border scroll-empty">
|
|
||||||
<div class="h-100% flex items-center justify-center">
|
|
||||||
<NEmpty description="暂无归档包详情数据" class="text-xs"></NEmpty>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</NSpin>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</NCard>
|
|
||||||
<!-- 附件在线预览弹窗 -->
|
|
||||||
<NModal
|
|
||||||
v-model:show="previewModal"
|
v-model:show="previewModal"
|
||||||
preset="card"
|
preset="card"
|
||||||
:auto-focus="false"
|
:auto-focus="false"
|
||||||
:style="{ width: '80%', height: 'auto' }"
|
:style="{ width: '80%', height: 'auto' }"
|
||||||
:segmented="{ content: true, footer: true }"
|
:segmented="{ content: true, footer: true }"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
||||||
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
||||||
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
||||||
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
|
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
|
||||||
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
||||||
</div>
|
|
||||||
<previewComponent v-if="previewFile" :previewFile="previewFile" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NModal>
|
</NModal>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.scroll-left {
|
.scroll-left {
|
||||||
height: calc(100vh - 303px);
|
height: calc(100vh - 303px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.scroll-right {
|
.scroll-right {
|
||||||
height: calc(100vh - 416px);
|
height: calc(100vh - 375px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.scroll-empty {
|
.scroll-empty {
|
||||||
|
|||||||
@@ -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)}">
|
<NGrid cols="s:1 m:4 l:4" responsive="screen" :x-gap="16" :y-gap="16" :style="{'--theme-color': themeStore.themeColor,'--theme-bg': getColorWithOpacity(themeStore.themeColor, 0.1)}">
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<div
|
||||||
class="bg-white px-16px py-12px text-[#94a3b8]"
|
class="bg-white px-16px py-12px text-[#94a3b8] shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600 text-center">按期关闭率</p>
|
<p class="text-12px font-600 text-center">按期关闭率</p>
|
||||||
@@ -63,7 +63,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600 text-center">PSSR 完成率</p>
|
<p class="text-12px font-600 text-center">PSSR 完成率</p>
|
||||||
@@ -81,7 +81,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<div
|
||||||
class="bg-white px-16px py-12px text-[#94a3b8] flex flex-col justify-center items-center"
|
class="bg-white px-16px py-12px text-[#94a3b8] flex flex-col justify-center items-center shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600 text-center">当月临时变更数量</p>
|
<p class="text-12px font-600 text-center">当月临时变更数量</p>
|
||||||
@@ -91,7 +91,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<div
|
||||||
class="bg-white px-16px py-12px text-[#94a3b8] flex flex-col justify-center items-center"
|
class="bg-white px-16px py-12px text-[#94a3b8] flex flex-col justify-center items-center shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600 text-center">平均关闭时长</p>
|
<p class="text-12px font-600 text-center">平均关闭时长</p>
|
||||||
@@ -103,7 +103,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:24 l:12">
|
<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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">月度变更数量</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<ColumnChart v-if="homeData" :data="homeData?.monthly_counts || []" />
|
<ColumnChart v-if="homeData" :data="homeData?.monthly_counts || []" />
|
||||||
@@ -111,7 +111,7 @@ onMounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:6">
|
<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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">变更类型分布</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
|
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
|
||||||
@@ -119,7 +119,7 @@ onMounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:6">
|
<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="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -167,7 +167,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:24 l:16">
|
<NGi span="24 s:24 m:24 l:16">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
|
<NCard :bordered="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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">平均审批周期趋势(天)</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<ColumnChart2 v-if="homeData" :data="homeData?.cycle_trend || []" />
|
<ColumnChart2 v-if="homeData" :data="homeData?.cycle_trend || []" />
|
||||||
@@ -175,7 +175,7 @@ onMounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}"
|
||||||
:style="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
|||||||
@@ -74,7 +74,11 @@ onUnmounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<div class="space-y-3.5">
|
<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>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:16">
|
<NGi span="24 s:24 m:16">
|
||||||
<div class="flex-y-center">
|
<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">
|
<NGrid cols="s:1 m:2 l:2" responsive="screen" :x-gap="16" :y-gap="16">
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">待关闭确认</p>
|
<p class="text-12px font-600">待关闭确认</p>
|
||||||
@@ -115,7 +119,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
@@ -136,7 +140,11 @@ onUnmounted(() => {
|
|||||||
<!-- 左侧 -->
|
<!-- 左侧 -->
|
||||||
<NGi span="24 s:24 m:24 l:16">
|
<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 justify-between mb-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Icon icon="ix:success" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="ix:success" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
@@ -155,11 +163,10 @@ onUnmounted(() => {
|
|||||||
<!-- 状态标签 -->
|
<!-- 状态标签 -->
|
||||||
<NTag size="small" type="error">待关闭</NTag>
|
<NTag size="small" type="error">待关闭</NTag>
|
||||||
<p class="flex-1 truncate text-sm font-medium text-slate-800 dark:text-white">{{ row.title }}</p>
|
<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" 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>
|
<NButton size="small" ghost class="text-xs" @click="jumpTo('details_index', row.change_id)">打开变更单</NButton>
|
||||||
</div>
|
</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.change_no }}</span>
|
||||||
<span>{{row.org_name}}</span>
|
<span>{{row.org_name}}</span>
|
||||||
<span>{{row.applicant_name}}</span>
|
<span>{{row.applicant_name}}</span>
|
||||||
@@ -175,7 +182,11 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</NCard>
|
</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 justify-between mb-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<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">
|
<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">
|
<div class="flex items-center flex-wrap gap-2">
|
||||||
<Icon icon="keyline-icons:sliders-horizontal" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="keyline-icons:sliders-horizontal" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">系统设置</p>
|
<p class="text-13px font-700 text-#64749a">系统设置</p>
|
||||||
@@ -246,7 +261,7 @@ onUnmounted(() => {
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">审批流程设置</span>
|
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">审批流程设置</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-[11px] text-slate-400">按等级路由 · 会签 · 最终批准</div>
|
<div class="mt-1 text-xs text-slate-400">按等级路由 · 会签 · 最终批准</div>
|
||||||
</div>
|
</div>
|
||||||
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -255,7 +270,7 @@ onUnmounted(() => {
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">变更类型管理</span>
|
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">变更类型管理</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-[11px] text-slate-400">工艺 / 设备 / 管理 · 支持自定义</div>
|
<div class="mt-1 text-xs text-slate-400">工艺 / 设备 / 管理 · 支持自定义</div>
|
||||||
</div>
|
</div>
|
||||||
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -264,7 +279,7 @@ onUnmounted(() => {
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">变更等级规则</span>
|
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">变更等级规则</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-[11px] text-slate-400">等级评分表 · 重要 / 一般判定</div>
|
<div class="mt-1 text-xs text-slate-400">等级评分表 · 重要 / 一般判定</div>
|
||||||
</div>
|
</div>
|
||||||
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -273,7 +288,7 @@ onUnmounted(() => {
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">账号设定</span>
|
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">账号设定</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-[11px] text-slate-400">用户账号 · 组织 · 启停用</div>
|
<div class="mt-1 text-xs text-slate-400">用户账号 · 组织 · 启停用</div>
|
||||||
</div>
|
</div>
|
||||||
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -282,7 +297,7 @@ onUnmounted(() => {
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">角色和权限分配</span>
|
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">角色和权限分配</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-[11px] text-slate-400">角色 · 菜单 · 数据权限</div>
|
<div class="mt-1 text-xs text-slate-400">角色 · 菜单 · 数据权限</div>
|
||||||
</div>
|
</div>
|
||||||
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -291,7 +306,7 @@ onUnmounted(() => {
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">通知提醒设置</span>
|
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">通知提醒设置</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-[11px] text-slate-400">超期 · 临期 · 关闭提醒</div>
|
<div class="mt-1 text-xs text-slate-400">超期 · 临期 · 关闭提醒</div>
|
||||||
</div>
|
</div>
|
||||||
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -300,7 +315,7 @@ onUnmounted(() => {
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">AI 开关与维护</span>
|
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">AI 开关与维护</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-[11px] text-slate-400">AI 辅助开关 · 模型与提示词</div>
|
<div class="mt-1 text-xs text-slate-400">AI 辅助开关 · 模型与提示词</div>
|
||||||
</div>
|
</div>
|
||||||
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -313,11 +328,11 @@ onUnmounted(() => {
|
|||||||
'--success-bg': getColorWithOpacity(themeStore.otherColor.success, 0.1),
|
'--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" />
|
<Icon icon="lucide:sparkles" class="size-12px" />
|
||||||
AI 辅助当前已启用 · 可在「AI 开关与维护」调整
|
AI 辅助当前已启用 · 可在「AI 开关与维护」调整
|
||||||
</div>
|
</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" />
|
<Icon icon="lucide:sparkles" class="size-12px" />
|
||||||
AI 辅助当前未启用 · 可在「AI 开关与维护」调整
|
AI 辅助当前未启用 · 可在「AI 开关与维护」调整
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -102,7 +102,11 @@ onUnmounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<div class="space-y-3.5">
|
<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>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:12">
|
<NGi span="24 s:24 m:12">
|
||||||
<div class="flex-y-center">
|
<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">
|
<NGrid cols="s:1 m:3 l:3" responsive="screen" :x-gap="16" :y-gap="16">
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
@@ -150,7 +154,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
@@ -170,7 +174,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
@@ -194,7 +198,11 @@ onUnmounted(() => {
|
|||||||
<!-- 左侧 -->
|
<!-- 左侧 -->
|
||||||
<NGi span="24 s:24 m:24 l:16">
|
<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 justify-between mb-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<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]">
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="mt-[16px]">
|
||||||
<!-- 全厂变更阶段分布 -->
|
<!-- 全厂变更阶段分布 -->
|
||||||
<NGi span="24 s:24 m:24 l:24">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">全厂变更阶段分布</p>
|
<p class="text-13px font-700 text-#64749a">全厂变更阶段分布</p>
|
||||||
@@ -263,7 +275,11 @@ onUnmounted(() => {
|
|||||||
<!-- 右侧 -->
|
<!-- 右侧 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">资料快捷查阅</p>
|
<p class="text-13px font-700 text-#64749a">资料快捷查阅</p>
|
||||||
@@ -300,7 +316,11 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</NCard>
|
</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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">全厂统计概览</p>
|
<p class="text-13px font-700 text-#64749a">全厂统计概览</p>
|
||||||
|
|||||||
@@ -125,7 +125,11 @@ onUnmounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<div class="space-y-3.5">
|
<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>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:16">
|
<NGi span="24 s:24 m:16">
|
||||||
<div class="flex-y-center">
|
<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">
|
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_mytask')"
|
@click="jumpTo('my_mytask')"
|
||||||
>
|
>
|
||||||
@@ -168,7 +172,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_mytask')"
|
@click="jumpTo('my_mytask')"
|
||||||
>
|
>
|
||||||
@@ -185,7 +189,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_mytask')"
|
@click="jumpTo('my_mytask')"
|
||||||
>
|
>
|
||||||
@@ -202,7 +206,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_mytask')"
|
@click="jumpTo('my_mytask')"
|
||||||
>
|
>
|
||||||
@@ -223,7 +227,11 @@ onUnmounted(() => {
|
|||||||
<!-- 左侧 -->
|
<!-- 左侧 -->
|
||||||
<NGi span="24 s:24 m:24 l:16">
|
<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 justify-between mb-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<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]">
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="mt-[16px]">
|
||||||
<!-- 临时变更到期提醒情况 -->
|
<!-- 临时变更到期提醒情况 -->
|
||||||
<NGi span="12 s:12 m:12 l:12">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">临时变更到期提醒情况</p>
|
<p class="text-13px font-700 text-#64749a">临时变更到期提醒情况</p>
|
||||||
@@ -342,7 +354,11 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<!-- 尚未关闭的变更所处状态 -->
|
<!-- 尚未关闭的变更所处状态 -->
|
||||||
<NGi span="12 s:12 m:12 l:12">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">尚未关闭的变更所处状态</p>
|
<p class="text-13px font-700 text-#64749a">尚未关闭的变更所处状态</p>
|
||||||
@@ -379,7 +395,11 @@ onUnmounted(() => {
|
|||||||
<!-- 右侧 -->
|
<!-- 右侧 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="system-uicons:reset" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="system-uicons:reset" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">被驳回信息处理</p>
|
<p class="text-13px font-700 text-#64749a">被驳回信息处理</p>
|
||||||
@@ -417,7 +437,11 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</NCard>
|
</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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="quill:folder-open" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="quill:folder-open" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">资料归档待办</p>
|
<p class="text-13px font-700 text-#64749a">资料归档待办</p>
|
||||||
|
|||||||
@@ -114,7 +114,11 @@ onUnmounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<div class="space-y-3.5">
|
<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>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:12">
|
<NGi span="24 s:24 m:12">
|
||||||
<div class="flex-y-center">
|
<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">
|
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_mytask')"
|
@click="jumpTo('my_mytask')"
|
||||||
>
|
>
|
||||||
@@ -164,7 +168,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('professionalstatistics')"
|
@click="jumpTo('professionalstatistics')"
|
||||||
>
|
>
|
||||||
@@ -184,7 +188,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_highrisk')"
|
@click="jumpTo('my_highrisk')"
|
||||||
>
|
>
|
||||||
@@ -204,7 +208,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('professionalstatistics')"
|
@click="jumpTo('professionalstatistics')"
|
||||||
>
|
>
|
||||||
@@ -228,7 +232,11 @@ onUnmounted(() => {
|
|||||||
<!-- 左侧 -->
|
<!-- 左侧 -->
|
||||||
<NGi span="24 s:24 m:24 l:16">
|
<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 justify-between mb-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Icon icon="lucide:clipboard-check" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<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]">
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="mt-[16px]">
|
||||||
<!-- 最近已会签 -->
|
<!-- 最近已会签 -->
|
||||||
<NGi span="24 s:24 m:24 l:24">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="ix:success" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="ix:success" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">最近已会签</p>
|
<p class="text-13px font-700 text-#64749a">最近已会签</p>
|
||||||
@@ -336,7 +348,11 @@ onUnmounted(() => {
|
|||||||
<!-- 右侧 -->
|
<!-- 右侧 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">高风险变更</p>
|
<p class="text-13px font-700 text-#64749a">高风险变更</p>
|
||||||
@@ -360,7 +376,11 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</NCard>
|
</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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">我的会签效率</p>
|
<p class="text-13px font-700 text-#64749a">我的会签效率</p>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useRouterPush } from '@/hooks/common/router';
|
|||||||
import { getColorWithOpacity, tagTextColor, tagBgColor, tagBorderColor, downloadFileFun } from '@/utils/common';
|
import { getColorWithOpacity, tagTextColor, tagBgColor, tagBorderColor, downloadFileFun } from '@/utils/common';
|
||||||
import { systemConfigApi } from "@/service/api/system";
|
import { systemConfigApi } from "@/service/api/system";
|
||||||
import { homeSafeApi } from "@/service/api/statistics";
|
import { homeSafeApi } from "@/service/api/statistics";
|
||||||
import previewComponent from '@/components/previewFile/index.vue'
|
import PreviewComponent from '@/components/previewFile/index.vue'
|
||||||
|
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const { routerPushByKey } = useRouterPush();
|
const { routerPushByKey } = useRouterPush();
|
||||||
@@ -64,7 +64,7 @@ const viewFile = (file: any) => {
|
|||||||
};
|
};
|
||||||
// 下载
|
// 下载
|
||||||
const downloadFile = (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) => {
|
const downloadTemplate = (record: any) => {
|
||||||
@@ -82,7 +82,7 @@ const getData = async () => {
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
const {data,error} = await homeSafeApi();
|
const {data,error} = await homeSafeApi();
|
||||||
if(!error){
|
if(!error){
|
||||||
homeData.value = data;
|
homeData.value = data;
|
||||||
}
|
}
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
@@ -129,311 +129,332 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<div class="space-y-3.5">
|
<div class="space-y-3.5">
|
||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NCard
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
:bordered="false"
|
||||||
<NGi span="24 s:24 m:10">
|
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">
|
<div class="flex-y-center">
|
||||||
<p
|
<p
|
||||||
class="py-1.5 px-1.5 flex items-center justify-center bg-[rgb(var(--primary-color))] text-white text-xs font-bold"
|
class="py-1.5 px-1.5 flex items-center justify-center bg-[rgb(var(--primary-color))] text-white text-xs font-bold"
|
||||||
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
MOC
|
MOC
|
||||||
</p>
|
</p>
|
||||||
<p class="pl-12px text-14px font-semibold">我的工作台</p>
|
<p class="pl-12px text-14px font-semibold">我的工作台</p>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:14" class="flex items-center justify-end gap-12px">
|
<NGi span="24 s:24 m:14" class="flex items-center justify-end gap-12px">
|
||||||
<p class="text-#999 text-xs">{{clock}}</p>
|
<p class="text-#999 text-xs">{{ clock }}</p>
|
||||||
<NButton ghost size="small" @click="jumpTo('my_pssr')">
|
<NButton ghost size="small" @click="jumpTo('my_pssr')">
|
||||||
投用与验收总览
|
投用与验收总览
|
||||||
</NButton>
|
</NButton>
|
||||||
<NButton ghost size="small" @click="jumpTo('my_highrisk')">
|
<NButton ghost size="small" @click="jumpTo('my_highrisk')">
|
||||||
全厂风险看板
|
全厂风险看板
|
||||||
</NButton>
|
</NButton>
|
||||||
<NButton type="primary" size="small" @click="jumpTo('my_mytask')">
|
<NButton type="primary" size="small" @click="jumpTo('my_mytask')">
|
||||||
<Icon icon="lucide:clipboard-check" class="size-14px" />
|
<Icon icon="lucide:clipboard-check" class="size-14px" />
|
||||||
审批待办
|
审批待办
|
||||||
</NButton>
|
</NButton>
|
||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
</NCard>
|
</NCard>
|
||||||
<!-- 卡片数据 -->
|
<!-- 卡片数据 -->
|
||||||
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
|
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_mytask')"
|
@click="jumpTo('my_mytask')"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="homeData?.todo_sign?.total || 0"
|
:end-value="homeData?.todo_sign?.total || 0"
|
||||||
class="text-26px font-600"
|
class="text-26px font-600"
|
||||||
:style="{ color: homeData?.todo_sign?.overdue_count > 0?themeStore.otherColor.error:themeStore.themeColor }"
|
:style="{ color: homeData?.todo_sign?.overdue_count > 0?themeStore.otherColor.error:themeStore.themeColor }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-12px font-600">待我审批</p>
|
<p class="text-12px font-600">待我审批</p>
|
||||||
<p v-if="homeData?.todo_sign?.overdue_count > 0" class="text-12px" :style="{ color: themeStore.otherColor.error }">其中超期 {{homeData?.todo_sign?.overdue_count}} 项 · 请优先处理</p>
|
<p v-if="homeData?.todo_sign?.overdue_count > 0" class="text-12px" :style="{ color: themeStore.otherColor.error }">其中超期 {{ homeData?.todo_sign?.overdue_count }} 项 · 请优先处理</p>
|
||||||
<p v-else class="text-12px text-slate-400">安全审查节点 · 与我的任务同源</p>
|
<p v-else class="text-12px text-slate-400">安全审查节点 · 与我的任务同源</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_pssr')"
|
@click="jumpTo('my_pssr')"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="homeData?.pssr_supervise?.total || 0"
|
:end-value="homeData?.pssr_supervise?.total || 0"
|
||||||
class="text-26px font-600"
|
class="text-26px font-600"
|
||||||
:style="{ color: themeStore.themeColor }"
|
:style="{ color: themeStore.themeColor }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-12px font-600">PSSR监督</p>
|
<p class="text-12px font-600">PSSR监督</p>
|
||||||
<p class="text-12px text-slate-400">待PSSR / 待验收 · 按变更状态实时统计</p>
|
<p class="text-12px text-slate-400">待PSSR / 待验收 · 按变更状态实时统计</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_highrisk')"
|
@click="jumpTo('my_highrisk')"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="homeData?.high_risk?.total || 0"
|
:end-value="homeData?.high_risk?.total || 0"
|
||||||
class="text-26px font-600"
|
class="text-26px font-600"
|
||||||
:style="{ color: themeStore.otherColor.error }"
|
:style="{ color: themeStore.otherColor.error }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-12px font-600">高风险在办</p>
|
<p class="text-12px font-600">高风险在办</p>
|
||||||
<p class="text-12px" :style="{ color: themeStore.otherColor.error }">全厂 · 需重点跟踪</p>
|
<p class="text-12px" :style="{ color: themeStore.otherColor.error }">全厂 · 需重点跟踪</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="homeData?.temp_expiry_scope?.total || 0"
|
:end-value="homeData?.temp_expiry_scope?.total || 0"
|
||||||
class="text-26px font-600"
|
class="text-26px font-600"
|
||||||
:style="{ color: homeData?.temp_expiry_scope?.overdue_count > 0?themeStore.otherColor.error:themeStore.themeColor }"
|
:style="{ color: homeData?.temp_expiry_scope?.overdue_count > 0?themeStore.otherColor.error:themeStore.themeColor }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-12px font-600">临时变更到期</p>
|
<p class="text-12px font-600">临时变更到期</p>
|
||||||
<p v-if="homeData?.temp_expiry_scope?.overdue_count > 0" class="text-12px" :style="{ color: themeStore.otherColor.error }">含超期 {{homeData?.temp_expiry_scope?.overdue_count}} 项 · 红色预警</p>
|
<p v-if="homeData?.temp_expiry_scope?.overdue_count > 0" class="text-12px" :style="{ color: themeStore.otherColor.error }">含超期 {{ homeData?.temp_expiry_scope?.overdue_count }} 项 · 红色预警</p>
|
||||||
<p v-else class="text-12px text-slate-400">无超期</p>
|
<p v-else class="text-12px text-slate-400">无超期</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
|
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
|
||||||
<!-- 左侧 -->
|
|
||||||
<NGi span="24 s:24 m:24 l:16">
|
|
||||||
<!-- 审批待办 -->
|
|
||||||
<NCard :bordered="false" size="small" :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 }" />
|
|
||||||
<p class="text-13px font-700 text-#64749a">审批待办</p>
|
|
||||||
<p class="text-11px text-#94a3b8 mt-2px">安全审查 · 审批时自动展示相关附件</p>
|
|
||||||
</div>
|
|
||||||
<NButton v-if="homeData?.todo_sign?.total > 0" ghost size="small" class="text-xs" @click="jumpTo('my_mytask')">
|
|
||||||
我的任务
|
|
||||||
<Icon icon="formkit:right" class="size-12px" />
|
|
||||||
</NButton>
|
|
||||||
</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"
|
|
||||||
:style="{
|
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
|
||||||
'--hover-border': themeStore.themeColor,
|
|
||||||
'--hover-bg': getColorWithOpacity(themeStore.themeColor, 0.05)
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<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
|
|
||||||
size="small"
|
|
||||||
:color="{
|
|
||||||
color: tagBgColor(matchObj(levelList,row.change_level)?.color),
|
|
||||||
textColor: tagTextColor(matchObj(levelList,row.change_level)?.color),
|
|
||||||
borderColor: tagBorderColor(matchObj(levelList,row.change_level)?.color),
|
|
||||||
}"
|
|
||||||
>{{matchObj(levelList,row.change_level)?.label}}</NTag>
|
|
||||||
<!-- 变更类型 -->
|
|
||||||
<NTag
|
|
||||||
size="small"
|
|
||||||
:color="{
|
|
||||||
color: tagBgColor(matchObj(typeList,row.change_type)?.color),
|
|
||||||
textColor: tagTextColor(matchObj(typeList,row.change_type)?.color),
|
|
||||||
borderColor: tagBorderColor(matchObj(typeList,row.change_type)?.color),
|
|
||||||
}"
|
|
||||||
>{{matchObj(typeList,row.change_type)?.label}}</NTag>
|
|
||||||
|
|
||||||
<NTag class="text-[11px]" v-if="row.duration_type===2" type="warning" size="small">临时</NTag>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NTag class="text-[11px]" v-else 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>
|
<NGi span="24 s:24 m:24 l:16">
|
||||||
</div>
|
<!-- 审批待办 -->
|
||||||
<div class="mt-1 flex items-center gap-2">
|
<NCard
|
||||||
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800 dark:text-white">{{ row.title }}</span>
|
:bordered="false"
|
||||||
<NTag v-if="row.step_name" size="small" type="warning">审批:{{row.step_name}}</NTag>
|
size="small"
|
||||||
<NButton size="small" type="primary" class="text-xs">
|
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 }" />
|
||||||
|
<p class="text-13px font-700 text-#64749a">审批待办</p>
|
||||||
|
<p class="text-11px text-#94a3b8 mt-2px">安全审查 · 审批时自动展示相关附件</p>
|
||||||
|
</div>
|
||||||
|
<NButton v-if="homeData?.todo_sign?.total > 0" ghost size="small" class="text-xs" @click="jumpTo('my_mytask')">
|
||||||
|
我的任务
|
||||||
|
<Icon icon="formkit:right" class="size-12px" />
|
||||||
|
</NButton>
|
||||||
|
</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" 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 v-if="row.urgent===1" class="text-[11px]" type="error" size="small">紧急</NTag>
|
||||||
|
<!-- 变更等级 -->
|
||||||
|
<NTag
|
||||||
|
size="small"
|
||||||
|
:color="{
|
||||||
|
color: tagBgColor(matchObj(levelList,row.change_level)?.color),
|
||||||
|
textColor: tagTextColor(matchObj(levelList,row.change_level)?.color),
|
||||||
|
borderColor: tagBorderColor(matchObj(levelList,row.change_level)?.color),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ matchObj(levelList,row.change_level)?.label }}
|
||||||
|
</NTag>
|
||||||
|
<!-- 变更类型 -->
|
||||||
|
<NTag
|
||||||
|
size="small"
|
||||||
|
:color="{
|
||||||
|
color: tagBgColor(matchObj(typeList,row.change_type)?.color),
|
||||||
|
textColor: tagTextColor(matchObj(typeList,row.change_type)?.color),
|
||||||
|
borderColor: tagBorderColor(matchObj(typeList,row.change_type)?.color),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ matchObj(typeList,row.change_type)?.label }}
|
||||||
|
</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">
|
||||||
|
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800 dark:text-white">{{ row.title }}</span>
|
||||||
|
<NTag v-if="row.step_name" size="small" type="warning">审批:{{ row.step_name }}</NTag>
|
||||||
|
<NButton size="small" type="primary" class="text-xs">
|
||||||
去审批
|
去审批
|
||||||
<Icon icon="akar-icons:arrow-right" class="size-13px ml-1" />
|
<Icon icon="akar-icons:arrow-right" class="size-13px ml-1" />
|
||||||
</NButton>
|
</NButton>
|
||||||
|
</div>
|
||||||
|
<div class="mt-0.5 truncate text-[11px] text-slate-400 flex items-center gap-2">
|
||||||
|
<p>申请人:{{ row.applicant_name }}</p>
|
||||||
|
<span v-if="row.apply_time" class="text-[11px] text-slate-400">{{ row.apply_time?.slice(0, 10) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-0.5 truncate text-[11px] text-slate-400 flex items-center gap-2">
|
<NButton dashed type="primary" size="small" class="text-xs" @click="jumpTo('my_mytask')">
|
||||||
<p>申请人:{{row.applicant_name}}</p>
|
还有{{ (homeData?.my_changes_progress?.total - 3) > 0 ? (homeData?.my_changes_progress?.total - 3) : 0 }}条,前往我的任务查看全部 →
|
||||||
<span v-if="row.apply_time" class="text-[11px] text-slate-400">{{ row.apply_time?.slice(0, 10) }}</span>
|
</NButton>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
<template v-else>
|
||||||
</div>
|
|
||||||
<NButton dashed type="primary" size="small" class="text-xs" @click="jumpTo('my_mytask')">
|
|
||||||
还有{{(homeData?.my_changes_progress?.total - 3) > 0 ? (homeData?.my_changes_progress?.total - 3) : 0}}条,前往我的任务查看全部 →
|
|
||||||
</NButton>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div class="h-100% flex items-center justify-center py-3">
|
<div class="h-100% flex items-center justify-center py-3">
|
||||||
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
|
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="mt-[16px]">
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="mt-[16px]">
|
||||||
<!-- 通过审批的变更(未关闭) -->
|
<!-- 通过审批的变更(未关闭) -->
|
||||||
<NGi span="24 s:24 m:24 l:24">
|
<NGi span="24 s:24 m:24 l:24">
|
||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NCard
|
||||||
<div class="flex items-center mb-3 gap-2">
|
:bordered="false"
|
||||||
<Icon icon="lucide:list-checks" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
size="small"
|
||||||
<p class="text-13px font-700 text-#64749a">通过审批的变更(未关闭)</p>
|
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
<p class="text-11px text-#94a3b8 mt-2px">PSSR 材料同行显示 · 查看 / 下载</p>
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<NButton ghost size="small" class="text-xs ml-auto" @click="jumpTo('my_pssr')">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
进入管理
|
<Icon icon="lucide:list-checks" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<Icon icon="formkit:right" class="size-12px" />
|
<p class="text-13px font-700 text-#64749a">通过审批的变更(未关闭)</p>
|
||||||
</NButton>
|
<p class="text-11px text-#94a3b8 mt-2px">PSSR 材料同行显示 · 查看 / 下载</p>
|
||||||
|
<NButton ghost size="small" class="text-xs ml-auto" @click="jumpTo('my_pssr')">
|
||||||
|
进入管理
|
||||||
|
<Icon icon="formkit:right" class="size-12px" />
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
<div v-if="homeData?.pssr_supervise?.total>0" class="flex flex-col gap-3">
|
||||||
|
<div v-for="item in homeData?.pssr_supervise?.items" :key="item.id" class="rounded-md border px-3 py-2 flex items-center justify-between gap-2">
|
||||||
|
<div>
|
||||||
|
<p class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800 dark:text-white">{{ item.title }}</p>
|
||||||
|
<p class="mt-0.5 text-[11px] text-slate-400 flex items-center gap-2">
|
||||||
|
<span>{{ item.change_no }}</span>
|
||||||
|
<span>{{ item.org_name }}</span>
|
||||||
|
<span>{{ item.applicant_name }}</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="homeData?.pssr_supervise?.total>0" class="flex flex-col gap-3">
|
<div class="flex items-center gap-1.5 rounded-md border bg-slate-50/60 px-2 py-1">
|
||||||
<div v-for="item in homeData?.pssr_supervise?.items" :key="item.id" class="rounded-md border px-3 py-2 flex items-center justify-between gap-2">
|
<Icon icon="akar-icons:file" class="size-14px" :style="{ color: themeStore.themeColor }" />
|
||||||
<div>
|
<span class="text-[11px] font-medium text-slate-600">PSSR 确认表</span>
|
||||||
<p class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800 dark:text-white">{{item.title}}</p>
|
<template v-if="item.pssr_attachment">
|
||||||
<p class="mt-0.5 text-[11px] text-slate-400 flex items-center gap-2">
|
<span class="max-w-[150px] truncate text-[11px] text-slate-400">{{ item.pssr_attachment?.file_name }}</span>
|
||||||
<span>{{item.change_no}}</span>
|
<NButton ghost size="tiny" class="text-11px" @click="viewFile(item.pssr_attachment)">查看</NButton>
|
||||||
<span>{{item.org_name}}</span>
|
<NButton ghost size="tiny" class="text-11px" @click="downloadFile(item.pssr_attachment)">下载</NButton>
|
||||||
<span>{{item.applicant_name}}</span>
|
</template>
|
||||||
</p>
|
<template v-else>
|
||||||
</div>
|
<span class="text-[11px]" :style="{ color: themeStore.otherColor.warning }">待传</span>
|
||||||
<div class="flex items-center gap-1.5 rounded-md border bg-slate-50/60 px-2 py-1">
|
<NButton ghost size="tiny" class="text-11px" @click="downloadTemplate(item)">下载模板</NButton>
|
||||||
<Icon icon="akar-icons:file" class="size-14px" :style="{ color: themeStore.themeColor }" />
|
</template>
|
||||||
<span class="text-[11px] font-medium text-slate-600">PSSR 确认表</span>
|
|
||||||
<template v-if="item.pssr_attachment">
|
|
||||||
<span class="max-w-[150px] truncate text-[11px] text-slate-400">{{ item.pssr_attachment?.file_name }}</span>
|
|
||||||
<NButton ghost size="tiny" class="text-11px" @click="viewFile(item.pssr_attachment)">查看</NButton>
|
|
||||||
<NButton ghost size="tiny" class="text-11px" @click="downloadFile(item.pssr_attachment)">下载</NButton>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span class="text-[11px]" :style="{ color: themeStore.otherColor.warning }">待传</span>
|
|
||||||
<NButton ghost size="tiny" class="text-11px" @click="downloadTemplate(item)">下载模板</NButton>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="h-100% flex items-center justify-center py-3">
|
</div>
|
||||||
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
</div>
|
||||||
</div>
|
<div v-else class="h-100% flex items-center justify-center py-3">
|
||||||
</NCard>
|
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
||||||
|
</div>
|
||||||
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
</NGi>
|
</NGi>
|
||||||
<!-- 右侧 -->
|
<!-- 右侧 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<!-- 高风险变更跟踪 -->
|
<!-- 高风险变更跟踪 -->
|
||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NCard
|
||||||
<div class="flex items-center mb-3 gap-2">
|
:bordered="false"
|
||||||
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
size="small"
|
||||||
<p class="text-13px font-700 text-#64749a">高风险变更跟踪</p>
|
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
<p class="text-11px text-#94a3b8">重要等级 · 未关闭 · 实时推导</p>
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
</div>
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<template v-if="homeData?.high_risk?.total > 0">
|
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<div class="space-y-2.5">
|
<p class="text-13px font-700 text-#64749a">高风险变更跟踪</p>
|
||||||
|
<p class="text-11px text-#94a3b8">重要等级 · 未关闭 · 实时推导</p>
|
||||||
|
</div>
|
||||||
|
<template v-if="homeData?.high_risk?.total > 0">
|
||||||
|
<div class="space-y-2.5">
|
||||||
<div v-for="c in homeData?.high_risk?.items || []" :key="c.record_id" class="rounded-lg border border-red-200 bg-red-50/50 px-3.5 py-2.5">
|
<div v-for="c in homeData?.high_risk?.items || []" :key="c.record_id" class="rounded-lg border border-red-200 bg-red-50/50 px-3.5 py-2.5">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">{{ c.change_title }}</span>
|
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800">{{ c.change_title }}</span>
|
||||||
<NButton size="tiny" ghost type="error" class="text-xs" @click="jumpTo('my_highrisk')">查看分析</NButton>
|
<NButton size="tiny" ghost type="error" class="text-xs" @click="jumpTo('my_highrisk')">查看分析</NButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-[11px] flex items-center gap-2" :style="{ color: themeStore.otherColor.error }">
|
<div class="mt-1 text-[11px] flex items-center gap-2" :style="{ color: themeStore.otherColor.error }">
|
||||||
<span>{{ c.change_no }}</span>
|
<span>{{ c.change_no }}</span>
|
||||||
<!-- 状态 -->
|
<!-- 状态 -->
|
||||||
<span v-if="c.change_status==='APPROVING'">审批中</span>
|
<span v-if="c.change_status==='APPROVING'">审批中</span>
|
||||||
<span v-if="c.change_status==='APPROVED'">已通过</span>
|
<span v-if="c.change_status==='APPROVED'">已通过</span>
|
||||||
<span v-if="c.change_status==='IMPLEMENTED'">待验收</span>
|
<span v-if="c.change_status==='IMPLEMENTED'">待验收</span>
|
||||||
<span v-if="c.change_status==='ACCEPTED'">待关闭</span>
|
<span v-if="c.change_status==='ACCEPTED'">待关闭</span>
|
||||||
<span v-if="c.change_status==='CLOSED'">已关闭</span>
|
<span v-if="c.change_status==='CLOSED'">已关闭</span>
|
||||||
<span v-if="c.change_status==='WITHDRAWN'">已撤回</span>
|
<span v-if="c.change_status==='WITHDRAWN'">已撤回</span>
|
||||||
<span v-if="c.change_status==='REJECTED'">已驳回</span>
|
<span v-if="c.change_status==='REJECTED'">已驳回</span>
|
||||||
<span>{{c.org_name}}</span>
|
<span>{{ c.org_name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="h-100% flex items-center justify-center py-3">
|
<div class="h-100% flex items-center justify-center py-3">
|
||||||
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
</div>
|
</div>
|
||||||
<!-- 附件在线预览弹窗 -->
|
<!-- 附件在线预览弹窗 -->
|
||||||
<NModal
|
<NModal
|
||||||
v-model:show="previewModal"
|
v-model:show="previewModal"
|
||||||
preset="card"
|
preset="card"
|
||||||
:auto-focus="false"
|
:auto-focus="false"
|
||||||
:style="{ width: '80%', height: 'auto' }"
|
:style="{ width: '80%', height: 'auto' }"
|
||||||
:segmented="{ content: true, footer: true }"
|
:segmented="{ content: true, footer: true }"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
||||||
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
||||||
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
||||||
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
|
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
|
||||||
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
||||||
</div>
|
</div>
|
||||||
<previewComponent v-if="previewFile" :previewFile="previewFile" />
|
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</NModal>
|
</NModal>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@@ -170,466 +170,508 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<div class="space-y-3.5">
|
<div class="space-y-3.5">
|
||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NCard
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
:bordered="false"
|
||||||
<NGi span="24 s:24 m:8">
|
size="small"
|
||||||
<div class="flex-y-center">
|
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
<p
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
class="py-1.5 px-1.5 flex items-center justify-center bg-[rgb(var(--primary-color))] text-white text-xs font-bold"
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
|
<NGi span="24 s:24 m:8">
|
||||||
|
<div class="flex-y-center">
|
||||||
|
<p
|
||||||
|
class="py-1.5 px-1.5 flex items-center justify-center bg-[rgb(var(--primary-color))] text-white text-xs font-bold"
|
||||||
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
|
>
|
||||||
|
MOC
|
||||||
|
</p>
|
||||||
|
<p class="pl-12px text-14px font-semibold">我的工作台</p>
|
||||||
|
</div>
|
||||||
|
</NGi>
|
||||||
|
<NGi span="24 s:24 m:16" class="flex items-center justify-end gap-12px">
|
||||||
|
<p class="text-#999 text-xs">{{ clock }}</p>
|
||||||
|
<NButton ghost size="small" @click="jumpTo('workshopstatistics')">
|
||||||
|
车间变更看板
|
||||||
|
</NButton>
|
||||||
|
<NButton type="primary" size="small" @click="jumpTo('my_mytask')">
|
||||||
|
<Icon icon="lucide:clipboard-check" class="size-14px" />
|
||||||
|
审批待办
|
||||||
|
</NButton>
|
||||||
|
</NGi>
|
||||||
|
</NGrid>
|
||||||
|
</NCard>
|
||||||
|
<!-- 卡片数据 -->
|
||||||
|
<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 transition hover:-translate-y-1 shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
|
||||||
MOC
|
|
||||||
</p>
|
|
||||||
<p class="pl-12px text-14px font-semibold">我的工作台</p>
|
|
||||||
</div>
|
|
||||||
</NGi>
|
|
||||||
<NGi span="24 s:24 m:16" class="flex items-center justify-end gap-12px">
|
|
||||||
<p class="text-#999 text-xs">{{clock}}</p>
|
|
||||||
<NButton ghost size="small" @click="jumpTo('workshopstatistics')">
|
|
||||||
车间变更看板
|
|
||||||
</NButton>
|
|
||||||
<NButton type="primary" size="small" @click="jumpTo('my_mytask')">
|
|
||||||
<Icon icon="lucide:clipboard-check" class="size-14px" />
|
|
||||||
审批待办
|
|
||||||
</NButton>
|
|
||||||
</NGi>
|
|
||||||
</NGrid>
|
|
||||||
</NCard>
|
|
||||||
<!-- 卡片数据 -->
|
|
||||||
<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"
|
|
||||||
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
|
||||||
@click="jumpTo('my_mytask')"
|
@click="jumpTo('my_mytask')"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">待我审批</p>
|
<p class="text-12px font-600">待我审批</p>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="homeData?.todo_approvals?.total || 0"
|
:end-value="homeData?.todo_approvals?.total || 0"
|
||||||
class="text-26px font-600 text-[#333]"
|
class="text-26px font-600 text-[#333]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="homeData?.todo_approvals?.overdue_count" class="text-12px text-[#e15555]">含超期未审 · 请优先处理</p>
|
<p v-if="homeData?.todo_approvals?.overdue_count" class="text-12px text-[#e15555]">含超期未审 · 请优先处理</p>
|
||||||
<p v-else class="text-12px">属地变更审批</p>
|
<p v-else class="text-12px">属地变更审批</p>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">重要变更未关闭</p>
|
<p class="text-12px font-600">重要变更未关闭</p>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="homeData?.important_open?.total || 0"
|
:end-value="homeData?.important_open?.total || 0"
|
||||||
class="text-26px font-600"
|
class="text-26px font-600"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-12px">本车间 · 需重点跟踪</p>
|
<p class="text-12px">本车间 · 需重点跟踪</p>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_mytask')"
|
@click="jumpTo('my_mytask')"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">未通过审批</p>
|
<p class="text-12px font-600">未通过审批</p>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="homeData?.rejected_open?.total || 0"
|
:end-value="homeData?.rejected_open?.total || 0"
|
||||||
class="text-26px font-600 text-[#333]"
|
class="text-26px font-600 text-[#333]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-12px">本车间 · 驳回待修改重报</p>
|
<p class="text-12px">本车间 · 驳回待修改重报</p>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">超期未关闭</p>
|
<p class="text-12px font-600">超期未关闭</p>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<CountTo
|
<CountTo
|
||||||
:start-value="0"
|
:start-value="0"
|
||||||
:end-value="homeData?.overdue_open?.total || 0"
|
:end-value="homeData?.overdue_open?.total || 0"
|
||||||
class="text-26px font-600"
|
class="text-26px font-600"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-12px">本车间 · 红色预警</p>
|
<p class="text-12px">本车间 · 红色预警</p>
|
||||||
</div>
|
</div>
|
||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
|
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<!-- 左侧 -->
|
<!-- 左侧 -->
|
||||||
<NGi span="24 s:24 m:24 l:16">
|
<NGi span="24 s:24 m:24 l:16">
|
||||||
<!-- 审批待办 -->
|
<!-- 审批待办 -->
|
||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NCard
|
||||||
<div class="flex items-center justify-between mb-3">
|
:bordered="false"
|
||||||
<div class="flex items-center gap-2">
|
size="small"
|
||||||
<Icon icon="lucide:clipboard-check" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
<p class="text-13px font-700 text-#64749a">审批待办</p>
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<p class="text-11px text-#94a3b8 mt-2px">属地变更审批</p>
|
<div class="flex items-center justify-between mb-3">
|
||||||
</div>
|
<div class="flex items-center gap-2">
|
||||||
<NButton v-if="homeData?.todo_approvals?.total > 0" ghost size="small" class="text-xs" @click="jumpTo('my_mytask')">
|
<Icon icon="lucide:clipboard-check" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
我的任务
|
<p class="text-13px font-700 text-#64749a">审批待办</p>
|
||||||
<Icon icon="formkit:right" class="size-12px" />
|
<p class="text-11px text-#94a3b8 mt-2px">属地变更审批</p>
|
||||||
</NButton>
|
</div>
|
||||||
</div>
|
<NButton v-if="homeData?.todo_approvals?.total > 0" ghost size="small" class="text-xs" @click="jumpTo('my_mytask')">
|
||||||
<div class="flex flex-col gap-3">
|
我的任务
|
||||||
<template v-if="homeData?.todo_approvals?.total > 0">
|
<Icon icon="formkit:right" class="size-12px" />
|
||||||
<div v-for="row in homeData?.todo_approvals?.items || []" :key="row.change_id" @click="jumpTo('details_approval',row.change_id)"
|
</NButton>
|
||||||
class="cursor-pointer flex w-full items-center gap-3 border px-3.5 py-2.5 text-left transition"
|
</div>
|
||||||
:style="{
|
<div class="flex flex-col gap-3">
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
<template v-if="homeData?.todo_approvals?.total > 0">
|
||||||
borderColor: row.overdue_status===2?getColorWithOpacity(themeStore.otherColor.error, 0.25):getColorWithOpacity(themeStore.themeColor, 0.25),
|
<div
|
||||||
backgroundColor: row.overdue_status===2?getColorWithOpacity(themeStore.otherColor.error, 0.05):getColorWithOpacity(themeStore.themeColor, 0.05),
|
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 v-if="row.urgent===1" class="text-[11px]" type="error" size="small">紧急</NTag>
|
||||||
|
<!-- 变更等级 -->
|
||||||
|
<NTag
|
||||||
|
size="small"
|
||||||
|
:color="{
|
||||||
|
color: tagBgColor(matchObj(levelList,row.change_level)?.color),
|
||||||
|
textColor: tagTextColor(matchObj(levelList,row.change_level)?.color),
|
||||||
|
borderColor: tagBorderColor(matchObj(levelList,row.change_level)?.color),
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="min-w-0 flex-1">
|
{{ matchObj(levelList,row.change_level)?.label }}
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
</NTag>
|
||||||
<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
|
||||||
<!-- 变更等级 -->
|
size="small"
|
||||||
<NTag
|
:color="{
|
||||||
size="small"
|
color: tagBgColor(matchObj(typeList,row.change_type)?.color),
|
||||||
:color="{
|
textColor: tagTextColor(matchObj(typeList,row.change_type)?.color),
|
||||||
color: tagBgColor(matchObj(levelList,row.change_level)?.color),
|
borderColor: tagBorderColor(matchObj(typeList,row.change_type)?.color),
|
||||||
textColor: tagTextColor(matchObj(levelList,row.change_level)?.color),
|
}"
|
||||||
borderColor: tagBorderColor(matchObj(levelList,row.change_level)?.color),
|
>
|
||||||
}"
|
{{ matchObj(typeList,row.change_type)?.label }}
|
||||||
>{{matchObj(levelList,row.change_level)?.label}}</NTag>
|
</NTag>
|
||||||
<!-- 变更类型 -->
|
|
||||||
<NTag
|
|
||||||
size="small"
|
|
||||||
:color="{
|
|
||||||
color: tagBgColor(matchObj(typeList,row.change_type)?.color),
|
|
||||||
textColor: tagTextColor(matchObj(typeList,row.change_type)?.color),
|
|
||||||
borderColor: tagBorderColor(matchObj(typeList,row.change_type)?.color),
|
|
||||||
}"
|
|
||||||
>{{matchObj(typeList,row.change_type)?.label}}</NTag>
|
|
||||||
|
|
||||||
<NTag class="text-[11px]" v-if="row.duration_type===2" type="warning" size="small">临时</NTag>
|
<NTag v-if="row.duration_type===2" class="text-[11px]" type="warning" size="small">临时</NTag>
|
||||||
<NTag class="text-[11px]" v-else type="info" 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>
|
<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">
|
|
||||||
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800 dark:text-white">{{ row.title }}</span>
|
|
||||||
<NTag size="small" type="warning">审批:{{row.step_name}}</NTag>
|
|
||||||
<NButton size="small" type="primary" class="text-xs">
|
|
||||||
去审批
|
|
||||||
<Icon icon="akar-icons:arrow-right" class="size-13px ml-1" />
|
|
||||||
</NButton>
|
|
||||||
</div>
|
|
||||||
<div class="mt-0.5 truncate text-[11px] text-slate-400 flex items-center gap-2">
|
|
||||||
<p>申请人:{{row.applicant_name}}</p>
|
|
||||||
<span v-if="row.apply_time" class="text-[11px] text-slate-400">{{ row.apply_time?.slice(0, 10) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<div class="mt-1 flex items-center gap-2">
|
||||||
<template v-else>
|
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800 dark:text-white">{{ row.title }}</span>
|
||||||
<div class="h-100% flex items-center justify-center py-3">
|
<NTag size="small" type="warning">审批:{{ row.step_name }}</NTag>
|
||||||
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
<NButton size="small" type="primary" class="text-xs">
|
||||||
</div>
|
去审批
|
||||||
</template>
|
<Icon icon="akar-icons:arrow-right" class="size-13px ml-1" />
|
||||||
</div>
|
</NButton>
|
||||||
</NCard>
|
|
||||||
<!-- 未关闭变更进度一览 -->
|
|
||||||
<NCard :bordered="false" size="small" class="mt-16px" :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 }" />
|
|
||||||
<p class="text-13px font-700 text-#64749a">未关闭变更进度一览</p>
|
|
||||||
<p class="text-11px text-#94a3b8 mt-2px">本车间 · 全部未关闭变更</p>
|
|
||||||
</div>
|
</div>
|
||||||
<NButton v-if="homeData?.unclosed_progress?.total > 0" ghost size="small" class="text-xs" @click="jumpTo('changeledger')">
|
<div class="mt-0.5 truncate text-[11px] text-slate-400 flex items-center gap-2">
|
||||||
变更台账
|
<p>申请人:{{ row.applicant_name }}</p>
|
||||||
<Icon icon="formkit:right" class="size-12px" />
|
<span v-if="row.apply_time" class="text-[11px] text-slate-400">{{ row.apply_time?.slice(0, 10) }}</span>
|
||||||
</NButton>
|
|
||||||
</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)]"
|
|
||||||
:style="{
|
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
|
||||||
'--hover-border': getColorWithOpacity(themeStore.themeColor, 0.25),
|
|
||||||
'--hover-bg': getColorWithOpacity(themeStore.themeColor, 0.05)
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
|
||||||
<!-- 状态标签 -->
|
|
||||||
<NTag v-if="row.status==='APPROVING'" size="small" type="warning">审批中</NTag>
|
|
||||||
<NTag v-if="row.status==='APPROVED'" size="small" type="success">已通过</NTag>
|
|
||||||
<NTag v-if="row.status==='IMPLEMENTED'" size="small" type="success">待验收</NTag>
|
|
||||||
<NTag v-if="row.status==='ACCEPTED'" size="small" type="error">待关闭</NTag>
|
|
||||||
<NTag v-if="row.status==='CLOSED'" size="small" type="success">已关闭</NTag>
|
|
||||||
<NTag v-if="row.status==='WITHDRAWN'" size="small" type="error">已撤回</NTag>
|
|
||||||
<NTag v-if="row.status==='REJECTED'" size="small" type="error">已驳回</NTag>
|
|
||||||
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800 dark:text-white">{{ row.title }}</span>
|
|
||||||
<!-- 变更等级 -->
|
|
||||||
<NTag
|
|
||||||
size="small"
|
|
||||||
:color="{
|
|
||||||
color: tagBgColor(matchObj(levelList,row.change_level)?.color),
|
|
||||||
textColor: tagTextColor(matchObj(levelList,row.change_level)?.color),
|
|
||||||
borderColor: tagBorderColor(matchObj(levelList,row.change_level)?.color),
|
|
||||||
}"
|
|
||||||
>{{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>
|
|
||||||
<span>{{row.org_name}}</span>
|
|
||||||
<span>{{row.applicant_name}}</span>
|
|
||||||
</div>
|
|
||||||
<!-- 审批流程 -->
|
|
||||||
<div v-if="row.flow" class="mt-0.5 truncate text-[11px] flex items-center gap-2">
|
|
||||||
<div v-for="(item,index) in row.flow" :key="index" class="flex items-center gap-1">
|
|
||||||
<Icon v-if="index!==0" icon="formkit:right" class="size-12px text-slate-400" />
|
|
||||||
<!-- 通过 -->
|
|
||||||
<div v-if="item.status==='PASSED'" class="w-6px h-6px rounded-6px" :style="{ backgroundColor: themeStore.otherColor.success }"></div>
|
|
||||||
<p v-if="item.status==='PASSED'" :style="{ color: themeStore.otherColor.success }">{{item.name}}</p>
|
|
||||||
<!-- 进行中 -->
|
|
||||||
<div v-if="item.status==='ACTIVE'" class="w-6px h-6px rounded-6px" :style="{ backgroundColor: themeStore.otherColor.warning }"></div>
|
|
||||||
<p v-if="item.status==='ACTIVE'" :style="{ color: themeStore.otherColor.warning }">{{item.name}}</p>
|
|
||||||
<!-- 待 -->
|
|
||||||
<div v-if="item.status==='PENDING'" class="w-6px h-6px rounded-6px bg-slate-400"></div>
|
|
||||||
<p v-if="item.status==='PENDING'" class="text-slate-400">{{item.name}}</p>
|
|
||||||
<!-- 驳回 -->
|
|
||||||
<div v-if="item.status==='REJECTED'" class="w-6px h-6px rounded-6px" :style="{ backgroundColor: themeStore.otherColor.error }"></div>
|
|
||||||
<p v-if="item.status==='REJECTED'" :style="{ color: themeStore.otherColor.error }">{{item.name}}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div class="h-100% flex items-center justify-center py-3">
|
|
||||||
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</NCard>
|
|
||||||
<!-- 临时变更投用后情况 -->
|
|
||||||
<NCard :bordered="false" size="small" class="mt-16px" :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 }" />
|
|
||||||
<p class="text-13px font-700 text-#64749a">临时变更投用后情况</p>
|
|
||||||
<p class="text-11px text-#94a3b8 mt-2px">临期提醒 · 超期催办 · 延期 / 转永久跟踪</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-3">
|
</template>
|
||||||
<template v-if="homeData?.temp_after_use?.total > 0">
|
<template v-else>
|
||||||
<div v-for="row in homeData?.temp_after_use?.items || []" :key="row.change_id"
|
<div class="h-100% flex items-center justify-center py-3">
|
||||||
class="flex w-full items-center gap-3 border px-3.5 py-2.5 text-left transition"
|
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
||||||
: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),
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
|
||||||
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800 dark:text-white">{{ row.title }}</span>
|
|
||||||
<NTag v-if="row.overdue_status===2" size="small" type="error">已超期{{row.overdue_days}}天</NTag>
|
|
||||||
<NButton v-if="row.overdue_status===2" size="tiny" :disabled="btnLoading" @click="urge(row.change_id)">去催办</NButton>
|
|
||||||
<NButton v-else size="tiny" @click="jumpTo('changeledger')">查看</NButton>
|
|
||||||
</div>
|
|
||||||
<div class="mt-1 flex items-center gap-2 text-[11px] text-slate-400">
|
|
||||||
<span>{{ row.change_no }}</span>
|
|
||||||
<!-- 状态 -->
|
|
||||||
<span v-if="row.status==='APPROVING'">审批中</span>
|
|
||||||
<span v-if="row.status==='APPROVED'">已通过</span>
|
|
||||||
<span v-if="row.status==='IMPLEMENTED'">待验收</span>
|
|
||||||
<span v-if="row.status==='ACCEPTED'">待关闭</span>
|
|
||||||
<span v-if="row.status==='CLOSED'">已关闭</span>
|
|
||||||
<span v-if="row.status==='WITHDRAWN'">已撤回</span>
|
|
||||||
<span v-if="row.status==='REJECTED'">已驳回</span>
|
|
||||||
<span v-if="row.use_time">{{row.use_time?.slice(0, 16)}} 投用</span>
|
|
||||||
<span v-else>尚未投用</span>
|
|
||||||
<p v-if="row.use_time">计划恢复 {{row.restore_deadline?.slice(0, 10)}}<span>(已延期 1 次)</span></p>
|
|
||||||
<span v-else>原计划恢复 {{row.restore_deadline?.slice(0, 10)}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div class="h-100% flex items-center justify-center py-3">
|
|
||||||
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</template>
|
||||||
<!-- 重点关注 -->
|
</div>
|
||||||
<NCard :bordered="false" size="small" class="mt-16px" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
</NCard>
|
||||||
<div class="flex items-center justify-between mb-3">
|
<!-- 未关闭变更进度一览 -->
|
||||||
<div class="flex items-center gap-2">
|
<NCard
|
||||||
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
:bordered="false"
|
||||||
<p class="text-13px font-700 text-#64749a">重点关注</p>
|
size="small"
|
||||||
<p class="text-11px text-#94a3b8 mt-2px">安全管控重点 · 点击打开变更单(含风险分析)</p>
|
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 }" />
|
||||||
|
<p class="text-13px font-700 text-#64749a">未关闭变更进度一览</p>
|
||||||
|
<p class="text-11px text-#94a3b8 mt-2px">本车间 · 全部未关闭变更</p>
|
||||||
|
</div>
|
||||||
|
<NButton v-if="homeData?.unclosed_progress?.total > 0" ghost size="small" class="text-xs" @click="jumpTo('changeledger')">
|
||||||
|
变更台账
|
||||||
|
<Icon icon="formkit:right" class="size-12px" />
|
||||||
|
</NButton>
|
||||||
|
</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" 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">
|
||||||
|
<!-- 状态标签 -->
|
||||||
|
<NTag v-if="row.status==='APPROVING'" size="small" type="warning">审批中</NTag>
|
||||||
|
<NTag v-if="row.status==='APPROVED'" size="small" type="success">已通过</NTag>
|
||||||
|
<NTag v-if="row.status==='IMPLEMENTED'" size="small" type="success">待验收</NTag>
|
||||||
|
<NTag v-if="row.status==='ACCEPTED'" size="small" type="error">待关闭</NTag>
|
||||||
|
<NTag v-if="row.status==='CLOSED'" size="small" type="success">已关闭</NTag>
|
||||||
|
<NTag v-if="row.status==='WITHDRAWN'" size="small" type="error">已撤回</NTag>
|
||||||
|
<NTag v-if="row.status==='REJECTED'" size="small" type="error">已驳回</NTag>
|
||||||
|
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800 dark:text-white">{{ row.title }}</span>
|
||||||
|
<!-- 变更等级 -->
|
||||||
|
<NTag
|
||||||
|
size="small"
|
||||||
|
:color="{
|
||||||
|
color: tagBgColor(matchObj(levelList,row.change_level)?.color),
|
||||||
|
textColor: tagTextColor(matchObj(levelList,row.change_level)?.color),
|
||||||
|
borderColor: tagBorderColor(matchObj(levelList,row.change_level)?.color),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ matchObj(levelList,row.change_level)?.label }}
|
||||||
|
</NTag>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-1 flex items-center gap-2 font-mono text-[11px] text-slate-400">
|
||||||
|
<span>{{ row.change_no }}</span>
|
||||||
|
<span>{{ row.org_name }}</span>
|
||||||
|
<span>{{ row.applicant_name }}</span>
|
||||||
|
</div>
|
||||||
|
<!-- 审批流程 -->
|
||||||
|
<div v-if="row.flow" class="mt-0.5 truncate text-[11px] flex items-center gap-2">
|
||||||
|
<div v-for="(item,index) in row.flow" :key="index" class="flex items-center gap-1">
|
||||||
|
<Icon v-if="index!==0" icon="formkit:right" class="size-12px text-slate-400" />
|
||||||
|
<!-- 通过 -->
|
||||||
|
<div v-if="item.status==='PASSED'" class="w-6px h-6px rounded-6px" :style="{ backgroundColor: themeStore.otherColor.success }"></div>
|
||||||
|
<p v-if="item.status==='PASSED'" :style="{ color: themeStore.otherColor.success }">{{ item.name }}</p>
|
||||||
|
<!-- 进行中 -->
|
||||||
|
<div v-if="item.status==='ACTIVE'" class="w-6px h-6px rounded-6px" :style="{ backgroundColor: themeStore.otherColor.warning }"></div>
|
||||||
|
<p v-if="item.status==='ACTIVE'" :style="{ color: themeStore.otherColor.warning }">{{ item.name }}</p>
|
||||||
|
<!-- 待 -->
|
||||||
|
<div v-if="item.status==='PENDING'" class="w-6px h-6px rounded-6px bg-slate-400"></div>
|
||||||
|
<p v-if="item.status==='PENDING'" class="text-slate-400">{{ item.name }}</p>
|
||||||
|
<!-- 驳回 -->
|
||||||
|
<div v-if="item.status==='REJECTED'" class="w-6px h-6px rounded-6px" :style="{ backgroundColor: themeStore.otherColor.error }"></div>
|
||||||
|
<p v-if="item.status==='REJECTED'" :style="{ color: themeStore.otherColor.error }">{{ item.name }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-3">
|
</template>
|
||||||
<template v-if="homeData?.key_attention?.total > 0">
|
<template v-else>
|
||||||
<div v-for="row in homeData?.key_attention?.items || []" :key="row.change_id"
|
<div class="h-100% flex items-center justify-center py-3">
|
||||||
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)]"
|
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
||||||
@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),
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
|
||||||
<NTag size="small" type="error">重要</NTag>
|
|
||||||
<span class="text-xs text-slate-400">{{ row.change_no }}</span>
|
|
||||||
</div>
|
|
||||||
<p class="mt-1 truncate text-sm font-medium text-slate-800">{{ row.title }}</p>
|
|
||||||
<div class="mt-1 flex items-center gap-2 text-[11px] text-slate-400">
|
|
||||||
<!-- 状态 -->
|
|
||||||
<span v-if="row.status==='APPROVING'">审批中</span>
|
|
||||||
<span v-if="row.status==='APPROVED'">已通过</span>
|
|
||||||
<span v-if="row.status==='IMPLEMENTED'">待验收</span>
|
|
||||||
<span v-if="row.status==='ACCEPTED'">待关闭</span>
|
|
||||||
<span v-if="row.status==='CLOSED'">已关闭</span>
|
|
||||||
<span v-if="row.status==='WITHDRAWN'">已撤回</span>
|
|
||||||
<span v-if="row.status==='REJECTED'">已驳回</span>
|
|
||||||
<span>{{row.applicant_name}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div class="h-100% flex items-center justify-center py-3">
|
|
||||||
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</template>
|
||||||
|
</div>
|
||||||
|
</NCard>
|
||||||
|
<!-- 临时变更投用后情况 -->
|
||||||
|
<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 }" />
|
||||||
|
<p class="text-13px font-700 text-#64749a">临时变更投用后情况</p>
|
||||||
|
<p class="text-11px text-#94a3b8 mt-2px">临期提醒 · 超期催办 · 延期 / 转永久跟踪</p>
|
||||||
|
</div>
|
||||||
|
</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"
|
||||||
|
class="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),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<span class="min-w-0 flex-1 truncate text-sm font-medium text-slate-800 dark:text-white">{{ row.title }}</span>
|
||||||
|
<NTag v-if="row.overdue_status===2" size="small" type="error">已超期{{ row.overdue_days }}天</NTag>
|
||||||
|
<NButton v-if="row.overdue_status===2" size="tiny" :disabled="btnLoading" @click="urge(row.change_id)">去催办</NButton>
|
||||||
|
<NButton v-else size="tiny" @click="jumpTo('changeledger')">查看</NButton>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 flex items-center gap-2 text-[11px] text-slate-400">
|
||||||
|
<span>{{ row.change_no }}</span>
|
||||||
|
<!-- 状态 -->
|
||||||
|
<span v-if="row.status==='APPROVING'">审批中</span>
|
||||||
|
<span v-if="row.status==='APPROVED'">已通过</span>
|
||||||
|
<span v-if="row.status==='IMPLEMENTED'">待验收</span>
|
||||||
|
<span v-if="row.status==='ACCEPTED'">待关闭</span>
|
||||||
|
<span v-if="row.status==='CLOSED'">已关闭</span>
|
||||||
|
<span v-if="row.status==='WITHDRAWN'">已撤回</span>
|
||||||
|
<span v-if="row.status==='REJECTED'">已驳回</span>
|
||||||
|
<span v-if="row.use_time">{{ row.use_time?.slice(0, 16) }} 投用</span>
|
||||||
|
<span v-else>尚未投用</span>
|
||||||
|
<p v-if="row.use_time">计划恢复 {{ row.restore_deadline?.slice(0, 10) }}<span>(已延期 1 次)</span></p>
|
||||||
|
<span v-else>原计划恢复 {{ row.restore_deadline?.slice(0, 10) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="h-100% flex items-center justify-center py-3">
|
||||||
|
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</NCard>
|
||||||
|
<!-- 重点关注 -->
|
||||||
|
<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 }" />
|
||||||
|
<p class="text-13px font-700 text-#64749a">重点关注</p>
|
||||||
|
<p class="text-11px text-#94a3b8 mt-2px">安全管控重点 · 点击打开变更单(含风险分析)</p>
|
||||||
|
</div>
|
||||||
|
</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"
|
||||||
|
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)]"
|
||||||
|
: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">
|
||||||
|
<NTag size="small" type="error">重要</NTag>
|
||||||
|
<span class="text-xs text-slate-400">{{ row.change_no }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-1 truncate text-sm font-medium text-slate-800">{{ row.title }}</p>
|
||||||
|
<div class="mt-1 flex items-center gap-2 text-[11px] text-slate-400">
|
||||||
|
<!-- 状态 -->
|
||||||
|
<span v-if="row.status==='APPROVING'">审批中</span>
|
||||||
|
<span v-if="row.status==='APPROVED'">已通过</span>
|
||||||
|
<span v-if="row.status==='IMPLEMENTED'">待验收</span>
|
||||||
|
<span v-if="row.status==='ACCEPTED'">待关闭</span>
|
||||||
|
<span v-if="row.status==='CLOSED'">已关闭</span>
|
||||||
|
<span v-if="row.status==='WITHDRAWN'">已撤回</span>
|
||||||
|
<span v-if="row.status==='REJECTED'">已驳回</span>
|
||||||
|
<span>{{ row.applicant_name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NButton text><Icon icon="formkit:right" class="size-14px text-slate-400" /></NButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="h-100% flex items-center justify-center py-3">
|
||||||
|
<NEmpty description="暂无数据" class="!text-xs"></NEmpty>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<!-- 右侧 -->
|
<!-- 右侧 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<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">
|
<div class="flex items-center flex-wrap gap-2">
|
||||||
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">本车间统计</p>
|
<p class="text-13px font-700 text-#64749a">本车间统计</p>
|
||||||
<p class="text-11px text-#94a3b8">一车间 · 近一年</p>
|
<p class="text-11px text-#94a3b8">一车间 · 近一年</p>
|
||||||
<NSelect :value="dateRange" size="small" :options="DATE_RANGE_OPTIONS" class="ml-auto w-120px" :on-update:value="timeChange"></NSelect>
|
<NSelect :value="dateRange" size="small" :options="DATE_RANGE_OPTIONS" class="ml-auto w-120px" :on-update:value="timeChange"></NSelect>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="dateRange==='custom'" class="flex items-center gap-2 mt-2">
|
<div v-if="dateRange==='custom'" class="flex items-center gap-2 mt-2">
|
||||||
<NDatePicker :value="from" type="date" size="small" class="w-full" :on-update:value="(v) => customTimeChange('start',v)" />
|
<NDatePicker :value="from" type="date" size="small" class="w-full" :on-update:value="(v) => customTimeChange('start',v)" />
|
||||||
<span class="text-xs text-slate-400 flex-1">至</span>
|
<span class="text-xs text-slate-400 flex-1">至</span>
|
||||||
<NDatePicker :value="to" type="date" size="small" class="w-full" :on-update:value="(v) => customTimeChange('end',v)" />
|
<NDatePicker :value="to" type="date" size="small" class="w-full" :on-update:value="(v) => customTimeChange('end',v)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-3 mt-3">
|
<div class="flex flex-col gap-3 mt-3">
|
||||||
<div class="flex items-end justify-between">
|
<div class="flex items-end justify-between">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-3xl font-extrabold text-slate-800">{{ homeData?.dept_summary?.total || 0 }}</span>
|
<span class="text-3xl font-extrabold text-slate-800">{{ homeData?.dept_summary?.total || 0 }}</span>
|
||||||
<span class="ml-1.5 text-[11px] text-slate-500">变更总数</span>
|
<span class="ml-1.5 text-[11px] text-slate-500">变更总数</span>
|
||||||
</div>
|
|
||||||
<span class="text-[11px] text-slate-400">状态构成占比条</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex h-3 w-full overflow-hidden rounded-full bg-slate-100">
|
<span class="text-[11px] text-slate-400">状态构成占比条</span>
|
||||||
<div :title="`已关闭 ${homeData?.dept_summary?.closed || 0} 起`" :style="{width: `${(homeData?.dept_summary?.closed || 0) / (homeData?.dept_summary?.total || 0) * 100}%`, background: themeStore.themeColor}"></div>
|
</div>
|
||||||
<div :title="`审批中 ${homeData?.dept_summary?.approving || 0} 起`" :style="{width: `${(homeData?.dept_summary?.approving || 0) / (homeData?.dept_summary?.total || 0) * 100}%`, background: themeStore.otherColor.warning}"></div>
|
<div class="flex h-3 w-full overflow-hidden rounded-full bg-slate-100">
|
||||||
<div :title="`实施中 ${homeData?.dept_summary?.executing || 0} 起`" :style="{width: `${(homeData?.dept_summary?.executing || 0) / (homeData?.dept_summary?.total || 0) * 100}%`, background: themeStore.otherColor.success}"></div>
|
<div :title="`已关闭 ${homeData?.dept_summary?.closed || 0} 起`" :style="{width: `${(homeData?.dept_summary?.closed || 0) / (homeData?.dept_summary?.total || 0) * 100}%`, background: themeStore.themeColor}"></div>
|
||||||
<div :title="`超期未关闭 ${homeData?.dept_summary?.overdue || 0} 起`" :style="{width: `${(homeData?.dept_summary?.overdue || 0) / (homeData?.dept_summary?.total || 0) * 100}%`, background: themeStore.otherColor.error}"></div>
|
<div :title="`审批中 ${homeData?.dept_summary?.approving || 0} 起`" :style="{width: `${(homeData?.dept_summary?.approving || 0) / (homeData?.dept_summary?.total || 0) * 100}%`, background: themeStore.otherColor.warning}"></div>
|
||||||
|
<div :title="`实施中 ${homeData?.dept_summary?.executing || 0} 起`" :style="{width: `${(homeData?.dept_summary?.executing || 0) / (homeData?.dept_summary?.total || 0) * 100}%`, background: themeStore.otherColor.success}"></div>
|
||||||
|
<div :title="`超期未关闭 ${homeData?.dept_summary?.overdue || 0} 起`" :style="{width: `${(homeData?.dept_summary?.overdue || 0) / (homeData?.dept_summary?.total || 0) * 100}%`, background: themeStore.otherColor.error}"></div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-1.5">
|
||||||
|
<div class="flex items-center gap-1.5 rounded-lg px-2 py-1.5 bg-slate-50">
|
||||||
|
<span class="h-2 w-2 shrink-0 rounded-full" :style="{ background: themeStore.themeColor }"></span>
|
||||||
|
<span class="text-[11px] text-slate-500">已关闭</span>
|
||||||
|
<span class="ml-auto text-sm font-bold text-slate-800">{{ homeData?.dept_summary?.closed || 0 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-1.5">
|
<div class="flex items-center gap-1.5 rounded-lg px-2 py-1.5 bg-slate-50">
|
||||||
<div class="flex items-center gap-1.5 rounded-lg px-2 py-1.5 bg-slate-50">
|
<span class="h-2 w-2 shrink-0 rounded-full" :style="{ background: themeStore.otherColor.warning }"></span>
|
||||||
<span class="h-2 w-2 shrink-0 rounded-full" :style="{ background: themeStore.themeColor }"></span>
|
<span class="text-[11px] text-slate-500">审批中</span>
|
||||||
<span class="text-[11px] text-slate-500">已关闭</span>
|
<span class="ml-auto text-sm font-bold text-slate-800">{{ homeData?.dept_summary?.approving || 0 }}</span>
|
||||||
<span class="ml-auto text-sm font-bold text-slate-800">{{ homeData?.dept_summary?.closed || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-1.5 rounded-lg px-2 py-1.5 bg-slate-50">
|
|
||||||
<span class="h-2 w-2 shrink-0 rounded-full" :style="{ background: themeStore.otherColor.warning }"></span>
|
|
||||||
<span class="text-[11px] text-slate-500">审批中</span>
|
|
||||||
<span class="ml-auto text-sm font-bold text-slate-800">{{ homeData?.dept_summary?.approving || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-1.5 rounded-lg px-2 py-1.5 bg-slate-50">
|
|
||||||
<span class="h-2 w-2 shrink-0 rounded-full" :style="{ background: themeStore.otherColor.success }"></span>
|
|
||||||
<span class="text-[11px] text-slate-500">实施中</span>
|
|
||||||
<span class="ml-auto text-sm font-bold text-slate-800">{{ homeData?.dept_summary?.executing || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-1.5 rounded-lg px-2 py-1.5 bg-red-50">
|
|
||||||
<span class="h-2 w-2 shrink-0 rounded-full" :style="{ background: themeStore.otherColor.error }"></span>
|
|
||||||
<span class="text-[11px] text-slate-500">超期未关闭</span>
|
|
||||||
<span class="ml-auto text-sm font-bold" :style="{ color: themeStore.otherColor.error }">{{ homeData?.dept_summary?.overdue || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center gap-1.5 rounded-lg px-2 py-1.5 bg-slate-50">
|
||||||
|
<span class="h-2 w-2 shrink-0 rounded-full" :style="{ background: themeStore.otherColor.success }"></span>
|
||||||
|
<span class="text-[11px] text-slate-500">实施中</span>
|
||||||
|
<span class="ml-auto text-sm font-bold text-slate-800">{{ homeData?.dept_summary?.executing || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1.5 rounded-lg px-2 py-1.5 bg-red-50">
|
||||||
|
<span class="h-2 w-2 shrink-0 rounded-full" :style="{ background: themeStore.otherColor.error }"></span>
|
||||||
|
<span class="text-[11px] text-slate-500">超期未关闭</span>
|
||||||
|
<span class="ml-auto text-sm font-bold" :style="{ color: themeStore.otherColor.error }">{{ homeData?.dept_summary?.overdue || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="material-symbols:folder-open-outline" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="material-symbols:folder-open-outline" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">资料管理</p>
|
<p class="text-13px font-700 text-#64749a">资料管理</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="space-y-2"
|
class="space-y-2"
|
||||||
:style="{
|
:style="{
|
||||||
'--hover-border': getColorWithOpacity(themeStore.themeColor, 0.25),
|
'--hover-border': getColorWithOpacity(themeStore.themeColor, 0.25),
|
||||||
'--hover-bg': getColorWithOpacity(themeStore.themeColor, 0.05)
|
'--hover-bg': getColorWithOpacity(themeStore.themeColor, 0.05)
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="cursor-pointer flex w-full items-center gap-3 rounded-lg border px-3.5 py-2.5 text-left transition hover:border-[var(--hover-border)] hover:bg-[var(--hover-bg)]"
|
class="cursor-pointer flex w-full items-center gap-3 rounded-lg border px-3.5 py-2.5 text-left transition hover:border-[var(--hover-border)] hover:bg-[var(--hover-bg)]"
|
||||||
@click="jumpTo('docs_archives')"
|
@click="jumpTo('docs_archives')"
|
||||||
>
|
>
|
||||||
<Icon icon="material-symbols:folder-open-outline" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="material-symbols:folder-open-outline" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="truncate text-xs font-semibold text-slate-700">本车间操作规程</p>
|
<p class="truncate text-xs font-semibold text-slate-700">本车间操作规程</p>
|
||||||
<p class="truncate text-[11px] text-slate-400">现行受控版本查阅</p>
|
<p class="truncate text-[11px] text-slate-400">现行受控版本查阅</p>
|
||||||
</div>
|
|
||||||
<Icon icon="formkit:right" class="size-14px text-slate-400" />
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<Icon icon="formkit:right" class="size-14px text-slate-400" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
class="cursor-pointer flex w-full items-center gap-3 rounded-lg border px-3.5 py-2.5 text-left transition hover:border-[var(--hover-border)] hover:bg-[var(--hover-bg)]"
|
class="cursor-pointer flex w-full items-center gap-3 rounded-lg border px-3.5 py-2.5 text-left transition hover:border-[var(--hover-border)] hover:bg-[var(--hover-bg)]"
|
||||||
@click="jumpTo('docs_archives')"
|
@click="jumpTo('docs_archives')"
|
||||||
>
|
>
|
||||||
<Icon icon="material-symbols:folder-open-outline" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="material-symbols:folder-open-outline" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="truncate text-xs font-semibold text-slate-700">本车间变更档案</p>
|
<p class="truncate text-xs font-semibold text-slate-700">本车间变更档案</p>
|
||||||
<p class="truncate text-[11px] text-slate-400">历史变更记录与验收材料</p>
|
<p class="truncate text-[11px] text-slate-400">历史变更记录与验收材料</p>
|
||||||
</div>
|
|
||||||
<Icon icon="formkit:right" class="size-14px text-slate-400" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<Icon icon="formkit:right" class="size-14px text-slate-400" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">近一年验收结论占比</p>
|
<p class="text-13px font-700 text-#64749a">近一年验收结论占比</p>
|
||||||
<p class="text-11px text-#94a3b8">本车间 · 已验收变更</p>
|
<p class="text-11px text-#94a3b8">本车间 · 已验收变更</p>
|
||||||
</div>
|
</div>
|
||||||
<RingChart v-if="homeData" :data="homeData?.accept_conclusion?.items || []" :total="homeData?.accept_conclusion?.total_count || 0" />
|
<RingChart v-if="homeData" :data="homeData?.accept_conclusion?.items || []" :total="homeData?.accept_conclusion?.total_count || 0" />
|
||||||
<div class="flex items-center justify-between border-slate-200 border-t pt-2 border-dashed">
|
<div class="flex items-center justify-between border-slate-200 border-t pt-2 border-dashed">
|
||||||
<span class="text-[11px] text-slate-400">投入合计(变更材料和实施费)</span>
|
<span class="text-[11px] text-slate-400">投入合计(变更材料和实施费)</span>
|
||||||
<span class="text-xs font-bold" :style="{ color: themeStore.themeColor }">{{homeData?.accept_conclusion?.total_fee || 0}} 万元</span>
|
<span class="text-xs font-bold" :style="{ color: themeStore.themeColor }">{{ homeData?.accept_conclusion?.total_fee || 0 }} 万元</span>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
</div>
|
</div>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@@ -113,7 +113,11 @@ onUnmounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<div class="space-y-3.5">
|
<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>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:8">
|
<NGi span="24 s:24 m:8">
|
||||||
<div class="flex-y-center">
|
<div class="flex-y-center">
|
||||||
@@ -153,7 +157,7 @@ onUnmounted(() => {
|
|||||||
>
|
>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_mytask')"
|
@click="jumpTo('my_mytask')"
|
||||||
>
|
>
|
||||||
@@ -172,7 +176,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
@@ -194,7 +198,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
@@ -216,7 +220,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
@@ -241,7 +245,11 @@ onUnmounted(() => {
|
|||||||
<!-- 左侧 -->
|
<!-- 左侧 -->
|
||||||
<NGi span="24 s:24 m:24 l:16">
|
<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 justify-between mb-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Icon icon="lucide:clipboard-check" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<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]">
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="mt-[16px]">
|
||||||
<!-- 各部门 / 人员审批时长 -->
|
<!-- 各部门 / 人员审批时长 -->
|
||||||
<NGi span="24 s:24 m:24 l:24">
|
<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 justify-between mb-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<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">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="proicons:alert-triangle" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="proicons:alert-triangle" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">超期未关闭变更</p>
|
<p class="text-13px font-700 text-#64749a">超期未关闭变更</p>
|
||||||
@@ -408,7 +424,11 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</NCard>
|
</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 items-center justify-between">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|||||||
@@ -114,7 +114,11 @@ onUnmounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<div class="space-y-3.5">
|
<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>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:12">
|
<NGi span="24 s:24 m:12">
|
||||||
<div class="flex-y-center">
|
<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">
|
<NGrid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_mytask')"
|
@click="jumpTo('my_mytask')"
|
||||||
>
|
>
|
||||||
@@ -164,7 +168,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('signstatistics')"
|
@click="jumpTo('signstatistics')"
|
||||||
>
|
>
|
||||||
@@ -183,7 +187,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
@@ -203,7 +207,7 @@ onUnmounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('signstatistics')"
|
@click="jumpTo('signstatistics')"
|
||||||
>
|
>
|
||||||
@@ -226,7 +230,11 @@ onUnmounted(() => {
|
|||||||
<!-- 左侧 -->
|
<!-- 左侧 -->
|
||||||
<NGi span="24 s:24 m:24 l:16">
|
<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 justify-between mb-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Icon icon="lucide:clipboard-check" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<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]">
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="mt-[16px]">
|
||||||
<!-- 最近已会签 -->
|
<!-- 最近已会签 -->
|
||||||
<NGi span="24 s:24 m:24 l:24">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="ix:success" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="ix:success" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">最近已会签</p>
|
<p class="text-13px font-700 text-#64749a">最近已会签</p>
|
||||||
@@ -331,7 +343,11 @@ onUnmounted(() => {
|
|||||||
<!-- 右侧 -->
|
<!-- 右侧 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="lucide:shield-alert" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">质量关注变更</p>
|
<p class="text-13px font-700 text-#64749a">质量关注变更</p>
|
||||||
@@ -355,7 +371,11 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</NCard>
|
</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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
<Icon icon="lets-icons:clock" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||||
<p class="text-13px font-700 text-#64749a">我的会签效率</p>
|
<p class="text-13px font-700 text-#64749a">我的会签效率</p>
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ onMounted(() => {
|
|||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<NSpace vertical :size="16">
|
<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">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<p class="text-sm font-bold text-slate-800">全厂变更数据中心</p>
|
<p class="text-sm font-bold text-slate-800">全厂变更数据中心</p>
|
||||||
<p class="text-[11px] text-slate-400 mt-[1px]">时间相关数据统一在此查询 · 统计时段</p>
|
<p class="text-[11px] text-slate-400 mt-[1px]">时间相关数据统一在此查询 · 统计时段</p>
|
||||||
@@ -128,7 +128,7 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
@@ -147,7 +147,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
@@ -166,7 +166,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_highrisk')"
|
@click="jumpTo('my_highrisk')"
|
||||||
>
|
>
|
||||||
@@ -193,7 +193,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_effect')"
|
@click="jumpTo('my_effect')"
|
||||||
>
|
>
|
||||||
@@ -222,7 +222,7 @@ onMounted(() => {
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<NGi span="24 s:24 m:24 l:14">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
|
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
|
||||||
<p class="text-13px font-700 text-#64749a">各车间变更数量</p>
|
<p class="text-13px font-700 text-#64749a">各车间变更数量</p>
|
||||||
@@ -232,7 +232,7 @@ onMounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:10">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
|
<Icon icon="akar-icons:file" class="size-16px text-[--theme-color]" />
|
||||||
<p class="text-13px font-700 text-#64749a">变更类型分布</p>
|
<p class="text-13px font-700 text-#64749a">变更类型分布</p>
|
||||||
@@ -253,7 +253,7 @@ onMounted(() => {
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<NGi span="24 s:24 m:24 l:10">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="lucide:shield-alert" class="size-16px text-[--theme-color]" />
|
<Icon icon="lucide:shield-alert" class="size-16px text-[--theme-color]" />
|
||||||
<p class="text-13px font-700 text-#64749a">风险增量与管控</p>
|
<p class="text-13px font-700 text-#64749a">风险增量与管控</p>
|
||||||
@@ -262,19 +262,19 @@ onMounted(() => {
|
|||||||
<div class="space-y-2.5">
|
<div class="space-y-2.5">
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<div class="rounded-lg bg-red-50 px-3 py-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 class="text-xl font-bold text-[var(--error-color)]">{{homeData?.risk?.inherent_high || 0}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg bg-amber-50 px-3 py-2">
|
<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 class="text-xl font-bold text-[var(--warning-color)]">{{homeData?.risk?.inherent_mid || 0}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg bg-slate-50 px-3 py-2">
|
<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 class="text-xl font-bold text-slate-800">{{homeData?.risk?.residual_high || 0}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg bg-slate-50 px-3 py-2">
|
<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 class="text-xl font-bold text-slate-800">{{homeData?.risk?.residual_mid || 0}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -297,7 +297,7 @@ onMounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:14">
|
<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">
|
<div class="flex items-center mb-3 gap-2">
|
||||||
<Icon icon="material-symbols:check-circle-outline" class="size-16px text-[--theme-color]" />
|
<Icon icon="material-symbols:check-circle-outline" class="size-16px text-[--theme-color]" />
|
||||||
<p class="text-13px font-700 text-#64749a">变更投入和效果</p>
|
<p class="text-13px font-700 text-#64749a">变更投入和效果</p>
|
||||||
@@ -305,11 +305,11 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<RingChart2 v-if="homeData" :data="homeData?.invest?.items" :range="dateLabel" />
|
<RingChart2 v-if="homeData" :data="homeData?.invest?.items" :range="dateLabel" />
|
||||||
<div class="flex items-center justify-between border-slate-200 pt-1.5">
|
<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>
|
<span class="text-xs font-bold text-[var(--theme-color)]">{{homeData?.invest?.total_fee || 0}} 万元</span>
|
||||||
</div>
|
</div>
|
||||||
<div @click="jumpTo('my_effect')"
|
<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" />
|
验收明细(名称 / 日期链 / 费用 / 结论)见左边栏「变更投入和效果」<ChevronRight class="h-3 w-3" />
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ onMounted(() => {
|
|||||||
</NButton>
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg bg-white shadow-[0_2px_8px_rgba(0,0,0,0.06)]">
|
<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="min-w-0 flex-1">变更名称 / 编号</span>
|
||||||
<span class="w-[92px] shrink-0">申请日期</span>
|
<span class="w-[92px] shrink-0">申请日期</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===2" size="small" type="warning">临时</NTag>
|
||||||
<NTag v-if="r.duration_type===1" size="small" type="info">永久</NTag>
|
<NTag v-if="r.duration_type===1" size="small" type="info">永久</NTag>
|
||||||
</span>
|
</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>
|
||||||
<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.apply_time?.slice(0,10) }}</span>
|
||||||
<span class="w-[92px] shrink-0 text-xs text-slate-600">{{ r.use_time?.slice(0,10) }}</span>
|
<span class="w-[92px] shrink-0 text-xs text-slate-600">{{ r.use_time?.slice(0,10) }}</span>
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ onMounted(() => {
|
|||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
|
<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" :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
|
<div
|
||||||
class="flex flex-wrap items-center gap-x-3 gap-y-2 border-b px-4 py-2 border-[var(--border-color)]"
|
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)}"
|
: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 { AiError, aiTypeJudge, aiLevelScore, aiApplication } from "@/service/api/ai";
|
||||||
import { uploadFileApi, deleteFileApi } from "@/service/api/file";
|
import { uploadFileApi, deleteFileApi } from "@/service/api/file";
|
||||||
import Footer from './footer.vue'
|
import Footer from './footer.vue'
|
||||||
import previewComponent from '@/components/previewFile/index.vue'
|
import PreviewComponent from '@/components/previewFile/index.vue'
|
||||||
|
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ const form = ref<{
|
|||||||
level_score: any[],
|
level_score: any[],
|
||||||
}>({
|
}>({
|
||||||
change_type: null,
|
change_type: null,
|
||||||
change_level: null,
|
change_level: 1,
|
||||||
duration_type: null,
|
duration_type: null,
|
||||||
restore_deadline: null,
|
restore_deadline: null,
|
||||||
org_id: null,
|
org_id: null,
|
||||||
@@ -94,7 +94,7 @@ const RISK_TOOLS_FORM = [
|
|||||||
const questions = ref<any>([
|
const questions = ref<any>([
|
||||||
{
|
{
|
||||||
question_id: 1,
|
question_id: 1,
|
||||||
name: "1. 设备影响",
|
name: "1. 设备影响",
|
||||||
note: "不合适的材质将会导致",
|
note: "不合适的材质将会导致",
|
||||||
score: 0,
|
score: 0,
|
||||||
options: [
|
options: [
|
||||||
@@ -106,7 +106,7 @@ const questions = ref<any>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
question_id: 2,
|
question_id: 2,
|
||||||
name: "2. 财务影响",
|
name: "2. 财务影响",
|
||||||
note: "工艺设计错误将会导致",
|
note: "工艺设计错误将会导致",
|
||||||
score: 0,
|
score: 0,
|
||||||
options: [
|
options: [
|
||||||
@@ -118,7 +118,7 @@ const questions = ref<any>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
question_id: 3,
|
question_id: 3,
|
||||||
name: "3. 质量影响",
|
name: "3. 质量影响",
|
||||||
note: "",
|
note: "",
|
||||||
score: 0,
|
score: 0,
|
||||||
options: [
|
options: [
|
||||||
@@ -130,7 +130,7 @@ const questions = ref<any>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
question_id: 4,
|
question_id: 4,
|
||||||
name: "4. 设计的复杂程度",
|
name: "4. 设计的复杂程度",
|
||||||
note: "工艺设计包含了",
|
note: "工艺设计包含了",
|
||||||
score: 0,
|
score: 0,
|
||||||
options: [
|
options: [
|
||||||
@@ -142,7 +142,7 @@ const questions = ref<any>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
question_id: 5,
|
question_id: 5,
|
||||||
name: "5. 工艺设计的成熟度",
|
name: "5. 工艺设计的成熟度",
|
||||||
note: "",
|
note: "",
|
||||||
score: 0,
|
score: 0,
|
||||||
options: [
|
options: [
|
||||||
@@ -154,7 +154,7 @@ const questions = ref<any>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
question_id: 6,
|
question_id: 6,
|
||||||
name: "6. 工艺危害",
|
name: "6. 工艺危害",
|
||||||
note: "物料性质和工艺条件",
|
note: "物料性质和工艺条件",
|
||||||
score: 0,
|
score: 0,
|
||||||
options: [
|
options: [
|
||||||
@@ -172,15 +172,17 @@ const currentSteps = ref<any>(null);
|
|||||||
watch(() => props.currentForm, (newVal:any) => {
|
watch(() => props.currentForm, (newVal:any) => {
|
||||||
if (!newVal) return;
|
if (!newVal) return;
|
||||||
isSyncingFromParent.value = true;
|
isSyncingFromParent.value = true;
|
||||||
|
uploadFile.value = newVal?.attachments || [];
|
||||||
// 过滤出需要的字段
|
// 过滤出需要的字段
|
||||||
const {
|
// 过滤出需要的字段
|
||||||
|
const {
|
||||||
approval_flow,
|
approval_flow,
|
||||||
attachments,
|
attachments,
|
||||||
change_id,
|
change_id,
|
||||||
created_at,
|
created_at,
|
||||||
id,
|
id,
|
||||||
updated_at,
|
updated_at,
|
||||||
...rest
|
...rest
|
||||||
} = newVal;
|
} = newVal;
|
||||||
form.value = rest;
|
form.value = rest;
|
||||||
// 用 Map 建立 question_id -> score 的映射,查找 O(1)
|
// 用 Map 建立 question_id -> score 的映射,查找 O(1)
|
||||||
@@ -232,7 +234,7 @@ const filterMembersBySpecialties = (steps: any, specialties: string[]) => {
|
|||||||
}
|
}
|
||||||
// 更新当前模板步骤
|
// 更新当前模板步骤
|
||||||
const updateCurrentTemplate = () => {
|
const updateCurrentTemplate = () => {
|
||||||
let template = props.templateList.find((item: any) =>
|
const template = props.templateList.find((item: any) =>
|
||||||
item.level === form.value?.change_level &&
|
item.level === form.value?.change_level &&
|
||||||
item.category === form.value?.change_type &&
|
item.category === form.value?.change_type &&
|
||||||
item.duration === form.value?.duration_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 typeAiLoading = ref<boolean>(false);
|
||||||
// 打开变更类型抽屉
|
// 打开变更类型抽屉
|
||||||
const openTypeSheet = async () => {
|
const openTypeSheet = async () => {
|
||||||
let form = props.formInfo;
|
const form = props.formInfo;
|
||||||
if (!form?.description) {
|
if (!form?.description) {
|
||||||
return window.$message?.warning("缺少变更描述,请先在 [变更预识别] 里面输入变更描述");
|
return window.$message?.warning("缺少变更描述,请先在 [变更预识别] 里面输入变更描述");
|
||||||
}
|
}
|
||||||
@@ -296,7 +298,7 @@ const levelAiBasis = ref<string[]>([]);
|
|||||||
const levelAiLoading = ref<boolean>(false);
|
const levelAiLoading = ref<boolean>(false);
|
||||||
// 打开等级判定表抽屉
|
// 打开等级判定表抽屉
|
||||||
const openLevelSheet = async () => {
|
const openLevelSheet = async () => {
|
||||||
let form = props.formInfo;
|
const form = props.formInfo;
|
||||||
if (!form?.description && props.isAiOpen) {
|
if (!form?.description && props.isAiOpen) {
|
||||||
return window.$message?.warning("缺少变更描述,请先在 [变更预识别] 里面输入变更描述");
|
return window.$message?.warning("缺少变更描述,请先在 [变更预识别] 里面输入变更描述");
|
||||||
}
|
}
|
||||||
@@ -305,7 +307,7 @@ const openLevelSheet = async () => {
|
|||||||
};
|
};
|
||||||
// 使用AI 评分
|
// 使用AI 评分
|
||||||
const useAiLevel = async () => {
|
const useAiLevel = async () => {
|
||||||
let form = props.formInfo;
|
const form = props.formInfo;
|
||||||
if (levelAiLoading.value) return;
|
if (levelAiLoading.value) return;
|
||||||
levelAiLoading.value = true;
|
levelAiLoading.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -447,82 +449,82 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="space-y-4 scroll">
|
<div class="space-y-4 scroll">
|
||||||
<!-- 申请部门 / 申请人 -->
|
<!-- 申请部门 / 申请人 -->
|
||||||
<div class="grid gap-4 grid-cols-2">
|
<div class="grid gap-4 grid-cols-2">
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">申请部门 <span class="text-red-500">*</span></div>
|
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">申请部门 <span class="text-red-500">*</span></div>
|
||||||
<NTreeSelect
|
<NTreeSelect
|
||||||
v-model:value="form.org_id"
|
v-model:value="form.org_id"
|
||||||
default-expand-all
|
default-expand-all
|
||||||
label-field="label"
|
label-field="label"
|
||||||
key-field="id"
|
key-field="id"
|
||||||
:options="props.orgTree"
|
:options="props.orgTree"
|
||||||
:render-switcher-icon="renderSwitcherIcon"
|
:render-switcher-icon="renderSwitcherIcon"
|
||||||
placeholder="请选择申请部门"
|
placeholder="请选择申请部门"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-3">
|
<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>
|
||||||
<NInput :value="userInfo?.real_name" disabled />
|
<NInput :value="userInfo?.real_name" disabled />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 变更目的 -->
|
<!-- 变更目的 -->
|
||||||
<div class="flex gap-3">
|
<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="rounded-lg bg-[#EAF0F9]/40 p-3">
|
<div class="rounded-lg bg-[#EAF0F9]/40 p-3">
|
||||||
<NCheckboxGroup v-model:value="form.purposes">
|
<NCheckboxGroup v-model:value="form.purposes">
|
||||||
<div class="flex flex-wrap gap-y-2 gap-x-4">
|
<div class="flex flex-wrap gap-y-2 gap-x-4">
|
||||||
<NCheckbox v-for="p in PURPOSE_OPTIONS" :key="p" :value="p" :label="p" />
|
<NCheckbox v-for="p in PURPOSE_OPTIONS" :key="p" :value="p" :label="p" />
|
||||||
</div>
|
</div>
|
||||||
</NCheckboxGroup>
|
</NCheckboxGroup>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 预期效果 -->
|
<!-- 预期效果 -->
|
||||||
<div class="flex gap-3">
|
<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>
|
||||||
<NInput type="textarea" v-model:value="form.effect" rows="2" placeholder="" />
|
<NInput v-model:value="form.effect" type="textarea" rows="2" placeholder="" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 主要变更内容 -->
|
<!-- 主要变更内容 -->
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">主要变更内容 <span class="text-red-500">*</span></div>
|
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">主要变更内容 <span class="text-red-500">*</span></div>
|
||||||
<div class="flex-1 space-y-2">
|
<div class="flex-1 space-y-2">
|
||||||
<div v-for="(_, i) in form.main_changes" :key="i" class="flex items-center gap-2">
|
<div v-for="(_, i) in form.main_changes" :key="i" class="flex items-center gap-2">
|
||||||
<span class="w-5 shrink-0 text-sm text-slate-400">{{ i + 1 }}.</span>
|
<span class="w-5 shrink-0 text-sm text-slate-400">{{ i + 1 }}.</span>
|
||||||
<NInput v-model:value="form.main_changes[i]" type="text" placeholder="" />
|
<NInput v-model:value="form.main_changes[i]" type="text" placeholder="" />
|
||||||
<NPopconfirm
|
<NPopconfirm
|
||||||
v-if="form.main_changes.length > 1"
|
v-if="form.main_changes.length > 1"
|
||||||
positive-text="确定"
|
positive-text="确定"
|
||||||
:positiveButtonProps="{ size: 'tiny' }"
|
:positive-button-props="{ size: 'tiny' }"
|
||||||
:negativeButtonProps="{ size: 'tiny' }"
|
:negative-button-props="{ size: 'tiny' }"
|
||||||
@positive-click="form.main_changes.splice(i, 1)"
|
@positive-click="form.main_changes.splice(i, 1)"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton text type="error">
|
<NButton text type="error">
|
||||||
<Icon icon="ci:close-circle" class="size-16px" />
|
<Icon icon="ci:close-circle" class="size-16px" />
|
||||||
</NButton>
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
确定删除吗?
|
确定删除吗?
|
||||||
</NPopconfirm>
|
</NPopconfirm>
|
||||||
</div>
|
</div>
|
||||||
<NButton text type="primary" class="text-xs" @click="form.main_changes = [...form.main_changes, '']">
|
<NButton text type="primary" class="text-xs" @click="form.main_changes = [...form.main_changes, '']">
|
||||||
<Icon icon="ic:round-plus" class="size-14px" /> 添加变更点
|
<Icon icon="ic:round-plus" class="size-14px" /> 添加变更点
|
||||||
</NButton>
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 连带变更内容 -->
|
<!-- 连带变更内容 -->
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600">连带变更内容</div>
|
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600">连带变更内容</div>
|
||||||
<div class="flex-1 space-y-2">
|
<div class="flex-1 space-y-2">
|
||||||
<div v-for="(_, i) in form.related_changes" :key="i" class="flex items-center gap-2">
|
<div v-for="(_, i) in form.related_changes" :key="i" class="flex items-center gap-2">
|
||||||
<span class="w-5 shrink-0 text-sm text-slate-400">{{ i + 1 }}.</span>
|
<span class="w-5 shrink-0 text-sm text-slate-400">{{ i + 1 }}.</span>
|
||||||
<NInput v-model:value="form.related_changes[i]" placeholder="连带变更点(如:更新操作规程)" />
|
<NInput v-model:value="form.related_changes[i]" placeholder="连带变更点(如:更新操作规程)" />
|
||||||
<NPopconfirm
|
<NPopconfirm
|
||||||
v-if="form.related_changes.length > 1"
|
v-if="form.related_changes.length > 1"
|
||||||
positive-text="确定"
|
positive-text="确定"
|
||||||
:positiveButtonProps="{ size: 'tiny' }"
|
:positive-button-props="{ size: 'tiny' }"
|
||||||
:negativeButtonProps="{ size: 'tiny' }"
|
:negative-button-props="{ size: 'tiny' }"
|
||||||
@positive-click="form.related_changes.splice(i, 1)"
|
@positive-click="form.related_changes.splice(i, 1)"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@@ -532,108 +534,109 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
|
|||||||
</template>
|
</template>
|
||||||
确定删除吗?
|
确定删除吗?
|
||||||
</NPopconfirm>
|
</NPopconfirm>
|
||||||
</div>
|
</div>
|
||||||
<NButton text type="primary" class="text-xs" @click="form.related_changes = [...form.related_changes, '']">
|
<NButton text type="primary" class="text-xs" @click="form.related_changes = [...form.related_changes, '']">
|
||||||
<Icon icon="ic:round-plus" class="size-14px" /> 添加连带变更点
|
<Icon icon="ic:round-plus" class="size-14px" /> 添加连带变更点
|
||||||
</NButton>
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 变更类型 / 变更等级 -->
|
<!-- 变更类型 / 变更等级 -->
|
||||||
<div class="grid gap-4 grid-cols-2">
|
<div class="grid gap-4 grid-cols-2">
|
||||||
<!-- 变更类型 -->
|
<!-- 变更类型 -->
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">变更类型 <span class="text-red-500">*</span></div>
|
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">变更类型 <span class="text-red-500">*</span></div>
|
||||||
<div class="flex flex-1 items-center gap-4 rounded-lg bg-slate-50 px-3 py-2">
|
<div class="flex flex-1 items-center gap-4 rounded-lg bg-slate-50 px-3 py-2">
|
||||||
<NRadioGroup v-model:value="form.change_type">
|
<NRadioGroup v-model:value="form.change_type">
|
||||||
<div class="flex gap-x-2">
|
<div class="flex gap-x-2">
|
||||||
<NRadio v-for="t in props.typeList" :key="t.value" :value="t.value">{{t.label}}</NRadio>
|
<NRadio v-for="t in props.typeList" :key="t.value" :value="t.value">{{ t.label }}</NRadio>
|
||||||
</div>
|
</div>
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
<NButton v-if="props.isAiOpen" text type="primary" class="ml-auto text-xs hover:underline" @click="openTypeSheet">
|
<NButton v-if="props.isAiOpen" text type="primary" class="ml-auto text-xs hover:underline" @click="openTypeSheet">
|
||||||
<Icon icon="lucide:sparkles" class="size-12px" />
|
<Icon icon="lucide:sparkles" class="size-12px" />
|
||||||
AI 判定说明
|
AI 判定说明
|
||||||
</NButton>
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 变更等级 -->
|
<!-- 变更等级 -->
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">变更等级 <span class="text-red-500">*</span></div>
|
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">变更等级 <span class="text-red-500">*</span></div>
|
||||||
<div class="flex flex-1 items-center gap-4 rounded-lg bg-slate-50 px-3 py-2">
|
<div class="flex flex-1 items-center gap-4 rounded-lg bg-slate-50 px-3 py-2">
|
||||||
<NRadioGroup v-model:value="form.change_level" disabled>
|
<NRadioGroup v-model:value="form.change_level" disabled>
|
||||||
<div class="flex gap-x-2">
|
<div class="flex gap-x-2">
|
||||||
<NRadio v-for="t in props.levelList" :key="t.value" :value="t.value">{{t.label}}</NRadio>
|
<NRadio v-for="t in props.levelList" :key="t.value" :value="t.value">{{ t.label }}</NRadio>
|
||||||
</div>
|
</div>
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
<NButton text type="primary" class="ml-auto text-xs hover:underline" @click="openLevelSheet">
|
<NButton text type="primary" class="ml-auto text-xs hover:underline" @click="openLevelSheet">
|
||||||
<Icon icon="lucide:list-checks" class="size-12px" /> 等级判定表 ({{ totalScore }}分) →
|
<Icon icon="lucide:list-checks" class="size-12px" /> 等级判定表 ({{ totalScore }}分) →
|
||||||
</NButton>
|
</NButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- 变更时限 / 风险分析 -->
|
||||||
<!-- 变更时限 / 风险分析 -->
|
<div class="grid gap-4 grid-cols-2">
|
||||||
<div class="grid gap-4 grid-cols-2">
|
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">变更时限 <span class="text-red-500">*</span></div>
|
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600 text-right">变更时限 <span class="text-red-500">*</span></div>
|
||||||
<div class="flex flex-1 items-center gap-4 rounded-lg bg-slate-50 px-3 py-2">
|
<div class="flex flex-1 items-center gap-4 rounded-lg bg-slate-50 px-3 py-2">
|
||||||
<NRadioGroup v-model:value="form.duration_type">
|
<NRadioGroup v-model:value="form.duration_type">
|
||||||
<div class="flex gap-x-2">
|
<div class="flex gap-x-2">
|
||||||
<NRadio v-for="t in [1, 2]" :key="t" :value="t">
|
<NRadio v-for="t in [1, 2]" :key="t" :value="t">
|
||||||
{{ t === 1 ? '永久' : '临时' }}
|
{{ t === 1 ? '永久' : '临时' }}
|
||||||
</NRadio>
|
</NRadio>
|
||||||
</div>
|
</div>
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
<NCheckbox v-model:checked="form.urgent" :checked-value="1" :unchecked-value="0">紧急</NCheckbox>
|
<NCheckbox v-model:checked="form.urgent" :checked-value="1" :unchecked-value="0">紧急</NCheckbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-3">
|
<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 flex-1 flex-wrap items-center gap-2 rounded-lg bg-slate-50 px-3 py-2">
|
<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 辅助分析在各工具插件内使用"
|
<NButton
|
||||||
class="text-sm"
|
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"
|
:ghost="form.risk_tools?.includes(t.key) ? false : true"
|
||||||
:type="form.risk_tools?.includes(t.key) ? 'primary' : 'default'"
|
:type="form.risk_tools?.includes(t.key) ? 'primary' : 'default'"
|
||||||
|
@click="toggleTool(t)"
|
||||||
>
|
>
|
||||||
{{ t.label }}
|
{{ t.label }}
|
||||||
</NButton>
|
</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>
|
</NButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-if="form.urgent" class="text-xs text-red-500 ml-27">紧急变更为单独标记:走快速通道,事后限期补办风险分析与完整审批</div>
|
||||||
<div v-if="form.urgent" class="text-xs text-red-500 ml-27">紧急变更为单独标记:走快速通道,事后限期补办风险分析与完整审批</div>
|
<!-- 计划投用时间 -->
|
||||||
<!-- 计划投用时间 -->
|
<div class="flex gap-3">
|
||||||
<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 flex-1 flex-wrap items-center gap-2">
|
<div class="flex flex-1 flex-wrap items-center gap-2">
|
||||||
<NDatePicker v-model:value="form.plan_use_date" type="date" value-format="yyyy-MM-dd" class="!max-w-[220px]" />
|
<NDatePicker v-model:value="form.plan_use_date" type="date" value-format="yyyy-MM-dd" class="!max-w-[220px]" />
|
||||||
<!-- 临时 -->
|
<!-- 临时 -->
|
||||||
<template v-if="form.duration_type === 2">
|
<template v-if="form.duration_type === 2">
|
||||||
<span class="whitespace-nowrap text-sm text-slate-500">计划恢复时间 <span class="text-red-500">*</span></span>
|
<span class="whitespace-nowrap text-sm text-slate-500">计划恢复时间 <span class="text-red-500">*</span></span>
|
||||||
<NDatePicker v-model:value="form.restore_deadline" type="date" value-format="yyyy-MM-dd" class="!max-w-[220px]" />
|
<NDatePicker v-model:value="form.restore_deadline" type="date" value-format="yyyy-MM-dd" class="!max-w-[220px]" />
|
||||||
<span class="text-xs text-amber-600">临时变更到期系统自动提醒恢复原状</span>
|
<span class="text-xs text-amber-600">临时变更到期系统自动提醒恢复原状</span>
|
||||||
</template>
|
</template>
|
||||||
<!-- 永久 -->
|
<!-- 永久 -->
|
||||||
<span v-else class="whitespace-nowrap text-xs text-slate-300">(勾选「临时」后在此并排填写计划恢复时间)</span>
|
<span v-else class="whitespace-nowrap text-xs text-slate-300">(勾选「临时」后在此并排填写计划恢复时间)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 所需材料 -->
|
<!-- 所需材料 -->
|
||||||
<div class="flex gap-3">
|
<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="flex-1 space-y-2">
|
||||||
<div class="text-[11px] text-slate-400">施工所需材料(生成变更申请表时系统自动生成,支持手动修改)</div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<!-- 资料上传 -->
|
<!-- 资料上传 -->
|
||||||
<div class="flex gap-3">
|
<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 flex-1 flex-wrap items-center gap-2 rounded-lg border border-dashed p-3">
|
<div class="flex flex-1 flex-wrap items-center gap-2 rounded-lg border border-dashed p-3">
|
||||||
<div
|
<div
|
||||||
class="relative"
|
v-for="(f,index) in uploadFile"
|
||||||
:style="{'--theme-color':themeStore.themeColor}"
|
:key="f.id"
|
||||||
v-for="(f,index) in uploadFile" :key="f.id"
|
class="relative" :style="{'--theme-color':themeStore.themeColor}"
|
||||||
>
|
>
|
||||||
<div
|
<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)]"
|
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>
|
</div>
|
||||||
<NPopconfirm
|
<NPopconfirm
|
||||||
positive-text="确定"
|
positive-text="确定"
|
||||||
:positiveButtonProps="{ size: 'tiny' }"
|
:positive-button-props="{ size: 'tiny' }"
|
||||||
:negativeButtonProps="{ size: 'tiny' }"
|
:negative-button-props="{ size: 'tiny' }"
|
||||||
@positive-click="removeFile(index)"
|
@positive-click="removeFile(index)"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@@ -664,102 +667,102 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
|
|||||||
<input ref="uploadFileRef" type="file" accept="image/*,.pdf,.docx,.xlsx" class="hidden" @change="onUploadFile" />
|
<input ref="uploadFileRef" type="file" accept="image/*,.pdf,.docx,.xlsx" class="hidden" @change="onUploadFile" />
|
||||||
<span class="text-xs text-slate-400">(点击附件名弹窗预览,支持 PDF / Word / 图片在线查看)</span>
|
<span class="text-xs text-slate-400">(点击附件名弹窗预览,支持 PDF / Word / 图片在线查看)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 需更新的资料 -->
|
<!-- 需更新的资料 -->
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600">需更新的资料</div>
|
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600">需更新的资料</div>
|
||||||
<div class="flex flex-1 flex-wrap items-center gap-x-4 gap-y-1.5 rounded-lg bg-slate-50 px-3 py-2">
|
<div class="flex flex-1 flex-wrap items-center gap-x-4 gap-y-1.5 rounded-lg bg-slate-50 px-3 py-2">
|
||||||
<NCheckboxGroup v-model:value="form.update_docs">
|
<NCheckboxGroup v-model:value="form.update_docs">
|
||||||
<div class="flex flex-wrap gap-y-2 gap-x-4">
|
<div class="flex flex-wrap gap-y-2 gap-x-4">
|
||||||
<NCheckbox v-for="p in UPDATE_DOC_OPTIONS" :key="p" :value="p" :label="p" />
|
<NCheckbox v-for="p in UPDATE_DOC_OPTIONS" :key="p" :value="p" :label="p" />
|
||||||
</div>
|
|
||||||
</NCheckboxGroup>
|
|
||||||
<span class="self-center text-xs text-slate-400">(多选,变更关闭前逐项确认上传最新版本)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 涉及专业 -->
|
|
||||||
<div class="flex gap-3">
|
|
||||||
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600">涉及专业</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<div class="flex flex-wrap items-center gap-x-4 gap-y-1.5 rounded-lg bg-slate-50 px-3 py-2">
|
|
||||||
<NCheckboxGroup v-model:value="form.disciplines">
|
|
||||||
<div class="flex flex-wrap gap-y-2 gap-x-4">
|
|
||||||
<NCheckbox v-for="p in specialties" :key="p" :value="p" :label="p" />
|
|
||||||
</div>
|
</div>
|
||||||
</NCheckboxGroup>
|
</NCheckboxGroup>
|
||||||
<span class="self-center text-xs text-slate-400">(多选,决定审批流中专业会签/审批的路由范围)</span>
|
<span class="self-center text-xs text-slate-400">(多选,变更关闭前逐项确认上传最新版本)</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="signers && signers.length" class="mt-1.5 flex flex-wrap items-center gap-1.5 text-xs text-slate-500">
|
</div>
|
||||||
|
<!-- 涉及专业 -->
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<div class="w-24 shrink-0 pt-2 text-sm font-medium text-slate-600">涉及专业</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex flex-wrap items-center gap-x-4 gap-y-1.5 rounded-lg bg-slate-50 px-3 py-2">
|
||||||
|
<NCheckboxGroup v-model:value="form.disciplines">
|
||||||
|
<div class="flex flex-wrap gap-y-2 gap-x-4">
|
||||||
|
<NCheckbox v-for="p in specialties" :key="p" :value="p" :label="p" />
|
||||||
|
</div>
|
||||||
|
</NCheckboxGroup>
|
||||||
|
<span class="self-center text-xs text-slate-400">(多选,决定审批流中专业会签/审批的路由范围)</span>
|
||||||
|
</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" />已联动专业会签:
|
<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>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Footer
|
||||||
<Footer
|
ref="footerRef"
|
||||||
ref="footerRef"
|
:type="props.type"
|
||||||
:type="props.type"
|
:current-id="props.currentId"
|
||||||
:currentId="props.currentId"
|
:title="props.title"
|
||||||
:title="props.title"
|
:is-ai-open="props.isAiOpen"
|
||||||
:isAiOpen="props.isAiOpen"
|
:current-form="form"
|
||||||
:currentForm="form"
|
:confirmed="props.confirmed"
|
||||||
:confirmed="props.confirmed"
|
:members="props.members"
|
||||||
:members="props.members"
|
:steps="currentSteps"
|
||||||
:steps="currentSteps"
|
@save="saveDraft"
|
||||||
@save="saveDraft"
|
@submit="submitConfirm"
|
||||||
@submit="submitConfirm"
|
@confirm-tab="confirmTab"
|
||||||
@confirmTab="confirmTab"
|
@regenerate="onRegenerate"
|
||||||
@regenerate="onRegenerate"
|
@update:steps="updateSteps"
|
||||||
@update:steps="updateSteps"
|
/>
|
||||||
/>
|
<!-- 侧边抽屉:AI 类型判定 / 等级判定表 -->
|
||||||
<!-- 侧边抽屉:AI 类型判定 / 等级判定表 -->
|
<NDrawer :show="sheet !== ''" placement="right" :width="sheet === 'level' ? '45rem' : '28rem'" :auto-focus="false" @update:show="(v) => !v && (sheet = '')">
|
||||||
<NDrawer :show="sheet !== ''" @update:show="(v) => !v && (sheet = '')" placement="right" :width="sheet === 'level' ? '45rem' : '28rem'" :auto-focus="false">
|
<NDrawerContent>
|
||||||
<NDrawerContent>
|
<template #header>
|
||||||
<template #header>
|
<div v-if="sheet === 'type'" class="text-base font-semibold text-slate-800">AI 变更类型判定说明</div>
|
||||||
<div v-if="sheet === 'type'" class="text-base font-semibold text-slate-800">AI 变更类型判定说明</div>
|
<div v-if="sheet === 'level'" class="text-base font-semibold text-slate-800">变更等级判定表</div>
|
||||||
<div v-if="sheet === 'level'" class="text-base font-semibold text-slate-800">变更等级判定表</div>
|
|
||||||
</template>
|
|
||||||
<div class="h-full space-y-4 overflow-y-auto">
|
|
||||||
<!-- AI 变更类型判定说明 -->
|
|
||||||
<template v-if="sheet === 'type'">
|
|
||||||
<NTag v-if="typeAiLoading" type="info" size="small">
|
|
||||||
<template #icon>
|
|
||||||
<Icon icon="ri:loader-4-fill" class="size-12px animate-spin" />
|
|
||||||
</template>
|
|
||||||
AI 判定中…
|
|
||||||
</NTag>
|
|
||||||
<div v-else class="flex items-center gap-2 max-h-[22px]">
|
|
||||||
<span class="inline-flex items-center gap-1 rounded-full border border-violet-200 bg-violet-100 px-2 py-0.5 text-xs font-medium text-violet-700">
|
|
||||||
✨ AI 生成 · 需人工确认
|
|
||||||
</span>
|
|
||||||
<NTag v-if="typeAi?.type" type="info" size="small">判定:{{ typeAi?.type }}</NTag>
|
|
||||||
<NTag v-if="typeAi" size="small">置信度:{{ `${Math.round(typeAi.confidence * 100)}%` }}</NTag>
|
|
||||||
</div>
|
|
||||||
<div v-if="typeAi" class="space-y-3 text-sm leading-relaxed text-slate-700">
|
|
||||||
<p><b>判定结论:</b>{{ typeAi.conclusion }}</p>
|
|
||||||
<p><b>判定依据:</b></p>
|
|
||||||
<ul class="list-disc space-y-1 pl-5">
|
|
||||||
<li v-for="r in typeAi.hit_rules" :key="r">命中规则:{{ r }}</li>
|
|
||||||
<li v-for="r in typeAi.exclude_rules" :key="r">排除规则:{{ r }}</li>
|
|
||||||
</ul>
|
|
||||||
<p v-if="typeAi.knowledge_refs.length"><b>知识库引用:</b>{{ typeAi.knowledge_refs.join('、') }}。</p>
|
|
||||||
<p class="rounded-lg bg-amber-50 p-3 text-xs text-amber-700">AI 判定结果仅供参考,最终类型以人工确认为准;如调整为其他类型,系统将记录修改留痕。</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
<div class="h-full space-y-4 overflow-y-auto">
|
||||||
|
<!-- AI 变更类型判定说明 -->
|
||||||
|
<template v-if="sheet === 'type'">
|
||||||
|
<NTag v-if="typeAiLoading" type="info" size="small">
|
||||||
|
<template #icon>
|
||||||
|
<Icon icon="ri:loader-4-fill" class="size-12px animate-spin" />
|
||||||
|
</template>
|
||||||
|
AI 判定中…
|
||||||
|
</NTag>
|
||||||
|
<div v-else class="flex items-center gap-2 max-h-[22px]">
|
||||||
|
<span class="inline-flex items-center gap-1 rounded-full border border-violet-200 bg-violet-100 px-2 py-0.5 text-xs font-medium text-violet-700">
|
||||||
|
✨ AI 生成 · 需人工确认
|
||||||
|
</span>
|
||||||
|
<NTag v-if="typeAi?.type" type="info" size="small">判定:{{ typeAi?.type }}</NTag>
|
||||||
|
<NTag v-if="typeAi" size="small">置信度:{{ `${Math.round(typeAi.confidence * 100)}%` }}</NTag>
|
||||||
|
</div>
|
||||||
|
<div v-if="typeAi" class="space-y-3 text-sm leading-relaxed text-slate-700">
|
||||||
|
<p><b>判定结论:</b>{{ typeAi.conclusion }}</p>
|
||||||
|
<p><b>判定依据:</b></p>
|
||||||
|
<ul class="list-disc space-y-1 pl-5">
|
||||||
|
<li v-for="r in typeAi.hit_rules" :key="r">命中规则:{{ r }}</li>
|
||||||
|
<li v-for="r in typeAi.exclude_rules" :key="r">排除规则:{{ r }}</li>
|
||||||
|
</ul>
|
||||||
|
<p v-if="typeAi.knowledge_refs.length"><b>知识库引用:</b>{{ typeAi.knowledge_refs.join('、') }}。</p>
|
||||||
|
<p class="rounded-lg bg-amber-50 p-3 text-xs text-amber-700">AI 判定结果仅供参考,最终类型以人工确认为准;如调整为其他类型,系统将记录修改留痕。</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 变更等级判定表 -->
|
<!-- 变更等级判定表 -->
|
||||||
<template v-if="sheet === 'level'">
|
<template v-if="sheet === 'level'">
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<NButton v-if="props.isAiOpen" size="small" type="primary">
|
|
||||||
<div v-if="levelAiLoading" class="flex items-center gap-1 text-xs">
|
|
||||||
<Icon icon="ri:loader-4-fill" class="size-14px animate-spin" />AI 评分中
|
|
||||||
</div>
|
|
||||||
<div v-else class="flex items-center gap-1 text-xs" @click="useAiLevel">
|
|
||||||
使用AI 评分
|
|
||||||
</div>
|
|
||||||
</NButton>
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
<NButton v-if="props.isAiOpen" size="small" type="primary">
|
||||||
|
<div v-if="levelAiLoading" class="flex items-center gap-1 text-xs">
|
||||||
|
<Icon icon="ri:loader-4-fill" class="size-14px animate-spin" />AI 评分中
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex items-center gap-1 text-xs" @click="useAiLevel">
|
||||||
|
使用AI 评分
|
||||||
|
</div>
|
||||||
|
</NButton>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
<NTag
|
<NTag
|
||||||
v-if="currentLevel"
|
v-if="currentLevel"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -769,86 +772,87 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
|
|||||||
borderColor: tagBorderColor(currentLevel?.color),
|
borderColor: tagBorderColor(currentLevel?.color),
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ currentLevel?.label ? currentLevel.label+'变更' : '' }}
|
{{ currentLevel?.label ? currentLevel.label+'变更' : '' }}
|
||||||
</NTag>
|
</NTag>
|
||||||
<span class="text-xs text-slate-400">合计得分 <b class="text-[#17407F]">{{ totalScore }}</b> 分(<20 分为一般,≥20 分为重要)</span>
|
<span class="text-xs text-slate-400">合计得分 <b class="text-[#17407F]">{{ totalScore }}</b> 分(<20 分为一般,≥20 分为重要)</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="grid gap-3 grid-cols-2">
|
||||||
<div class="grid gap-3 grid-cols-2">
|
|
||||||
<div v-for="(dim, di) in questions" :key="dim.name" class="rounded-lg border p-3">
|
<div v-for="(dim, di) in questions" :key="dim.name" class="rounded-lg border p-3">
|
||||||
<div class="mb-2 flex items-baseline justify-between gap-2">
|
<div class="mb-2 flex items-baseline justify-between gap-2">
|
||||||
<span class="text-sm font-semibold text-slate-700">
|
<span class="text-sm font-semibold text-slate-700">
|
||||||
{{ dim.name }}
|
{{ dim.name }}
|
||||||
<span v-if="dim.note" class="ml-1 text-xs font-normal text-slate-400">({{ dim.note }})</span>
|
<span v-if="dim.note" class="ml-1 text-xs font-normal text-slate-400">({{ dim.note }})</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm text-slate-500">得分 <b class="text-[#17407F]">{{ questions[di].score }}</b></span>
|
<span class="text-sm text-slate-500">得分 <b class="text-[#17407F]">{{ questions[di].score }}</b></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<NRadioGroup v-model:value="questions[di].score" size="small" :disabled="levelAiLoading" name="radiogroup" :on-update:value="(v)=>handleScoreChange(v,di)">
|
<NRadioGroup v-model:value="questions[di].score" size="small" :disabled="levelAiLoading" name="radiogroup" :on-update:value="(v)=>handleScoreChange(v,di)">
|
||||||
<div class="flex flex-col justify-center gap-2">
|
<div class="flex flex-col justify-center gap-2">
|
||||||
<NRadio v-for="op in dim.options" :key="op.score" :value="op.score">
|
<NRadio v-for="op in dim.options" :key="op.score" :value="op.score">
|
||||||
<p class="text-xs">{{ op.label }}({{ op.score }}分)</p>
|
<p class="text-xs">{{ op.label }}({{ op.score }}分)</p>
|
||||||
</NRadio>
|
</NRadio>
|
||||||
</div>
|
</div>
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="props.isAiOpen && (levelAiText || levelAiBasis.some((x) => x))" class="rounded-lg border border-violet-200 bg-violet-50/50 p-4">
|
|
||||||
<div class="flex items-center gap-2 text-sm font-semibold text-violet-700">
|
|
||||||
<Icon icon="lucide:sparkles" class="size-14px" />
|
|
||||||
AI 评分分析过程
|
|
||||||
<span v-if="levelAiLoading" class="flex items-center gap-1 text-xs font-normal text-violet-500">
|
|
||||||
<Icon icon="ri:loader-4-fill" class="size-12px animate-spin" /> AI 评分中…
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 space-y-3 text-xs leading-relaxed text-slate-600">
|
<div v-if="props.isAiOpen && (levelAiText || levelAiBasis.some((x) => x))" class="rounded-lg border border-violet-200 bg-violet-50/50 p-4">
|
||||||
<p v-if="levelAiText">{{ levelAiText }}</p>
|
<div class="flex items-center gap-2 text-sm font-semibold text-violet-700">
|
||||||
<p v-for="(dim, di) in questions" :key="dim.name">
|
<Icon icon="lucide:sparkles" class="size-14px" />
|
||||||
|
AI 评分分析过程
|
||||||
|
<span v-if="levelAiLoading" class="flex items-center gap-1 text-xs font-normal text-violet-500">
|
||||||
|
<Icon icon="ri:loader-4-fill" class="size-12px animate-spin" /> AI 评分中…
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 space-y-3 text-xs leading-relaxed text-slate-600">
|
||||||
|
<p v-if="levelAiText">{{ levelAiText }}</p>
|
||||||
|
<p v-for="(dim, di) in questions" :key="dim.name">
|
||||||
<template v-if="levelAiBasis[di]"><b class="text-[#17407F]">{{ dim.name }}:</b>{{ levelAiBasis[di] }}</template>
|
<template v-if="levelAiBasis[di]"><b class="text-[#17407F]">{{ dim.name }}:</b>{{ levelAiBasis[di] }}</template>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-slate-400">(各维度得分均可人工改判,修改留痕;改判后等级自动回填至申请表)</p>
|
<p class="text-slate-400">(各维度得分均可人工改判,修改留痕;改判后等级自动回填至申请表)</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</NDrawerContent>
|
|
||||||
</NDrawer>
|
|
||||||
|
|
||||||
<!-- 附件在线预览弹窗 -->
|
|
||||||
<NModal
|
|
||||||
:show="previewFile !== null"
|
|
||||||
preset="card"
|
|
||||||
:auto-focus="false"
|
|
||||||
:style="{ width: '80%', height: 'auto' }"
|
|
||||||
:segmented="{ content: true, footer: true }"
|
|
||||||
@update:show="(v) => !v && (previewFile = null)"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
|
||||||
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #default>
|
|
||||||
<div class="space-y-3">
|
|
||||||
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
|
||||||
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
|
||||||
<span>上传时间:{{ previewFile?.created_at }}</span>
|
|
||||||
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<previewComponent v-if="previewFile" :previewFile="previewFile" />
|
</NDrawerContent>
|
||||||
</div>
|
</NDrawer>
|
||||||
</template>
|
|
||||||
<template #footer>
|
<!-- 附件在线预览弹窗 -->
|
||||||
<div class="flex justify-end gap-[10px]">
|
<NModal
|
||||||
<NButton ghost size="small" @click="downloadFile(previewFile)">
|
:show="previewFile !== null"
|
||||||
<Icon icon="material-symbols:download" class="size-16px" /> 下载
|
preset="card"
|
||||||
</NButton>
|
:auto-focus="false"
|
||||||
<NButton size="small" type="primary" @click="previewFile = null">关闭</NButton>
|
:style="{ width: '80%', height: 'auto' }"
|
||||||
</div>
|
:segmented="{ content: true, footer: true }"
|
||||||
</template>
|
@update:show="(v) => !v && (previewFile = null)"
|
||||||
</NModal>
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
||||||
|
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
||||||
|
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
||||||
|
<span>上传时间:{{ previewFile?.created_at }}</span>
|
||||||
|
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
||||||
|
</div>
|
||||||
|
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-[10px]">
|
||||||
|
<NButton ghost size="small" @click="downloadFile(previewFile)">
|
||||||
|
<Icon icon="material-symbols:download" class="size-16px" /> 下载
|
||||||
|
</NButton>
|
||||||
|
<NButton size="small" type="primary" @click="previewFile = null">关闭</NButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
:deep(.n-radio) {
|
:deep(.n-radio) {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -858,4 +862,4 @@ defineExpose({form, questions, applyAiResult, generateLevel: useAiLevel})
|
|||||||
height: calc(100vh - 379px);
|
height: calc(100vh - 379px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { inject, watch, ref, computed } from 'vue';
|
import { inject, watch, ref, computed } from 'vue';
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
import { getColorWithOpacity } from "@/utils/common";
|
||||||
import { AiError, aiApplication, aiRecognize, aiRiskPreAnalysis } from "@/service/api/ai";
|
import { AiError, aiApplication, aiRecognize, aiRiskPreAnalysis } from "@/service/api/ai";
|
||||||
import ChangeCompareTable from "./ChangeCompareTable.vue";
|
import ChangeCompareTable from "./ChangeCompareTable.vue";
|
||||||
import PreRisk from "./preRisk.vue";
|
import PreRisk from "./preRisk.vue";
|
||||||
@@ -158,17 +159,17 @@ watch(() => props.currentForm, (newVal:any) => {
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</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)}">
|
||||||
<span class="text-xs text-slate-400">预识别阶段不设审批人与提交:完成识别与预分析后,请切换至「变更申请表」选择审批人并提交</span>
|
<span class="text-xs text-slate-500">预识别阶段不设审批人与提交:完成识别与预分析后,请切换至「变更申请表」选择审批人并提交</span>
|
||||||
<div class="ml-auto flex shrink-0 gap-2">
|
<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">
|
<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-if="analyzing" icon="ri:loader-4-fill" class="size-14px animate-spin" />
|
||||||
<Icon v-else icon="mdi:shield-alert-outline" class="size-14px" />风险预分析
|
<Icon v-else icon="mdi:shield-alert-outline" class="size-14px" />风险预分析
|
||||||
</NButton>
|
</NButton>
|
||||||
<NButton type="primary" size="small" :disabled="btnDisabled" :loading="saveLoading" @click="saveDraft">保存</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, inject, ref, watch } from 'vue';
|
import { computed, inject, ref, watch } from 'vue';
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
import { getColorWithOpacity } from "@/utils/common";
|
||||||
import { useAiAnalysisApi } from '@/service/api/change';
|
import { useAiAnalysisApi } from '@/service/api/change';
|
||||||
|
|
||||||
|
const themeStore = useThemeStore();
|
||||||
const props = defineProps([
|
const props = defineProps([
|
||||||
'type',
|
'type',
|
||||||
'currentId',
|
'currentId',
|
||||||
@@ -255,7 +258,7 @@ defineExpose({setRegenerateLoading})
|
|||||||
</NButton>
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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" />
|
<Icon icon="lucide:users" class="size-14px" />
|
||||||
<span class="min-w-0 truncate">
|
<span class="min-w-0 truncate">
|
||||||
@@ -286,7 +289,7 @@ defineExpose({setRegenerateLoading})
|
|||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<NTag type="info" size="small">{{Number(idx)+1}}</NTag>
|
<NTag type="info" size="small">{{Number(idx)+1}}</NTag>
|
||||||
<p class="text-sm">{{s.name}}</p>
|
<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>
|
<span class="text-xs text-slate-500">专业会签步骤</span>
|
||||||
</NCheckbox>
|
</NCheckbox>
|
||||||
<NButton ghost type="primary" size="tiny" @click="togglePicker(s)" title="添加预设人员" class="ml-auto">
|
<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)" />
|
<RichTextInput :disabled="!preRiskEdit" :modelValue="form[s.key]" :minRows="2" :bordered="false" @update:modelValue="(v) => onCellChange(s.key,v)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-lg bg-amber-50 px-3 py-2 text-xs leading-5 text-amber-700">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ onMounted(() => {
|
|||||||
<div class="flex flex-wrap items-center gap-3">
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<div class="text-sm font-semibold text-slate-800 dark:text-white">{{ d.title }}</div>
|
<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 class="font-mono">{{ d.change_no }}</span>
|
||||||
<span>保存于 {{ d.updated_at }}</span>
|
<span>保存于 {{ d.updated_at }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ onMounted(() => {
|
|||||||
<div v-if="item.status!=='REJECTED'" class="min-w-0 flex-1">
|
<div v-if="item.status!=='REJECTED'" class="min-w-0 flex-1">
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<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
|
<NTag
|
||||||
size="small"
|
size="small"
|
||||||
@@ -378,10 +378,10 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-1 flex flex-wrap items-baseline gap-x-2">
|
<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 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>
|
||||||
<!-- 当前节点与会签成员进度;状态更新时间跟在审核人员名字后面 -->
|
<!-- 当前节点与会签成员进度;状态更新时间跟在审核人员名字后面 -->
|
||||||
<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="text-slate-400">当前节点</span>
|
||||||
<span class="font-medium text-slate-600">{{ c.step_name }}</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">
|
<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">
|
<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="primary" @click="openDetail(c,'detail')">详情</NButton>
|
||||||
<NButton class="text-xs" text type="warning" @click="openProgress(c)">进度</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>
|
||||||
<!-- 驳回处理 -->
|
<!-- 驳回处理 -->
|
||||||
<div v-if="c.status === 'REJECTED'" class="flex gap-3">
|
<div v-if="c.status === 'REJECTED'" class="flex gap-3">
|
||||||
@@ -470,8 +471,8 @@ onMounted(() => {
|
|||||||
<NDrawerContent title="变更进度">
|
<NDrawerContent title="变更进度">
|
||||||
<div class="rounded-lg border bg-slate-50/60 px-3 py-2.5">
|
<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="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>
|
<span>{{ currentRow?.change_no }}</span>
|
||||||
<NTag v-if="currentRow.status==='APPROVING'" size="small" type="warning">审批中</NTag>
|
<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==='APPROVED'" size="small" type="success">已通过</NTag>
|
||||||
<NTag v-if="currentRow.status==='IMPLEMENTED'" 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>
|
<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>
|
<span v-if="currentRow?.overdue_status===2" class="font-medium text-red-500">超期 {{ currentRow?.overdue_days }} 天</span>
|
||||||
</div>
|
</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?.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-[11px] text-slate-400">{{ currentRow?.meta }}</div>
|
<div v-if="currentRow?.meta" class="mt-1 text-xs text-slate-400">{{ currentRow?.meta }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div v-for="(s, i) in currentRow?.approval_records" :key="s.id" class="flex gap-3">
|
<div v-for="(s, i) in currentRow?.approval_records" :key="s.id" class="flex gap-3">
|
||||||
@@ -498,11 +499,11 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="pb-5">
|
<div class="pb-5">
|
||||||
<p class="text-sm">{{ s.action }}</p>
|
<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>
|
||||||
</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 ?? '—' }} · 恢复 / 延期 / 转永久请在详情页中操作
|
临时变更:计划恢复 {{ currentRow?.restore_deadline ?? '—' }} · 恢复 / 延期 / 转永久请在详情页中操作
|
||||||
</div>
|
</div>
|
||||||
</NDrawerContent>
|
</NDrawerContent>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const viewFile = (file: any) => {
|
|||||||
};
|
};
|
||||||
// 下载
|
// 下载
|
||||||
const downloadFile = (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) => {
|
const openChange = (record: any) => {
|
||||||
@@ -99,121 +99,121 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace vertical :size="16">
|
<NSpace vertical :size="16">
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<div class="flex flex-wrap items-center gap-2 mb-3">
|
<div class="flex flex-wrap items-center gap-2 mb-3">
|
||||||
<span class="text-sm font-semibold text-slate-700">待投用变更(本车间 · {{ org }})</span>
|
<span class="text-sm font-semibold text-slate-700">待投用变更(本车间 · {{ org }})</span>
|
||||||
<NTag size="small" type="primary">{{ pagination.itemCount }} 起</NTag>
|
<NTag size="small" type="primary">{{ pagination.itemCount }} 起</NTag>
|
||||||
<span class="ml-auto text-[11px] text-slate-400">按审批通过时间排序 · 投用时间由发起人在资料关闭页填报 · 支持属地负责人在此代录投用日期</span>
|
<span class="ml-auto text-[11px] text-slate-400">按审批通过时间排序 · 投用时间由发起人在资料关闭页填报 · 支持属地负责人在此代录投用日期</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="scroll">
|
<div class="scroll">
|
||||||
<div
|
<div
|
||||||
class="space-y-3 h-100%"
|
class="space-y-3 h-100%"
|
||||||
:style="{
|
:style="{
|
||||||
'--theme-color': themeStore.themeColor,
|
'--theme-color': themeStore.themeColor,
|
||||||
'--warning-color': themeStore.otherColor.warning,
|
'--warning-color': themeStore.otherColor.warning,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div v-for="(s, i) in list" :key="`${s.id ?? 'x'}-${i}`" class="flex flex-wrap items-center gap-x-3 gap-y-2 border p-4" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<div v-for="(s, i) in list" :key="`${s.id ?? 'x'}-${i}`" class="flex flex-wrap items-center gap-x-3 gap-y-2 border p-4" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<!-- 状态标签 -->
|
<!-- 状态标签 -->
|
||||||
<NTag v-if="s.status==='APPROVING'" size="small" type="warning">审批中</NTag>
|
<NTag v-if="s.status==='APPROVING'" size="small" type="warning">审批中</NTag>
|
||||||
<NTag v-if="s.status==='APPROVED'" size="small" type="success">已通过</NTag>
|
<NTag v-if="s.status==='APPROVED'" size="small" type="success">已通过</NTag>
|
||||||
<NTag v-if="s.status==='IMPLEMENTED'" size="small" type="success">待验收</NTag>
|
<NTag v-if="s.status==='IMPLEMENTED'" size="small" type="success">待验收</NTag>
|
||||||
<NTag v-if="s.status==='ACCEPTED'" size="small" type="error">待关闭</NTag>
|
<NTag v-if="s.status==='ACCEPTED'" size="small" type="error">待关闭</NTag>
|
||||||
<NTag v-if="s.status==='CLOSED'" size="small" type="success">已关闭</NTag>
|
<NTag v-if="s.status==='CLOSED'" size="small" type="success">已关闭</NTag>
|
||||||
<NTag v-if="s.status==='WITHDRAWN'" size="small" type="error">已撤回</NTag>
|
<NTag v-if="s.status==='WITHDRAWN'" size="small" type="error">已撤回</NTag>
|
||||||
<NTag v-if="s.status==='REJECTED'" size="small" type="error">已驳回</NTag>
|
<NTag v-if="s.status==='REJECTED'" size="small" type="error">已驳回</NTag>
|
||||||
|
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<div class="flex flex-wrap items-center gap-1.5">
|
<div class="flex flex-wrap items-center gap-1.5">
|
||||||
<p class="truncate text-sm font-medium text-slate-800">{{ s.title }}</p>
|
<p class="truncate text-sm font-medium text-slate-800">{{ s.title }}</p>
|
||||||
<!-- 等级 -->
|
<!-- 等级 -->
|
||||||
<NTag
|
<NTag
|
||||||
size="small"
|
size="small"
|
||||||
:color="{
|
:color="{
|
||||||
color: tagBgColor(matchObj(levelList,s.change_level)?.color),
|
color: tagBgColor(matchObj(levelList,s.change_level)?.color),
|
||||||
textColor: tagTextColor(matchObj(levelList,s.change_level)?.color),
|
textColor: tagTextColor(matchObj(levelList,s.change_level)?.color),
|
||||||
borderColor: tagBorderColor(matchObj(levelList,s.change_level)?.color),
|
borderColor: tagBorderColor(matchObj(levelList,s.change_level)?.color),
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ matchObj(levelList,s.change_level)?.label }}
|
{{ matchObj(levelList,s.change_level)?.label }}
|
||||||
</NTag>
|
</NTag>
|
||||||
<!-- 类型 -->
|
<!-- 类型 -->
|
||||||
<NTag
|
<NTag
|
||||||
size="small"
|
size="small"
|
||||||
:color="{
|
:color="{
|
||||||
color: tagBgColor(matchObj(typeList,s.change_type)?.color),
|
color: tagBgColor(matchObj(typeList,s.change_type)?.color),
|
||||||
textColor: tagTextColor(matchObj(typeList,s.change_type)?.color),
|
textColor: tagTextColor(matchObj(typeList,s.change_type)?.color),
|
||||||
borderColor: tagBorderColor(matchObj(typeList,s.change_type)?.color),
|
borderColor: tagBorderColor(matchObj(typeList,s.change_type)?.color),
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ matchObj(typeList,s.change_type)?.label }}
|
{{ matchObj(typeList,s.change_type)?.label }}
|
||||||
</NTag>
|
</NTag>
|
||||||
<NTag v-if="s.duration_type===2" size="small" type="warning">临时</NTag>
|
<NTag v-if="s.duration_type===2" size="small" type="warning">临时</NTag>
|
||||||
<NTag v-if="s.duration_type===1" size="small" type="info">永久</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>
|
|
||||||
</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>
|
|
||||||
<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>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<NButton size="small" ghost class="text-xs" @click="openChange(s)">打开变更单</NButton>
|
|
||||||
</div>
|
|
||||||
<div v-if="list.length === 0" class="h-100% flex items-center justify-center">
|
|
||||||
<NEmpty description="暂无数据"></NEmpty>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<p class="mt-0.5 text-xs text-slate-400">{{ s.change_no }} · {{ s.applicant_name }}</p>
|
||||||
<div class="flex justify-end mt-3">
|
<p class="text-slate-400 text-xs">审批通过时间:<span class="font-medium text-slate-600">{{ s.approve_time?.slice(0,16) }}</span></p>
|
||||||
<NPagination
|
<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>
|
||||||
v-model:page="pagination.page"
|
</div>
|
||||||
:page-size="pagination.pageSize"
|
<div class="flex items-center gap-1.5 rounded-md border bg-slate-50/60 px-2 py-1">
|
||||||
:item-count="pagination.itemCount"
|
<Icon icon="akar-icons:file" class="size-14px text-[var(--theme-color)]" />
|
||||||
:on-update:page="pageChange"
|
<span class="text-xs font-medium text-slate-600">PSSR 确认表</span>
|
||||||
>
|
<template v-if="s.pssr_attachment">
|
||||||
<template #prefix="{ itemCount }">
|
<NButton ghost size="tiny" class="text-11px" @click="viewFile(s)">查看</NButton>
|
||||||
共 {{ itemCount }} 条
|
<NButton ghost size="tiny" class="text-11px" @click="downloadFile(s)">下载</NButton>
|
||||||
</template>
|
</template>
|
||||||
</NPagination>
|
<template v-else>
|
||||||
|
<span class="text-xs text-[var(--warning-color)]">待传</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<NButton size="small" ghost class="text-xs" @click="openChange(s)">打开变更单</NButton>
|
||||||
</div>
|
</div>
|
||||||
</NCard>
|
<div v-if="list.length === 0" class="h-100% flex items-center justify-center">
|
||||||
<!-- 附件在线预览弹窗 -->
|
<NEmpty description="暂无数据"></NEmpty>
|
||||||
<NModal
|
</div>
|
||||||
v-model:show="previewModal"
|
</div>
|
||||||
preset="card"
|
</div>
|
||||||
:auto-focus="false"
|
<div class="flex justify-end mt-3">
|
||||||
:style="{ width: '80%', height: 'auto' }"
|
<NPagination
|
||||||
:segmented="{ content: true, footer: true }"
|
v-model:page="pagination.page"
|
||||||
>
|
:page-size="pagination.pageSize"
|
||||||
<template #header>
|
:item-count="pagination.itemCount"
|
||||||
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
:on-update:page="pageChange"
|
||||||
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
>
|
||||||
</div>
|
<template #prefix="{ itemCount }">
|
||||||
|
共 {{ itemCount }} 条
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
</NPagination>
|
||||||
<div class="space-y-3">
|
</div>
|
||||||
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
</NCard>
|
||||||
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
<!-- 附件在线预览弹窗 -->
|
||||||
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
|
<NModal
|
||||||
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
v-model:show="previewModal"
|
||||||
</div>
|
preset="card"
|
||||||
<previewComponent v-if="previewFile" :previewFile="previewFile" />
|
:auto-focus="false"
|
||||||
</div>
|
:style="{ width: '80%', height: 'auto' }"
|
||||||
</template>
|
:segmented="{ content: true, footer: true }"
|
||||||
</NModal>
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
||||||
|
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
||||||
|
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
||||||
|
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
|
||||||
|
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
||||||
|
</div>
|
||||||
|
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NModal>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
+123
-119
@@ -5,7 +5,7 @@ import { Icon } from '@iconify/vue'
|
|||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
import { formatTimestamp, getTimeRangeStamp, downloadFileFun } from '@/utils/common';
|
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';
|
import { pssrListApi } from '@/service/api/pssr';
|
||||||
|
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
@@ -71,7 +71,7 @@ const viewFile = (file: any) => {
|
|||||||
};
|
};
|
||||||
// 下载
|
// 下载
|
||||||
const downloadFile = (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) => {
|
const downloadTemplate = (record: any) => {
|
||||||
@@ -115,128 +115,132 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace vertical :size="16">
|
<NSpace vertical :size="16">
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<div
|
<div
|
||||||
class="flex flex-wrap items-center gap-2 mb-3"
|
class="flex flex-wrap items-center gap-2 mb-3"
|
||||||
:style="{ '--theme-color': themeStore.themeColor}"
|
: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'">
|
|
||||||
未关闭的变更 <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'">
|
|
||||||
已关闭的变更 <b>{{ closedCount }}</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<NInput v-model:value="formInfo.kw" size="small" class="text-xs !w-160px" clearable placeholder="搜索变更名称 / 编号" @clear="formInfo.kw = ''; searchHandle()">
|
|
||||||
<template #suffix>
|
|
||||||
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" @click="searchHandle" />
|
|
||||||
</template>
|
|
||||||
</NInput>
|
|
||||||
<div class="flex items-center gap-1.5">
|
|
||||||
<NButton
|
|
||||||
size="tiny"
|
|
||||||
round
|
|
||||||
v-for="t in timeRange"
|
|
||||||
:key="t.value"
|
|
||||||
class="text-[11px]"
|
|
||||||
:type="range === t.value ? 'primary' : 'default'"
|
|
||||||
@click="timeRangeChange(t.value)"
|
|
||||||
>
|
|
||||||
{{ t.label }}
|
|
||||||
</NButton>
|
|
||||||
</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">
|
|
||||||
培训 / PSSR 为线下组织并签字,拍照或扫描上传即视为完成确认并随单归档;系统不做内容识别,本页为 PSSR 检查表的查看 / 下载入口。
|
|
||||||
</div>
|
|
||||||
<!-- 列表 -->
|
|
||||||
<div class="scroll">
|
|
||||||
<div
|
|
||||||
v-if="list.length"
|
|
||||||
class="space-y-3 h-100%"
|
|
||||||
:style="{
|
|
||||||
'--theme-color': themeStore.themeColor,
|
|
||||||
'--warning-color': themeStore.otherColor.warning,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div v-for="(s, i) in list" :key="`${s.id ?? 'x'}-${i}`" class="flex flex-wrap items-center gap-x-3 gap-y-2 border p-4" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
|
||||||
<!-- 状态标签 -->
|
|
||||||
<NTag v-if="s.status==='APPROVING'" size="small" type="warning">审批中</NTag>
|
|
||||||
<NTag v-if="s.status==='APPROVED'" size="small" type="success">已通过</NTag>
|
|
||||||
<NTag v-if="s.status==='IMPLEMENTED'" size="small" type="success">待验收</NTag>
|
|
||||||
<NTag v-if="s.status==='ACCEPTED'" size="small" type="error">待关闭</NTag>
|
|
||||||
<NTag v-if="s.status==='CLOSED'" size="small" type="success">已关闭</NTag>
|
|
||||||
<NTag v-if="s.status==='WITHDRAWN'" size="small" type="error">已撤回</NTag>
|
|
||||||
<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>
|
|
||||||
</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>
|
|
||||||
<template v-if="s.pssr_attachment">
|
|
||||||
<span class="max-w-[150px] truncate text-[11px] 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>
|
|
||||||
<NButton ghost size="tiny" class="text-11px" @click="downloadTemplate(s)">下载模板</NButton>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<NButton v-if="s.status==='APPROVING' || s.status==='APPROVED' || s.status==='ACCEPTED'" size="small" type="primary" class="text-xs" @click="openChange(s)">打开变更单</NButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="h-100% flex items-center justify-center">
|
|
||||||
<NEmpty description="暂无数据"></NEmpty>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-end mt-3">
|
|
||||||
<NPagination
|
|
||||||
v-model:page="pagination.page"
|
|
||||||
:page-size="pagination.pageSize"
|
|
||||||
:item-count="pagination.itemCount"
|
|
||||||
:on-update:page="pageChange"
|
|
||||||
>
|
|
||||||
<template #prefix="{ itemCount }">
|
|
||||||
共 {{ itemCount }} 条
|
|
||||||
</template>
|
|
||||||
</NPagination>
|
|
||||||
</div>
|
|
||||||
</NCard>
|
|
||||||
<!-- 附件在线预览弹窗 -->
|
|
||||||
<NModal
|
|
||||||
v-model:show="previewModal"
|
|
||||||
preset="card"
|
|
||||||
:auto-focus="false"
|
|
||||||
:style="{ width: '80%', height: 'auto' }"
|
|
||||||
:segmented="{ content: true, footer: true }"
|
|
||||||
>
|
>
|
||||||
<template #header>
|
<div class="flex overflow-hidden rounded-full border">
|
||||||
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
<div
|
||||||
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
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>
|
@click="tabChange(0)"
|
||||||
|
>
|
||||||
|
未关闭的变更 <b>{{ openCount }}</b>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
<NInput v-model:value="formInfo.kw" size="small" class="text-xs !w-160px" clearable placeholder="搜索变更名称 / 编号" @clear="formInfo.kw = ''; searchHandle()">
|
||||||
|
<template #suffix>
|
||||||
|
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" @click="searchHandle" />
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
</NInput>
|
||||||
<div class="space-y-3">
|
<div class="flex items-center gap-1.5">
|
||||||
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
<NButton
|
||||||
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
v-for="t in timeRange"
|
||||||
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
|
:key="t.value"
|
||||||
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
size="tiny"
|
||||||
</div>
|
round
|
||||||
<previewComponent v-if="previewFile" :previewFile="previewFile" />
|
class="text-[11px]"
|
||||||
</div>
|
:type="range === t.value ? 'primary' : 'default'"
|
||||||
|
@click="timeRangeChange(t.value)"
|
||||||
|
>
|
||||||
|
{{ t.label }}
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
<span class="ml-auto text-[11px] text-slate-400">培训 / PSSR 为线下确认 + 资料上传制 · PSSR 检查表同行查看 / 下载</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs leading-4 text-slate-400 pb-2">
|
||||||
|
培训 / PSSR 为线下组织并签字,拍照或扫描上传即视为完成确认并随单归档;系统不做内容识别,本页为 PSSR 检查表的查看 / 下载入口。
|
||||||
|
</div>
|
||||||
|
<!-- 列表 -->
|
||||||
|
<div class="scroll">
|
||||||
|
<div
|
||||||
|
v-if="list.length"
|
||||||
|
class="space-y-3 h-100%"
|
||||||
|
:style="{
|
||||||
|
'--theme-color': themeStore.themeColor,
|
||||||
|
'--warning-color': themeStore.otherColor.warning,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div v-for="(s, i) in list" :key="`${s.id ?? 'x'}-${i}`" class="flex flex-wrap items-center gap-x-3 gap-y-2 border p-4" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
|
<!-- 状态标签 -->
|
||||||
|
<NTag v-if="s.status==='APPROVING'" size="small" type="warning">审批中</NTag>
|
||||||
|
<NTag v-if="s.status==='APPROVED'" size="small" type="success">已通过</NTag>
|
||||||
|
<NTag v-if="s.status==='IMPLEMENTED'" size="small" type="success">待验收</NTag>
|
||||||
|
<NTag v-if="s.status==='ACCEPTED'" size="small" type="error">待关闭</NTag>
|
||||||
|
<NTag v-if="s.status==='CLOSED'" size="small" type="success">已关闭</NTag>
|
||||||
|
<NTag v-if="s.status==='WITHDRAWN'" size="small" type="error">已撤回</NTag>
|
||||||
|
<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-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-xs font-medium text-slate-600">PSSR 检查确认表</span>
|
||||||
|
<template v-if="s.pssr_attachment">
|
||||||
|
<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-xs text-[var(--warning-color)]">待传</span>
|
||||||
|
<NButton ghost size="tiny" class="text-11px" @click="downloadTemplate(s)">下载模板</NButton>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<NButton v-if="s.status==='APPROVING' || s.status==='APPROVED' || s.status==='ACCEPTED'" size="small" type="primary" class="text-xs" @click="openChange(s)">打开变更单</NButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="h-100% flex items-center justify-center">
|
||||||
|
<NEmpty description="暂无数据"></NEmpty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end mt-3">
|
||||||
|
<NPagination
|
||||||
|
v-model:page="pagination.page"
|
||||||
|
:page-size="pagination.pageSize"
|
||||||
|
:item-count="pagination.itemCount"
|
||||||
|
:on-update:page="pageChange"
|
||||||
|
>
|
||||||
|
<template #prefix="{ itemCount }">
|
||||||
|
共 {{ itemCount }} 条
|
||||||
</template>
|
</template>
|
||||||
</NModal>
|
</NPagination>
|
||||||
|
</div>
|
||||||
|
</NCard>
|
||||||
|
<!-- 附件在线预览弹窗 -->
|
||||||
|
<NModal
|
||||||
|
v-model:show="previewModal"
|
||||||
|
preset="card"
|
||||||
|
:auto-focus="false"
|
||||||
|
:style="{ width: '80%', height: 'auto' }"
|
||||||
|
:segmented="{ content: true, footer: true }"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
||||||
|
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
|
||||||
|
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
||||||
|
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
|
||||||
|
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
||||||
|
</div>
|
||||||
|
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NModal>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">本月会签 / 审批</p>
|
<p class="text-12px font-600">本月会签 / 审批</p>
|
||||||
@@ -69,7 +69,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">我的会签平均耗时</p>
|
<p class="text-12px font-600">我的会签平均耗时</p>
|
||||||
@@ -86,7 +86,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">按时完成率</p>
|
<p class="text-12px font-600">按时完成率</p>
|
||||||
@@ -102,7 +102,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">参与风险分析</p>
|
<p class="text-12px font-600">参与风险分析</p>
|
||||||
@@ -118,7 +118,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">当前待我审批</p>
|
<p class="text-12px font-600">当前待我审批</p>
|
||||||
@@ -136,7 +136,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}"
|
||||||
:style="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -179,7 +179,7 @@ onMounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">本专业变更类型分布(近一年)</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
|
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
|
||||||
@@ -187,7 +187,7 @@ onMounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">高风险在办(直达风险分析)</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<div v-if="homeData?.high_risk?.total > 0" class="space-y-2 pt-1">
|
<div v-if="homeData?.high_risk?.total > 0" class="space-y-2 pt-1">
|
||||||
|
|||||||
+42
-16
@@ -78,7 +78,7 @@ onMounted(() => {
|
|||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<NSpace vertical :size="16">
|
<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 flex-wrap items-center justify-between">
|
||||||
<div class="flex items-center gap-1.5 text-sm font-semibold text-slate-700">
|
<div class="flex items-center gap-1.5 text-sm font-semibold text-slate-700">
|
||||||
<span>统计中心</span>
|
<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}">
|
<NGrid cols="s:1 m:4 l:4" responsive="screen" :x-gap="16" :y-gap="16" :style="{'--theme-color': themeStore.themeColor}">
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">变更总数</p>
|
<p class="text-12px font-600">变更总数</p>
|
||||||
@@ -115,7 +115,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">重要变更</p>
|
<p class="text-12px font-600">重要变更</p>
|
||||||
@@ -131,7 +131,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">变更费用合计</p>
|
<p class="text-12px font-600">变更费用合计</p>
|
||||||
@@ -148,7 +148,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">完全达到率</p>
|
<p class="text-12px font-600">完全达到率</p>
|
||||||
@@ -167,7 +167,7 @@ onMounted(() => {
|
|||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<!-- 各车间变更数量 -->
|
<!-- 各车间变更数量 -->
|
||||||
<NGi span="24 s:24 m:24 l:14">
|
<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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">各车间变更数量({{dateLabel}})</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<ColumnChart v-if="homeData" :data="homeData?.workshop_counts?.items || []" />
|
<ColumnChart v-if="homeData" :data="homeData?.workshop_counts?.items || []" />
|
||||||
@@ -176,7 +176,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:10">
|
<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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">变更类型分布({{dateLabel}})</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<RingChart v-if="homeData" :data="homeData?.type_dist?.items || []" />
|
<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>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<!-- 各审批人审批时长 -->
|
<!-- 各审批人审批时长 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
<NCard :bordered="false" size="small" class="shadow-[0_0_15px_rgba(0,0,0,0.1)]" :content-style="{padding: 0}"
|
||||||
:style="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -244,8 +244,9 @@ onMounted(() => {
|
|||||||
<!-- 变更是否达到预期效果及费用 -->
|
<!-- 变更是否达到预期效果及费用 -->
|
||||||
<NGi span="24 s:24 m:24 l:16">
|
<NGi span="24 s:24 m:24 l:16">
|
||||||
<NCard
|
<NCard
|
||||||
:bordered="true"
|
:bordered="false"
|
||||||
size="small"
|
size="small"
|
||||||
|
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
:content-style="{padding: 0}"
|
:content-style="{padding: 0}"
|
||||||
:style="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
@@ -317,8 +318,9 @@ onMounted(() => {
|
|||||||
<!-- 变更带来的风险增量及管控 -->
|
<!-- 变更带来的风险增量及管控 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard
|
<NCard
|
||||||
:bordered="true"
|
:bordered="false"
|
||||||
size="small"
|
size="small"
|
||||||
|
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
:content-style="{padding: 0}"
|
:content-style="{padding: 0}"
|
||||||
:style="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
@@ -358,7 +360,11 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<!-- 当前未关闭变更 -->
|
<!-- 当前未关闭变更 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
<NCard
|
||||||
|
:bordered="false"
|
||||||
|
size="small"
|
||||||
|
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
|
:content-style="{padding: 0}"
|
||||||
:style="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -395,7 +401,11 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<!-- 驳回的变更申请 -->
|
<!-- 驳回的变更申请 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
<NCard
|
||||||
|
:bordered="false"
|
||||||
|
size="small"
|
||||||
|
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
|
:content-style="{padding: 0}"
|
||||||
:style="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -431,7 +441,11 @@ onMounted(() => {
|
|||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<!-- 各部门审批时长 -->
|
<!-- 各部门审批时长 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">各部门审批时长(天 · {{dateLabel}})</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
@@ -456,7 +470,11 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<!-- 临时变更后续处置方式 -->
|
<!-- 临时变更后续处置方式 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
<NCard
|
||||||
|
:bordered="false"
|
||||||
|
size="small"
|
||||||
|
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
|
:content-style="{padding: 0}"
|
||||||
:style="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -519,7 +537,11 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<!-- 主要驳回理由汇总 -->
|
<!-- 主要驳回理由汇总 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
<NCard
|
||||||
|
:bordered="false"
|
||||||
|
size="small"
|
||||||
|
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
|
:content-style="{padding: 0}"
|
||||||
:style="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -557,7 +579,11 @@ onMounted(() => {
|
|||||||
<!-- 变更归档资料情况 -->
|
<!-- 变更归档资料情况 -->
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:24 l:24">
|
<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="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('my_highrisk')"
|
@click="jumpTo('my_highrisk')"
|
||||||
>
|
>
|
||||||
@@ -82,7 +82,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -100,7 +100,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -118,7 +118,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
@click="jumpTo('changeledger')"
|
@click="jumpTo('changeledger')"
|
||||||
>
|
>
|
||||||
@@ -146,7 +146,12 @@ onMounted(() => {
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{ padding: '0' }" :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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">月度新增固有风险项(管控前)</p>
|
||||||
<ColumnChart v-if="homeData" :data="homeData?.monthly_inherent || []" />
|
<ColumnChart v-if="homeData" :data="homeData?.monthly_inherent || []" />
|
||||||
</NCard>
|
</NCard>
|
||||||
@@ -170,7 +175,12 @@ onMounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{ padding: '0' }" :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>
|
<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">
|
<div class="space-y-5 px-3 pt-5 pb-3">
|
||||||
<!-- 投用前完成率 -->
|
<!-- 投用前完成率 -->
|
||||||
@@ -201,7 +211,12 @@ onMounted(() => {
|
|||||||
</NGrid>
|
</NGrid>
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive :style="{'--theme-color':themeStore.themeColor}">
|
<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">
|
<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>
|
<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="flex justify-center pt-5 pb-3">
|
||||||
<div class="w-50% flex flex-col items-center gap-1.5">
|
<div class="w-50% flex flex-col items-center gap-1.5">
|
||||||
@@ -218,7 +233,12 @@ onMounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{ padding: '0' }" :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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">超期未关闭清单</p>
|
||||||
<div class="space-y-2 p-3">
|
<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">
|
<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>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{ padding: '0' }" :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>
|
<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 class="grid grid-cols-2 gap-3 p-3">
|
||||||
<div v-for="r in SAFETY_EXPORTS" :key="r" @click="handleExport(r)"
|
<div v-for="r in SAFETY_EXPORTS" :key="r" @click="handleExport(r)"
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">本月会签 / 审批</p>
|
<p class="text-12px font-600">本月会签 / 审批</p>
|
||||||
@@ -69,7 +69,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">我的会签平均耗时</p>
|
<p class="text-12px font-600">我的会签平均耗时</p>
|
||||||
@@ -86,7 +86,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">按时完成率</p>
|
<p class="text-12px font-600">按时完成率</p>
|
||||||
@@ -102,7 +102,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">参与风险分析</p>
|
<p class="text-12px font-600">参与风险分析</p>
|
||||||
@@ -118,7 +118,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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%' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">当前待我审批</p>
|
<p class="text-12px font-600">当前待我审批</p>
|
||||||
@@ -136,7 +136,11 @@ onMounted(() => {
|
|||||||
|
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}"
|
<NCard
|
||||||
|
:bordered="false"
|
||||||
|
size="small"
|
||||||
|
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
|
:content-style="{padding: 0}"
|
||||||
:style="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -179,7 +183,12 @@ onMounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">本专业变更类型分布(近一年)</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
|
<RingChart v-if="homeData" :data="homeData?.type_dist || []" />
|
||||||
@@ -187,7 +196,12 @@ onMounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">高风险在办(直达风险分析)</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<div v-if="homeData?.high_risk?.total > 0" class="space-y-2 pt-1">
|
<div v-if="homeData?.high_risk?.total > 0" class="space-y-2 pt-1">
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ onMounted(() => {
|
|||||||
<NGrid cols="s:1 m:4 l:4" responsive="screen" :x-gap="16" :y-gap="16">
|
<NGrid cols="s:1 m:4 l:4" responsive="screen" :x-gap="16" :y-gap="16">
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">本车间变更总数</p>
|
<p class="text-12px font-600">本车间变更总数</p>
|
||||||
@@ -81,7 +81,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">在办变更</p>
|
<p class="text-12px font-600">在办变更</p>
|
||||||
@@ -98,7 +98,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">超期未关闭</p>
|
<p class="text-12px font-600">超期未关闭</p>
|
||||||
@@ -115,7 +115,7 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
<div
|
<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' }"
|
:style="{ borderRadius: themeStore.themeRadius + 'px' }"
|
||||||
>
|
>
|
||||||
<p class="text-12px font-600">我的审批按时率</p>
|
<p class="text-12px font-600">我的审批按时率</p>
|
||||||
@@ -136,7 +136,12 @@ onMounted(() => {
|
|||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<!-- 我的审批效率 -->
|
<!-- 我的审批效率 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">我的审批效率(考核口径)</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
@@ -161,7 +166,12 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<!-- 本车间变更状态构成 -->
|
<!-- 本车间变更状态构成 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">本车间变更状态构成(实时)</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<div class="space-y-2.5">
|
<div class="space-y-2.5">
|
||||||
@@ -185,7 +195,12 @@ onMounted(() => {
|
|||||||
</NGi>
|
</NGi>
|
||||||
<!-- 临时变更监控 -->
|
<!-- 临时变更监控 -->
|
||||||
<NGi span="24 s:24 m:24 l:8">
|
<NGi span="24 s:24 m:24 l:8">
|
||||||
<NCard :bordered="true" size="small" :content-style="{padding: 0}" :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>
|
<p class=" text-base font-semibold border-b border-slate-100 p-3">临时变更监控(管理抓手)</p>
|
||||||
<div class="py-3 px-4">
|
<div class="py-3 px-4">
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
@@ -220,7 +235,11 @@ onMounted(() => {
|
|||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<!-- 变更类型分布 -->
|
<!-- 变更类型分布 -->
|
||||||
<NGi span="24 s:24 m:24 l:10">
|
<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="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -234,8 +253,9 @@ onMounted(() => {
|
|||||||
<!-- 风险增量与管控 -->
|
<!-- 风险增量与管控 -->
|
||||||
<NGi span="24 s:24 m:24 l:14">
|
<NGi span="24 s:24 m:24 l:14">
|
||||||
<NCard
|
<NCard
|
||||||
:bordered="true"
|
:bordered="false"
|
||||||
size="small"
|
size="small"
|
||||||
|
class="shadow-[0_0_15px_rgba(0,0,0,0.1)]"
|
||||||
:content-style="{padding: 0}"
|
:content-style="{padding: 0}"
|
||||||
:style="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
@@ -277,7 +297,11 @@ onMounted(() => {
|
|||||||
<!-- 变更投入和效果 -->
|
<!-- 变更投入和效果 -->
|
||||||
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive>
|
||||||
<NGi span="24 s:24 m:24 l:24">
|
<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="{
|
:style="{
|
||||||
borderRadius: themeStore.themeRadius + 'px',
|
borderRadius: themeStore.themeRadius + 'px',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
|||||||
Reference in New Issue
Block a user