| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <div class="search_form mb20"> |
| | | <div> |
| | | <span class="search_title">应急预案名称:</span> |
| | | <el-input v-model="searchForm.planName" |
| | |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="120px"> |
| | | label-position="top" |
| | | label-width="150px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="应急预案编码" |
| | | <el-form-item label="应急预案编码:" |
| | | prop="planCode"> |
| | | <el-input v-model="form.planCode" |
| | | placeholder="请输入应急预案编码" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="应急预案名称" |
| | | <el-form-item label="应急预案名称:" |
| | | prop="planName"> |
| | | <el-input v-model="form.planName" |
| | | placeholder="请输入应急预案名称" /> |
| | |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="预案类型" |
| | | <el-form-item label="预案类型:" |
| | | prop="planType"> |
| | | <el-select v-model="form.planType" |
| | | placeholder="请选择预案类型" |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="备注" |
| | | <el-form-item label="备注:" |
| | | prop="remark"> |
| | | <el-input v-model="form.remark" |
| | | placeholder="请输入备注" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="适用范围" |
| | | <el-form-item label="适用范围:" |
| | | prop="applyScope"> |
| | | <el-checkbox-group v-model="form.applyScope"> |
| | | <el-checkbox label="all">全体员工</el-checkbox> |
| | | <el-checkbox label="manager">管理层</el-checkbox> |
| | | <el-checkbox label="hr">人事部门</el-checkbox> |
| | | <el-checkbox label="finance">财务部门</el-checkbox> |
| | | <el-checkbox label="tech">技术部门</el-checkbox> |
| | | <el-checkbox-group v-model="form.applyScope" |
| | | class="apply-scope-checkbox-group"> |
| | | <el-checkbox v-for="dept in applyScopeOptions" |
| | | :key="dept.deptId" |
| | | :value="String(dept.deptId)"> |
| | | {{ dept.deptName }} |
| | | </el-checkbox> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | <el-form-item label="应急处置步骤" |
| | | <el-form-item label="应急处置步骤:" |
| | | prop="execSteps"> |
| | | <div class="exec-steps-container" |
| | | style="width:100%"> |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm">确定</el-button> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | {{ currentKnowledge.coreResponsorUserName }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="预案类型"> |
| | | <el-tag type="warning"> {{ currentKnowledge.planType }}</el-tag> |
| | | <el-tag type="warning"> {{ emergencyPlanTypeLabel(currentKnowledge.planType) }}</el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="备注"> |
| | | {{ currentKnowledge.remark }} |
| | |
| | | <div class="detail-section"> |
| | | <h4>适用范围</h4> |
| | | <div class="key-points"> |
| | | <el-tag v-for="(point, index) in currentKnowledge.applyScope.split(',')" |
| | | :key="index" |
| | | <el-tag v-for="(scope, index) in getApplyScopeTags(currentKnowledge)" |
| | | :key="`${scope.value}-${index}`" |
| | | type="primary" |
| | | style="margin-right: 8px; margin-bottom: 8px;"> |
| | | {{ getApplyScopeLabel(point.trim()) }} |
| | | {{ scope.label }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | |
| | | <div v-for="(step, index) in JSON.parse(currentKnowledge.execSteps)" |
| | | :key="index" |
| | | class="exec-step-view"> |
| | | <span class="step-number">{{ index + 1 }}.</span> |
| | | <!-- <span class="step-number">{{ index + 1 }}.</span> --> |
| | | <span class="step-title">{{ step.step }}:</span> |
| | | <span>{{ step.description }}</span> |
| | | </div> |
| | |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { listDept } from "@/api/system/dept.js"; |
| | | import { |
| | | safeContingencyPlanListPage, |
| | | safeContingencyPlanAdd, |
| | |
| | | // 表单引用 |
| | | const formRef = ref(); |
| | | const execStepsList = ref([]); |
| | | const applyScopeOptions = ref([]); |
| | | const applyScopeNameMap = computed(() => { |
| | | return applyScopeOptions.value.reduce((map, dept) => { |
| | | map[String(dept.deptId)] = dept.deptName; |
| | | return map; |
| | | }, {}); |
| | | }); |
| | | |
| | | // 表格列配置 |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "应急预案编码", |
| | | prop: "planCode", |
| | | minWidth: 200, |
| | | align: "center", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "应急预案名称", |
| | | prop: "planName", |
| | | minWidth: 240, |
| | | align: "center", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "发布生效时间", |
| | | prop: "publishTime", |
| | | minWidth: 160, |
| | | align: "center", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "核心责任人", |
| | | prop: "coreResponsorUserName", |
| | | minWidth: 140, |
| | | align: "center", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | | label: "预案类型", |
| | | prop: "planType", |
| | | minWidth: 160, |
| | | align: "center", |
| | | showOverflowTooltip: true, |
| | | formatData: params => { |
| | | return emergencyPlanTypeLabel(params); |
| | | }, |
| | | }, |
| | | { |
| | | label: "备注", |
| | | prop: "remark", |
| | | minWidth: 220, |
| | | align: "center", |
| | | showOverflowTooltip: true, |
| | | }, |
| | | { |
| | |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 200, |
| | | width: 120, |
| | | operation: [ |
| | | { |
| | | name: "编辑", |
| | |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | loadApplyScopeOptions(); |
| | | getList(); |
| | | startAutoRefresh(); |
| | | }); |
| | |
| | | execStepsList.value = []; |
| | | } |
| | | }; |
| | | |
| | | const loadApplyScopeOptions = () => { |
| | | listDept({ status: "0" }).then(res => { |
| | | applyScopeOptions.value = res.data || []; |
| | | }); |
| | | }; |
| | | |
| | | const parseApplyScope = scope => { |
| | | if (Array.isArray(scope)) { |
| | | return scope.map(item => String(item)).filter(Boolean); |
| | | } |
| | | return String(scope || "") |
| | | .split(",") |
| | | .map(item => item.trim()) |
| | | .filter(Boolean); |
| | | }; |
| | | // 开始自动刷新 |
| | | const startAutoRefresh = () => { |
| | | setInterval(() => { |
| | |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | page.value.total = res.data.total; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | |
| | | const pagination = obj => { |
| | | page.value.current = obj.page; |
| | | page.value.size = obj.limit; |
| | | handleQuery(); |
| | | getList(); |
| | | }; |
| | | |
| | | // 选择变化处理 |
| | |
| | | dialogTitle.value = "新增应急预案"; |
| | | // 重置表单 |
| | | Object.assign(form.value, { |
| | | id: undefined, |
| | | planCode: "", // 预案编号 |
| | | applyScope: [], // 适用范围 |
| | | planType: "", // 预案类型 |
| | |
| | | Object.assign(form.value, { |
| | | id: row.id, |
| | | planCode: row.planCode, // 预案编号 |
| | | applyScope: row.applyScope ? row.applyScope.split(",") : [], // 适用范围 |
| | | applyScope: parseApplyScope(row.applyScope), // 适用范围 |
| | | planType: row.planType, // 预案类型 |
| | | planName: row.planName, // 预案名称 |
| | | publishTime: row.publishTime, // 发布时间 |
| | |
| | | viewDialogVisible.value = true; |
| | | }; |
| | | const getApplyScopeLabel = scope => { |
| | | const scopeMap = { |
| | | all: "全体员工", |
| | | manager: "管理层", |
| | | hr: "人事部门", |
| | | finance: "财务部门", |
| | | tech: "技术部门", |
| | | }; |
| | | return scopeMap[scope] || scope; |
| | | return applyScopeNameMap.value[String(scope)] || scope; |
| | | }; |
| | | const getApplyScopeTags = row => { |
| | | return parseApplyScope(row?.applyScope).map((scope, index) => ({ |
| | | value: scope, |
| | | label: getApplyScopeLabel(scope), |
| | | })); |
| | | }; |
| | | |
| | | // 获取类型标签类型 |
| | |
| | | }; |
| | | return timeMap[efficiency] || "未知"; |
| | | }; |
| | | const emergencyPlanTypeOptions = computed(() => [ |
| | | { |
| | | value: "预防性应急预案", |
| | | label: "预防性应急预案", |
| | | }, |
| | | { |
| | | value: "应急处理预案", |
| | | label: "应急处理预案", |
| | | }, |
| | | { |
| | | value: "恢复性应急预案", |
| | | label: "恢复性应急预案", |
| | | }, |
| | | ]); |
| | | const { proxy } = getCurrentInstance(); |
| | | const { emergency_plan_type } = proxy.useDict("emergency_plan_type"); |
| | | const emergencyPlanTypeOptions = computed( |
| | | () => emergency_plan_type?.value || [] |
| | | ); |
| | | const emergencyPlanTypeLabel = val => { |
| | | const item = emergencyPlanTypeOptions.value.find( |
| | | i => String(i.value) === String(val) |
| | | ); |
| | | return item ? item.label : val; |
| | | }; |
| | | // 提交应急预案表单 |
| | | const submitForm = async () => { |
| | | try { |
| | |
| | | } |
| | | } |
| | | |
| | | // 将应急处置步骤转换为JSON字符串 |
| | | form.value.execSteps = JSON.stringify(execStepsList.value); |
| | | const submitData = { |
| | | ...form.value, |
| | | applyScope: form.value.applyScope.join(","), |
| | | execSteps: JSON.stringify(execStepsList.value), |
| | | }; |
| | | if (dialogType.value === "add") { |
| | | // 新增应急预案 |
| | | form.value.applyScope = form.value.applyScope.join(","); |
| | | safeContingencyPlanAdd({ ...form.value }) |
| | | safeContingencyPlanAdd(submitData) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("添加成功"); |
| | |
| | | ElMessage.error(err.msg); |
| | | }); |
| | | } else { |
| | | form.value.applyScope = form.value.applyScope.join(","); |
| | | safeContingencyPlanUpdate({ ...form.value }) |
| | | safeContingencyPlanUpdate(submitData) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success("更新成功"); |
| | |
| | | }; |
| | | |
| | | // 导出 |
| | | const { proxy } = getCurrentInstance(); |
| | | const { knowledge_type } = proxy.useDict("knowledge_type"); |
| | | |
| | | // 字典工具 |
| | | const knowledgeTypeOptions = computed(() => knowledge_type?.value || []); |
| | |
| | | gap: 8px; |
| | | } |
| | | |
| | | .apply-scope-checkbox-group { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px 16px; |
| | | } |
| | | |
| | | .apply-scope-checkbox-group :deep(.el-checkbox) { |
| | | margin-right: 0; |
| | | } |
| | | |
| | | .usage-stats { |
| | | margin-top: 16px; |
| | | } |
| | |
| | | |
| | | .exec-steps-container { |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 4px; |
| | | padding: 15px; |
| | | border-radius: 8px; |
| | | padding: 20px; |
| | | background-color: #f9fafc; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .exec-step-item { |
| | | margin-bottom: 10px; |
| | | padding: 10px; |
| | | margin-bottom: 12px; |
| | | padding: 12px; |
| | | background-color: #ffffff; |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 4px; |
| | | border-radius: 6px; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .exec-step-item:hover { |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | border-color: #c6e2ff; |
| | | } |
| | | |
| | | .step-header { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .exec-step-view { |
| | | margin-bottom: 8px; |
| | | padding-left: 20px; |
| | | margin-bottom: 16px; |
| | | padding-left: 24px; |
| | | position: relative; |
| | | line-height: 1.6; |
| | | } |
| | | |
| | | .exec-step-view::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 10px; |
| | | top: 20px; |
| | | bottom: -16px; |
| | | width: 2px; |
| | | background-color: #eaeaea; |
| | | } |
| | | |
| | | .exec-step-view:last-child::before { |
| | | display: none; |
| | | } |
| | | |
| | | .step-number { |
| | | position: absolute; |
| | | left: 0; |
| | | top: 0; |
| | | width: 20px; |
| | | height: 20px; |
| | | line-height: 20px; |
| | | text-align: center; |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | color: #ffffff; |
| | | background-color: #409eff; |
| | | border-radius: 50%; |
| | | font-size: 12px; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .step-title { |
| | | font-weight: bold; |
| | | margin-right: 5px; |
| | | font-weight: 600; |
| | | margin-right: 8px; |
| | | color: #395a9c; |
| | | } |
| | | |
| | | .no-data { |
| | | color: #909399; |
| | | font-style: italic; |
| | | text-align: center; |
| | | padding: 20px; |
| | | background-color: #f8f9fa; |
| | | border-radius: 4px; |
| | | margin-top: 10px; |
| | | } |
| | | </style> |