Files
moc/src/views/my/initiateChange/index.vue
T
2026-09-07 17:27:28 +08:00

384 lines
12 KiB
Vue

<script setup lang="ts">
import { computed, onMounted, provide, ref } from 'vue';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { getColorWithOpacity } from "@/utils/common";
import { localStg } from '@/utils/storage';
import { queryUnsavedApi, saveChangeApi, submitChangeApi } from '@/service/api/change';
import { systemConfigApi, templateListApi, } from '@/service/api/system';
import { orgListApi } from '@/service/api/user';
import ChangePre from "./modules/changePre.vue";
import ChangeApply from "./modules/changeApply.vue";
import ChangeTraining from "./modules/changeTraining.vue";
import RiskAnalysis from "./modules/riskAnalysis.vue";
import Pssr from "./modules/pssr.vue";
import Evaluation from "./modules/evaluation.vue";
import CloseConfirm from "./modules/closeConfirm.vue";
const themeStore = useThemeStore();
const saveLoading = ref<boolean>(false)
const btnDisabled = ref<boolean>(false);
provide('generalLoading', {saveLoading,btnDisabled})
// **********************通用字段***********************
const changePreRef = ref<any>(null);
const changeApplyRef = ref<any>(null);
const riskAnalysisRef = ref<any>(null);
const changeTrainingRef = ref<any>(null);
const pssrRef = ref<any>(null);
const evaluationRef = ref<any>(null);
const closeConfirmRef = ref<any>(null);
// 组织树
const orgTree = ref<any>([]);
// 变更类型选项
const typeList = ref<{label: string, value: number, color: string}[]>([]);
// 变更等级选项
const levelList = ref<{label: string, value: number, color: string}[]>([]);
// 模板列表
const templateList = ref<any[]>([]);
// 是否开启ai功能
const aiEnabled = ref<boolean>(localStg.get('userInfo')?.ai_assist_enabled=='1' ? true : false);
// 变更名称
const title = ref<string>('');
// 变更编号
const changeNo = ref<string>('');
// 当前id
const currentId = ref<number>(0);
// 是否确认
const confirmed = ref<{[key: string]: boolean}>({
'pre': false,
'form': false,
'risk': false,
'train': false,
'pssr': false,
'evaluation': false,
// 'close': false,
});
// 变更预分析表单数据
const changePreForm = ref<any>(null)
const changeApplyForm = ref<any>(null)
// **********************tab切换***********************
const TABS = [
{ id: "pre", name: "变更预识别", icon: 'lucide:sparkles' },
{ id: "form", name: "变更申请表", icon: 'akar-icons:file' },
{ id: "risk", name: "风险分析记录表", icon: 'proicons:alert-triangle' },
{ id: "train", name: "变更培训内容", icon: 'lucide:graduation-cap' },
{ id: "pssr", name: "PSSR 检查内容", icon: 'tabler:clipboard-check' },
{ id: "evaluation", name: "验收评价", icon: 'material-symbols:check-circle-outline' },
// { id: "close", name: "变更关闭确认表", icon: 'quill:folder-open' },
];
// 当前标签
const tab = ref<string>(aiEnabled.value ? "pre" : "form");
// tab 列表
const tabList = computed(() => {
return aiEnabled.value ? TABS : TABS.filter((x) => x.id !== "pre");
});
// tab 切换
const tabChange = (id: string) => {
if(tab.value==='pre'){
changePreForm.value = changePreRef.value?.form;
// if(!changePreForm.value?.description){
// return window.$message?.warning('请先填写变更内容描述');
// }
}
if(tab.value==='form'){
changeApplyForm.value = changeApplyRef.value?.form;
}
if(tab.value === id) {
return;
}
tab.value = id;
}
// 标签内容确认
const confirmTab = (tab: string, val: boolean) => {
confirmed.value[tab] = val;
}
// 查询变更未保存数据
const getId = async () => {
const {data,error} = await queryUnsavedApi();
if(!error){
currentId.value = data.id;
changeNo.value = data.change_no;
}
}
// 获取配置
const getConfig = async () => {
const {data,error} = await systemConfigApi();
if(!error){
// 解析 变更类型设置
const changeTypeGroup = data.find((group:{group_key:string}) => group.group_key === 'change_type');
if (changeTypeGroup) {
typeList.value = changeTypeGroup.items.map((item:any) => ({
label: item.label,
value: Number(item.value), // 或者 parseInt(item.value, 10)
color: item.color
}));
}
// 解析 变更等级设置
const changeLevelGroup = data.find((group:{group_key:string}) => group.group_key === 'change_level');
if (changeLevelGroup) {
levelList.value = changeLevelGroup.items.map((item:any) => ({
label: item.label,
value: Number(item.value), // 或者 parseInt(item.value, 10)
color: item.color
}));
}
}
}
// 获取模板配置
const getTemplateConfig = async () => {
const {data,error} = await templateListApi();
if(!error){
templateList.value = data;
}
}
// 获取组织列表
const getOrgList = async () => {
const {data,error} = await orgListApi();
if(!error){
orgTree.value = data;
}
}
// 获取所有表单数据
const getAllFormData = () => {
let changePreForm = changePreRef.value?.form;
let changeApplyForm = changeApplyRef.value?.form;
let changeRiskForm = riskAnalysisRef.value?.form;
let changeTrainingForm = changeTrainingRef.value?.form;
let pssrForm = pssrRef.value?.list;
let evaluationForm = evaluationRef.value?.list;
// let closeConfirmForm = closeConfirmRef.value?.form;
return {
title: title.value,
...changePreForm,
...changeApplyForm,
...changeRiskForm,
...changeTrainingForm,
...pssrForm,
...evaluationForm,
};
}
// 保存草稿
const saveDraft = async () => {
let form = getAllFormData();
saveLoading.value = true;
btnDisabled.value = true;
const {error} = await saveChangeApi(currentId.value, form);
if(!error){
window.$message?.success("已保存为草稿,可在左侧「我的草稿」中继续编辑");
}
saveLoading.value = false;
btnDisabled.value = false;
}
// 变更申请表表单必填判断
const changeApplyRules = (form:any) => {
if(form.org_id === null){
window.$message?.warning('请选择申请部门');
return true
}
if(form.main_changes?.length===0){
window.$message?.warning('请添加主要变更内容');
return true
}
if(form.main_changes.some((str:string)=>str.trim()==='')){
window.$message?.warning('请填写主要变更内容');
return true
}
if(form.change_type===null){
window.$message?.warning('请选择变更类型');
return true
}
if(form.change_level===null){
window.$message?.warning('请选择变更等级');
return true
}
if(form.duration_type===null){
window.$message?.warning('请选择变更时限');
return true
}
if(form.duration_type === 1 && form.restore_deadline===null){
window.$message?.warning('请选择计划恢复时间');
return true
}
return false
}
// 提交
const submitConfirm = async (type: string) => {
if(aiEnabled.value){
const allTrue = Object.values(confirmed.value).every(value => value === true);
if(!allTrue){
return window.$message?.warning('全部标签页确认后方可提交(标签栏已用红点标出)');
}
}
let form = getAllFormData();
if(form.title.trim() === ''){
return window.$message?.warning('请填写变更名称');
}
// 变更申请表
if(type==='form'){
if(changeApplyRules(form)){
return;
}
}
// 变更培训内容
if(type==='train'){
if(form.content.trim() === ''){
return window.$message?.warning('请填写培训内容');
}
}
const {error} = await submitChangeApi(currentId.value, form);
if(!error){
window.$message?.success("提交成功");
}
}
onMounted(() => {
getOrgList();
getConfig();
getTemplateConfig();
getId();
});
</script>
<template>
<NSpace vertical :size="16">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px', height: '100%' }">
<!-- 顶部 -->
<div class="border-b bg-[var(--bg-color)]" :style="{'--bg-color': getColorWithOpacity(themeStore.themeColor, 0.1)}">
<div
class="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)}"
>
<span class="whitespace-nowrap text-sm font-medium text-slate-600">变更名称 <span class="text-red-500">*</span></span>
<NInput v-model:value="title" maxlength="28" show-count placeholder="AI 自动总结(28 字以内),可手动修改" class="!w-420px" />
<span class="ml-auto whitespace-nowrap text-sm text-slate-500">变更编号</span>
<NInput :value="changeNo" disabled class="!w-44" placeholder="" />
</div>
<div class="flex items-center gap-1 overflow-x-auto px-3 pt-2">
<div v-for="t in tabList" :key="t.id" @click="tabChange(t.id)"
class="flex items-center gap-1.5 whitespace-nowrap px-3 py-2 text-sm transition cursor-pointer border hover:bg-white/60 rounded-t-lg"
:class="tab === t.id ? 'border-b-transparent border-[var(--border-color)] bg-white text-[var(--active-color)]':'border-transparent text-slate-500'"
:style="{
'--border-color': getColorWithOpacity(themeStore.themeColor, 0.3),
'--active-color': themeStore.themeColor
}"
>
<Icon :icon="t.icon" class="size-14px" />
{{ t.name }}
<span v-if="!confirmed[t.id] && t.id!=='pre' && aiEnabled" class="h-1.5 w-1.5 rounded-full bg-red-500"></span>
<Icon v-if="confirmed[t.id] && t.id!=='pre' && aiEnabled" icon="material-symbols:check-circle-outline" class="size-14px text-emerald-500" />
</div>
</div>
</div>
<!-- ===== 变更预识别 ===== -->
<div v-show="tab === 'pre'">
<ChangePre
ref="changePreRef"
:title="title"
:currentId="currentId"
@save="saveDraft"
/>
</div>
<!-- ===== 变更申请表 ===== -->
<div v-show="tab === 'form'">
<ChangeApply
ref="changeApplyRef"
:type="tab"
:orgTree="orgTree"
:templateList="templateList"
:typeList="typeList"
:levelList="levelList"
:isAiOpen="aiEnabled"
:title="title"
:currentId="currentId"
:formInfo="changePreForm"
@save="saveDraft"
@submit="submitConfirm"
@confirmTab="confirmTab"
/>
</div>
<!-- ===== 风险分析记录表 ===== -->
<div v-show="tab === 'risk'">
<RiskAnalysis
ref="riskAnalysisRef"
:type="tab"
:isAiOpen="aiEnabled"
:title="title"
:currentId="currentId"
:formInfo="changeApplyForm"
@save="saveDraft"
@submit="submitConfirm"
@confirmTab="confirmTab"
/>
</div>
<!-- 变更培训内容 -->
<div v-show="tab === 'train'">
<ChangeTraining
ref="changeTrainingRef"
:type="tab"
:isAiOpen="aiEnabled"
:title="title"
:currentId="currentId"
@save="saveDraft"
@submit="submitConfirm"
@confirmTab="confirmTab"
/>
</div>
<!-- PSSR 检查内容 -->
<div v-show="tab === 'pssr'">
<Pssr
ref="pssrRef"
:type="tab"
:isAiOpen="aiEnabled"
:title="title"
:currentId="currentId"
@save="saveDraft"
@submit="submitConfirm"
@confirmTab="confirmTab"
/>
</div>
<!-- 验收评价 -->
<div v-show="tab === 'evaluation'">
<Evaluation
ref="evaluationRef"
:type="tab"
:orgTree="orgTree"
:isAiOpen="aiEnabled"
:title="title"
:currentId="currentId"
:formInfo="changeApplyForm"
@save="saveDraft"
@submit="submitConfirm"
@confirmTab="confirmTab"
/>
</div>
<!-- 变更关闭确认表 -->
<div v-show="tab === 'close'">
<CloseConfirm
ref="closeConfirmRef"
:type="tab"
:isAiOpen="aiEnabled"
:title="title"
:currentId="currentId"
@save="saveDraft"
@submit="submitConfirm"
@confirmTab="confirmTab"
/>
</div>
</div>
</NCard>
</NSpace>
</template>