发起变更保存接口统一修改

This commit is contained in:
2026-09-04 14:20:59 +08:00
parent 7d05236399
commit 8706ec4b92
6 changed files with 221 additions and 195 deletions
+19 -62
View File
@@ -10,80 +10,37 @@ export function queryUnsavedApi() {
}); });
} }
/** 变更预识别查询 // 保存
* export function saveChangeApi(id: number, params: any) {
*/
export function precheckApi(id: number) {
return request({ return request({
url: `/api/changes/${id}/precheck`, url: `/api/changes/${id}/form`,
method: 'get',
});
}
/**
* 变更预识别保存
*
*/
export function precheckSaveApi(params: {
change_id: number,
title: string,
description: string,
severity: string,
probability: string,
protection: string,
compare_rows: {item: string, before_text: string, after_text: string}[]
}) {
return request({
url: '/api/changes/precheck',
method: 'put', method: 'put',
data: params data: params
}); });
} }
/** 变更申请表查询 // 提交
* export function submitChangeApi(id: number, params: any) {
*/
export function applicationApi(id: number) {
return request({ return request({
url: `/api/changes/${id}/apply-form`, url: `/api/changes/${id}/form/submit`,
method: 'get', method: 'post',
});
}
/**
* 变更申请表保存
*
*/
export function applicationSaveApi(id: number, params: {
change_type: number | null,
change_level: any,
duration_type: number | null,
restore_deadline: number | null,
org_id: number | null,
urgent: number,
purposes: string[],
effect: string,
main_changes: string[],
related_changes: string[],
materials: string,
update_docs: string[],
disciplines: string[],
manage_dept: string,
plan_use_date: number | null,
risk_tools: string[],
}) {
return request({
url: `/api/changes/${id}/apply-form`,
method: 'put',
data: params data: params
}); });
} }
// 变更培训内容保存 // 获取详情
export function trainingSaveApi(id: number, params: { export function getChangeDetailApi(id: number) {
content: string,
}) {
return request({ return request({
url: `/api/changes/${id}/train/content`, url: `/api/changes/${id}`,
method: 'put', method: 'get',
});
}
// 使用ai分析-步骤确认/取消
export function useAiAnalysisApi(id: number, stepId: number, params: {action: string}) {
return request({
url: `/api/changes/${id}/steps/${stepId}/confirm`,
method: 'post',
data: params data: params
}); });
} }
+142 -23
View File
@@ -1,16 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue'; import { computed, onMounted, provide, ref } 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 { getColorWithOpacity } from "@/utils/common";
import {
AiError,
aiApplication,
aiRiskOrganize,
aiRiskPreAnalysis,
} from "@/service/api/ai";
import { localStg } from '@/utils/storage'; import { localStg } from '@/utils/storage';
import { queryUnsavedApi } from '@/service/api/change'; import { queryUnsavedApi, saveChangeApi, submitChangeApi } from '@/service/api/change';
import { systemConfigApi, templateListApi, } from '@/service/api/system'; import { systemConfigApi, templateListApi, } from '@/service/api/system';
import { orgListApi } from '@/service/api/user'; import { orgListApi } from '@/service/api/user';
import ChangePre from "./modules/changePre.vue"; import ChangePre from "./modules/changePre.vue";
@@ -23,10 +17,17 @@ import CloseConfirm from "./modules/closeConfirm.vue";
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const saveLoading = ref<boolean>(false)
const btnDisabled = ref<boolean>(false);
provide('generalLoading', {saveLoading,btnDisabled})
// **********************通用字段*********************** // **********************通用字段***********************
const changePreRef = ref<any>(null); const changePreRef = ref<any>(null);
const changeApplyRef = ref<any>(null); const changeApplyRef = ref<any>(null);
const riskAnalysisRef = ref<any>(null);
const changeTrainingRef = 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 orgTree = ref<any>([]);
// 变更类型选项 // 变更类型选项
@@ -38,8 +39,6 @@ const templateList = ref<any[]>([]);
// 是否开启ai功能 // 是否开启ai功能
const aiEnabled = ref<boolean>(localStg.get('userInfo')?.ai_assist_enabled=='1' ? true : false); const aiEnabled = ref<boolean>(localStg.get('userInfo')?.ai_assist_enabled=='1' ? true : false);
// btn disabled
const btnDisabled = ref<boolean>(false);
// 变更名称 // 变更名称
const title = ref<string>(''); const title = ref<string>('');
// 变更编号 // 变更编号
@@ -80,9 +79,9 @@ const tabList = computed(() => {
const tabChange = (id: string) => { const tabChange = (id: string) => {
if(tab.value==='pre'){ if(tab.value==='pre'){
changePreForm.value = changePreRef.value?.form; changePreForm.value = changePreRef.value?.form;
if(!changePreForm.value?.description){ // if(!changePreForm.value?.description){
return window.$message?.warning('请先填写变更内容描述'); // return window.$message?.warning('请先填写变更内容描述');
} // }
} }
if(tab.value === id) { if(tab.value === id) {
return; return;
@@ -95,11 +94,6 @@ const confirmTab = (tab: string, val: boolean) => {
confirmed.value[tab] = val; confirmed.value[tab] = val;
} }
// 更新按钮禁用状态
const updateBtn = (val: boolean) => {
btnDisabled.value = val;
};
// 查询变更未保存数据 // 查询变更未保存数据
const getId = async () => { const getId = async () => {
const {data,error} = await queryUnsavedApi(); const {data,error} = await queryUnsavedApi();
@@ -147,6 +141,103 @@ const getOrgList = async () => {
} }
} }
// 获取所有表单数据
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?.form;
let evaluationForm = evaluationRef.value?.form;
let closeConfirmForm = closeConfirmRef.value?.form;
return {
title: title.value,
...changePreForm,
...changeApplyForm,
...changeRiskForm,
...changeTrainingForm,
...pssrForm,
...evaluationForm,
...closeConfirmForm
};
}
// 保存草稿
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(() => { onMounted(() => {
getOrgList(); getOrgList();
getConfig(); getConfig();
@@ -182,18 +273,37 @@ onMounted(() => {
> >
<Icon :icon="t.icon" class="size-14px" /> <Icon :icon="t.icon" class="size-14px" />
{{ t.name }} {{ t.name }}
<span v-if="!confirmed[t.id] && t.id!=='pre'" class="h-1.5 w-1.5 rounded-full bg-red-500"></span> <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'" icon="material-symbols:check-circle-outline" class="size-14px text-emerald-500" /> <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>
</div> </div>
<!-- ===== 变更预识别 ===== --> <!-- ===== 变更预识别 ===== -->
<div v-show="tab === 'pre'"> <div v-show="tab === 'pre'">
<ChangePre ref="changePreRef" :title="title" :currentId="currentId" @updateBtn="updateBtn" /> <ChangePre
ref="changePreRef"
:title="title"
:currentId="currentId"
@save="saveDraft"
/>
</div> </div>
<!-- ===== 变更申请表 ===== --> <!-- ===== 变更申请表 ===== -->
<div v-show="tab === 'form'"> <div v-show="tab === 'form'">
<ChangeApply ref="changeApplyRef" :orgTree="orgTree" :templateList="templateList" :typeList="typeList" :levelList="levelList" :isAiOpen="aiEnabled" :title="title" :currentId="currentId" :formInfo="changePreForm" @confirmTab="confirmTab" /> <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>
<!-- ===== 风险分析记录表 ===== --> <!-- ===== 风险分析记录表 ===== -->
<div v-show="tab === 'risk'"> <div v-show="tab === 'risk'">
@@ -201,7 +311,16 @@ onMounted(() => {
</div> </div>
<!-- 变更培训内容 --> <!-- 变更培训内容 -->
<div v-show="tab === 'train'"> <div v-show="tab === 'train'">
<ChangeTraining ref="changeTrainingRef" :isAiOpen="aiEnabled" :title="title" :currentId="currentId" @confirmTab="confirmTab" /> <ChangeTraining
ref="changeTrainingRef"
:type="tab"
:isAiOpen="aiEnabled"
:title="title"
:currentId="currentId"
@save="saveDraft"
@submit="submitConfirm"
@confirmTab="confirmTab"
/>
</div> </div>
<!-- PSSR 检查内容 --> <!-- PSSR 检查内容 -->
<div v-show="tab === 'pssr'"> <div v-show="tab === 'pssr'">
@@ -12,8 +12,8 @@ import { AiError, aiTypeJudge, aiLevelScore } from "@/service/api/ai";
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const changeStore = useChangeStore(); const changeStore = useChangeStore();
const props = defineProps(['orgTree','templateList','typeList','levelList','isAiOpen','title','currentId','formInfo']) const props = defineProps(['type','orgTree','templateList','typeList','levelList','isAiOpen','title','currentId','formInfo'])
const emit = defineEmits(['confirmTab']); const emit = defineEmits(['save','submit','confirmTab']);
const aiFail = (e: any) => { const aiFail = (e: any) => {
if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`); if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`);
@@ -158,6 +158,7 @@ const filterMembersBySpecialties = (data: any, specialties: string[]) => {
}); });
return result; return result;
} }
// 当前模板 // 当前模板
const currentTemplate = computed(() => { const currentTemplate = computed(() => {
let template = props.templateList.find((item: any) => let template = props.templateList.find((item: any) =>
@@ -278,6 +279,19 @@ const renderSwitcherIcon = (node:any) => {
}); });
} }
// 保存草稿
const saveDraft = () => {
emit('save');
}
// 提交
const submitConfirm = () => {
emit('submit', props.type);
}
// // 确认标签内容
const confirmTab = (tab:string, val:boolean) => {
emit('confirmTab', tab, val);
}
defineExpose({form}) defineExpose({form})
</script> </script>
@@ -522,11 +536,14 @@ defineExpose({form})
</div> </div>
<Footer <Footer
ref="footerRef" ref="footerRef"
type="form" :type="props.type"
:currentId="props.currentId" :currentId="props.currentId"
:title="props.title" :title="props.title"
:isAiOpen="props.isAiOpen" :isAiOpen="props.isAiOpen"
:currentForm="form" :currentForm="form"
@save="saveDraft"
@submit="submitConfirm"
@confirmTab="confirmTab"
/> />
<!-- 侧边抽屉AI 类型判定 / 等级判定表 --> <!-- 侧边抽屉AI 类型判定 / 等级判定表 -->
<NDrawer :show="sheet !== ''" @update:show="(v) => !v && (sheet = '')" placement="right" :width="sheet === 'level' ? '45rem' : '28rem'"> <NDrawer :show="sheet !== ''" @update:show="(v) => !v && (sheet = '')" placement="right" :width="sheet === 'level' ? '45rem' : '28rem'">
@@ -1,16 +1,16 @@
<!-- 变更预识别 --> <!-- 变更预识别 -->
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { inject, ref } 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 { AiError, aiRecognize, aiRiskPreAnalysis } from "@/service/api/ai"; import { AiError, aiRecognize, aiRiskPreAnalysis } from "@/service/api/ai";
import { precheckSaveApi } from "@/service/api/change";
import ChangeCompareTable from "./ChangeCompareTable.vue"; import ChangeCompareTable from "./ChangeCompareTable.vue";
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const props = defineProps(['title','currentId']) const emit = defineEmits(['save'])
const generalLoading = inject('generalLoading', {saveLoading: ref<boolean>(false),btnDisabled: ref<boolean>(false)})
const {saveLoading,btnDisabled} = generalLoading
const aiFail = (e: any) => { const aiFail = (e: any) => {
if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`); if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`);
@@ -22,16 +22,12 @@ const isAiGenerated = ref<boolean>(false);
const preLoading = ref<boolean>(false); const preLoading = ref<boolean>(false);
// 生成变更申请单loading // 生成变更申请单loading
const generateLoading = ref<boolean>(false); const generateLoading = ref<boolean>(false);
// 保存loading
const saveLoading = ref<boolean>(false);
// 风险预分析loading // 风险预分析loading
const analyzing = ref<boolean>(false); const analyzing = ref<boolean>(false);
// 编辑风险预分析 // 编辑风险预分析
const preRiskEdit = ref<string>(''); const preRiskEdit = ref<string>('');
// 是否显示险预分析 // 是否显示险预分析
const preRiskShow = ref<boolean>(false); const preRiskShow = ref<boolean>(false);
// 按钮禁用
const btnDisabled = ref<boolean>(false);
// 表单 // 表单
const form = ref<{ const form = ref<{
description: string, description: string,
@@ -134,30 +130,8 @@ const genForm = async () => {
}; };
// 保存草稿 // 保存草稿
const saveDraft = async () => { const saveDraft = () => {
// 变更预识别 emit('save');
if(props.title?.trim()===''){
return window.$message?.warning('请填写变更名称');
}
if(form.value.description?.trim() === ''){
return window.$message?.warning('请填写变更内容描述');
}
saveLoading.value = true;
btnDisabled.value = true;
const {error} = await precheckSaveApi({
change_id: props.currentId,
title: props.title,
description: form.value.description,
severity: form.value.severity,
probability: form.value.probability,
protection: form.value.protection,
compare_rows: form.value.compare_rows,
})
if(!error){
window.$message?.success("已保存为草稿,可在左侧「我的草稿」中继续编辑");
}
saveLoading.value = false;
btnDisabled.value = false;
} }
defineExpose({form}) defineExpose({form})
@@ -7,13 +7,28 @@ import Footer from './footer.vue';
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const props = defineProps(['isAiOpen','title','currentId']) const props = defineProps(['type','isAiOpen','title','currentId'])
const emit = defineEmits(['confirmTab']); const emit = defineEmits(['save','submit','confirmTab']);
// 是否是ai生成 // 是否是ai生成
const isAiGenerated = ref<boolean>(false); const isAiGenerated = ref<boolean>(false);
const form = ref<{ content: string }>({ const form = ref<{ content: string }>({
content: '' content: ''
}); });
// 保存草稿
const saveDraft = () => {
emit('save');
}
// 提交
const submitConfirm = () => {
emit('submit', props.type);
}
// // 确认标签内容
const confirmTab = (tab:string, val:boolean) => {
emit('confirmTab', tab, val);
}
defineExpose({form})
</script> </script>
<template> <template>
@@ -41,11 +56,14 @@ const form = ref<{ content: string }>({
</div> </div>
</div> </div>
<Footer <Footer
type="train" ref="footerRef"
:type="props.type"
:currentId="props.currentId" :currentId="props.currentId"
:title="props.title" :title="props.title"
:isAiOpen="props.isAiOpen" :isAiOpen="props.isAiOpen"
:currentForm="form" @save="saveDraft"
@submit="submitConfirm"
@confirmTab="confirmTab"
/> />
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
+12 -71
View File
@@ -1,10 +1,9 @@
<!-- 通用底部操作 --> <!-- 通用底部操作 -->
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive, ref } from 'vue'; import { computed, inject, ref } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { Icon } from '@iconify/vue' import { Icon } from '@iconify/vue'
import { useChangeStore } from '@/store/modules/change'; import { useChangeStore } from '@/store/modules/change';
import { applicationSaveApi, trainingSaveApi } from '@/service/api/change';
const changeStore = useChangeStore(); const changeStore = useChangeStore();
const { getStepsInfo, getFlowInfo } = storeToRefs(changeStore); const { getStepsInfo, getFlowInfo } = storeToRefs(changeStore);
@@ -16,13 +15,14 @@ const props = defineProps([
'isAiOpen', 'isAiOpen',
'currentForm' 'currentForm'
]) ])
const emit = defineEmits(['confirmTab']); const emit = defineEmits(['save','submit','confirmTab']);
const generalLoading = inject('generalLoading', {saveLoading: ref<boolean>(false),btnDisabled: ref<boolean>(false)})
const {saveLoading,btnDisabled} = generalLoading
const approvalDrawerShow = ref<boolean>(false); const approvalDrawerShow = ref<boolean>(false);
const btnDisabled = ref<boolean>(false);
const downloadLoading = ref<boolean>(false); const downloadLoading = ref<boolean>(false);
const regenerateLoading = ref<boolean>(false); const regenerateLoading = ref<boolean>(false);
const saveLoading = ref<boolean>(false);
const submitLoading = ref<boolean>(false); const submitLoading = ref<boolean>(false);
const confirmed = ref<boolean>(false); const confirmed = ref<boolean>(false);
@@ -91,78 +91,19 @@ const download = async () => {
const regenerate = async () => { const regenerate = async () => {
} }
// 确认标签内容完毕
// 确认标签内容
const confirmTab = async () => { const confirmTab = async () => {
confirmed.value = !confirmed.value;
} emit('confirmTab', props.type, confirmed.value);
// 变更申请表表单必填判断
const changeApplyRules = () => {
let form = props.currentForm;
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 saveDraft = async () => { const saveDraft = async () => {
if(props.type==='form'){ emit('save');
if(changeApplyRules()){
return
}
}
if(props.type==='train'){
if(props.currentForm.content.trim() === ''){
return window.$message?.warning(`请输入变更培训内容`);
}
}
saveLoading.value = true;
btnDisabled.value = true;
// 保存变更申请表
if(props.type==='form'){
const {error} = await applicationSaveApi(props.currentId, props.currentForm);
if(!error){
window.$message?.success("已保存为草稿,可在左侧「我的草稿」中继续编辑");
}
}
// 保存变更培训内容
if(props.type==='train'){
const {error} = await trainingSaveApi(props.currentId, {content: props.currentForm.content});
if(!error){
window.$message?.success(`变更培训内容已保存`);
}
}
saveLoading.value = false;
btnDisabled.value = false;
} }
// 提交 // 提交
const submitApply = async () => { const submitApply = () => {
emit('submit');
} }
// 更新选中审批人 user_id // 更新选中审批人 user_id