发起变更AI功能接线:变更预识别生成申请单、各标签页重新生成(申请表/培训内容/PSSR检查/验收评价)对接ai-server并适配新字段渲染;修复等级判定表AI评分重复点击总分累加问题
This commit is contained in:
@@ -59,6 +59,18 @@ const confirmed = ref<{[key: string]: boolean}>({
|
||||
// 变更预分析表单数据
|
||||
const changePreForm = ref<any>(null)
|
||||
const changeApplyForm = ref<any>(null)
|
||||
// 当前申请表所选变更类型的文本编码(供 PSSR 等 AI 接口使用)
|
||||
const applyTypeLabel = computed(() => typeList.value.find((t) => t.value === changeApplyForm.value?.change_type)?.label ?? '');
|
||||
|
||||
// AI 生成变更申请单(变更预识别页派发):回填申请表并切换标签
|
||||
const onApplyGenerated = (res: any) => {
|
||||
if (res?.name) title.value = res.name;
|
||||
changeApplyRef.value?.applyAiResult(res);
|
||||
// 直接切 tab 不经过 tabChange,需同步捕获预识别表单供其他标签页 AI 接口使用
|
||||
changePreForm.value = changePreRef.value?.form;
|
||||
tab.value = 'form';
|
||||
window.$message?.success("变更申请单已生成:AI 自动总结名称、预填内容,均可手动修改");
|
||||
}
|
||||
|
||||
// **********************tab切换***********************
|
||||
const TABS = [
|
||||
@@ -289,6 +301,7 @@ onMounted(() => {
|
||||
:title="title"
|
||||
:currentId="currentId"
|
||||
@save="saveDraft"
|
||||
@apply-generated="onApplyGenerated"
|
||||
/>
|
||||
</div>
|
||||
<!-- ===== 变更申请表 ===== -->
|
||||
@@ -307,6 +320,7 @@ onMounted(() => {
|
||||
@save="saveDraft"
|
||||
@submit="submitConfirm"
|
||||
@confirmTab="confirmTab"
|
||||
@update:title="(v: string) => title = v"
|
||||
/>
|
||||
</div>
|
||||
<!-- ===== 风险分析记录表 ===== -->
|
||||
@@ -331,6 +345,7 @@ onMounted(() => {
|
||||
:isAiOpen="aiEnabled"
|
||||
:title="title"
|
||||
:currentId="currentId"
|
||||
:formInfo="changePreForm"
|
||||
@save="saveDraft"
|
||||
@submit="submitConfirm"
|
||||
@confirmTab="confirmTab"
|
||||
@@ -344,6 +359,8 @@ onMounted(() => {
|
||||
:isAiOpen="aiEnabled"
|
||||
:title="title"
|
||||
:currentId="currentId"
|
||||
:formInfo="changePreForm"
|
||||
:changeType="applyTypeLabel"
|
||||
@save="saveDraft"
|
||||
@submit="submitConfirm"
|
||||
@confirmTab="confirmTab"
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useThemeStore } from '@/store/modules/theme';
|
||||
import { tagTextColor, tagBgColor, tagBorderColor } from '@/utils/common';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { useChangeStore } from '@/store/modules/change';
|
||||
import { AiError, aiTypeJudge, aiLevelScore } from "@/service/api/ai";
|
||||
import { AiError, aiTypeJudge, aiLevelScore, aiApplication } from "@/service/api/ai";
|
||||
import { uploadFileApi, deleteFileApi } from "@/service/api/file";
|
||||
import Footer from './footer.vue'
|
||||
|
||||
@@ -18,7 +18,7 @@ const themeStore = useThemeStore();
|
||||
const changeStore = useChangeStore();
|
||||
|
||||
const props = defineProps(['type','orgTree','templateList','typeList','levelList','isAiOpen','title','currentId','formInfo'])
|
||||
const emit = defineEmits(['save','submit','confirmTab']);
|
||||
const emit = defineEmits(['save','submit','confirmTab','update:title']);
|
||||
|
||||
const aiFail = (e: any) => {
|
||||
if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`);
|
||||
@@ -83,6 +83,8 @@ const PURPOSE_OPTIONS = [
|
||||
"达到环保要求", "法律合规整改", "减少能耗物损", "稳定系统运行",
|
||||
"完善管理制度", "达到质量改进", "设备可靠性升级",
|
||||
];
|
||||
// 需更新的资料选项
|
||||
const UPDATE_DOC_OPTIONS = ['PID图纸', '操作规程', '总图', '设备台账', '工艺卡片', '联锁台账', '应急预案'];
|
||||
// 风险分析选项
|
||||
const RISK_TOOLS_FORM = [
|
||||
{ key: "HAZOP", label: "HAZOP" },
|
||||
@@ -247,10 +249,12 @@ const useAiLevel = async () => {
|
||||
let form = props.formInfo;
|
||||
if (levelAiLoading.value) return;
|
||||
levelAiLoading.value = true;
|
||||
levelAiLoading.value = true;
|
||||
try {
|
||||
const dims = LEVEL_DIMS.value.map((d:any, i:any) => ({ dim: `DIM_${i + 1}`, name: d.name, max: Math.max(...d.options.map((o:any) => o.score)) }));
|
||||
const res = await aiLevelScore(form.description, (form.compare_rows ?? []).map((x: any) => ({ item: x.item, before_text: x.before_text, after_text: x.after_text })), dims);
|
||||
// 重新评分前清零,避免多次点击分数累加
|
||||
totalScore.value = 0;
|
||||
levelAiBasis.value = [];
|
||||
res?.dims?.forEach((d, i) => {
|
||||
totalScore.value += d.score ?? 0;
|
||||
LEVEL_DIMS.value[i].score = d.score ?? 0;
|
||||
@@ -263,6 +267,53 @@ const useAiLevel = async () => {
|
||||
levelAiLoading.value = false;
|
||||
}
|
||||
}
|
||||
// AI 申请单结果回填(生成变更申请单 / 重新生成变更申请表共用)
|
||||
const applyAiResult = (res: any) => {
|
||||
if (!res) return;
|
||||
const f = form.value;
|
||||
const main = (res.main_changes ?? []).filter((x: string) => x && x.trim());
|
||||
if (main.length) f.main_changes = main;
|
||||
const related = (res.related_changes ?? []).filter((x: string) => x && x.trim());
|
||||
if (related.length) f.related_changes = related;
|
||||
const purposes = (res.purposes ?? []).filter((x: string) => PURPOSE_OPTIONS.includes(x));
|
||||
if (purposes.length) f.purposes = purposes;
|
||||
if (res.effect) f.effect = res.effect;
|
||||
// 变更类型:AI 输出 MOC 中文编码 → typeList 数值
|
||||
if (res.change_type) {
|
||||
const t = props.typeList.find((x: any) => x.label === res.change_type || x.label.includes(res.change_type) || res.change_type.includes(x.label));
|
||||
if (t) f.change_type = t.value;
|
||||
}
|
||||
// 变更时限:永久→1,临时→2
|
||||
if (res.duration_suggestion === '永久') f.duration_type = 1;
|
||||
else if (res.duration_suggestion === '临时') f.duration_type = 2;
|
||||
if (res.materials_text) f.materials = res.materials_text;
|
||||
const docs = (res.update_docs ?? []).filter((x: string) => UPDATE_DOC_OPTIONS.includes(x));
|
||||
if (docs.length) f.update_docs = docs;
|
||||
const discs = (res.disciplines ?? []).filter((x: string) => specialties.includes(x));
|
||||
if (discs.length) f.disciplines = discs;
|
||||
const aiTools = (res.risk_tools ?? [])
|
||||
.map((x: string) => RISK_TOOLS_FORM.find((t) => t.key === x || t.label === x)?.key)
|
||||
.filter((x: string | undefined): x is string => !!x);
|
||||
if (aiTools.length) f.risk_tools = aiTools;
|
||||
}
|
||||
// 重新生成变更申请表(footer 派发)
|
||||
const onRegenerate = async () => {
|
||||
const info = props.formInfo;
|
||||
if (!info?.description) {
|
||||
return window.$message?.warning("缺少变更描述,请先在 [变更预识别] 里面输入变更描述");
|
||||
}
|
||||
footerRef.value?.setRegenerateLoading(true);
|
||||
try {
|
||||
const res = await aiApplication(info.description, (info.compare_rows ?? []).map((x: any) => ({ item: x.item, before_text: x.before_text, after_text: x.after_text })));
|
||||
applyAiResult(res);
|
||||
if (res?.name) emit('update:title', res.name);
|
||||
window.$message?.success("变更申请表已由 AI 重新生成,内容均可手动修改");
|
||||
} catch (e: any) {
|
||||
aiFail(e);
|
||||
} finally {
|
||||
footerRef.value?.setRegenerateLoading(false);
|
||||
}
|
||||
}
|
||||
// 处理等级判定表评分变化
|
||||
const handleScoreChange = (val:number,index:number) => {
|
||||
LEVEL_DIMS.value[index].score = val;
|
||||
@@ -347,7 +398,7 @@ const confirmTab = (tab:string, val:boolean) => {
|
||||
emit('confirmTab', tab, val);
|
||||
}
|
||||
|
||||
defineExpose({form})
|
||||
defineExpose({form, applyAiResult})
|
||||
|
||||
</script>
|
||||
|
||||
@@ -576,7 +627,7 @@ defineExpose({form})
|
||||
<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">
|
||||
<div class="flex flex-wrap gap-y-2 gap-x-4">
|
||||
<NCheckbox v-for="p in ['PID图纸', '操作规程', '总图', '设备台账', '工艺卡片', '联锁台账', '应急预案']" :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>
|
||||
@@ -612,6 +663,7 @@ defineExpose({form})
|
||||
@save="saveDraft"
|
||||
@submit="submitConfirm"
|
||||
@confirmTab="confirmTab"
|
||||
@regenerate="onRegenerate"
|
||||
/>
|
||||
<!-- 侧边抽屉:AI 类型判定 / 等级判定表 -->
|
||||
<NDrawer :show="sheet !== ''" @update:show="(v) => !v && (sheet = '')" placement="right" :width="sheet === 'level' ? '45rem' : '28rem'">
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
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 { AiError, aiApplication, aiRecognize, aiRiskPreAnalysis } from "@/service/api/ai";
|
||||
import ChangeCompareTable from "./ChangeCompareTable.vue";
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const emit = defineEmits(['save'])
|
||||
const emit = defineEmits(['save', 'apply-generated'])
|
||||
const generalLoading = inject('generalLoading', {saveLoading: ref<boolean>(false),btnDisabled: ref<boolean>(false)})
|
||||
const {saveLoading,btnDisabled} = generalLoading
|
||||
|
||||
@@ -89,44 +89,21 @@ const runRiskAnalysis = async () => {
|
||||
};
|
||||
// 生成变更申请单
|
||||
const genForm = async () => {
|
||||
// window.$message?.info("AI 正在生成变更申请单");
|
||||
// generateLoading.value = true;
|
||||
// try {
|
||||
// const res = await aiApplication(desc.value, (form.compare_rows ?? []).map((x: any) => ({ item: x.item, before_text: x.before_text, after_text: x.after_text })));
|
||||
// const pick = (v: string | null | undefined, opts: string[], cur: string) => (v && opts.includes(v) ? v : cur);
|
||||
// const main = (res?.main_changes ?? []).filter((x) => x && x.trim());
|
||||
// const purposes = (res?.purposes ?? []).filter((x) => PURPOSE_OPTIONS.includes(x));
|
||||
// const docs = (res?.update_docs ?? []).filter((x) => UPDATE_DOC_OPTIONS.includes(x));
|
||||
// const discs = (res?.disciplines ?? []).filter((x) => DISCIPLINE_OPTIONS.includes(x));
|
||||
// const aiTools = (res?.risk_tools ?? [])
|
||||
// .map((x) => RISK_TOOLS_FORM.find((t) => t.key === x || t.label === x)?.key)
|
||||
// .filter((x): x is string => !!x);
|
||||
// form.value = {
|
||||
// ...form.value,
|
||||
// name: res?.name || form.value.name,
|
||||
// mainChanges: main.length ? main : form.value.mainChanges,
|
||||
// related: (res?.related_changes ?? []).filter(Boolean).join("、") || form.value.related,
|
||||
// purposes: purposes.length ? purposes : form.value.purposes,
|
||||
// effect: res?.effect || form.value.effect,
|
||||
// type: pick(res?.change_type, ['工艺', '设备', '管理'], form.value.type),
|
||||
// duration: pick(res?.duration_suggestion, ['永久', '临时'], form.value.duration),
|
||||
// };
|
||||
// if (res?.materials_text) materialsText.value = res.materials_text;
|
||||
// if (docs.length) updateDocs.value = docs;
|
||||
// if (discs.length) {
|
||||
// disciplines.value = discs;
|
||||
// toggleDiscipline(discs);
|
||||
// }
|
||||
// if (res?.signers && Object.keys(res.signers).length) signers.value = { ...res.signers };
|
||||
// if (aiTools.length) tools.value = aiTools;
|
||||
// currentPlugin.value = "";
|
||||
// tab.value = "form";
|
||||
// window.$message?.success("变更申请单已生成:AI 自动总结名称、预填内容,均可手动修改");
|
||||
// } catch (e: any) {
|
||||
// aiFail(e);
|
||||
// } finally {
|
||||
// generateLoading.value = false;
|
||||
// }
|
||||
if (!form.value.description.trim()) {
|
||||
return window.$message?.warning("请先输入变更内容描述");
|
||||
}
|
||||
generateLoading.value = true;
|
||||
btnDisabled.value = true;
|
||||
try {
|
||||
const res = await aiApplication(form.value.description, form.value.compare_rows);
|
||||
// 由父组件把 AI 结果回填到「变更申请表」并切换标签
|
||||
emit('apply-generated', res);
|
||||
} catch (e: any) {
|
||||
aiFail(e);
|
||||
} finally {
|
||||
generateLoading.value = false;
|
||||
btnDisabled.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 保存草稿
|
||||
|
||||
@@ -3,18 +3,49 @@
|
||||
import { ref } from 'vue';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { AiError, aiTraining } from "@/service/api/ai";
|
||||
import Footer from './footer.vue';
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const props = defineProps(['type','isAiOpen','title','currentId'])
|
||||
const props = defineProps(['type','isAiOpen','title','currentId','formInfo'])
|
||||
const emit = defineEmits(['save','submit','confirmTab']);
|
||||
|
||||
const footerRef = ref<any>(null);
|
||||
|
||||
const aiFail = (e: any) => {
|
||||
if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`);
|
||||
else window.$message?.warning("AI 分析失败,可手动填写");
|
||||
};
|
||||
// 是否是ai生成
|
||||
const isAiGenerated = ref<boolean>(false);
|
||||
const form = ref<{ content: string }>({
|
||||
content: ''
|
||||
});
|
||||
|
||||
// 重新生成变更培训内容(footer 派发)
|
||||
const onRegenerate = async () => {
|
||||
const info = props.formInfo;
|
||||
if (!info?.description) {
|
||||
return window.$message?.warning("缺少变更描述,请先在 [变更预识别] 里面输入变更描述");
|
||||
}
|
||||
footerRef.value?.setRegenerateLoading(true);
|
||||
try {
|
||||
const res = await aiTraining(info.description, (info.compare_rows ?? []).map((x: any) => ({ item: x.item, before_text: x.before_text, after_text: x.after_text })));
|
||||
if (res?.training_text) {
|
||||
form.value.content = res.training_text;
|
||||
isAiGenerated.value = true;
|
||||
window.$message?.success("变更培训内容已由 AI 生成,可直接修改");
|
||||
} else {
|
||||
window.$message?.warning("AI 未返回培训内容,请手动填写");
|
||||
}
|
||||
} catch (e: any) {
|
||||
aiFail(e);
|
||||
} finally {
|
||||
footerRef.value?.setRegenerateLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存草稿
|
||||
const saveDraft = () => {
|
||||
emit('save');
|
||||
@@ -64,6 +95,7 @@ defineExpose({form})
|
||||
@save="saveDraft"
|
||||
@submit="submitConfirm"
|
||||
@confirmTab="confirmTab"
|
||||
@regenerate="onRegenerate"
|
||||
/>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -4,16 +4,70 @@ import { h, ref } from 'vue';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { getColorWithOpacity } from "@/utils/common";
|
||||
import { AiError, aiAcceptance } from "@/service/api/ai";
|
||||
import Footer from './footer.vue';
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const props = defineProps(['type','orgTree','isAiOpen','title','currentId','formInfo'])
|
||||
const emit = defineEmits(['save','submit','confirmTab']);
|
||||
|
||||
|
||||
const footerRef = ref<any>(null);
|
||||
const editing = ref<any>(null);
|
||||
const list = ref<{id:number,item:string,basis:string,standard:string,method:string,org_id:number}[]>([]);
|
||||
|
||||
const aiFail = (e: any) => {
|
||||
if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`);
|
||||
else window.$message?.warning("AI 分析失败,可手动填写");
|
||||
};
|
||||
|
||||
// 重新生成验收评价(footer 派发):依据申请表的变更目的与预期效果生成量化验收标准
|
||||
const onRegenerate = async () => {
|
||||
const purpose = (props.formInfo?.purposes ?? []).filter((x: string) => x && x.trim()).join('、');
|
||||
const effect = props.formInfo?.effect ?? '';
|
||||
if (!purpose && !effect.trim()) {
|
||||
return window.$message?.warning("缺少变更目的与预期效果,请先在 [变更申请表] 填写");
|
||||
}
|
||||
footerRef.value?.setRegenerateLoading(true);
|
||||
try {
|
||||
const res = await aiAcceptance({ purpose, effect });
|
||||
const rows = res?.rows ?? [];
|
||||
if (!rows.length) {
|
||||
return window.$message?.warning("AI 未生成验收项,可手动添加");
|
||||
}
|
||||
list.value = rows.map((r: any) => ({
|
||||
id: 0,
|
||||
item: r.item ?? '',
|
||||
basis: r.basis ?? '',
|
||||
standard: r.standard ?? '',
|
||||
method: r.method ?? '',
|
||||
// 建议责任方文本 → 组织树节点(按名称模糊匹配,匹配不到留空人工选择)
|
||||
org_id: matchOrgId(r.owner_suggest),
|
||||
}));
|
||||
window.$message?.success("验收量化标准已由 AI 生成,均可手动修改");
|
||||
} catch (e: any) {
|
||||
aiFail(e);
|
||||
} finally {
|
||||
footerRef.value?.setRegenerateLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 按名称在组织树中查找节点 id
|
||||
const matchOrgId = (name: string): number => {
|
||||
if (!name || !name.trim()) return 0;
|
||||
const hit = findNodeByLabel(props.orgTree ?? [], name.trim());
|
||||
return hit?.id ?? 0;
|
||||
}
|
||||
const findNodeByLabel = (tree: any[], name: string): any => {
|
||||
for (const node of tree) {
|
||||
if (node.label && (name.includes(node.label) || node.label.includes(name))) return node;
|
||||
if (node.children && node.children.length) {
|
||||
const found: any = findNodeByLabel(node.children, name);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 添加验收项
|
||||
const addRow = () => {
|
||||
list.value.push({ id: 0, item: '', basis: '', standard: '', method: '',org_id: 0 });
|
||||
@@ -230,6 +284,7 @@ defineExpose({list})
|
||||
@save="saveDraft"
|
||||
@submit="submitConfirm"
|
||||
@confirmTab="confirmTab"
|
||||
@regenerate="onRegenerate"
|
||||
/>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -15,7 +15,7 @@ const props = defineProps([
|
||||
'isAiOpen',
|
||||
'currentForm'
|
||||
])
|
||||
const emit = defineEmits(['save','submit','confirmTab']);
|
||||
const emit = defineEmits(['save','submit','confirmTab','regenerate']);
|
||||
|
||||
const generalLoading = inject('generalLoading', {saveLoading: ref<boolean>(false),btnDisabled: ref<boolean>(false)})
|
||||
const {saveLoading,btnDisabled} = generalLoading
|
||||
@@ -87,9 +87,13 @@ const approvalChain = computed(() => {
|
||||
const download = async () => {
|
||||
|
||||
}
|
||||
// 重新生成
|
||||
// 重新生成:AI 调用逻辑由各标签页组件实现(按 type 分发),此处仅派发事件
|
||||
const regenerate = async () => {
|
||||
|
||||
emit('regenerate', props.type);
|
||||
}
|
||||
// 供父组件控制「重新生成」按钮 loading
|
||||
const setRegenerateLoading = (v: boolean) => {
|
||||
regenerateLoading.value = v;
|
||||
}
|
||||
|
||||
// 确认标签内容
|
||||
@@ -137,7 +141,7 @@ const finishApprover = async () => {
|
||||
approvalDrawerShow.value = false;
|
||||
}
|
||||
|
||||
defineExpose({clearApprovers})
|
||||
defineExpose({clearApprovers, setRegenerateLoading})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -4,17 +4,24 @@ import { ref } from 'vue';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import Footer from './footer.vue';
|
||||
import { AiError, aiPssr } from "@/service/api/ai";
|
||||
import { uploadFileApi } from "@/service/api/file";
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const props = defineProps(['type','isAiOpen','title','currentId'])
|
||||
const props = defineProps(['type','isAiOpen','title','currentId','formInfo','changeType'])
|
||||
const emit = defineEmits(['save','submit','confirmTab']);
|
||||
|
||||
const footerRef = ref<any>(null);
|
||||
const uploadLoading = ref<boolean>(false);
|
||||
const uploadFileRef = ref<any>(null);
|
||||
const isUploaded = ref<boolean>(false);
|
||||
const editing = ref<any>(null);
|
||||
|
||||
const aiFail = (e: any) => {
|
||||
if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`);
|
||||
else window.$message?.warning("AI 分析失败,可手动填写");
|
||||
};
|
||||
|
||||
const list = ref<{ g: string; type:number; na_flag:number; items: any[] }[]>([
|
||||
{ g: "一、风险分析行动项落实", type:1, na_flag:0, items: [] },
|
||||
{ g: "二、现场设备安装检查确认", type:2, na_flag:0, items: [] },
|
||||
@@ -23,6 +30,49 @@ const list = ref<{ g: string; type:number; na_flag:number; items: any[] }[]>([
|
||||
]);
|
||||
const pssrNa = ref<Record<string, boolean>>({});
|
||||
|
||||
// 重新生成 PSSR 检查内容(footer 派发):AI 输出按固定四组名称匹配回填
|
||||
const onRegenerate = async () => {
|
||||
const info = props.formInfo;
|
||||
if (!info?.description) {
|
||||
return window.$message?.warning("缺少变更描述,请先在 [变更预识别] 里面输入变更描述");
|
||||
}
|
||||
footerRef.value?.setRegenerateLoading(true);
|
||||
try {
|
||||
const res = await aiPssr({
|
||||
content: info.description,
|
||||
...(props.changeType ? { change_type: props.changeType } : {}),
|
||||
});
|
||||
const groups = (res?.groups ?? []).filter((g: any) => g && Array.isArray(g.items));
|
||||
if (!groups.length) {
|
||||
return window.$message?.warning("AI 未生成检查项,可手动添加");
|
||||
}
|
||||
const used = new Set<number>();
|
||||
list.value = list.value.map((g) => {
|
||||
// 优先按分组名匹配,兜底按剩余顺序对齐
|
||||
let gi = groups.findIndex((x: any, i: number) => !used.has(i) && (x.g === g.g || x.g.includes(g.g) || g.g.includes(x.g)));
|
||||
if (gi < 0) gi = groups.findIndex((_: any, i: number) => !used.has(i));
|
||||
if (gi < 0) return g;
|
||||
used.add(gi);
|
||||
return {
|
||||
...g,
|
||||
na_flag: 0,
|
||||
items: groups[gi].items.map((it: any) => ({
|
||||
id: 0,
|
||||
group_type: g.type,
|
||||
content: it.content ?? '',
|
||||
phase: it.phase_value ?? (it.phase === '关闭前' ? 2 : 1),
|
||||
na_flag: 0,
|
||||
})),
|
||||
};
|
||||
});
|
||||
window.$message?.success("PSSR 检查清单已由 AI 生成,点击条目可直接编辑");
|
||||
} catch (e: any) {
|
||||
aiFail(e);
|
||||
} finally {
|
||||
footerRef.value?.setRegenerateLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加检查项
|
||||
const addItem = (g:any, gi: number) => {
|
||||
list.value[gi].items.push({id:0, group_type:g.type, content: '新检查项(点击直接编辑内容)', phase: 1, na_flag: 0 });
|
||||
@@ -161,6 +211,7 @@ defineExpose({list})
|
||||
@save="saveDraft"
|
||||
@submit="submitConfirm"
|
||||
@confirmTab="confirmTab"
|
||||
@regenerate="onRegenerate"
|
||||
/>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
|
||||
Reference in New Issue
Block a user