查看风险预分析和风险检查表修改
This commit is contained in:
@@ -1,246 +1,88 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { riskCheckDetailApi, riskCheckSaveApi } from '@/service/api/plugin'
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
import { analysisRecordListApi } from '@/service/api/highRisk';
|
||||||
|
|
||||||
|
const themeStore = useThemeStore();
|
||||||
const props = defineProps(['isAiOpen','currentId'])
|
const props = defineProps(['isAiOpen','currentId'])
|
||||||
const emit = defineEmits(['close','toTable']);
|
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";
|
||||||
|
|
||||||
// answer 1 是
|
// 加载状态
|
||||||
// answer 0 否
|
|
||||||
// answer 2 NA
|
|
||||||
|
|
||||||
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: ""
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
];
|
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
const riskGroups = ref<any[]>(RISK_CHECKS_INIT);
|
// 风险分析记录
|
||||||
const riskFilter = ref<number>(-1);
|
const riskRecords = ref<any[]>([]);
|
||||||
const analyzing = ref<boolean>(false);
|
|
||||||
|
|
||||||
// 过滤风险项
|
// 获取详情
|
||||||
const filterChange = (v: number) => {
|
const getDetail = async () => {
|
||||||
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 save = async () => {
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const {error} = await riskCheckSaveApi(props.currentId,{
|
const {data,error} = await analysisRecordListApi(props.currentId);
|
||||||
groups: riskGroups.value
|
|
||||||
});
|
|
||||||
if(!error){
|
if(!error){
|
||||||
window.$message?.success("保存成功");
|
riskRecords.value = data?.risk_check || [];
|
||||||
getRiskCheckDetail();
|
|
||||||
}
|
|
||||||
loading.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// AI整理如表、入表
|
|
||||||
const enterToTable = (type: string, groupIndex: number = -1, questionIndex: number = -1) => {
|
|
||||||
if(type==='single'){
|
|
||||||
const row = riskGroups.value[groupIndex]?.items[questionIndex];
|
|
||||||
if(!row?.id){
|
|
||||||
return window.$message?.warning("请先保存再入表");
|
|
||||||
}
|
|
||||||
const newRow = {
|
|
||||||
id: row.id || 0,
|
|
||||||
scene: row.risk_desc || '',
|
|
||||||
source: 'RISK_CHECK',
|
|
||||||
item: row.question || '',
|
|
||||||
inherent_level: '',
|
|
||||||
safeguards: row?.control_measure || '',
|
|
||||||
suggestions: '',
|
|
||||||
residual_level: '',
|
|
||||||
to_pssr: 0,
|
|
||||||
source_row_id: row.id || 0,
|
|
||||||
}
|
|
||||||
emit('toTable',type,newRow?[newRow]:[]);
|
|
||||||
}
|
|
||||||
if(type==='all'){
|
|
||||||
const result = riskGroups.value.flatMap((group:any) =>
|
|
||||||
group.items.map((item:any, index:number) => ({ group_id: group.group_id, group_label: group.group_label, index, item })).filter(({ item }:any) => !item.id)
|
|
||||||
);
|
|
||||||
if(result.length){
|
|
||||||
return window.$message?.warning("请先保存再入表");
|
|
||||||
}
|
|
||||||
const enrichedRows:any[] = [];
|
|
||||||
riskGroups.value.forEach((group:any) => {
|
|
||||||
group.items.forEach((row:any) => {
|
|
||||||
enrichedRows.push({
|
|
||||||
id: row.id || 0,
|
|
||||||
scene: row.risk_desc || '',
|
|
||||||
source: 'RISK_CHECK',
|
|
||||||
item: row.question || '',
|
|
||||||
inherent_level: '',
|
|
||||||
safeguards: row?.control_measure || '',
|
|
||||||
suggestions: '',
|
|
||||||
residual_level: '',
|
|
||||||
to_pssr: 0,
|
|
||||||
source_row_id: row.id || 0,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
})
|
|
||||||
emit('toTable',type,enrichedRows);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AI 自动分析工序
|
|
||||||
const runAi = async () => {
|
|
||||||
analyzing.value = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
analyzing.value = false;
|
|
||||||
}, 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取风险检查表详情
|
|
||||||
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;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getRiskCheckDetail();
|
getDetail();
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpin :show="loading" size="small">
|
<NSpin :show="loading" size="small">
|
||||||
<div class="space-y-4">
|
<div class="space-y-3" :style="{'--theme-color': themeStore.themeColor}">
|
||||||
<div class="flex flex-wrap items-center gap-3">
|
<div class="bg-white">
|
||||||
<NButton size="small" ghost type="primary" class="text-xs" @click="emit('close')">
|
<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">
|
||||||
<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>
|
|
||||||
<div class="ml-auto flex items-center gap-1.5">
|
|
||||||
<NButton size="small" ghost type="primary" class="text-xs" @click="enterToTable('all')">
|
|
||||||
<Icon icon="carbon:document-import" class="size-14px mr-1" /> 全部整理入表
|
|
||||||
</NButton>
|
|
||||||
<NButton 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 自动分析
|
|
||||||
</NButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="text-[11px] text-slate-400">逐项判定「是 / 否 / NA」;判定为「是」的检查项输出风险描述与管控措施,完成后点「返回工具列表」回显完成项数</p>
|
|
||||||
<div class="space-y-4 scroll">
|
|
||||||
<div v-for="(group,groupIndex) 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,questionIndex) 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 class="flex items-start gap-2">
|
|
||||||
<NButton text type="primary" class="text-xs mt-2px" @click="enterToTable('single',Number(groupIndex),Number(questionIndex))">入表</NButton>
|
|
||||||
<div>
|
|
||||||
<div class="text-sm text-slate-700">{{ item.question }}</div>
|
|
||||||
<div class="mt-2">
|
|
||||||
<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>
|
|
||||||
</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>
|
||||||
|
<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-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">
|
||||||
|
<span>{{row.risk_item}}</span>
|
||||||
|
</td>
|
||||||
|
<td :class="TD">
|
||||||
|
<span>{{row.scene_desc}}</span>
|
||||||
|
</td>
|
||||||
|
<td :class="TD" class="text-center">
|
||||||
|
<span>{{row.risk_value}}</span>
|
||||||
|
</td>
|
||||||
|
<td :class="TD">
|
||||||
|
<span>{{row.safeguards}}</span>
|
||||||
|
</td>
|
||||||
|
<td :class="TD">
|
||||||
|
<span>{{row.suggestions}}</span>
|
||||||
|
</td>
|
||||||
|
<td :class="TD" class="text-center">
|
||||||
|
<span>{{row.residual_risk_value}}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.scroll{
|
.scroll{
|
||||||
height: calc(100vh - 110px);
|
height: calc(100vh - 135px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { peopleListApi } from '@/service/api/system';
|
import { analysisRecordListApi } from '@/service/api/highRisk';
|
||||||
import { riskPreDetailApi, riskPreSaveApi } from '@/service/api/plugin';
|
|
||||||
|
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const props = defineProps(['isAiOpen','currentId'])
|
const props = defineProps(['isAiOpen','currentId'])
|
||||||
@@ -13,231 +12,37 @@ const TH = "border bg-slate-100 px-2 py-1.5 text-[11px] font-medium text-slate-5
|
|||||||
const TD = "border px-1.5 py-1 align-top";
|
const TD = "border px-1.5 py-1 align-top";
|
||||||
|
|
||||||
// 加载状态
|
// 加载状态
|
||||||
const loading = ref(false);
|
const loading = ref<boolean>(false);
|
||||||
// 风险分析记录
|
// 风险分析记录
|
||||||
const riskRecords = ref<{id:number,risk_item:string,scene_desc:string,risk_value:string,residual_risk_value:string,safeguards:string,suggestions:string}[]>([]);
|
const riskRecords = ref<any[]>([]);
|
||||||
// 风险等级
|
|
||||||
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("保存成功");
|
|
||||||
getDetail();
|
|
||||||
}
|
|
||||||
loading.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// AI整理如表、入表
|
|
||||||
const enterToTable = (type: string, row: any) => {
|
|
||||||
if(type==='single'){
|
|
||||||
if(!row?.id){
|
|
||||||
return window.$message?.warning("请先保存再入表");
|
|
||||||
}
|
|
||||||
const enrichedRow:any[] = [];
|
|
||||||
enrichedRow.push({
|
|
||||||
id: row?.id || 0,
|
|
||||||
scene: row?.scene_desc || '',
|
|
||||||
source: 'RISK',
|
|
||||||
item: row?.risk_item || '',
|
|
||||||
inherent_level: row?.risk_value || '',
|
|
||||||
safeguards: row?.safeguards || '',
|
|
||||||
suggestions: row?.suggestions || '',
|
|
||||||
residual_level: row?.residual_risk_value || '',
|
|
||||||
to_pssr: 0,
|
|
||||||
source_row_id: row?.id || 0,
|
|
||||||
});
|
|
||||||
emit('toTable',type,enrichedRow);
|
|
||||||
}
|
|
||||||
if(type==='all'){
|
|
||||||
const result = riskRecords.value.filter((item:any) => !item.id);
|
|
||||||
if(result.length){
|
|
||||||
return window.$message?.warning("请先保存再入表");
|
|
||||||
}
|
|
||||||
const enrichedRows:any[] = [];
|
|
||||||
riskRecords.value.forEach((row:any) => {
|
|
||||||
enrichedRows.push({
|
|
||||||
id: row?.id || 0,
|
|
||||||
scene: row?.scene_desc || '',
|
|
||||||
source: 'RISK',
|
|
||||||
item: row?.risk_item || '',
|
|
||||||
inherent_level: row?.risk_value || '',
|
|
||||||
safeguards: row?.safeguards || '',
|
|
||||||
suggestions: row?.suggestions || '',
|
|
||||||
residual_level: row?.residual_risk_value || '',
|
|
||||||
to_pssr: 0,
|
|
||||||
source_row_id: row?.id || 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取详情
|
// 获取详情
|
||||||
const getDetail = async () => {
|
const getDetail = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const {data,error} = await riskPreDetailApi(props.currentId);
|
const {data,error} = await analysisRecordListApi(props.currentId);
|
||||||
if(!error){
|
if(!error){
|
||||||
riskRecords.value = data?.rows || [];
|
riskRecords.value = data?.risk_summary?.rows || [];
|
||||||
selectedWorkers.value = data?.users || [];
|
|
||||||
}
|
}
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getWorkers();
|
|
||||||
getDetail();
|
getDetail();
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpin :show="loading" size="small">
|
<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 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
|
|
||||||
</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="space-y-3" :style="{'--theme-color': themeStore.themeColor}">
|
||||||
<!-- 作业活动分析记录 -->
|
|
||||||
<div class="bg-white">
|
<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">
|
<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>
|
</div>
|
||||||
<p v-if="riskRecords.length===0" class="border py-5 text-center text-xs text-slate-400">暂无风险分析记录</p>
|
<p v-if="riskRecords.length===0" class="border py-5 text-center text-xs text-slate-400">暂无风险分析记录</p>
|
||||||
<div v-else class="scroll">
|
<div v-else class="scroll">
|
||||||
<table class="w-full">
|
<table class="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th :class="TH" class="w-80px">操作</th>
|
|
||||||
<th :class="TH" class="w-30">风险项</th>
|
<th :class="TH" class="w-30">风险项</th>
|
||||||
<th :class="TH" class="w-60">场景描述</th>
|
<th :class="TH" class="w-60">场景描述</th>
|
||||||
<th :class="TH" class="w-20">固有风险</th>
|
<th :class="TH" class="w-20">固有风险</th>
|
||||||
@@ -249,42 +54,22 @@ onMounted(() => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(row, rowIndex) in riskRecords" :key="rowIndex">
|
<tr v-for="(row, rowIndex) in riskRecords" :key="rowIndex">
|
||||||
<td :class="TD">
|
<td :class="TD">
|
||||||
<div class="flex items-center justify-center gap-2 px-1 mt-2">
|
<span>{{row.risk_item}}</span>
|
||||||
<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>
|
||||||
<td :class="TD">
|
<td :class="TD">
|
||||||
<NInput type="textarea" size="small" v-model:value="row.risk_item" :autosize="{minRows:1}" />
|
<span>{{row.scene_desc}}</span>
|
||||||
</td>
|
|
||||||
<td :class="TD">
|
|
||||||
<NInput type="textarea" size="small" v-model:value="row.scene_desc" :autosize="{minRows:1}" />
|
|
||||||
</td>
|
</td>
|
||||||
<td :class="TD" class="text-center">
|
<td :class="TD" class="text-center">
|
||||||
<NSelect v-model:value="row.risk_value" :options="risk_grades" size="small" class="w-full" />
|
<span>{{row.risk_value}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td :class="TD">
|
<td :class="TD">
|
||||||
<NInput type="textarea" size="small" v-model:value="row.safeguards" :autosize="{minRows:1}" />
|
<span>{{row.safeguards}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td :class="TD">
|
<td :class="TD">
|
||||||
<NInput type="textarea" size="small" v-model:value="row.suggestions" :autosize="{minRows:1}" />
|
<span>{{row.suggestions}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td :class="TD" class="text-center">
|
<td :class="TD" class="text-center">
|
||||||
<NSelect v-model:value="row.residual_risk_value" :options="risk_grades" size="small" class="w-full" />
|
<span>{{row.residual_risk_value}}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -292,7 +77,6 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user