146 lines
6.2 KiB
Vue
146 lines
6.2 KiB
Vue
<!-- 变更预识别 -->
|
||
<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> |