hazop插件修改完成
This commit is contained in:
Vendored
+6
@@ -42,6 +42,8 @@ declare module 'vue' {
|
||||
NCascader: typeof import('naive-ui')['NCascader']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
|
||||
NCollapse: typeof import('naive-ui')['NCollapse']
|
||||
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
|
||||
NColorPicker: typeof import('naive-ui')['NColorPicker']
|
||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
NDatePicker: typeof import('naive-ui')['NDatePicker']
|
||||
@@ -59,6 +61,7 @@ declare module 'vue' {
|
||||
NImage: typeof import('naive-ui')['NImage']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NInputGroup: typeof import('naive-ui')['NInputGroup']
|
||||
NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
|
||||
NMenu: typeof import('naive-ui')['NMenu']
|
||||
@@ -132,6 +135,8 @@ declare global {
|
||||
const NCascader: typeof import('naive-ui')['NCascader']
|
||||
const NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
const NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
|
||||
const NCollapse: typeof import('naive-ui')['NCollapse']
|
||||
const NCollapseItem: typeof import('naive-ui')['NCollapseItem']
|
||||
const NColorPicker: typeof import('naive-ui')['NColorPicker']
|
||||
const NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
const NDatePicker: typeof import('naive-ui')['NDatePicker']
|
||||
@@ -149,6 +154,7 @@ declare global {
|
||||
const NImage: typeof import('naive-ui')['NImage']
|
||||
const NInput: typeof import('naive-ui')['NInput']
|
||||
const NInputGroup: typeof import('naive-ui')['NInputGroup']
|
||||
const NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
|
||||
const NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
const NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
|
||||
const NMenu: typeof import('naive-ui')['NMenu']
|
||||
|
||||
@@ -69,7 +69,6 @@ const columns = computed<DataTableColumns<ChangeItem>>(() => {
|
||||
{
|
||||
title: '变更前',
|
||||
key: 'before_text',
|
||||
width: 200,
|
||||
render: (row: ChangeItem) =>
|
||||
props.editable
|
||||
? h(RichTextInput, {
|
||||
@@ -86,7 +85,6 @@ const columns = computed<DataTableColumns<ChangeItem>>(() => {
|
||||
{
|
||||
title: '变更后',
|
||||
key: 'after_text',
|
||||
width: 200,
|
||||
render: (row: ChangeItem) =>
|
||||
props.editable
|
||||
? h(RichTextInput, {
|
||||
|
||||
@@ -32,6 +32,16 @@ const regenerateLoading = ref<boolean>(false);
|
||||
const submitLoading = ref<boolean>(false);
|
||||
const confirmed = ref<boolean>(false);
|
||||
|
||||
// 是否重新生成
|
||||
const regenerateTypes = ref<Record<string, boolean>>({
|
||||
'form':false,
|
||||
'risk':false,
|
||||
'train':false,
|
||||
'pssr':false,
|
||||
'evaluation':false,
|
||||
'close':false,
|
||||
});
|
||||
|
||||
// 存储每个步骤选中的审核人 user_id
|
||||
const approvers = ref<Record<number, number[]>>({})
|
||||
const flowList = ref<any[]>([]);
|
||||
@@ -95,6 +105,7 @@ const download = async () => {
|
||||
}
|
||||
// 重新生成:AI 调用逻辑由各标签页组件实现(按 type 分发),此处仅派发事件
|
||||
const regenerate = async () => {
|
||||
regenerateTypes.value[props.type] = true;
|
||||
emit('regenerate', props.type);
|
||||
}
|
||||
// 供父组件控制「重新生成」按钮 loading
|
||||
@@ -173,25 +184,30 @@ defineExpose({clearApprovers, setRegenerateLoading})
|
||||
<div>
|
||||
<div class="flex flex-wrap items-center justify-between gap-2 border-t p-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<NButton ghost type="success" :disabled="btnDisabled" :loading="downloadLoading" @click="download">
|
||||
<Icon icon="material-symbols:download" class="size-16px" />
|
||||
<span v-if="props.type==='form'">下载变更申请表</span>
|
||||
<span v-if="props.type==='risk'">下载风险分析记录表</span>
|
||||
<span v-if="props.type==='train'">下载变更培训内容</span>
|
||||
<span v-if="props.type==='pssr'">下载PSSR检查内容</span>
|
||||
<span v-if="props.type==='evaluation'">下载验收评价</span>
|
||||
<span v-if="props.type==='close'">下载变更关闭确认表</span>
|
||||
<NButton v-if="isAiOpen && type!=='risk'" ghost type="warning" :disabled="btnDisabled || regenerateLoading" @click="regenerate">
|
||||
<Icon icon="tdesign:refresh" class="size-14px mr-1" :class="regenerateLoading?'animate-spin':''" />
|
||||
<template v-if="regenerateLoading">
|
||||
<span>生成中...</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span v-if="type==='form'">{{regenerateTypes[type]?'重新生成变更申请表':'生成变更申请表'}}</span>
|
||||
<span v-if="type==='train'">{{regenerateTypes[type]?'重新生成变更培训内容':'生成变更培训内容'}}</span>
|
||||
<span v-if="type==='pssr'">{{regenerateTypes[type]?'重新生成PSSR检查内容':'生成PSSR检查内容'}}</span>
|
||||
<span v-if="type==='evaluation'">{{regenerateTypes[type]?'重新生成验收评价':'生成验收评价'}}</span>
|
||||
<span v-if="type==='close'">{{regenerateTypes[type]?'重新生成变更关闭确认表':'生成变更关闭确认表'}}</span>
|
||||
</template>
|
||||
</NButton>
|
||||
<NButton v-if="props.isAiOpen" ghost type="warning" :disabled="btnDisabled" :loading="regenerateLoading" @click="regenerate">
|
||||
<Icon icon="tdesign:refresh" class="size-14px mr-1" />
|
||||
<span v-if="props.type==='form'">重新生成变更申请表</span>
|
||||
<span v-if="props.type==='risk'">重新生成风险分析记录表</span>
|
||||
<span v-if="props.type==='train'">重新生成变更培训内容</span>
|
||||
<span v-if="props.type==='pssr'">重新生成PSSR检查内容</span>
|
||||
<span v-if="props.type==='evaluation'">重新生成验收评价</span>
|
||||
<span v-if="props.type==='close'">重新生成变更关闭确认表</span>
|
||||
<NButton ghost type="success" :disabled="btnDisabled || downloadLoading" @click="download">
|
||||
<Icon v-if="downloadLoading" icon="tdesign:refresh" class="size-14px mr-1 animate-spin" />
|
||||
<Icon v-else icon="material-symbols:download" class="size-16px" />
|
||||
<span v-if="type==='form'">下载变更申请表</span>
|
||||
<span v-if="type==='risk'">下载风险分析记录表</span>
|
||||
<span v-if="type==='train'">下载变更培训内容</span>
|
||||
<span v-if="type==='pssr'">下载PSSR检查内容</span>
|
||||
<span v-if="type==='evaluation'">下载验收评价</span>
|
||||
<span v-if="type==='close'">下载变更关闭确认表</span>
|
||||
</NButton>
|
||||
<NButton v-if="props.isAiOpen" ghost :type="confirmed ? 'success' : 'primary'" :disabled="btnDisabled" @click="confirmTab">
|
||||
<NButton v-if="isAiOpen" ghost :type="confirmed ? 'success' : 'primary'" :disabled="btnDisabled" @click="confirmTab">
|
||||
<Icon icon="ix:success" class="size-16px mr-1" /> {{ confirmed ? '已确认完毕' : '本标签内容确认完毕' }}
|
||||
</NButton>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
||||
import { computed, h, onBeforeUnmount, onMounted, ref } from "vue";
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useDialog } from 'naive-ui';
|
||||
import { useDialog, NInput, NPopconfirm, NButton } from 'naive-ui';
|
||||
import { peopleListApi } from '@/service/api/system';
|
||||
import { hazopNodesApi, hazopSaveApi } from "@/service/api/plugin";
|
||||
|
||||
@@ -13,7 +13,10 @@ const props = defineProps(['isAiOpen','currentId','hazopRiskMatrixConfig'])
|
||||
const emit = defineEmits(['close','toTable']);
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const analyzing = ref(false);
|
||||
const analyzing = ref<boolean>(false);
|
||||
const inferring = ref<boolean>(false)
|
||||
const inferred = ref<boolean>(false)
|
||||
const keyword = ref<string>('');
|
||||
// 新增/编辑弹窗
|
||||
const editModal = ref<boolean>(false);
|
||||
// 当前弹框标题
|
||||
@@ -22,7 +25,266 @@ const currentModalTitle = ref<string>('');
|
||||
const currentModalType = ref<string>('');
|
||||
// 当前弹框对象
|
||||
const currentModalObj = ref<string>('');
|
||||
// 总体工艺描述与节点设置弹窗
|
||||
const openSetModal = ref<boolean>(false);
|
||||
// 总体工艺描述
|
||||
const description = ref<string>('');
|
||||
const nodeList = ref<any[]>([]);
|
||||
const name = ref<string>('');
|
||||
// 节点描述
|
||||
const node_description = ref<string>('');
|
||||
// 节点操作方式
|
||||
const node_way = ref<string>('');
|
||||
// 节点保护措施
|
||||
const node_protection = ref<string>('');
|
||||
// 分析列表弹窗
|
||||
const openListModal = ref<boolean>(false);
|
||||
|
||||
// 分析列表弹框使用节点
|
||||
const currentNodeListSource = ref<any>([])
|
||||
const currentNodeList = ref<any>([])
|
||||
const currentNodeIndex = ref<number>(-1)
|
||||
|
||||
// 保存数据
|
||||
const saveData = () => {
|
||||
currentNodeListSource.value = JSON.parse(JSON.stringify(currentNodeList.value))
|
||||
}
|
||||
// 搜索关键字
|
||||
const handleSearch = () => {
|
||||
const kw = keyword.value.trim()
|
||||
if(!kw){
|
||||
currentNodeList.value = JSON.parse(JSON.stringify(currentNodeListSource.value))
|
||||
return
|
||||
}
|
||||
currentNodeList.value = currentNodeListSource.value.map((item:any) => {
|
||||
const objMatched = item.obj_name?.includes(kw)
|
||||
const matchedDeviations = (item.deviations || []).filter((d:any) =>
|
||||
d.deviation?.includes(kw)
|
||||
)
|
||||
// obj_name 命中 → 保留全部 deviations;否则只保留命中的 deviations
|
||||
if (objMatched) {
|
||||
return { ...item } // 浅拷贝,结构不变
|
||||
}
|
||||
// 只有 deviation 命中 → 保留该对象,但 deviations 只留下命中的
|
||||
if (matchedDeviations.length) {
|
||||
return {
|
||||
...item, // 保留 id / node_id / obj_name 等
|
||||
deviations: matchedDeviations // 命中项原样保留,rows 等都在
|
||||
}
|
||||
}
|
||||
return null
|
||||
}).filter(Boolean)
|
||||
}
|
||||
// 点击偏离
|
||||
const clickDev = (dev:any,nodeIndex:number) => {
|
||||
currentNodeIndex.value = nodeIndex
|
||||
currentNodeList.value = nodes.value[nodeIndex]?.objects || []
|
||||
currentNodeListSource.value = JSON.parse(JSON.stringify(currentNodeList.value))
|
||||
currentDev.value = dev
|
||||
}
|
||||
// 分析列表弹框添加
|
||||
const addListItem = (type: string,objIndex:number = -1) => {
|
||||
// 分析对象
|
||||
if(type === 'obj'){
|
||||
currentNodeList.value.push({
|
||||
id: 0,
|
||||
obj_name: '',
|
||||
node_id: nodes.value[currentNodeIndex.value]?.id,
|
||||
deviations: []
|
||||
})
|
||||
}
|
||||
// 偏离
|
||||
if(type === 'dev'){
|
||||
currentNodeList.value[objIndex].deviations.push({
|
||||
id: 0,
|
||||
object_id: currentNodeList.value[objIndex]?.id,
|
||||
deviation: '',
|
||||
rows: []
|
||||
})
|
||||
}
|
||||
currentNodeListSource.value = JSON.parse(JSON.stringify(currentNodeList.value))
|
||||
}
|
||||
// 分析列表弹框删除
|
||||
const removeListItem = (type: string,objIndex:number = -1,devIndex:number = -1) => {
|
||||
// 分析对象
|
||||
if(type === 'obj'){
|
||||
currentNodeList.value.splice(objIndex, 1)
|
||||
}
|
||||
// 偏离
|
||||
if(type === 'dev'){
|
||||
currentNodeList.value[objIndex].deviations.splice(devIndex, 1)
|
||||
}
|
||||
currentNodeListSource.value = JSON.parse(JSON.stringify(currentNodeList.value))
|
||||
}
|
||||
// 分析列表弹框复制
|
||||
const copyListItem = (type: string,objIndex:number = -1,devIndex:number = -1) => {
|
||||
// 分析对象
|
||||
if(type === 'obj'){
|
||||
let list = currentNodeList.value[objIndex]?.deviations?.map((item:any) => ({
|
||||
...item,
|
||||
id: 0,
|
||||
object_id: 0
|
||||
}));
|
||||
currentNodeList.value.push({
|
||||
id: 0,
|
||||
obj_name: currentNodeList.value[objIndex]?.obj_name,
|
||||
node_id: currentNodeList.value[objIndex]?.node_id,
|
||||
deviations: list || []
|
||||
})
|
||||
}
|
||||
// 偏离
|
||||
if(type === 'dev'){
|
||||
let list = currentNodeList.value[objIndex]?.deviations[devIndex]?.rows?.map((item:any) => ({
|
||||
...item,
|
||||
id: 0,
|
||||
deviation_id: 0
|
||||
}));
|
||||
currentNodeList.value[objIndex].deviations.push({
|
||||
id: 0,
|
||||
object_id: currentNodeList.value[objIndex]?.id,
|
||||
deviation: currentNodeList.value[objIndex]?.deviations[devIndex]?.deviation,
|
||||
rows: list || []
|
||||
})
|
||||
}
|
||||
currentNodeListSource.value = JSON.parse(JSON.stringify(currentNodeList.value))
|
||||
}
|
||||
// 分析列表弹框保存
|
||||
const listSave = () => {
|
||||
const hasEmptyObjName = currentNodeList.value.some((item:any) => item.obj_name.trim() === '')
|
||||
if (hasEmptyObjName) {
|
||||
window.$message?.warning('分析对象名称不能为空')
|
||||
}
|
||||
const hasEmptyDeviation = currentNodeList.value.some((item:any) =>
|
||||
item.deviations?.some((d:any) => d.deviation.trim() === '')
|
||||
)
|
||||
if (hasEmptyDeviation) {
|
||||
window.$message?.warning('偏离名称不能为空')
|
||||
}
|
||||
nodes.value[currentNodeIndex.value].objects = currentNodeList.value
|
||||
openListModal.value = false
|
||||
}
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: '分析当前偏离',
|
||||
key: 'current'
|
||||
},
|
||||
{
|
||||
label: '分析全部偏离',
|
||||
key: 'all'
|
||||
}
|
||||
]
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
title: '序号',
|
||||
key: 'index',
|
||||
width: 60,
|
||||
align: 'center',
|
||||
render: (_:any, index:number) => {
|
||||
return h('span',{}, { default:()=>index+1 })
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '节点名称',
|
||||
key: 'node_name',
|
||||
align: 'center',
|
||||
render: (row:any) => {
|
||||
return h(NInput,{
|
||||
size:'small',
|
||||
type:'textarea',
|
||||
placeholder:'节点名称',
|
||||
class:'text-left',
|
||||
value:row.node_name,
|
||||
autosize: { minRows: 1 },
|
||||
clearable: true,
|
||||
onUpdateValue: (val:string) => {
|
||||
row.node_name = val
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '节点描述',
|
||||
key: 'description',
|
||||
align: 'center',
|
||||
render: (row:any) => {
|
||||
return h(NInput,{
|
||||
size:'small',
|
||||
type:'textarea',
|
||||
placeholder:'如:设计温度XX、设计压力XX、容积XX',
|
||||
class:'text-left',
|
||||
value:row.description,
|
||||
autosize: { minRows: 1 },
|
||||
clearable: true,
|
||||
onUpdateValue: (val:string) => {
|
||||
row.description = val
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 60,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
render: (_:any,index:number) => {
|
||||
return h('div', {class:'flex items-center justify-center gap-[5px]'}, {
|
||||
default: () => [
|
||||
h(NPopconfirm,{
|
||||
// 组件 Props
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
removeItem(index)
|
||||
},
|
||||
// 可选:自定义确认/取消按钮的样式
|
||||
positiveButtonProps: { size: 'tiny', type: 'primary' },
|
||||
negativeButtonProps: { size: 'tiny' }
|
||||
},{
|
||||
// 插槽 (Slots)
|
||||
trigger: () => h(
|
||||
NButton,
|
||||
{
|
||||
text: true,
|
||||
size: 'tiny',
|
||||
type: 'error',
|
||||
},
|
||||
{
|
||||
icon: () => h(Icon, { icon: 'ep:delete',class:'size-14px' }),
|
||||
default: () => '' // 按钮文字
|
||||
}
|
||||
),
|
||||
default: () => '确定要删除吗?' // 弹出框的提示内容
|
||||
}),
|
||||
]
|
||||
})
|
||||
}
|
||||
},
|
||||
])
|
||||
|
||||
// 弹框新增节点
|
||||
const addNode = () => {
|
||||
nodeList.value.push({id:0,node_name:name.value,change_id:props.currentId,objects:[]})
|
||||
}
|
||||
// 删除节点
|
||||
const removeItem = (index:number) => {
|
||||
nodeList.value.splice(index, 1);
|
||||
}
|
||||
const setSave = async () => {
|
||||
const hasEmptyNodeName = nodeList.value.some((item:any) => item.node_name === '');
|
||||
if(hasEmptyNodeName){
|
||||
return window.$message?.warning('请填写节点名称');
|
||||
}
|
||||
nodes.value = nodes.value.concat(nodeList.value);
|
||||
openSetModal.value = false;
|
||||
}
|
||||
|
||||
// 处理下拉菜单选择
|
||||
const handleSelect = (key: string) => {
|
||||
runAi(key);
|
||||
}
|
||||
|
||||
const selectOptions = computed(() => {
|
||||
const l = props.hazopRiskMatrixConfig?.probability?.map((item:{key:string,label:string}, index:number) => ({
|
||||
@@ -230,15 +492,31 @@ const exportWord = () => {
|
||||
window.$message?.info('还差接口')
|
||||
}
|
||||
|
||||
// AI 分析当前偏离
|
||||
const runAi = async () => {
|
||||
if(!currentDev.value){
|
||||
return window.$message?.warning("请先在左侧选择偏离")
|
||||
}
|
||||
// AI 自动分析偏离
|
||||
const runAi = async (type: string) => {
|
||||
if(currentDev.value.rows.length){
|
||||
return window.$message?.info("当前偏离已有分析记录,可手动新增行补充");
|
||||
}
|
||||
// 分析当前偏离
|
||||
if(type==='current'){
|
||||
if(!currentDev.value){
|
||||
return window.$message?.warning("请先在左侧选择偏离")
|
||||
}
|
||||
}
|
||||
// 分析全部偏离
|
||||
if(type==='all'){
|
||||
|
||||
}
|
||||
};
|
||||
// AI 自动推演
|
||||
const runInference = async () => {
|
||||
inferring.value = true
|
||||
inferred.value = false
|
||||
setTimeout(() => {
|
||||
inferring.value = false
|
||||
inferred.value = true
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 根据l和s计算RR风险等级
|
||||
const riskLevel = (l: number, s: number) => {
|
||||
@@ -329,11 +607,12 @@ const leftToggle = (id: string) => {
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openModal = (type: string,obj: string, title: string,editName: string = '') => {
|
||||
const openModal = (type: string,obj: string, title: string,editName: string = '',editDescription: string = '') => {
|
||||
if(type==='add'){
|
||||
name.value = '';
|
||||
}else{
|
||||
name.value = editName;
|
||||
node_description.value = editDescription;
|
||||
}
|
||||
currentModalType.value = type;
|
||||
currentModalObj.value = obj;
|
||||
@@ -348,13 +627,14 @@ const modalSave = async () => {
|
||||
}
|
||||
// 新增节点
|
||||
if(currentModalType.value==='add' && currentModalObj.value==='node'){
|
||||
nodes.value.push({id:0,node_name:name.value,change_id:props.currentId,objects:[]});
|
||||
nodes.value.push({id:0,node_name:name.value,change_id:props.currentId,objects:[],description:node_description.value});
|
||||
window.$message?.success("新增节点成功")
|
||||
editModal.value = false;
|
||||
}
|
||||
// 编辑节点
|
||||
if(currentModalType.value==='edit' && currentModalObj.value==='node'){
|
||||
nodes.value[contextTarget.value?.nodeIndex].node_name = name.value;
|
||||
nodes.value[contextTarget.value?.nodeIndex].description = node_description.value;
|
||||
window.$message?.success("编辑节点成功")
|
||||
editModal.value = false;
|
||||
}
|
||||
@@ -500,7 +780,7 @@ const handleMenuSelect = (key: string) => {
|
||||
break
|
||||
case 'editNode':
|
||||
// 编辑节点
|
||||
openModal('edit','node','编辑节点',node?.node_name);
|
||||
openModal('edit','node','编辑节点',node?.node_name,node?.description);
|
||||
break
|
||||
case 'delNode':
|
||||
// 删除
|
||||
@@ -556,7 +836,7 @@ const getHazopNodes = async () => {
|
||||
if(!error){
|
||||
nodes.value = data.nodes;
|
||||
selectedWorkers.value = data.users;
|
||||
currentDev.value = nodes.value[0]?.objects[0]?.deviations?.[0] || null;
|
||||
clickDev(nodes.value[0]?.objects[0]?.deviations?.[0] || null,0)
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -630,17 +910,28 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</div>
|
||||
<div class="ml-auto flex items-center gap-1.5">
|
||||
<NButton v-if="isAiOpen" size="small" ghost type="primary" class="text-xs" :disabled="recTotal === 0" @click="enterToTable('all')">
|
||||
<Icon icon="lucide:sparkles" class="size-12px mr-1" /> AI 整理入表
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="openSetModal = true;nodeList = [];description = '';">
|
||||
<Icon icon="akar-icons:file" class="size-12px mr-1" /> 总体工艺描述与节点设置
|
||||
</NButton>
|
||||
<NButton size="small" ghost type="primary" class="text-xs" :disabled="recTotal === 0" @click="enterToTable('all')">
|
||||
<Icon icon="carbon:document-import" class="size-14px mr-1" /> 全部整理入表
|
||||
</NButton>
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="exportWord">
|
||||
<Icon icon="material-symbols:download" class="size-14px" /> 导出 Word
|
||||
</NButton>
|
||||
<NButton v-if="isAiOpen" size="small" type="primary" class="text-xs" :disabled="analyzing" @click="runAi">
|
||||
<Icon v-if="analyzing" icon="ri:loader-4-fill" class="size-14px animate-spin" />
|
||||
<Icon v-else icon="lucide:sparkles" class="size-12px" />
|
||||
AI 分析当前偏离
|
||||
<Icon icon="material-symbols:download" class="size-14px" /> 导出 Word
|
||||
</NButton>
|
||||
<NDropdown
|
||||
v-if="isAiOpen"
|
||||
trigger="hover"
|
||||
:options="options"
|
||||
@select="handleSelect"
|
||||
>
|
||||
<NButton size="small" type="primary" class="text-xs" :disabled="analyzing">
|
||||
<Icon v-if="analyzing" icon="ri:loader-4-fill" class="size-14px animate-spin" />
|
||||
<Icon v-else icon="lucide:sparkles" class="size-12px" />
|
||||
AI 自动分析
|
||||
<Icon icon="ant-design:caret-down-filled" class="size-12px ml-1" />
|
||||
</NButton>
|
||||
</NDropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -666,6 +957,7 @@ onBeforeUnmount(() => {
|
||||
<Icon icon="ic:round-plus" class="size-14px" />新增节点
|
||||
</NButton>
|
||||
<div class="shrink-0 rounded-md border p-2 scroll">
|
||||
<p v-if="nodes.length === 0" class="text-center text-xs text-slate-400 py-10">暂无节点数据</p>
|
||||
<div v-for="(node, nodeIndex) in nodes" :key="nodeIndex">
|
||||
<div
|
||||
class="cursor-pointer flex items-center select-none"
|
||||
@@ -689,7 +981,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<template v-if="!collapsed[node.id+'_'+obj.id]">
|
||||
<div v-for="(dev, devIndex) in obj.deviations" :key="devIndex"
|
||||
@click="currentDev = dev"
|
||||
@click="clickDev(dev,Number(nodeIndex))"
|
||||
@contextmenu.prevent="handleContextMenu($event, 'dev', node, obj, dev, Number(nodeIndex), Number(objIndex), Number(devIndex))"
|
||||
class="cursor-pointer flex w-full items-center ml-2 gap-1.5 rounded-md px-3 py-1.5 text-left text-xs transition"
|
||||
:class="currentDev?.id === dev.id ? 'bg-[var(--theme-color)] text-white' : 'text-slate-600 hover:bg-slate-100'"
|
||||
@@ -713,7 +1005,15 @@ onBeforeUnmount(() => {
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="addRow">
|
||||
<Icon icon="ic:round-plus" class="size-14px" /> 新增行
|
||||
</NButton>
|
||||
<span class="text-[11px] text-slate-400">单元格点击即可修改;风险等级按风险矩阵L / S 取值实时重算;「入表」将本条选入风险分析记录表</span>
|
||||
<NButton v-if="currentDev" size="small" type="primary" class="text-xs" @click="openListModal = true">
|
||||
<Icon icon="ant-design:bars-outlined" class="size-14px" /> 分析列表
|
||||
</NButton>
|
||||
<NButton v-if="isAiOpen && currentDev" size="small" type="primary" class="text-xs" :disabled="analyzing" @click="runAi('current')">
|
||||
<Icon v-if="analyzing" icon="ri:loader-4-fill" class="size-14px animate-spin" />
|
||||
<Icon v-else icon="lucide:sparkles" class="size-12px" />
|
||||
AI 分析当前偏离
|
||||
</NButton>
|
||||
<span class="text-[11px] text-slate-400">风险等级按风险矩阵L / S 取值实时重算;「入表」将本条选入风险分析记录表</span>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<table class="w-full border-collapse text-xs">
|
||||
@@ -881,6 +1181,7 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<div>
|
||||
<NInput v-model:value="name" placeholder="请输入名称" />
|
||||
<NInput v-if="currentModalObj==='node'" class="mt-3" v-model:value="node_description" placeholder="请输入节点描述" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-[10px]">
|
||||
@@ -889,6 +1190,221 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</template>
|
||||
</NModal>
|
||||
<!-- 总体工艺描述与节点设置弹窗 -->
|
||||
<NModal
|
||||
v-model:show="openSetModal"
|
||||
preset="card"
|
||||
title="总体工艺描述与节点设置"
|
||||
:auto-focus="true"
|
||||
:style="{ width: '1000px', height: 'auto' }"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
>
|
||||
<div>
|
||||
<div v-if="isAiOpen" class="rounded-lg border border-indigo-100 bg-indigo-50/60 p-3 mb-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2 text-sm text-indigo-700">
|
||||
<Icon icon="bx:bot" class="size-16px" />
|
||||
{{
|
||||
inferring
|
||||
? 'AI正在根据MOC工艺描述推演工艺流程、涉及设备与关键参数…'
|
||||
: inferred
|
||||
? 'AI推演完成,已补全空缺内容,可手动修改后保存'
|
||||
: '可点击右侧按钮,由AI根据工艺描述自动推演需要分析的设备及参数'
|
||||
}}
|
||||
</div>
|
||||
<n-button size="small" type="primary" @click="runInference" :disabled="inferring || !description">
|
||||
<Icon v-if="inferring" icon="ri:loader-4-fill" class="size-14px animate-spin" />
|
||||
<Icon v-else icon="lucide:sparkles" class="size-12px" />
|
||||
{{ inferring ? '推演中…' : 'AI自动推演' }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-1">工艺描述</p>
|
||||
<NInput
|
||||
v-model:value="description"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
clearable
|
||||
placeholder="简述本节点的工艺流程、物料组成、上下游关系……"
|
||||
/>
|
||||
<div class="flex items-center justify-between mt-3">
|
||||
<label class="block text-sm font-medium text-slate-700">
|
||||
节点设置
|
||||
<span class="ml-2 text-xs font-normal text-slate-400">MOC场景通常一个节点即可,涉及多个时逐行罗列</span>
|
||||
</label>
|
||||
<NButton size="small" dashed type="primary" @click="addNode">
|
||||
<Icon icon="ic:round-plus" class="size-14px" />新增节点
|
||||
</NButton>
|
||||
</div>
|
||||
<NDataTable
|
||||
class="mt-1"
|
||||
size="small"
|
||||
:single-line="false"
|
||||
:columns="columns"
|
||||
:data="nodeList"
|
||||
:pagination="false"
|
||||
:max-height="220"
|
||||
>
|
||||
<template #empty>
|
||||
<p class="text-slate-400 text-sm">
|
||||
暂无节点 — 点击「新增节点」手动添加{{isAiOpen?',或点击上方「AI自动推演」自动生成':''}}
|
||||
</p>
|
||||
</template>
|
||||
</NDataTable>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-[10px]">
|
||||
<NButton size="small" @click="openSetModal = false">取消</NButton>
|
||||
<NButton size="small" type="primary" @click="setSave">确定</NButton>
|
||||
</div>
|
||||
</template>
|
||||
</NModal>
|
||||
<!-- 分析列表弹窗 -->
|
||||
<NModal
|
||||
v-model:show="openListModal"
|
||||
preset="card"
|
||||
:auto-focus="true"
|
||||
:style="{ width: '1200px', height: 'auto' }"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="flex items-center gap-2 text-base text-lg leading-none font-semibold">
|
||||
分析列表
|
||||
<span class="text-xs font-normal text-slate-400">编辑全部分析对象及对应偏离,支持增删改与拖拽排序</span>
|
||||
</h2>
|
||||
<div class="flex items-center gap-[10px] mr-[10px]">
|
||||
<NButton size="small" dashed type="primary" @click="addListItem('obj')"><Icon icon="ic:round-plus" class="size-14px" />分析对象</NButton>
|
||||
<NButton size="small" dashed type="primary">AI设置偏离</NButton>
|
||||
<NInput
|
||||
size="small"
|
||||
v-model:value="keyword"
|
||||
clearable
|
||||
placeholder="输入关键字"
|
||||
@clear="keyword = ''; handleSearch()"
|
||||
>
|
||||
<template #suffix>
|
||||
<NButton text @click="handleSearch">
|
||||
<Icon icon="akar-icons:search" class="size-14px text-slate-400 ml-1" />
|
||||
</NButton>
|
||||
</template>
|
||||
</NInput>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="flex">
|
||||
<div class="flex-1 max-h-[60vh] overflow-y-auto border-r mr-3 pr-3">
|
||||
<div>
|
||||
<p class="mb-1">本节点流程描述</p>
|
||||
<NInput v-model:value="node_description" type="textarea" :autosize="{ minRows: 4,}" clearable placeholder="请输入本节点流程描述" />
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<p class="mb-1">本节点操作方式描述</p>
|
||||
<NInput v-model:value="node_way" type="textarea" :autosize="{ minRows: 4,}" clearable placeholder="请输入本节点操作方式描述" />
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<p class="mb-1">本节点现有保护措施罗列</p>
|
||||
<NInput v-model:value="node_protection" type="textarea" :autosize="{ minRows: 4,}" clearable placeholder="请输入本节点现有保护措施罗列" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-500px max-h-[60vh] overflow-y-auto">
|
||||
<NCollapse v-if="currentNodeList.length" :trigger-areas="['arrow']">
|
||||
<template v-for="(obj,objIndex) in currentNodeList" :key="objIndex">
|
||||
<NCollapseItem
|
||||
:title="obj.obj_name"
|
||||
:name="objIndex"
|
||||
:draggable="true"
|
||||
@dragstart="onDeviceDragStart($event, objIndex)"
|
||||
@dragover.prevent
|
||||
@drop="onDeviceDrop($event, objIndex)"
|
||||
>
|
||||
<template #header>
|
||||
<div class="mr-[5px] cursor-grab" :draggable="true">
|
||||
<Icon icon="gravity-ui:grip" class="size-12px" />
|
||||
</div>
|
||||
<NInput v-model:value="obj.obj_name" size="small" clearable placeholder="分析对象名称" style="width: 300px;" @blur="saveData" />
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<div class="flex items-center gap-[10px]">
|
||||
<NButton size="tiny" dashed @click="addListItem('dev',Number(objIndex))"><Icon icon="ic:round-plus" class="size-12px" />偏离</NButton>
|
||||
<NButton text @click="copyListItem('obj',Number(objIndex))">
|
||||
<Icon icon="boxicons:copy" class="size-14px" />
|
||||
</NButton>
|
||||
<NPopconfirm
|
||||
positive-text="确定"
|
||||
:positiveButtonProps="{ size: 'tiny', type: 'primary' }"
|
||||
:negativeButtonProps="{ size: 'tiny' }"
|
||||
@positive-click="removeListItem('obj',Number(objIndex))"
|
||||
>
|
||||
<template #trigger>
|
||||
<NButton text type="error">
|
||||
<Icon icon="ep:delete" class="size-14px" />
|
||||
</NButton>
|
||||
</template>
|
||||
确定删除吗?
|
||||
</NPopconfirm>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="obj?.deviations?.length" class="flex flex-col gap-[15px] ml-[40px]">
|
||||
<div
|
||||
v-for="(dev, devIndex) in obj.deviations"
|
||||
:key="devIndex"
|
||||
class="flex items-center justify-between"
|
||||
:draggable="true"
|
||||
@dragstart="onDeviateDragStart($event, objIndex, devIndex)"
|
||||
@dragover.prevent
|
||||
@drop="onDeviateDrop($event, objIndex, devIndex)"
|
||||
>
|
||||
<div class="flex items-center gap-[5px]">
|
||||
<div class="mr-[5px] cursor-grab" :draggable="true">
|
||||
<Icon icon="gravity-ui:grip" class="size-12px" />
|
||||
</div>
|
||||
<Split :size="14" class="text-orange-400" />
|
||||
<NInput v-model:value="dev.deviation" size="small" clearable placeholder="偏离名称" style="width: 300px;" @blur="saveData" />
|
||||
</div>
|
||||
<div class="flex items-center gap-[10px]">
|
||||
<span v-if="dev?.rows?.length" class="rounded-full bg-emerald-100 px-1.5 text-[10px] text-emerald-600">
|
||||
{{ dev?.rows?.length }}条
|
||||
</span>
|
||||
<span v-else class="rounded-full bg-slate-100 px-1.5 text-[10px] text-slate-400">待分析</span>
|
||||
<NButton text @click="copyListItem('dev',Number(objIndex), Number(devIndex))">
|
||||
<Icon icon="boxicons:copy" class="size-14px" />
|
||||
</NButton>
|
||||
<NPopconfirm
|
||||
positive-text="确定"
|
||||
:positiveButtonProps="{ size: 'tiny', type: 'primary' }"
|
||||
:negativeButtonProps="{ size: 'tiny' }"
|
||||
@positive-click="removeListItem('dev',Number(objIndex), Number(devIndex))"
|
||||
>
|
||||
<template #trigger>
|
||||
<NButton text type="error">
|
||||
<Icon icon="ep:delete" class="size-14px" />
|
||||
</NButton>
|
||||
</template>
|
||||
确定删除吗?
|
||||
</NPopconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NCollapseItem>
|
||||
</template>
|
||||
</NCollapse>
|
||||
<NEmpty v-else description="暂无数据" size="large">
|
||||
<template #icon>
|
||||
<Icon icon="lucide:file-search-corner" class="size-48px" />
|
||||
</template>
|
||||
</NEmpty>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-[10px]">
|
||||
<NButton size="small" @click="openListModal = false">取消</NButton>
|
||||
<NButton size="small" type="primary" @click="listSave">确定</NButton>
|
||||
</div>
|
||||
</template>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -462,7 +462,7 @@ onMounted(() => {
|
||||
@confirmTab="confirmTab"
|
||||
/>
|
||||
<!-- 插件抽屉 -->
|
||||
<NDrawer v-model:show="pluginDrawer" width="85%" placement="right">
|
||||
<NDrawer v-model:show="pluginDrawer" width="100%" placement="right">
|
||||
<NDrawerContent>
|
||||
<template v-if="currentPlugin === 'RISK'">
|
||||
<RiskPre :isAiOpen="props.isAiOpen" :currentId="props.currentId" @close="closePlugin" @toTable="exportAll" />
|
||||
|
||||
@@ -199,8 +199,8 @@ onMounted(() => {
|
||||
</template>
|
||||
</div>
|
||||
<div class="ml-auto flex items-center gap-1.5">
|
||||
<NButton v-if="isAiOpen" size="small" ghost type="primary" class="text-xs" :disabled="riskRecords.length === 0" @click="enterToTable('all',null)">
|
||||
<Icon icon="lucide:sparkles" class="size-12px mr-1" /> AI 整理入表
|
||||
<NButton size="small" ghost type="primary" class="text-xs" :disabled="riskRecords.length === 0" @click="enterToTable('all',null)">
|
||||
<Icon icon="carbon:document-import" class="size-14px mr-1" /> 全部整理入表
|
||||
</NButton>
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="generateReport">
|
||||
<Icon icon="material-symbols:download" class="size-14px" /> 导出 Word
|
||||
|
||||
Reference in New Issue
Block a user