瀏覽代碼

feat(risk-accident): 事故上报新增处理弹窗(归档+附件上传)

yangjingjing 2 天之前
父節點
當前提交
b44d7fe6a1
共有 3 個文件被更改,包括 258 次插入9 次删除
  1. 9 0
      src/api/risk/accident.ts
  2. 20 0
      src/api/risk/types.ts
  3. 229 9
      src/views/risk/accident/index.vue

+ 9 - 0
src/api/risk/accident.ts

@@ -37,6 +37,15 @@ export const updateAccident = (data: RiskAccidentBo): AxiosPromise => {
   });
 };
 
+// 处理事故上报(状态强制变为'归档',不依赖前端传入)
+export const handleAccident = (id: string | number, data: RiskAccidentBo): AxiosPromise => {
+  return request({
+    url: '/risk/accident/handle/' + id,
+    method: 'put',
+    data: data
+  });
+};
+
 // 删除事故上报
 export const delAccident = (ids: string | number | Array<string | number>): AxiosPromise => {
   return request({

+ 20 - 0
src/api/risk/types.ts

@@ -619,6 +619,16 @@ export interface RiskAccidentBo {
   handlerId?: string | number;
   handlerName?: string;
   handleMeasures?: string;
+  /** 处理方式(字典:risk_accident_handle_way) */
+  handleResult?: string;
+  /** 处理期限 */
+  handleDeadline?: string;
+  /** 处理附件URL(MinIO对象存储路径) */
+  handleAttachmentUrl?: string;
+  /** 处理附件原始文件名 */
+  handleAttachmentName?: string;
+  /** 处理时间 */
+  handleTime?: string;
   investigationResult?: string;
   improvementMeasures?: string;
   remark?: string;
@@ -647,6 +657,16 @@ export interface RiskAccidentVo {
   handlerId: string | number;
   handlerName: string;
   handleMeasures: string;
+  /** 处理方式(字典:risk_accident_handle_way) */
+  handleResult: string;
+  /** 处理期限 */
+  handleDeadline: string;
+  /** 处理附件URL(MinIO对象存储路径) */
+  handleAttachmentUrl: string;
+  /** 处理附件原始文件名 */
+  handleAttachmentName: string;
+  /** 处理时间 */
+  handleTime: string;
   investigationResult: string;
   improvementMeasures: string;
   remark: string;

+ 229 - 9
src/views/risk/accident/index.vue

@@ -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>