Преглед на файлове

feat(risk-inspectionRecord): 巡检打卡审核前端补全

1. types.ts: RiskInspectionRecordBo/Vo 加 auditStatus/auditorId/auditorName/auditTime/auditOpinion 5 字段
2. 新建 inspectionRecord.ts API (含 auditInspectionRecord)
3. inspectionTask/index.vue:
   - 打卡记录列表加 '审核状态' 列
   - 待审核时显示 '审核' 按钮 (v-hasPermi: risk:inspectionRecord:review)
   - 新增审核弹窗 (radio 通过/不通过 + 不通过原因 textarea)
   - 打卡详情对话框增加审核状态/审核人/审核意见
klxj001 преди 3 дни
родител
ревизия
b3e320f283
променени са 3 файла, в които са добавени 250 реда и са изтрити 1 реда
  1. 64 0
      src/api/risk/inspectionRecord.ts
  2. 20 0
      src/api/risk/types.ts
  3. 166 1
      src/views/risk/inspectionTask/index.vue

+ 64 - 0
src/api/risk/inspectionRecord.ts

@@ -0,0 +1,64 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { RiskInspectionRecordBo, RiskInspectionRecordVo } from '@/api/risk/types';
+
+// 查询巡检打卡记录列表
+export const listInspectionRecord = (query: RiskInspectionRecordBo): AxiosPromise<RiskInspectionRecordVo[]> => {
+  return request({
+    url: '/risk/inspectionRecord/list',
+    method: 'get',
+    params: query
+  });
+};
+
+// 查询巡检打卡记录详情
+export const getInspectionRecord = (id: string | number): AxiosPromise<RiskInspectionRecordVo> => {
+  return request({
+    url: '/risk/inspectionRecord/' + id,
+    method: 'get'
+  });
+};
+
+// 新增巡检打卡记录
+export const addInspectionRecord = (data: RiskInspectionRecordBo): AxiosPromise => {
+  return request({
+    url: '/risk/inspectionRecord',
+    method: 'post',
+    data: data
+  });
+};
+
+// 修改巡检打卡记录
+export const updateInspectionRecord = (data: RiskInspectionRecordBo): AxiosPromise => {
+  return request({
+    url: '/risk/inspectionRecord',
+    method: 'put',
+    data: data
+  });
+};
+
+// 删除巡检打卡记录
+export const delInspectionRecord = (ids: string | number | Array<string | number>): AxiosPromise => {
+  return request({
+    url: '/risk/inspectionRecord/' + ids,
+    method: 'delete'
+  });
+};
+
+// 审核巡检打卡记录(后端 /risk/inspectionRecord/audit,无需 path id,id 在 body 内)
+export const auditInspectionRecord = (data: RiskInspectionRecordBo): AxiosPromise => {
+  return request({
+    url: '/risk/inspectionRecord/audit',
+    method: 'put',
+    data: data
+  });
+};
+
+// 导出巡检打卡记录
+export const exportInspectionRecord = (query: RiskInspectionRecordBo): AxiosPromise => {
+  return request({
+    url: '/risk/inspectionRecord/export',
+    method: 'post',
+    params: query
+  });
+};

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

