Files
moc/src/views/my/initiateChange/modules/changePre.vue
T
2026-09-02 15:03:50 +08:00

146 lines
6.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 变更预识别 -->
<script setup lang="ts">
import { ref } from 'vue';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { AiError, aiRecognize } from "@/service/api/ai";
import ChangeCompareTable from "./ChangeCompareTable.vue";
const themeStore = useThemeStore();
const emit = defineEmits(['updateBtn']);
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);
// 变更识别loading
const preLoading = ref<boolean>(false);
// 编辑风险预分析
const preRiskEdit = ref<string>('');
// 是否显示险预分析
const preRiskShow = ref<boolean>(false);
// 按钮禁用
const btnDisabled = ref<boolean>(false);
// 表单
const form = ref<{
description: string,
compare_rows: {item: string, before_text: string, after_text: string}[],
severity: string,
probability: string,
protection: string
}>({
description: '',
compare_rows: [],
severity: "",
probability: "",
protection: "",
});
// AI 变更识别
const runIdentify = async () => {
if (!form.value.description.trim()) {
return window.$message?.warning("请先输入变更内容描述");
}
form.value.compare_rows = [];
emit('updateBtn', true);
preLoading.value = true;
try {
const res = await aiRecognize(form.value.description);
form.value.compare_rows = (res?.rows ?? []).map((x, i) => ({ item: x.item, before_text: x.before_text, after_text: x.after_text }));
window.$message?.success(`AI 变更识别完成,共 ${form.value.compare_rows.length} 项,请逐项人工确认(可直接修改)`);
} catch (e: any) {
form.value.compare_rows = [];
aiFail(e);
} finally {
preLoading.value = false;
emit('updateBtn', false);
}
};
// 更新表单
const updateForm = (res: any, isShow: boolean) => {
form.value = {
...form.value,
severity: res?.severity ?? form.value.severity,
probability: res?.probability ?? form.value.probability,
protection: res?.protection ?? form.value.protection,
};
preRiskShow.value = isShow;
}
// 更新按钮禁用状态
const updateBtnDisabled = (disabled: boolean) => {
btnDisabled.value = disabled;
}
defineExpose({form,updateForm,updateBtnDisabled})
</script>
<template>
<!-- 变更内容描述 -->
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between px-4 py-2 border-b">
<div class="flex items-center">
<Icon icon="lucide:sparkles" class="size-16px text-violet-500" />
<span class="font-semibold text-base ml-2">变更内容描述</span>
</div>
<NButton ghost type="primary" @click="runIdentify" :disabled="btnDisabled || preLoading">
<Icon v-if="preLoading" icon="ri:loader-4-fill" class="size-16px animate-spin" />
<Icon v-else icon="fluent:wand-24-regular" class="size-16px" />
变更识别
</NButton>
</div>
<div class="p-4">
<NInput type="textarea" v-model:value="form.description" rows="4" placeholder="请输入变更内容描述,例如:将反应釜R-101的搅拌速度从120rpm提高至180rpm,同时将反应温度从80℃提高至95℃…" />
</div>
</div>
<!-- 变更识别结果 -->
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between px-4 py-2 border-b">
<div class="flex items-center">
<Icon icon="lucide:list-checks" class="size-16px text-emerald-500" />
<span class="font-semibold text-base ml-2">{{form.compare_rows.length ? `变更识别结果(${form.compare_rows.length}项)` : '变更识别结果(空表 · 可手动录入,或由 AI 识别生成)'}}</span>
</div>
<span v-if="isAiGenerated" 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>
</div>
<div class="p-4">
<ChangeCompareTable
:data="form.compare_rows"
:editable="true"
@update:data="(d) => form.compare_rows = d"
/>
</div>
</div>
<!-- 风险预分析报告 -->
<div v-if="preRiskShow" class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="flex items-center justify-between px-4 py-2 border-b">
<div class="flex items-center">
<Icon icon="lucide:list-checks" class="size-16px text-emerald-500" />
<span class="font-semibold text-base ml-2">风险预分析报告</span>
</div>
<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>
</div>
<div class="p-4">
<div class="space-y-4">
<div v-for="s in ([{ key: 'severity', name: '危害严重性分析' }, { key: 'probability', name: '事件概率分析' }, { key: 'protection', name: '保护措施影响分析' }])" :key="s.key" class="rounded-lg border">
<div class="flex items-center gap-2 border-b bg-slate-50/60 px-4 py-2">
<span class="text-sm font-semibold text-slate-700">{{ s.name }}</span>
<NButton text type="primary" class="ml-auto text-xs hover:underline" @click="preRiskEdit = preRiskEdit === s.key ? '' : s.key">
{{ preRiskEdit === s.key ? "完成" : "编辑" }}
</NButton>
</div>
<NInput v-if="preRiskEdit === s.key" type="textarea" :bordered="false" v-model:value="form[s.key]" autofocus rows="6" />
<p v-else class="whitespace-pre-wrap px-3 py-1.5 text-sm leading-relaxed text-slate-700">{{ form[s.key] }}</p>
</div>
<div class="rounded-lg bg-amber-50 px-3 py-2 text-xs leading-5 text-amber-700">
AI 风险预分析仅供参考用于申请阶段的快速研判正式风险识别请前往风险分析记录表标签页使用 HAZOP / JSA / 检查表法开展并留存记录
</div>
</div>
</div>
</div>
</template>