风险预分析和风险检查表插件完成
This commit is contained in:
@@ -35,12 +35,3 @@ export function getChangeDetailApi(id: number) {
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,3 +33,36 @@ export function jsaSaveApi(change_id: number, data: any) {
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取风险预分析详情
|
||||
export function riskPreDetailApi(change_id: number) {
|
||||
return request({
|
||||
url: `/api/changes/${change_id}/risk-summary`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
// 风险预分析保存
|
||||
export function riskPreSaveApi(change_id: number, data: any) {
|
||||
return request({
|
||||
url: `/api/changes/${change_id}/risk-summary`,
|
||||
method: 'put',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取风险检查表详情
|
||||
export function riskCheckDetailApi(change_id: number) {
|
||||
return request({
|
||||
url: `/api/changes/${change_id}/questionnaire`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
// 风险检查表保存
|
||||
export function riskCheckSaveApi(change_id: number, data: any) {
|
||||
return request({
|
||||
url: `/api/changes/${change_id}/questionnaire`,
|
||||
method: 'put',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
import { computed, nextTick, onMounted, provide, ref } from 'vue';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { getColorWithOpacity } from "@/utils/common";
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { queryUnsavedApi, saveChangeApi, submitChangeApi } from '@/service/api/change';
|
||||
import { queryUnsavedApi, saveChangeApi, submitChangeApi, getChangeDetailApi } from '@/service/api/change';
|
||||
import { systemConfigApi, templateListApi, } from '@/service/api/system';
|
||||
import { orgListApi } from '@/service/api/user';
|
||||
import ChangePre from "./modules/changePre.vue";
|
||||
@@ -16,6 +17,7 @@ import Evaluation from "./modules/evaluation.vue";
|
||||
import CloseConfirm from "./modules/closeConfirm.vue";
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const route = useRoute();
|
||||
|
||||
const saveLoading = ref<boolean>(false)
|
||||
const btnDisabled = ref<boolean>(false);
|
||||
@@ -124,54 +126,6 @@ const confirmTab = (tab: string, val: boolean) => {
|
||||
confirmed.value[tab] = val;
|
||||
}
|
||||
|
||||
// 查询变更未保存数据
|
||||
const getId = async () => {
|
||||
const {data,error} = await queryUnsavedApi();
|
||||
if(!error){
|
||||
currentId.value = data.id;
|
||||
changeNo.value = data.change_no;
|
||||
}
|
||||
}
|
||||
// 获取配置
|
||||
const getConfig = async () => {
|
||||
const {data,error} = await systemConfigApi();
|
||||
if(!error){
|
||||
// 解析 变更类型设置
|
||||
const changeTypeGroup = data.find((group:{group_key:string}) => group.group_key === 'change_type');
|
||||
if (changeTypeGroup) {
|
||||
typeList.value = changeTypeGroup.items.map((item:any) => ({
|
||||
label: item.label,
|
||||
value: Number(item.value), // 或者 parseInt(item.value, 10)
|
||||
color: item.color
|
||||
}));
|
||||
}
|
||||
// 解析 变更等级设置
|
||||
const changeLevelGroup = data.find((group:{group_key:string}) => group.group_key === 'change_level');
|
||||
if (changeLevelGroup) {
|
||||
levelList.value = changeLevelGroup.items.map((item:any) => ({
|
||||
label: item.label,
|
||||
value: Number(item.value), // 或者 parseInt(item.value, 10)
|
||||
color: item.color
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 获取模板配置
|
||||
const getTemplateConfig = async () => {
|
||||
const {data,error} = await templateListApi();
|
||||
if(!error){
|
||||
templateList.value = data;
|
||||
}
|
||||
}
|
||||
// 获取组织列表
|
||||
const getOrgList = async () => {
|
||||
const {data,error} = await orgListApi();
|
||||
if(!error){
|
||||
orgTree.value = data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 获取所有表单数据
|
||||
const getAllFormData = () => {
|
||||
let changePreForm = changePreRef.value?.form;
|
||||
@@ -259,7 +213,7 @@ const submitConfirm = async (type: string) => {
|
||||
}
|
||||
// 变更培训内容
|
||||
if(type==='train'){
|
||||
if(form.content.trim() === ''){
|
||||
if(form.train.content.trim() === ''){
|
||||
return window.$message?.warning('请填写培训内容');
|
||||
}
|
||||
}
|
||||
@@ -269,11 +223,72 @@ const submitConfirm = async (type: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取变更详情
|
||||
const getChangeDetail = async () => {
|
||||
const {data,error} = await getChangeDetailApi(currentId.value);
|
||||
if(!error){
|
||||
|
||||
}
|
||||
}
|
||||
// 查询变更未保存数据
|
||||
const getId = async () => {
|
||||
const {data,error} = await queryUnsavedApi();
|
||||
if(!error){
|
||||
currentId.value = data.id;
|
||||
changeNo.value = data.change_no;
|
||||
}
|
||||
}
|
||||
// 获取配置
|
||||
const getConfig = async () => {
|
||||
const {data,error} = await systemConfigApi();
|
||||
if(!error){
|
||||
// 解析 变更类型设置
|
||||
const changeTypeGroup = data.find((group:{group_key:string}) => group.group_key === 'change_type');
|
||||
if (changeTypeGroup) {
|
||||
typeList.value = changeTypeGroup.items.map((item:any) => ({
|
||||
label: item.label,
|
||||
value: Number(item.value), // 或者 parseInt(item.value, 10)
|
||||
color: item.color
|
||||
}));
|
||||
}
|
||||
// 解析 变更等级设置
|
||||
const changeLevelGroup = data.find((group:{group_key:string}) => group.group_key === 'change_level');
|
||||
if (changeLevelGroup) {
|
||||
levelList.value = changeLevelGroup.items.map((item:any) => ({
|
||||
label: item.label,
|
||||
value: Number(item.value), // 或者 parseInt(item.value, 10)
|
||||
color: item.color
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 获取模板配置
|
||||
const getTemplateConfig = async () => {
|
||||
const {data,error} = await templateListApi();
|
||||
if(!error){
|
||||
templateList.value = data;
|
||||
}
|
||||
}
|
||||
// 获取组织列表
|
||||
const getOrgList = async () => {
|
||||
const {data,error} = await orgListApi();
|
||||
if(!error){
|
||||
orgTree.value = data;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getOrgList();
|
||||
getConfig();
|
||||
getTemplateConfig();
|
||||
getId();
|
||||
if(route.query && route.query.id){
|
||||
currentId.value = Number(route.query.id);
|
||||
}
|
||||
if(currentId.value){
|
||||
getChangeDetail();
|
||||
}else{
|
||||
getId();
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -11,6 +11,8 @@ const themeStore = useThemeStore();
|
||||
const props = defineProps(['type','orgTree','isAiOpen','title','currentId','formInfo'])
|
||||
const emit = defineEmits(['save','submit','confirmTab']);
|
||||
|
||||
// 是否是ai生成
|
||||
const isAiGenerated = ref<boolean>(false);
|
||||
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}[]>([]);
|
||||
@@ -43,6 +45,7 @@ const onRegenerate = async () => {
|
||||
// 建议责任方文本 → 组织树节点(按名称模糊匹配,匹配不到留空人工选择)
|
||||
org_id: matchOrgId(r.owner_suggest),
|
||||
}));
|
||||
isAiGenerated.value = true;
|
||||
window.$message?.success("验收量化标准已由 AI 生成,均可手动修改");
|
||||
} catch (e: any) {
|
||||
aiFail(e);
|
||||
@@ -125,7 +128,7 @@ defineExpose({list, onRegenerate})
|
||||
<Icon icon="ix:success" class="size-18px text-emerald-500" />
|
||||
验收评价(依据预期效果设定可量化验收标准)
|
||||
</span>
|
||||
<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">
|
||||
<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>
|
||||
<span class="ml-auto text-xs text-slate-400">申请阶段由 AI 依据「预期效果」生成量化标准与验收方式,可手动修改;投用运行后由车间和各专业按标准验收并给出评估结论</span>
|
||||
|
||||
@@ -423,7 +423,7 @@ onMounted(() => {
|
||||
</div>
|
||||
<p v-if="steps.length===0" class="border py-5 text-center text-xs text-slate-400">暂无作业步骤</p>
|
||||
<div v-else class="scroll">
|
||||
<table>
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th :class="TH" class="w-96px">操作</th>
|
||||
@@ -513,7 +513,7 @@ onMounted(() => {
|
||||
<td :class="TD">
|
||||
<NInputNumber v-model:value="row.s_value" size="tiny" :min="s_options[0].key" :max="s_options[s_options.length-1].key" :show-button="false" placeholder="" class="text-center w-10" />
|
||||
</td>
|
||||
<td :class="TD">
|
||||
<td :class="TD" class="text-center">
|
||||
<NTag
|
||||
v-if="row.l_value>0 && row.s_value>0"
|
||||
class="text-[11px]"
|
||||
@@ -579,7 +579,7 @@ onMounted(() => {
|
||||
<Icon icon="ic:round-plus" class="size-12px" />增加
|
||||
</NButton>
|
||||
</td>
|
||||
<td :class="TD">
|
||||
<td :class="TD" class="text-center">
|
||||
<NTag
|
||||
v-if="row.l_value>0 && row.s_value>0"
|
||||
class="text-[11px]"
|
||||
|
||||
@@ -16,6 +16,8 @@ const uploadLoading = ref<boolean>(false);
|
||||
const uploadFileRef = ref<any>(null);
|
||||
const isUploaded = ref<boolean>(false);
|
||||
const editing = ref<any>(null);
|
||||
// 是否是ai生成
|
||||
const isAiGenerated = ref<boolean>(false);
|
||||
|
||||
const aiFail = (e: any) => {
|
||||
if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`);
|
||||
@@ -65,6 +67,7 @@ const onRegenerate = async () => {
|
||||
})),
|
||||
};
|
||||
});
|
||||
isAiGenerated.value = true;
|
||||
window.$message?.success("PSSR 检查清单已由 AI 生成,点击条目可直接编辑");
|
||||
} catch (e: any) {
|
||||
aiFail(e);
|
||||
@@ -118,7 +121,7 @@ defineExpose({list, onRegenerate})
|
||||
<Icon icon="tabler:clipboard-check" class="size-16px text-violet-500" />
|
||||
PSSR 投用前安全检查内容
|
||||
</span>
|
||||
<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">
|
||||
<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>
|
||||
<span class="flex items-center gap-2">
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import RiskPre from "./riskPre.vue";
|
||||
import Hazop from "./hazop.vue";
|
||||
import Jsa from "./jsa.vue";
|
||||
import Scl from "./scl.vue";
|
||||
@@ -24,16 +25,16 @@ const pluginDone = computed(() =>
|
||||
return acc;
|
||||
}, {} as Record<string, number>)
|
||||
);
|
||||
const tools = ref<string[]>(["HAZOP","JSA", "SCL", "RISK_CHECK"]);
|
||||
const tools = ref<string[]>(["RISK","HAZOP","JSA", "RISK_CHECK"]);
|
||||
const riskRecords = ref<any>([]);
|
||||
const pluginDrawer = ref<boolean>(false);
|
||||
const currentPlugin = ref<string>('');
|
||||
|
||||
// 风险识别分析记录表
|
||||
const TOOL_CARDS = [
|
||||
{ key: "RISK", name: "风险预分析" },
|
||||
{ key: "HAZOP", name: "HAZOP 分析" },
|
||||
{ key: "JSA", name: "JSA 分析" },
|
||||
{ key: "SCL", name: "设备 SCL" },
|
||||
{ key: "RISK_CHECK", name: "风险检查表" },
|
||||
];
|
||||
|
||||
@@ -160,12 +161,14 @@ const exportAll = (type: string, items: any[]) => {
|
||||
|
||||
// 使用插件弹框
|
||||
const usePlugin = (key: string) => {
|
||||
console.log(riskRecords.value)
|
||||
currentPlugin.value = key;
|
||||
pluginDrawer.value = true;
|
||||
}
|
||||
// 关闭插件弹框
|
||||
const closePlugin = () => {
|
||||
const closePlugin = (type: string = "", count: any = undefined) => {
|
||||
if(type){
|
||||
pluginDone.value[type] = count;
|
||||
}
|
||||
pluginDrawer.value = false;
|
||||
}
|
||||
|
||||
@@ -218,7 +221,7 @@ onMounted(() => {
|
||||
{{ formInfo?.change_level === 2 ? '重要变更' : '一般变更' }}
|
||||
</NTag>
|
||||
<span class="text-xs text-slate-400">
|
||||
{{ formInfo?.change_level === 2 ? '规则:HAZOP 必做,同时开展 JSA 与检查表法(SCL)' : '规则:JSA + 检查表法(SCL),涉及工艺安全边界的应升级 HAZOP' }}
|
||||
{{ formInfo?.change_level === 2 ? '规则:HAZOP 必做,同时开展 JSA' : '规则:JSA,涉及工艺安全边界的应升级 HAZOP' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid gap-2 grid-cols-4 mt-3">
|
||||
@@ -268,7 +271,7 @@ onMounted(() => {
|
||||
尚无风险记录:调用上方插件完成分析后逐条「入表」,或点击「AI 自动整理」形成记录表
|
||||
</div>
|
||||
<div v-else class="overflow-x-auto p-3">
|
||||
<table class="w-full min-w-[960px] border-collapse text-xs">
|
||||
<table class="w-full border-collapse text-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-40 border bg-slate-50 px-2 py-1.5 text-[11px] font-medium text-slate-500">风险项</th>
|
||||
@@ -283,13 +286,39 @@ onMounted(() => {
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(rec, i) in riskRecords" :key="i">
|
||||
<td class="border px-1.5 py-1 align-top font-medium text-slate-700">
|
||||
<td class="border px-1.5 py-1 font-medium text-slate-700">
|
||||
<NInput type="textarea" size="small" v-model:value="rec.risk_item" :autosize="{minRows:1}" class="text-xs" />
|
||||
</td>
|
||||
<td class="border px-1.5 py-1 align-top">
|
||||
<td class="border px-1.5 py-1">
|
||||
<NInput type="textarea" size="small" v-model:value="rec.scene" :autosize="{minRows:1}" class="text-xs" />
|
||||
</td>
|
||||
<td class="border px-1.5 py-1 text-center align-top">
|
||||
<div v-if="rec.source==='RISK'">
|
||||
<NTag
|
||||
v-if="rec.risk_value && rec.risk_value === '低'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="success"
|
||||
>
|
||||
{{rec.risk_value}}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="rec.risk_value && rec.risk_value === '中'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="warning"
|
||||
>
|
||||
{{rec.risk_value}}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="rec.risk_value && rec.risk_value === '高'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="error"
|
||||
>
|
||||
{{rec.risk_value}}
|
||||
</NTag>
|
||||
</div>
|
||||
<div v-if="rec.source==='HAZOP'">
|
||||
<NTag
|
||||
v-if="rec.l_value>0 && rec.s_value>0"
|
||||
@@ -319,13 +348,39 @@ onMounted(() => {
|
||||
</NTag>
|
||||
</div>
|
||||
</td>
|
||||
<td class="border px-1.5 py-1 align-top">
|
||||
<td class="border px-1.5 py-1">
|
||||
<NInput type="textarea" size="small" v-model:value="rec.safe" :autosize="{minRows:1}" class="text-xs" />
|
||||
</td>
|
||||
<td class="border px-1.5 py-1 align-top">
|
||||
<NInput type="textarea" size="small" v-model:value="rec.suggestion" :autosize="{minRows:1}" class="text-xs" />
|
||||
<td class="border px-1.5 py-1">
|
||||
<NInput type="textarea" size="small" v-model:value="rec.suggestion" :autosize="{minRows:1}" class="text-xs" />
|
||||
</td>
|
||||
<td class="border px-1.5 py-1 text-center align-top">
|
||||
<div v-if="rec.source==='RISK'">
|
||||
<NTag
|
||||
v-if="rec.residual_risk_value && rec.residual_risk_value === '低'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="success"
|
||||
>
|
||||
{{rec.residual_risk_value}}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="rec.residual_risk_value && rec.residual_risk_value === '中'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="warning"
|
||||
>
|
||||
{{rec.residual_risk_value}}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="rec.residual_risk_value && rec.residual_risk_value === '高'"
|
||||
class="text-[11px]"
|
||||
size="small"
|
||||
type="error"
|
||||
>
|
||||
{{rec.residual_risk_value}}
|
||||
</NTag>
|
||||
</div>
|
||||
<div v-if="rec.source==='HAZOP'">
|
||||
<NTag
|
||||
v-if="rec.l_value>0 && rec.s_value>0"
|
||||
@@ -360,7 +415,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</td>
|
||||
<td class="border px-1.5 py-1 text-center align-top">
|
||||
<NTag size="small" type="info" class="text-11px">{{ rec.source }}</NTag>
|
||||
<NTag size="small" type="info" class="text-11px">{{ rec.source==='RISK'?'风险预分析':rec.source }}</NTag>
|
||||
</td>
|
||||
<td class="border px-1.5 py-1 text-center align-top">
|
||||
<NTag v-if="rec.to_pssr" size="small" :color="{ color: '#f5f3ff', textColor: '#7c3aed', borderColor: '#f5f3ff' }">已转 PSSR</NTag>
|
||||
@@ -400,17 +455,20 @@ onMounted(() => {
|
||||
<!-- 插件抽屉 -->
|
||||
<NDrawer v-model:show="pluginDrawer" width="85%" placement="right">
|
||||
<NDrawerContent>
|
||||
<template v-if="currentPlugin === 'RISK'">
|
||||
<RiskPre :isAiOpen="props.isAiOpen" :currentId="props.currentId" :jsaRiskMatrixConfig="jsaRiskMatrixConfig" @close="closePlugin" @toTable="exportAll" />
|
||||
</template>
|
||||
<template v-if="currentPlugin === 'HAZOP'">
|
||||
<Hazop :isAiOpen="props.isAiOpen" :currentId="props.currentId" :hazopRiskMatrixConfig="hazopRiskMatrixConfig" @close="closePlugin" @toTable="exportAll" />
|
||||
</template>
|
||||
<template v-else-if="currentPlugin === 'JSA'">
|
||||
<template v-if="currentPlugin === 'JSA'">
|
||||
<Jsa :isAiOpen="props.isAiOpen" :currentId="props.currentId" :jsaRiskMatrixConfig="jsaRiskMatrixConfig" @close="closePlugin" @toTable="exportAll" />
|
||||
</template>
|
||||
<template v-else-if="currentPlugin === 'SCL'">
|
||||
<template v-if="currentPlugin === 'SCL'">
|
||||
<Scl @close="closePlugin" @toTable="exportAll" />
|
||||
</template>
|
||||
<template v-else-if="currentPlugin === 'RISK_CHECK'">
|
||||
<RiskCheck @close="closePlugin" />
|
||||
<template v-if="currentPlugin === 'RISK_CHECK'">
|
||||
<RiskCheck :isAiOpen="props.isAiOpen" :currentId="props.currentId" @close="closePlugin" />
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
|
||||
@@ -1,95 +1,183 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { useMessage } from "naive-ui";
|
||||
import {onMounted, ref } from "vue";
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { riskCheckDetailApi, riskCheckSaveApi } from '@/service/api/plugin'
|
||||
|
||||
const message = useMessage();
|
||||
const props = defineProps(['isAiOpen','currentId'])
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
// 检查项数据由父组件(riskAnalysis)持有:AI 预填 / 重新生成在父级完成,此处负责渲染与人工确认
|
||||
const props = defineProps<{ modelValue: any[]; loading?: boolean }>();
|
||||
const emit = defineEmits<{
|
||||
back: [];
|
||||
"update:modelValue": [value: any[]];
|
||||
}>();
|
||||
// answer 1 是
|
||||
// answer 0 否
|
||||
// answer 2 未回答
|
||||
|
||||
const riskChecks = computed<any[]>({
|
||||
get: () => props.modelValue ?? [],
|
||||
set: (v) => emit("update:modelValue", v),
|
||||
});
|
||||
const setAns = (item: any, a: string) => {
|
||||
riskChecks.value = riskChecks.value.map((x) => (x.id === item.id ? { ...x, ans: a } : x));
|
||||
const RISK_CHECKS_INIT = [
|
||||
{
|
||||
group_id: 1,
|
||||
group_label: "安全健康",
|
||||
items: [
|
||||
{
|
||||
question_id: 1,
|
||||
answer: null,
|
||||
question: "是否存在任何可能导致安全问题的化学反应?检查化学反应禁忌表。",
|
||||
risk_desc: "",
|
||||
control_measure: "",
|
||||
},
|
||||
{
|
||||
question_id: 2,
|
||||
answer: null,
|
||||
question: "是否存在任何易燃易爆化学物质或粉尘?",
|
||||
risk_desc: "",
|
||||
control_measure: "",
|
||||
},
|
||||
{
|
||||
question_id: 3,
|
||||
answer: null,
|
||||
question: "是否存在任何有危险的原材料流速、成分或温度条件变更?",
|
||||
risk_desc: "",
|
||||
control_measure: "",
|
||||
},
|
||||
{
|
||||
question_id: 4,
|
||||
answer: null,
|
||||
question: "是否会因加热、冷却或精确的温度控制失控而导致安全问题?",
|
||||
risk_desc: "",
|
||||
control_measure: "",
|
||||
},
|
||||
{
|
||||
question_id: 5,
|
||||
answer: null,
|
||||
question: "是否具有为安全操作所必需的仪表、控制、紧急制动、开关或报警?",
|
||||
risk_desc: "",
|
||||
control_measure: " ",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
group_id: 2,
|
||||
group_label: "工艺与操作",
|
||||
items: [
|
||||
{
|
||||
question_id: 6,
|
||||
answer: null,
|
||||
question: "变更后是否需要对操作人员进行专门培训?",
|
||||
risk_desc: "",
|
||||
control_measure: "",
|
||||
},
|
||||
{
|
||||
question_id: 7,
|
||||
answer: null,
|
||||
question: "是否涉及应急预案或消防设施的调整?",
|
||||
risk_desc: "",
|
||||
control_measure: ""
|
||||
},
|
||||
],
|
||||
}
|
||||
];
|
||||
// 是否是ai生成
|
||||
const isAiGenerated = ref<boolean>(false);
|
||||
const loading = ref<boolean>(false);
|
||||
const riskGroups = ref<any[]>(RISK_CHECKS_INIT);
|
||||
const riskFilter = ref<number>(-1);
|
||||
|
||||
// 过滤风险项
|
||||
const filterChange = (v: number) => {
|
||||
riskFilter.value = v;
|
||||
if(v===-1){
|
||||
riskGroups.value = RISK_CHECKS_INIT;
|
||||
}else{
|
||||
const result = RISK_CHECKS_INIT.map(group => ({
|
||||
...group,
|
||||
items: group.items.filter((item:any) => item.answer === 1)
|
||||
})).filter(group => group.items.length > 0);
|
||||
riskGroups.value = result;
|
||||
}
|
||||
};
|
||||
|
||||
const pluginDone = ref<Record<string, number>>({});
|
||||
const riskGroups = computed(() => [...new Set(riskChecks.value.map((x) => x.group))]);
|
||||
const riskFilter = ref<string>("全部");
|
||||
// 返回工具列表
|
||||
const onBack = () => {
|
||||
pluginDone.value = { ...pluginDone.value, RISK_CHECK: riskChecks.value.filter((x) => x.ans === '是').length };
|
||||
emit("back");
|
||||
const count = riskGroups.value.flatMap((group: any) =>group.items.filter((item: any) => item.answer !== null)).length;
|
||||
emit('close','RISK_CHECK',count===0?undefined:count);
|
||||
}
|
||||
const save = () => {
|
||||
pluginDone.value = { ...pluginDone.value, RISK_CHECK: riskChecks.value.filter((x) => x.ans === '是').length };
|
||||
message.success("已手动保存,分析记录数已同步至工具卡片(内容修改实时自动保存,退出不丢失)");
|
||||
// 保存
|
||||
const save = async () => {
|
||||
loading.value = true;
|
||||
const {error} = await riskCheckSaveApi(props.currentId,{
|
||||
groups: riskGroups.value
|
||||
});
|
||||
if(!error){
|
||||
window.$message?.success("保存成功");
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
// 获取风险检查表详情
|
||||
const getRiskCheckDetail = async () => {
|
||||
loading.value = true;
|
||||
const {data,error} = await riskCheckDetailApi(props.currentId);
|
||||
if(!error){
|
||||
if(data.length){
|
||||
riskGroups.value = data;
|
||||
}else{
|
||||
riskGroups.value = RISK_CHECKS_INIT;
|
||||
}
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
onMounted(() => {
|
||||
getRiskCheckDetail();
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="onBack">
|
||||
<Icon icon="basil:arrow-left-outline" class="size-18px" /> 返回工具列表
|
||||
</NButton>
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="save">保存</NButton>
|
||||
<span class="flex items-center gap-1.5 text-sm font-semibold text-slate-700">变更风险检查表(通用)</span>
|
||||
<NTag size="small" type="info">插件</NTag>
|
||||
<NTag v-if="loading" size="small" type="warning">
|
||||
<template #icon>
|
||||
<Icon icon="ri:loader-4-fill" class="size-12px animate-spin" />
|
||||
</template>
|
||||
AI 生成中…(整表约 1~3 分钟)
|
||||
</NTag>
|
||||
<div class="flex items-center gap-3">
|
||||
<NRadioGroup v-model:value="riskFilter" name="radiogroup">
|
||||
<NRadio v-for="song in [{label:'全部',value:'全部'},{label:'仅看「是」',value:'是'}]" :key="song.value" :value="song.value">
|
||||
{{ song.label }}
|
||||
</NRadio>
|
||||
</NRadioGroup>
|
||||
</div>
|
||||
<span class="ml-auto 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>
|
||||
<NSpin :show="loading" size="small">
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="onBack">
|
||||
<Icon icon="basil:arrow-left-outline" class="size-18px" /> 返回工具列表
|
||||
</NButton>
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="save">保存</NButton>
|
||||
<span class="flex items-center gap-1.5 text-sm font-semibold text-slate-700">变更风险检查表(通用)</span>
|
||||
<NTag size="small" type="info">插件</NTag>
|
||||
<div class="flex items-center gap-3">
|
||||
<NRadioGroup :value="riskFilter" name="radiogroup" :on-update:value="filterChange">
|
||||
<NRadio :value="-1">全部</NRadio>
|
||||
<NRadio :value=1>仅看「是」</NRadio>
|
||||
</NRadioGroup>
|
||||
</div>
|
||||
<span v-if="isAiGenerated" class="ml-auto 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>
|
||||
<p class="text-[11px] text-slate-400">逐项判定「是 / 否 / NA」;判定为「是」的检查项输出风险描述与管控措施,完成后点「返回工具列表」回显完成项数</p>
|
||||
<div class="space-y-4 scroll">
|
||||
<div v-for="group in riskGroups" :key="group.group_id" class="rounded-lg border">
|
||||
<div class="bg-slate-100 px-3 py-2 text-sm font-medium text-slate-700">{{ group.group_label }}</div>
|
||||
<div v-for="item in group.items" :key="item.question_id"
|
||||
class="grid gap-3 border-t px-3 py-3 grid-cols-[1.1fr_1fr_1fr] min-h-77px">
|
||||
<div>
|
||||
<div class="text-sm text-slate-700">{{ item.question }}</div>
|
||||
<div class="mt-2 flex items-center gap-4">
|
||||
<NRadioGroup v-model:value="item.answer" name="radiogroup">
|
||||
<div class="flex gap-2">
|
||||
<NRadio :value="1">是</NRadio>
|
||||
<NRadio :value=0>否</NRadio>
|
||||
<NRadio :value=2>NA</NRadio>
|
||||
</div>
|
||||
</NRadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="item.answer === 1">
|
||||
<NInput v-model:value="item.risk_desc" type="textarea" size="small" :autosize="{minRows:2}" placeholder="风险描述" />
|
||||
<NInput v-model:value="item.control_measure" type="textarea" size="small" :autosize="{minRows:2}" placeholder="管控措施" />
|
||||
</template>
|
||||
<div v-if="item.answer === 0 || item.answer === 2" class="text-xs text-slate-300 self-center col-span-2">(回答为「{{ item.answer===0?'否':'NA' }}」,无风险描述)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[11px] text-slate-400">逐项判定「是 / 否 / 不涉及」;判定为「是」的检查项输出风险描述与管控措施,完成后点「返回工具列表」回显完成项数</p>
|
||||
<div v-if="!riskChecks.length && !loading" class="rounded-lg border border-dashed px-4 py-6 text-center text-xs text-slate-400">
|
||||
暂无检查项:由「生成变更申请单」自动预填,或点击页脚「重新生成」由 AI 生成(耗时约 1~3 分钟)
|
||||
</div>
|
||||
<div v-for="g in riskGroups" :key="g" class="overflow-hidden rounded-lg border">
|
||||
<div class="bg-slate-100 px-3 py-2 text-sm font-medium text-slate-700">{{ g }}</div>
|
||||
<div v-for="item in riskChecks.filter((x) => x.group === g && (riskFilter === '全部' || x.ans === '是'))" :key="item.id"
|
||||
class="grid gap-3 border-t px-3 py-3 grid-cols-[1.1fr_1fr_1fr]">
|
||||
<div>
|
||||
<div class="text-sm text-slate-700">{{ item.q }}</div>
|
||||
<div class="mt-2 flex items-center gap-4">
|
||||
<span v-for="a in ['是', '否', 'NA']" :key="a">
|
||||
<NRadio
|
||||
:checked="item.ans === a"
|
||||
@change="setAns(item, a)"
|
||||
>
|
||||
{{ a }}
|
||||
</NRadio>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="item.ans === '是'">
|
||||
<div class="rounded-lg bg-slate-50 p-2.5 text-xs leading-relaxed text-slate-600">
|
||||
<span class="font-medium text-slate-500">风险描述:</span>{{ item.risk }}
|
||||
</div>
|
||||
<div class="rounded-lg bg-slate-50 p-2.5 text-xs leading-relaxed text-slate-600">
|
||||
<span class="font-medium text-slate-500">管控措施:</span>{{ item.measure }}
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="text-xs text-slate-300 self-center col-span-2">(回答为「{{ item.ans }}」,无风险描述)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NSpin>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.scroll{
|
||||
height: calc(100vh - 110px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { peopleListApi } from '@/service/api/system';
|
||||
import { riskPreDetailApi, riskPreSaveApi } from '@/service/api/plugin';
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const props = defineProps(['isAiOpen','currentId','jsaRiskMatrixConfig'])
|
||||
const emit = defineEmits(['close','toTable']);
|
||||
// 表格样式
|
||||
const TH = "border bg-slate-100 px-2 py-1.5 text-[11px] font-medium text-slate-500";
|
||||
const TD = "border px-1.5 py-1 align-top";
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
// 风险分析记录
|
||||
const riskRecords = ref<{id:number,risk_item:string,scene_desc:string,risk_value:string,residual_risk_value:string,safeguards:string,suggestions:string}[]>([]);
|
||||
// 风险等级
|
||||
const risk_grades = ref<{label:string,value:string}[]>([
|
||||
{label:'低',value:'低'},
|
||||
{label:'中',value:'中'},
|
||||
{label:'高',value:'高'},
|
||||
]);
|
||||
// 人员列表源数据
|
||||
const workers = ref<{user_id:number,user_name:string,org_name?:string}[]>([]);
|
||||
// 选中的人员
|
||||
const selectedWorkers = ref<{user_id:number,user_name:string,org_name?:string}[]>([]);
|
||||
// 选择分析人员弹窗
|
||||
const openSelectWorker = ref(false);
|
||||
// 搜索分析人员
|
||||
const searchWorker = ref("");
|
||||
// 分析人员列表
|
||||
const workerList = computed(() => workers.value.filter((x:{user_id:number,user_name:string,org_name?:string}) => !searchWorker.value || x.user_name.includes(searchWorker.value)));
|
||||
// 选择分析人员
|
||||
const selectWorker = (n: {user_id:number,user_name:string,org_name?:string}) => {
|
||||
selectedWorkers.value = selectedWorkers.value.includes(n) ? selectedWorkers.value.filter((x:any) => x.user_id !== n.user_id) : [...selectedWorkers.value, n];
|
||||
};
|
||||
// 匹配是否有人员
|
||||
const hasWorker = (id:number) => {
|
||||
return selectedWorkers.value.some((item:{user_id:number,user_name:string,org_name?:string}) => item.user_id === id);
|
||||
};
|
||||
|
||||
// 运行 AI 执行风险分析
|
||||
const runAi = async () => {
|
||||
|
||||
};
|
||||
|
||||
// 导出Word
|
||||
const generateReport = async () => {
|
||||
window.$message?.warning("还差接口");
|
||||
}
|
||||
|
||||
// 保存
|
||||
const save = async () => {
|
||||
loading.value = true;
|
||||
// 处理选中的人员,移除 org_name 字段
|
||||
const workers = (selectedWorkers.value ?? []).map((item: { org_name?:string; user_id: number; user_name: string }) => {
|
||||
const { org_name, ...rest } = item;
|
||||
return rest;
|
||||
});
|
||||
const {error} = await riskPreSaveApi(props.currentId, {
|
||||
rows: riskRecords.value,
|
||||
users: workers,
|
||||
});
|
||||
if(!error){
|
||||
window.$message?.success("保存成功");
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
// AI整理如表、入表
|
||||
const enterToTable = (type: string, row: any) => {
|
||||
if(type==='single'){
|
||||
const enrichedRow:any[] = [];
|
||||
enrichedRow.push({
|
||||
...row,
|
||||
scene: row?.scene_desc || '',
|
||||
source: 'RISK',
|
||||
risk_item: row?.risk_item || '',
|
||||
safe: row?.safeguards || '',
|
||||
suggestion: row?.suggestions || '',
|
||||
to_pssr: 0
|
||||
});
|
||||
emit('toTable',type,enrichedRow);
|
||||
}
|
||||
if(type==='all'){
|
||||
const enrichedRows:any[] = [];
|
||||
riskRecords.value.forEach((row:any) => {
|
||||
enrichedRows.push({
|
||||
...row,
|
||||
scene: row?.scene_desc || '',
|
||||
source: 'RISK',
|
||||
risk_item: row?.risk_item || '',
|
||||
safe: row?.safeguards || '',
|
||||
suggestion: row?.suggestions || '',
|
||||
to_pssr: 0
|
||||
});
|
||||
})
|
||||
emit('toTable',type,enrichedRows);
|
||||
}
|
||||
}
|
||||
|
||||
// 新增风险分析记录
|
||||
const addRisk = () => {
|
||||
riskRecords.value.push({
|
||||
id: 0,
|
||||
risk_item: '',
|
||||
scene_desc: '',
|
||||
risk_value: '',
|
||||
residual_risk_value: '',
|
||||
safeguards: '',
|
||||
suggestions: '',
|
||||
})
|
||||
}
|
||||
// 删除行
|
||||
const delRow = (rowIndex: number) => {
|
||||
riskRecords.value.splice(rowIndex, 1);
|
||||
}
|
||||
|
||||
// 获取人员列表
|
||||
const getWorkers = async () => {
|
||||
const {data,error} = await peopleListApi();
|
||||
if(!error){
|
||||
const transformed = data.map((item:any) => ({
|
||||
user_id: item.id,
|
||||
user_name: item.realname,
|
||||
org_name: item.user_departments.map((d:any) => d.label).join(';')
|
||||
}));
|
||||
workers.value = transformed;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取JSA详情
|
||||
const getJsaDetail = async () => {
|
||||
loading.value = true;
|
||||
const {data,error} = await riskPreDetailApi(props.currentId);
|
||||
if(!error){
|
||||
riskRecords.value = data?.rows || [];
|
||||
selectedWorkers.value = data?.users || [];
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getWorkers();
|
||||
getJsaDetail();
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpin :show="loading" size="small">
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
class="flex flex-wrap items-center gap-2 rounded-lg border border-[#ACC6E5] bg-[#EAF0F9] px-3 py-2"
|
||||
:style="{
|
||||
'--theme-color': themeStore.themeColor
|
||||
}"
|
||||
>
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="emit('close')">
|
||||
<Icon icon="basil:arrow-left-outline" class="size-18px" />
|
||||
返回工具列表
|
||||
</NButton>
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="save()">保存</NButton>
|
||||
<span class="text-sm font-bold text-[var(--theme-color)]">风险预分析插件</span>
|
||||
<NTag size="small" type="info">插件</NTag>
|
||||
<span class="text-[11px] text-slate-500">{{`共${riskRecords.length} 条风险项`}}</span>
|
||||
<div class="relative">
|
||||
<NButton size="small" ghost type="primary" class="text-[11px]" @click="openSelectWorker = !openSelectWorker">
|
||||
<Icon icon="lucide:users" class="size-12px" /> 分析组成员 · 已选 {{ selectedWorkers.length }} 人
|
||||
</NButton>
|
||||
<template v-if="openSelectWorker">
|
||||
<div class="fixed inset-0 z-10" @click="openSelectWorker = false"></div>
|
||||
<div class="absolute right-0 top-8 z-20 w-72 rounded-lg border bg-white p-2 shadow-xl">
|
||||
<div class="mb-1.5 flex items-center justify-between">
|
||||
<span class="text-xs font-semibold text-slate-700">选择分析组成员(岗位人员库)</span>
|
||||
<span class="text-[11px] text-slate-400">已选 {{ selectedWorkers.length }} 人</span>
|
||||
</div>
|
||||
<NInput v-model:value="searchWorker" size="small" placeholder="搜索人名…" class="mb-1.5 text-xs" />
|
||||
<div class="max-h-48 space-y-0.5 overflow-y-auto">
|
||||
<div v-for="x in workerList" :key="x.user_id" @click="selectWorker(x)"
|
||||
class="cursor-pointer flex w-full items-center min-h-34px gap-2 rounded-md px-2 py-1.5 text-left text-xs"
|
||||
:class="hasWorker(x.user_id) ? 'bg-[#DFE9F6]' : 'hover:bg-slate-50'">
|
||||
<span class="flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded border text-[10px]"
|
||||
:class="hasWorker(x.user_id) ? 'border-[var(--theme-color)] bg-[var(--theme-color)] text-white' : 'border-slate-300'">{{ hasWorker(x.user_id) ? "✓" : "" }}</span>
|
||||
<span class="font-medium text-slate-800">{{ x.user_name }}</span>
|
||||
<span class="text-[11px] text-slate-400">{{ x.org_name }}</span>
|
||||
<NTag v-if="selectedWorkers[0]?.user_id === x.user_id" size="small" type="primary" class="ml-auto">组长</NTag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1.5 border-t pt-1.5 text-[11px] leading-4 text-slate-400">
|
||||
首位选择者默认为组长;成员为选择式(非手写),参与记录自动归属到人,用于「参与风险分析次数」统计采集。
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
<NButton size="small" ghost type="primary" class="text-xs" @click="generateReport">
|
||||
<Icon icon="material-symbols:download" class="size-14px" /> 导出 Word
|
||||
</NButton>
|
||||
<NButton v-if="isAiOpen" size="small" type="primary" class="text-xs" :disabled="loading" @click="runAi">
|
||||
<Icon v-if="loading" icon="ri:loader-4-fill" class="size-14px animate-spin" />
|
||||
<Icon v-else icon="lucide:sparkles" class="size-12px" />
|
||||
AI 执行风险分析
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-3" :style="{'--theme-color': themeStore.themeColor}">
|
||||
<!-- 作业活动分析记录 -->
|
||||
<div class="bg-white">
|
||||
<div class="flex items-center gap-2 px-3 py-2 text-xs font-semibold text-slate-600 border border-b-0 rounded-tl-lg rounded-tr-lg">
|
||||
风险分析记录表
|
||||
<NButton size="small" ghost type="primary" class="text-xs ml-2" @click="addRisk">
|
||||
<Icon icon="ic:round-plus" class="size-14px" /> 新增风险分析记录
|
||||
</NButton>
|
||||
<span class="ml-auto text-[11px] font-normal text-slate-400">「入表」将本条选入风险分析记录表</span>
|
||||
</div>
|
||||
<p v-if="riskRecords.length===0" class="border py-5 text-center text-xs text-slate-400">暂无风险分析记录</p>
|
||||
<div v-else class="scroll">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th :class="TH" class="w-80px">操作</th>
|
||||
<th :class="TH" class="w-30">风险项</th>
|
||||
<th :class="TH" class="w-60">场景描述</th>
|
||||
<th :class="TH" class="w-20">固有风险</th>
|
||||
<th :class="TH" class="w-50">现有措施</th>
|
||||
<th :class="TH" class="w-60">建议措施</th>
|
||||
<th :class="TH" class="w-20">剩余风险</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, rowIndex) in riskRecords" :key="rowIndex">
|
||||
<td :class="TD">
|
||||
<div class="flex items-center justify-center gap-2 px-1 mt-2">
|
||||
<NButton text type="primary" size="tiny" @click="enterToTable('single',row)">
|
||||
入表
|
||||
</NButton>
|
||||
<NPopconfirm
|
||||
positive-text="确定"
|
||||
:positiveButtonProps="{ size: 'tiny' }"
|
||||
:negativeButtonProps="{ size: 'tiny' }"
|
||||
@positive-click="delRow(Number(rowIndex))"
|
||||
>
|
||||
<template #trigger>
|
||||
<NButton text type="error" size="tiny" title="删除行">
|
||||
<Icon icon="ep:delete" class="size-14px" />
|
||||
</NButton>
|
||||
</template>
|
||||
确定删除此行吗?
|
||||
</NPopconfirm>
|
||||
</div>
|
||||
</td>
|
||||
<td :class="TD">
|
||||
<NInput type="textarea" size="small" v-model:value="row.risk_item" :autosize="{minRows:1}" />
|
||||
</td>
|
||||
<td :class="TD">
|
||||
<NInput type="textarea" size="small" v-model:value="row.scene_desc" :autosize="{minRows:1}" />
|
||||
</td>
|
||||
<td :class="TD" class="text-center">
|
||||
<NSelect v-model:value="row.risk_value" :options="risk_grades" size="small" class="w-full" />
|
||||
</td>
|
||||
<td :class="TD">
|
||||
<NInput type="textarea" size="small" v-model:value="row.safeguards" :autosize="{minRows:1}" />
|
||||
</td>
|
||||
<td :class="TD">
|
||||
<NInput type="textarea" size="small" v-model:value="row.suggestions" :autosize="{minRows:1}" />
|
||||
</td>
|
||||
<td :class="TD" class="text-center">
|
||||
<NSelect v-model:value="row.residual_risk_value" :options="risk_grades" size="small" class="w-full" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NSpin>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll{
|
||||
height: calc(100vh - 135px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user