|
|
@@ -24,7 +24,7 @@
|
|
|
<el-card shadow="hover" class="stat-card">
|
|
|
<div class="stat-card-content">
|
|
|
<div class="stat-info">
|
|
|
- <div class="stat-title">调查中</div>
|
|
|
+ <div class="stat-title">待处理</div>
|
|
|
<div class="stat-value">{{ stats.investigating || 0 }}</div>
|
|
|
<div class="stat-extra" :class="(stats.investigatingRate || 0) >= 0 ? 'stat-extra-up' : 'stat-extra-down'">
|
|
|
<el-icon v-if="(stats.investigatingRate || 0) >= 0"><CaretTop /></el-icon>
|
|
|
@@ -42,7 +42,7 @@
|
|
|
<el-card shadow="hover" class="stat-card">
|
|
|
<div class="stat-card-content">
|
|
|
<div class="stat-info">
|
|
|
- <div class="stat-title">已结束</div>
|
|
|
+ <div class="stat-title">归档</div>
|
|
|
<div class="stat-value">{{ stats.finished || 0 }}</div>
|
|
|
<div class="stat-extra" :class="(stats.finishedRate || 0) >= 0 ? 'stat-extra-up' : 'stat-extra-down'">
|
|
|
<el-icon v-if="(stats.finishedRate || 0) >= 0"><CaretTop /></el-icon>
|
|
|
@@ -134,9 +134,10 @@
|
|
|
<el-tag :type="getStatusTag(scope.row.handleStatus)">{{ scope.row.handleStatus }}</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="操作" fixed="right" align="center" width="240" class-name="small-padding fixed-width">
|
|
|
+ <el-table-column label="操作" fixed="right" align="center" width="280" class-name="small-padding fixed-width">
|
|
|
<template #default="scope">
|
|
|
<el-button v-hasPermi="['risk:accident:query']" link type="primary" icon="View" @click="handleView(scope.row)">详情</el-button>
|
|
|
+ <el-button v-if="scope.row.handleStatus === '待处理'" v-hasPermi="['risk:accident:handle']" link type="primary" icon="CircleCheck" @click="openHandleDialog(scope.row)">处理</el-button>
|
|
|
<el-button v-hasPermi="['risk:accident:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)">编辑</el-button>
|
|
|
<el-button v-hasPermi="['risk:accident:remove']" link type="danger" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
|
|
|
</template>
|
|
|
@@ -262,6 +263,78 @@
|
|
|
<el-button @click="viewDialog.visible = false">关 闭</el-button>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
+
|
|
|
+ <!-- 事故处理弹窗(独立,字段:处理方式/处理意见/责任人/处理期限/附件) -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="handleDialog.visible"
|
|
|
+ :title="handleDialog.title"
|
|
|
+ width="640px"
|
|
|
+ append-to-body
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ @close="cancelHandle"
|
|
|
+ >
|
|
|
+ <el-form ref="handleFormRef" :model="handleForm" :rules="handleRules" label-width="100px">
|
|
|
+ <el-form-item label="事故编号">
|
|
|
+ <span class="readonly-text">{{ handleForm.accidentCode }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="处理方式" prop="handleResult">
|
|
|
+ <el-select v-model="handleForm.handleResult" placeholder="请选择处理方式" style="width: 100%" clearable>
|
|
|
+ <el-option v-for="dict in accident_handle_way_dict" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="处理意见" prop="handleMeasures">
|
|
|
+ <el-input
|
|
|
+ v-model="handleForm.handleMeasures"
|
|
|
+ type="textarea"
|
|
|
+ :rows="5"
|
|
|
+ placeholder="请详细填写本次事故处理过程、措施与结论..."
|
|
|
+ maxlength="2000"
|
|
|
+ show-word-limit
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="责任人" prop="handlerName">
|
|
|
+ <el-input v-model="handleForm.handlerName" placeholder="请输入责任人姓名(默认当前登录用户)" maxlength="50" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="处理期限" prop="handleDeadline">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="handleForm.handleDeadline"
|
|
|
+ type="date"
|
|
|
+ placeholder="请选择处理期限"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="附件上传">
|
|
|
+ <el-upload
|
|
|
+ v-model:file-list="handleAttachmentFileList"
|
|
|
+ :http-request="customHandleAttachmentUpload"
|
|
|
+ list-type="text"
|
|
|
+ :limit="1"
|
|
|
+ :on-success="onHandleAttachmentSuccess"
|
|
|
+ :on-error="onHandleAttachmentError"
|
|
|
+ :on-exceed="onHandleAttachmentExceed"
|
|
|
+ :on-remove="onHandleAttachmentRemove"
|
|
|
+ :before-upload="beforeHandleAttachmentUpload"
|
|
|
+ >
|
|
|
+ <el-button type="primary" plain icon="Upload">点击上传</el-button>
|
|
|
+ <template #tip>
|
|
|
+ <div class="el-upload__tip">支持任意格式文件,大小不超过 20MB</div>
|
|
|
+ </template>
|
|
|
+ </el-upload>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button @click="cancelHandle">取 消</el-button>
|
|
|
+ <el-button
|
|
|
+ v-hasPermi="['risk:accident:handle']"
|
|
|
+ type="primary"
|
|
|
+ :loading="handleSubmitting"
|
|
|
+ @click="submitHandle"
|
|
|
+ >提交处理</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
@@ -275,7 +348,8 @@ import {
|
|
|
updateAccident,
|
|
|
delAccident,
|
|
|
getAccidentStats,
|
|
|
- getAccidentTrend
|
|
|
+ getAccidentTrend,
|
|
|
+ handleAccident
|
|
|
} from '@/api/risk/accident';
|
|
|
import type { RiskAccidentBo, RiskAccidentVo, AccidentStatsVo, AccidentTrendVo } from '@/api/risk/types';
|
|
|
import { FormInstance, UploadFile, UploadRawFile, UploadUserFile } from 'element-plus';
|
|
|
@@ -303,9 +377,15 @@ const viewPhotoList = ref<string[]>([]);
|
|
|
const {
|
|
|
risk_accident_level: accident_level_dict,
|
|
|
risk_accident_type: accident_type_dict,
|
|
|
- risk_accident_status: accident_status_dict
|
|
|
+ risk_accident_status: accident_status_dict,
|
|
|
+ risk_accident_handle_way: accident_handle_way_dict
|
|
|
} = toRefs<any>(
|
|
|
- proxy?.useDict('risk_accident_level', 'risk_accident_type', 'risk_accident_status')
|
|
|
+ proxy?.useDict(
|
|
|
+ 'risk_accident_level',
|
|
|
+ 'risk_accident_type',
|
|
|
+ 'risk_accident_status',
|
|
|
+ 'risk_accident_handle_way'
|
|
|
+ )
|
|
|
);
|
|
|
|
|
|
// 统计
|
|
|
@@ -377,6 +457,28 @@ const dialog = reactive<DialogOption>({ visible: false, title: '' });
|
|
|
const viewDialog = reactive({ visible: false });
|
|
|
const viewForm = ref<any>({});
|
|
|
|
|
|
+// 事故处理弹窗(独立,不与新增/编辑表单混用)
|
|
|
+const handleDialog = reactive({ visible: false, title: '事故处理' });
|
|
|
+const handleSubmitting = ref(false);
|
|
|
+const handleForm = ref<any>({
|
|
|
+ id: undefined,
|
|
|
+ accidentCode: '',
|
|
|
+ handleResult: '',
|
|
|
+ handleMeasures: '',
|
|
|
+ handlerName: '',
|
|
|
+ handleDeadline: '',
|
|
|
+ handleAttachmentUrl: '',
|
|
|
+ handleAttachmentName: ''
|
|
|
+});
|
|
|
+const handleFormRef = ref<FormInstance>();
|
|
|
+const handleAttachmentFileList = ref<UploadUserFile[]>([]);
|
|
|
+const handleRules = {
|
|
|
+ handleResult: [{ required: true, message: '请选择处理方式', trigger: 'change' }],
|
|
|
+ handleMeasures: [{ required: true, message: '请输入处理意见文案', trigger: 'blur' }],
|
|
|
+ handlerName: [{ required: true, message: '请输入责任人', trigger: 'blur' }],
|
|
|
+ handleDeadline: [{ required: true, message: '请选择处理期限', trigger: 'change' }]
|
|
|
+};
|
|
|
+
|
|
|
const getList = async () => {
|
|
|
loading.value = true;
|
|
|
try {
|
|
|
@@ -504,6 +606,103 @@ const handleView = async (row: RiskAccidentVo) => {
|
|
|
viewDialog.visible = true;
|
|
|
};
|
|
|
|
|
|
+// ============ 事故处理(独立弹窗) ============
|
|
|
+// 打开处理弹窗:重置表单 → 加载事故详情到只读字段 → 显示弹窗
|
|
|
+const openHandleDialog = async (row: RiskAccidentVo) => {
|
|
|
+ if (!row || row.handleStatus !== '待处理') {
|
|
|
+ proxy?.$modal.msgWarning('只有"待处理"状态的事故才能处理');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ resetHandleForm();
|
|
|
+ const res = await getAccident(row.id);
|
|
|
+ const data: any = res.data || {};
|
|
|
+ handleForm.value.id = data.id;
|
|
|
+ handleForm.value.accidentCode = data.accidentCode || '';
|
|
|
+ // 责任人默认当前登录用户名(由后端 LoginHelper.getUsername() 兜底,前端可空)
|
|
|
+ handleForm.value.handlerName = data.handlerName || '';
|
|
|
+ handleDialog.visible = true;
|
|
|
+};
|
|
|
+
|
|
|
+// 提交处理(后端强制将状态置为"归档")
|
|
|
+const submitHandle = () => {
|
|
|
+ handleFormRef.value?.validate(async (valid: boolean) => {
|
|
|
+ if (!valid) return;
|
|
|
+ handleSubmitting.value = true;
|
|
|
+ try {
|
|
|
+ await handleAccident(handleForm.value.id, handleForm.value as RiskAccidentBo);
|
|
|
+ proxy?.$modal.msgSuccess('处理成功,事故已归档');
|
|
|
+ handleDialog.visible = false;
|
|
|
+ await loadAll();
|
|
|
+ } finally {
|
|
|
+ handleSubmitting.value = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const cancelHandle = () => {
|
|
|
+ handleDialog.visible = false;
|
|
|
+ resetHandleForm();
|
|
|
+};
|
|
|
+
|
|
|
+const resetHandleForm = () => {
|
|
|
+ handleForm.value = {
|
|
|
+ id: undefined,
|
|
|
+ accidentCode: '',
|
|
|
+ handleResult: '',
|
|
|
+ handleMeasures: '',
|
|
|
+ handlerName: '',
|
|
|
+ handleDeadline: '',
|
|
|
+ handleAttachmentUrl: '',
|
|
|
+ handleAttachmentName: ''
|
|
|
+ };
|
|
|
+ handleAttachmentFileList.value = [];
|
|
|
+ handleFormRef.value?.resetFields();
|
|
|
+};
|
|
|
+
|
|
|
+// ============ 处理弹窗附件上传(走 axios baseURL,避免依赖 :action) ============
|
|
|
+const customHandleAttachmentUpload = async (options: any) => {
|
|
|
+ try {
|
|
|
+ const res: any = await uploadFile(options.file);
|
|
|
+ if (res && res.code === 200 && res.data) {
|
|
|
+ options.onSuccess(res, options.file);
|
|
|
+ } else {
|
|
|
+ options.onError(new Error(res?.msg || '上传失败'));
|
|
|
+ }
|
|
|
+ } catch (err: any) {
|
|
|
+ options.onError(err);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const beforeHandleAttachmentUpload = (file: UploadRawFile) => {
|
|
|
+ const isLt20M = file.size / 1024 / 1024 < 20;
|
|
|
+ if (!isLt20M) {
|
|
|
+ proxy?.$modal.msgError('附件大小不能超过 20MB!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+};
|
|
|
+
|
|
|
+const onHandleAttachmentSuccess = (response: any, uploadFile: UploadFile) => {
|
|
|
+ const url = response?.fileName || response?.data?.fileName || response?.url || response?.data?.url || '';
|
|
|
+ uploadFile.url = getMinioFileUrl(url);
|
|
|
+ handleForm.value.handleAttachmentUrl = uploadFile.url;
|
|
|
+ handleForm.value.handleAttachmentName = uploadFile.name;
|
|
|
+ proxy?.$modal.msgSuccess('上传成功');
|
|
|
+};
|
|
|
+
|
|
|
+const onHandleAttachmentError = () => {
|
|
|
+ proxy?.$modal.msgError('附件上传失败,请重试');
|
|
|
+};
|
|
|
+
|
|
|
+const onHandleAttachmentExceed = () => {
|
|
|
+ proxy?.$modal.msgWarning('最多只能上传 1 个附件');
|
|
|
+};
|
|
|
+
|
|
|
+const onHandleAttachmentRemove = () => {
|
|
|
+ handleForm.value.handleAttachmentUrl = '';
|
|
|
+ handleForm.value.handleAttachmentName = '';
|
|
|
+};
|
|
|
+
|
|
|
const parsePhotoList = (val: any): string[] => {
|
|
|
if (!val) return [];
|
|
|
if (Array.isArray(val)) return val as string[];
|
|
|
@@ -638,9 +837,7 @@ const getLevelTag = (level: string): 'primary' | 'success' | 'warning' | 'info'
|
|
|
const getStatusTag = (status: string): 'primary' | 'success' | 'warning' | 'info' | 'danger' => {
|
|
|
const map: Record<string, 'primary' | 'success' | 'warning' | 'info' | 'danger'> = {
|
|
|
待处理: 'danger',
|
|
|
- 调查中: 'warning',
|
|
|
- 已结束: 'success',
|
|
|
- 待考核: 'info'
|
|
|
+ 归档: 'success'
|
|
|
};
|
|
|
return (map[status] || 'info') as any;
|
|
|
};
|
|
|
@@ -729,4 +926,27 @@ onBeforeUnmount(() => {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
+/* ============ 事故处理弹窗专用样式 ============ */
|
|
|
+.readonly-text {
|
|
|
+ display: inline-block;
|
|
|
+ width: 100%;
|
|
|
+ padding: 0 12px;
|
|
|
+ line-height: 32px;
|
|
|
+ background: #f5f7fa;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ border-radius: 4px;
|
|
|
+ color: #606266;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+.upload-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+.upload-tip {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
</style>
|