@@ -773,6 +773,16 @@ export interface RiskInspectionRecordBo {
   inspectorId?: string | number;
   inspectorName?: string;
   recordStatus?: string;
+  /** 审核状态(待审核/审核通过/审核不通过) */
+  auditStatus?: string;
+  /** 审核人ID */
+  auditorId?: string | number;
+  /** 审核人 */
+  auditorName?: string;
+  /** 审核时间 */
+  auditTime?: string;
+  /** 审核意见/不通过原因 */
+  auditOpinion?: string;
   problemCount?: number;
   totalCount?: number;
   remark?: string;
@@ -799,6 +809,16 @@ export interface RiskInspectionRecordVo {
   inspectorId: string | number;
   inspectorName: string;
   recordStatus: string;
+  /** 审核状态(待审核/审核通过/审核不通过) */
+  auditStatus: string;
+  /** 审核人ID */
+  auditorId: string | number;
+  /** 审核人 */
+  auditorName: string;
+  /** 审核时间 */
+  auditTime: string;
+  /** 审核意见/不通过原因 */
+  auditOpinion: string;
   checkResult: string;
   location: string;
   problemCount: number;

+ 166 - 1
src/views/risk/inspectionTask/index.vue

@@ -324,9 +324,22 @@
             <span v-else>-</span>
           </template>
         </el-table-column>
-        <el-table-column label="操作" align="center" width="100" fixed="right">
+        <el-table-column label="审核状态" align="center" prop="auditStatus" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.auditStatus" :type="getAuditStatusTag(scope.row.auditStatus)">{{ scope.row.auditStatus }}</el-tag>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="180" fixed="right">
           <template #default="scope">
             <el-button link type="primary" @click="handleViewDetail(scope.row)">查看详情</el-button>
+            <el-button
+              v-if="!scope.row.auditStatus || scope.row.auditStatus === '待审核'"
+              v-hasPermi="['risk:inspectionRecord:review']"
+              link
+              type="primary"
+              @click="handleAuditRecord(scope.row)"
+            >审核</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -350,6 +363,16 @@
         </el-descriptions-item>
         <el-descriptions-item label="打卡地点">{{ detailForm.checkInLocation }}</el-descriptions-item>
         <el-descriptions-item label="问题数/总数">{{ detailForm.problemCount || 0 }}/{{ detailForm.totalCount || 0 }}</el-descriptions-item>
+        <el-descriptions-item label="审核状态">
+          <el-tag v-if="detailForm.auditStatus" :type="getAuditStatusTag(detailForm.auditStatus)">{{ detailForm.auditStatus }}</el-tag>
+          <span v-else>-</span>
+        </el-descriptions-item>
+        <el-descriptions-item v-if="detailForm.auditorName" label="审核人">{{ detailForm.auditorName }} ({{ detailForm.auditTime || '-' }})</el-descriptions-item>
+        <el-descriptions-item v-if="detailForm.auditOpinion" label="审核意见" :span="1">
+          <span :class="['audit-opinion', detailForm.auditStatus === '审核不通过' ? 'reject' : 'pass']">
+            {{ detailForm.auditOpinion }}
+          </span>
+        </el-descriptions-item>
       </el-descriptions>
       <h4 style="margin: 16px 0 8px">巡检项目明细</h4>
       <el-table :data="detailForm.itemList" border size="small">
@@ -373,6 +396,50 @@
       </template>
     </el-dialog>
 
+    <!-- 审核弹窗 -->
+    <el-dialog
+      v-model="auditDialog.visible"
+      title="巡检打卡审核"
+      width="520px"
+      append-to-body
+      :close-on-click-modal="false"
+      @close="closeAuditDialog"
+    >
+      <el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="100px">
+        <el-form-item label="任务名称">
+          <span class="readonly-text">{{ auditForm.taskName }}</span>
+        </el-form-item>
+        <el-form-item label="打卡人">
+          <span class="readonly-text">{{ auditForm.inspectorName }}</span>
+        </el-form-item>
+        <el-form-item label="打卡时间">
+          <span class="readonly-text">{{ auditForm.checkInTime }}</span>
+        </el-form-item>
+        <el-form-item label="审核结果" prop="auditStatus">
+          <el-radio-group v-model="auditForm.auditStatus">
+            <el-radio value="审核通过">通过</el-radio>
+            <el-radio value="审核不通过">不通过</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item v-if="auditForm.auditStatus === '审核不通过'" label="不通过原因" prop="auditOpinion">
+          <el-input
+            v-model="auditForm.auditOpinion"
+            type="textarea"
+            :rows="4"
+            placeholder="请输入不通过原因"
+            maxlength="500"
+            show-word-limit
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancelAudit">取消</el-button>
+          <el-button v-hasPermi="['risk:inspectionRecord:review']" type="primary" @click="submitAudit">确认审核</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
   </div>
 </template>
 
@@ -388,11 +455,13 @@ import {
   listInspectionRecordByTask
 } from '@/api/risk/inspectionTask';
 import { listInspectionItemByTask } from '@/api/risk/inspectionItem';
+import { auditInspectionRecord } from '@/api/risk/inspectionRecord';
 import type {
   RiskInspectionTaskBo,
   RiskInspectionTaskVo,
   RiskInspectionItemVo,
   RiskInspectionRecordVo,
+  RiskInspectionRecordBo,
   InspectionStatsVo
 } from '@/api/risk/types';
 import { listByCurrentDeptChainUsers } from '@/api/system/user';
@@ -510,6 +579,8 @@ const dialog = reactive<DialogOption>({ visible: false, title: '' });
 const recordDialog = reactive<DialogOption>({ visible: false, title: '打卡记录' });
 const detailDialog = reactive({ visible: false });
 const detailForm = ref<any>({ itemList: [] });
+const auditDialog = reactive({ visible: false, title: '巡检打卡审核' });
+const auditFormRef = ref<FormInstance>();
 const getList = async () => {
   loading.value = true;
   try {
@@ -705,6 +776,91 @@ const handleViewDetail = (row: RiskInspectionRecordVo) => {
   detailDialog.visible = true;
 };
 
+// ============ 审核相关 ============
+const auditData = reactive({
+  auditForm: {
+    id: undefined as string | number | undefined,
+    taskName: '',
+    inspectorName: '',
+    checkInTime: '',
+    auditStatus: '审核通过',
+    auditOpinion: ''
+  },
+  auditRules: {
+    auditStatus: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
+    auditOpinion: [
+      {
+        validator: (_rule: any, value: any, callback: any) => {
+          if (auditData.auditForm.auditStatus === '审核不通过' && !value) {
+            callback(new Error('审核不通过时必须填写原因'));
+          } else {
+            callback();
+          }
+        },
+        trigger: 'blur'
+      }
+    ]
+  }
+});
+
+const { auditForm, auditRules } = toRefs(auditData);
+
+/** 打卡记录列表 - 打开审核弹窗 */
+const handleAuditRecord = (row: RiskInspectionRecordVo) => {
+  auditForm.value = {
+    id: row.id,
+    taskName: row.taskName || '',
+    inspectorName: row.inspectorName || '',
+    checkInTime: row.checkInTime || '',
+    auditStatus: '审核通过',
+    auditOpinion: ''
+  };
+  auditDialog.visible = true;
+};
+
+/** 审核弹窗 - 提交 */
+const submitAudit = () => {
+  auditFormRef.value?.validate(async (valid: boolean) => {
+    if (!valid) return;
+    const submitData: RiskInspectionRecordBo = {
+      id: auditForm.value.id,
+      auditStatus: auditForm.value.auditStatus,
+      auditOpinion: auditForm.value.auditOpinion
+    };
+    await auditInspectionRecord(submitData);
+    proxy?.$modal.msgSuccess('审核成功');
+    auditDialog.visible = false;
+    // 刷新当前打卡记录列表(若弹窗开着则一起刷新)
+    if (recordDialog.visible && recordList.value.length > 0) {
+      const taskId = recordList.value[0]?.taskId;
+      if (taskId) {
+        const res = await listInspectionRecordByTask(taskId);
+        recordList.value = res.data || [];
+      }
+    }
+  });
+};
+
+const cancelAudit = () => {
+  auditDialog.visible = false;
+  auditFormRef.value?.resetFields();
+};
+
+const closeAuditDialog = () => {
+  auditDialog.visible = false;
+  auditFormRef.value?.resetFields();
+};
+
+/** 审核状态 tag 颜色(待审核=warning/审核通过=success/审核不通过=danger) */
+const getAuditStatusTag = (s: string): 'primary' | 'success' | 'warning' | 'info' | 'danger' => {
+  const map: Record<string, 'primary' | 'success' | 'warning' | 'info' | 'danger'> = {
+    待审核: 'warning',
+    审核通过: 'success',
+    审核不通过: 'danger'
+  };
+  return (map[s] || 'info') as any;
+};
+
 const handleDelete = async (row: RiskInspectionTaskVo) => {
   await proxy?.$modal.confirm('是否确认删除任务 "' + row.taskName + '" ?');
   await delInspectionTask(row.id);
@@ -829,4 +985,13 @@ onMounted(async () => {
 .stat-icon-red { background: #fef0f0; color: #F56C6C; }
 .mb-2 { margin-bottom: 8px; }
 .mt-2 { margin-top: 8px; }
+.readonly-text {
+  color: #303133;
+}
+.audit-opinion.pass {
+  color: #67c23a;
+}
+.audit-opinion.reject {
+  color: #f56c6c;
+}
 </style>