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

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 precheckApi(id: number) {
// 保存
export function saveChangeApi(id: number, params: any) {
return request({
url: `/api/changes/${id}/precheck`,
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',
url: `/api/changes/${id}/form`,
method: 'put',
data: params
});
}
/** 变更申请表查询
*
*/
export function applicationApi(id: number) {
// 提交
export function submitChangeApi(id: number, params: any) {
return request({
url: `/api/changes/${id}/apply-form`,
method: 'get',
});
}
/**
* 变更申请表保存
*
*/
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',
url: `/api/changes/${id}/form/submit`,
method: 'post',
data: params
});
}
// 变更培训内容保存
export function trainingSaveApi(id: number, params: {
content: string,
}) {
// 获取详情
export function getChangeDetailApi(id: number) {
return request({
url: `/api/changes/${id}/train/content`,
method: 'put',
url: `/api/changes/${id}`,
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
});
}
+142 -23
View File
@@ -1,16 +1,10 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { computed, onMounted, provide, ref } from 'vue';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { getColorWithOpacity } from "@/utils/common";
import {
AiError,
aiApplication,
aiRiskOrganize,
aiRiskPreAnalysis,
} from "@/service/api/ai";
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 { orgListApi } from '@/service/api/user';
import ChangePre from "./modules/changePre.vue";
@@ -23,10 +17,17 @@ 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>([]);
// 变更类型选项
@@ -38,8 +39,6 @@ const templateList = ref<any[]>([]);
// 是否开启ai功能
const aiEnabled = ref<boolean>(localStg.get('userInfo')?.ai_assist_enabled=='1' ? true : false);
// btn disabled
const btnDisabled = ref<boolean>(false);
// 变更名称
const title = ref<string>('');
// 变更编号
@@ -80,9 +79,9 @@ const tabList = computed(() => {
const tabChange = (id: string) => {
if(tab.value==='pre'){
changePreForm.value = changePreRef.value?.form;
if(!changePreForm.value?.description){
return window.$message?.warning('请先填写变更内容描述');
}
// if(!changePreForm.value?.description){
// return window.$message?.warning('请先填写变更内容描述');
// }
}
if(tab.value === id) {
return;
@@ -95,11 +94,6 @@ const confirmTab = (tab: string, val: boolean) => {
confirmed.value[tab] = val;
}
// 更新按钮禁用状态
const updateBtn = (val: boolean) => {
btnDisabled.value = val;
};
// 查询变更未保存数据
const getId = async () => {
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(() => {
getOrgList();
getConfig();
@@ -182,18 +273,37 @@ onMounted(() => {
>
<Icon :icon="t.icon" class="size-14px" />
{{ t.name }}
<span v-if="!confirmed[t.id] && t.id!=='pre'" 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" />
<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" @updateBtn="updateBtn" />
<ChangePre
ref="changePreRef"
:title="title"
:currentId="currentId"
@save="saveDraft"
/>
</div>
<!-- ===== 变更申请表 ===== -->
<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 v-show="tab === 'risk'">
@@ -201,7 +311,16 @@ onMounted(() => {
</div>
<!-- 变更培训内容 -->
<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>
<!-- PSSR 检查内容 -->
<div v-show="tab === 'pssr'">
@@ -12,8 +12,8 @@ import { AiError, aiTypeJudge, aiLevelScore } from "@/service/api/ai";
const themeStore = useThemeStore();
const changeStore = useChangeStore();
const props = defineProps(['orgTree','templateList','typeList','levelList','isAiOpen','title','currentId','formInfo'])
const emit = defineEmits(['confirmTab']);
const props = defineProps(['type','orgTree','templateList','typeList','levelList','isAiOpen','title','currentId','formInfo'])
const emit = defineEmits(['save','submit','confirmTab']);
const aiFail = (e: any) => {
if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`);
@@ -158,6 +158,7 @@ const filterMembersBySpecialties = (data: any, specialties: string[]) => {
});
return result;
}
// 当前模板
const currentTemplate = computed(() => {
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})
</script>
@@ -522,11 +536,14 @@ defineExpose({form})
</div>
<Footer
ref="footerRef"
type="form"
:type="props.type"
:currentId="props.currentId"
:title="props.title"
:isAiOpen="props.isAiOpen"
:currentForm="form"
@save="saveDraft"
@submit="submitConfirm"
@confirmTab="confirmTab"
/>
<!-- 侧边抽屉AI 类型判定 / 等级判定表 -->
<NDrawer :show="sheet !== ''" @update:show="(v) => !v && (sheet = '')" placement="right" :width="sheet === 'level' ? '45rem' : '28rem'">
@@ -1,16 +1,16 @@
<!-- 变更预识别 -->
<script setup lang="ts">
import { ref } from 'vue';
import { inject, ref } from 'vue';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { AiError, aiRecognize, aiRiskPreAnalysis } from "@/service/api/ai";
import { precheckSaveApi } from "@/service/api/change";
import ChangeCompareTable from "./ChangeCompareTable.vue";
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) => {
if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`);
@@ -22,16 +22,12 @@ const isAiGenerated = ref<boolean>(false);
const preLoading = ref<boolean>(false);
// 生成变更申请单loading
const generateLoading = ref<boolean>(false);
// 保存loading
const saveLoading = ref<boolean>(false);
// 风险预分析loading
const analyzing = ref<boolean>(false);
// 编辑风险预分析
const preRiskEdit = ref<string>('');
// 是否显示险预分析
const preRiskShow = ref<boolean>(false);
// 按钮禁用
const btnDisabled = ref<boolean>(false);
// 表单
const form = ref<{
description: string,
@@ -134,30 +130,8 @@ const genForm = async () => {
};
// 保存草稿
const saveDraft = async () => {
// 变更预识别
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;
const saveDraft = () => {
emit('save');
}
defineExpose({form})
@@ -7,13 +7,28 @@ import Footer from './footer.vue';
const themeStore = useThemeStore();
const props = defineProps(['isAiOpen','title','currentId'])
const emit = defineEmits(['confirmTab']);
const props = defineProps(['type','isAiOpen','title','currentId'])
const emit = defineEmits(['save','submit','confirmTab']);
// 是否是ai生成
const isAiGenerated = ref<boolean>(false);
const form = ref<{ content: string }>({
content: ''
});
// 保存草稿
const saveDraft = () => {
emit('save');
}
// 提交
const submitConfirm = () => {
emit('submit', props.type);
}
// // 确认标签内容
const confirmTab = (tab:string, val:boolean) => {
emit('confirmTab', tab, val);
}
defineExpose({form})
</script>
<template>
@@ -41,11 +56,14 @@ const form = ref<{ content: string }>({
</div>
</div>
<Footer
type="train"
ref="footerRef"
:type="props.type"
:currentId="props.currentId"
:title="props.title"
:isAiOpen="props.isAiOpen"
:currentForm="form"
@save="saveDraft"
@submit="submitConfirm"
@confirmTab="confirmTab"
/>
</template>
<style scoped lang="scss">
+12 -71
View File
@@ -1,10 +1,9 @@
<!-- 通用底部操作 -->
<script setup lang="ts">
import { computed, reactive, ref } from 'vue';
import { computed, inject, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { Icon } from '@iconify/vue'
import { useChangeStore } from '@/store/modules/change';
import { applicationSaveApi, trainingSaveApi } from '@/service/api/change';
const changeStore = useChangeStore();
const { getStepsInfo, getFlowInfo } = storeToRefs(changeStore);
@@ -16,13 +15,14 @@ const props = defineProps([
'isAiOpen',
'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 btnDisabled = ref<boolean>(false);
const downloadLoading = ref<boolean>(false);
const regenerateLoading = ref<boolean>(false);
const saveLoading = ref<boolean>(false);
const submitLoading = ref<boolean>(false);
const confirmed = ref<boolean>(false);
@@ -91,78 +91,19 @@ const download = async () => {
const regenerate = async () => {
}
// 确认标签内容完毕
// 确认标签内容
const confirmTab = async () => {
}
// 变更申请表表单必填判断
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
confirmed.value = !confirmed.value;
emit('confirmTab', props.type, confirmed.value);
}
// 保存
const saveDraft = async () => {
if(props.type==='form'){
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;
emit('save');
}
// 提交
const submitApply = async () => {
const submitApply = () => {
emit('submit');
}
// 更新选中审批人 user_id