Преглед изворни кода

Merge remote-tracking branch 'origin/feat/production-safety' into feat/production-safety

sunqijun пре 2 месеци
родитељ
комит
522da6f327

+ 105 - 0
src/api/production-safety/special-equipment.ts

@@ -0,0 +1,105 @@
+import { http } from '@/utils/http/axios';
+import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
+
+/**
+ * 特种设备设施管理 - 实体与接口
+ * 基础路径: /api/specialEquipment
+ */
+
+/** 分页查询条件 */
+export interface SpecialEquipmentQueryParam {
+  deviceName?: string; // 设备名称(模糊)
+  useUnit?: string; // 使用单位(模糊)
+  categoryId?: number; // 设备类别ID
+  typeId?: number; // 设备种类ID
+  isUseDepartment?: number; // 是否使用部门 1-是,0-否
+  responsibilityDeptId?: number; // 责任部门ID
+  deviceStatus?: number; // 设备状态 1-在用,2-停用,3-报废
+}
+
+/** 特种设备设施实体(列表 + 详情统一类型) */
+export interface SpecialEquipment {
+  id?: number; // 设备主键ID
+  deviceId?: string; // 设备ID,企业内部唯一标识编号
+  deviceName?: string; // 设备名称
+  useUnit?: string; // 使用单位名称
+  categoryId?: number; // 设备类别ID
+  categoryName?: string; // 设备类别名称
+  typeId?: number; // 设备种类ID
+  typeName?: string; // 设备种类名称
+  registerCode?: string; // 注册代码
+  deviceCode?: string; // 设备编码
+  useDepartment?: string; // 使用部门名称
+  responsiblePerson?: string; // 设备责任人姓名
+  factoryNo?: string; // 出厂编号
+  startUseDate?: string; // 启用日期 yyyy-MM-dd
+  useYears?: number; // 使用年限(年)
+  inspectionTime?: string; // 检测时间 yyyy-MM-dd
+  assetId?: string; // 设备固资ID
+  licenseNo?: string; // 使用证号
+  safeLocation?: string; // 安全地点
+  responsibilityDeptId?: number; // 责任部门ID
+  responsibilityDeptName?: string; // 责任部门名称
+  jobNo?: string; // 责任人工号
+  productionDate?: string; // 生产日期 yyyy-MM-dd
+  inspectionCycle?: number; // 检测周期(天)
+  deviceStatus?: number; // 设备状态 1-在用,2-停用,3-报废
+  deviceStatusName?: string; // 设备状态名称
+  isUseDepartment?: number; // 是否使用部门 1-是,0-否
+  isUseDepartmentName?: string; // 是否使用部门名称
+  nextInspectionDate?: string; // 下次检测时间 yyyy-MM-dd
+  remark?: string; // 备注
+  attachments?: {
+    file_name: string;
+    url: string;
+  }[]; // 附件列表
+  [key: string]: any;
+}
+
+/** 分页查询特种设备设施列表 */
+export function querySpecialEquipmentPage(
+  query: QueryPageRequest<SpecialEquipmentQueryParam>,
+) {
+  return http.request<QueryPageResponse<SpecialEquipment>>({
+    url: '/specialEquipment/queryPage',
+    method: 'post',
+    data: query,
+  });
+}
+
+/** 查询特种设备设施详情 */
+export function querySpecialEquipmentDetail(id: number) {
+  return http.request<SpecialEquipment>({
+    url: '/specialEquipment/queryDetail',
+    method: 'get',
+    params: { id },
+  });
+}
+
+/** 新增特种设备设施 */
+export function saveSpecialEquipment(data: SpecialEquipment) {
+  return http.request({
+    url: '/specialEquipment/save',
+    method: 'post',
+    data,
+  });
+}
+
+/** 编辑特种设备设施 */
+export function updateSpecialEquipment(data: SpecialEquipment) {
+  return http.request({
+    url: '/specialEquipment/update',
+    method: 'put',
+    data,
+  });
+}
+
+/** 删除特种设备设施 */
+export function deleteSpecialEquipment(id: number) {
+  return http.request({
+    url: '/specialEquipment/delete',
+    method: 'delete',
+    params: { id },
+  });
+}
+

+ 156 - 1
src/views/production-safety/risk-identification-and-control/special-equipment-manage/add.vue

@@ -1 +1,156 @@
-<template>add</template>
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">新增特种设备设施</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <BasicForm
+        ref="basicFormRef"
+        :formData="formData"
+        :formRules="formRules"
+        :formConfig="SPECIAL_EQUIPMENT_FORM_CONFIG"
+        class="equipment-form"
+      >
+        <template #deviceStatus>
+          <el-radio-group v-model="formData.deviceStatus">
+            <el-radio :label="1">在用</el-radio>
+            <el-radio :label="2">停用</el-radio>
+            <el-radio :label="3">报废</el-radio>
+          </el-radio-group>
+        </template>
+        <template #isUseDepartment>
+          <el-radio-group v-model="formData.isUseDepartment">
+            <el-radio :label="1">是</el-radio>
+            <el-radio :label="0">否</el-radio>
+          </el-radio-group>
+        </template>
+        <template #responsibilityDept>
+          <el-cascader
+            v-model="responsibilityDeptPath"
+            :options="deptOptions"
+            :props="deptCascaderProps"
+            :show-all-levels="false"
+            placeholder="请选择责任部门"
+            filterable
+            clearable
+            @change="handleDeptChange"
+          />
+        </template>
+      </BasicForm>
+    </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="router.back()">返回</el-button>
+      <el-button type="primary" :loading="submitting" @click="handleSubmit">提交</el-button>
+    </footer>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { reactive, ref, onMounted } from 'vue';
+  import { useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import { saveSpecialEquipment, type SpecialEquipment } from '@/api/production-safety/special-equipment';
+  import { getAllDepartments } from '@/api/auth/dept';
+  import { formatDeptTree } from '@/views/disaster/utils/formatDeptTree';
+  import BasicForm from '@/components/BasicForm.vue';
+  import {
+    SPECIAL_EQUIPMENT_FORM_CONFIG,
+    SPECIAL_EQUIPMENT_FORM_DATA,
+    SPECIAL_EQUIPMENT_FORM_RULES,
+  } from './configs/form';
+
+  const router = useRouter();
+  const submitting = ref(false);
+
+  const formData = reactive({
+    ...SPECIAL_EQUIPMENT_FORM_DATA,
+  });
+
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+
+  // 部门树(queryAllDeptTree 样式)
+  const deptOptions = ref<any[]>([]);
+  const responsibilityDeptPath = ref<number[]>([]);
+  const deptCascaderProps = {
+    expandTrigger: 'click',
+    checkStrictly: true,
+    value: 'id',
+    label: 'deptName',
+  };
+
+  const formRules = SPECIAL_EQUIPMENT_FORM_RULES;
+
+  const loadDeptTree = async () => {
+    try {
+      const res = await getAllDepartments();
+      deptOptions.value = formatDeptTree(res);
+    } catch (e) {
+      console.error('获取部门树失败:', e);
+      deptOptions.value = [];
+    }
+  };
+
+  const handleDeptChange = (val: number[]) => {
+    if (Array.isArray(val) && val.length) {
+      (formData as any).responsibilityDeptId = val[val.length - 1];
+    } else {
+      (formData as any).responsibilityDeptId = undefined;
+    }
+  };
+
+  const handleSubmit = async () => {
+    const valid = await basicFormRef.value?.validateForm().catch(() => false);
+    if (!valid) return;
+    submitting.value = true;
+    try {
+      const payload: SpecialEquipment = {
+        ...(formData as any),
+        deviceId: formData.deviceId || undefined,
+        deviceName: formData.deviceName || undefined,
+        useUnit: formData.useUnit || undefined,
+        registerCode: (formData as any).registerCode || undefined,
+        deviceCode: (formData as any).deviceCode || undefined,
+        useDepartment: (formData as any).useDepartment || undefined,
+        responsiblePerson: (formData as any).responsiblePerson || undefined,
+        factoryNo: (formData as any).factoryNo || undefined,
+        startUseDate: formData.startUseDate || undefined,
+        inspectionTime: formData.inspectionTime || undefined,
+        assetId: formData.assetId || undefined,
+        licenseNo: formData.licenseNo || undefined,
+        safeLocation: formData.safeLocation || undefined,
+        jobNo: (formData as any).jobNo || undefined,
+        productionDate: formData.productionDate || undefined,
+        nextInspectionDate: formData.nextInspectionDate || undefined,
+        remark: formData.remark || undefined,
+      };
+      await saveSpecialEquipment(payload);
+      ElMessage.success('新增成功');
+      router.back();
+    } catch (e) {
+      console.error('新增特种设备设施失败:', e);
+      ElMessage.error('新增失败,请重试');
+    } finally {
+      submitting.value = false;
+    }
+  };
+
+  onMounted(() => {
+    loadDeptTree();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+  .equipment-form {
+    max-width: 700px;
+  }
+</style>

+ 226 - 0
src/views/production-safety/risk-identification-and-control/special-equipment-manage/configs/form.ts

@@ -0,0 +1,226 @@
+import type { FormConfig } from '@/types/basic-form';
+
+// 特种设备设施 - 基础表单配置(新增/编辑可用,查看时外层禁用)
+export const SPECIAL_EQUIPMENT_FORM_CONFIG: FormConfig[] = [
+  {
+    prop: 'deviceId',
+    label: '设备ID:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'assetId',
+    label: '设备固资ID:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'deviceName',
+    label: '设备名称:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'useUnit',
+    label: '使用单位:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'categoryName',
+    label: '设备类别:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'typeName',
+    label: '设备种类:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'registerCode',
+    label: '注册代码:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'licenseNo',
+    label: '使用证号:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'deviceCode',
+    label: '设备编码:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'safeLocation',
+    label: '安全地点:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'useDepartment',
+    label: '使用部门:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'responsibilityDeptId',
+    label: '责任部门:',
+    slot: 'responsibilityDept',
+  },
+  {
+    prop: 'responsiblePerson',
+    label: '责任人:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'jobNo',
+    label: '工号:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'factoryNo',
+    label: '出厂编号:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '',
+    },
+  },
+  {
+    prop: 'productionDate',
+    label: '生产日期:',
+    component: 'ElDatePicker',
+    componentProps: {
+      type: 'date',
+      valueFormat: 'YYYY-MM-DD',
+      placeholder: '请选择生产日期',
+    },
+  },
+  {
+    prop: 'startUseDate',
+    label: '启用日期:',
+    component: 'ElDatePicker',
+    componentProps: {
+      type: 'date',
+      valueFormat: 'YYYY-MM-DD',
+      placeholder: '请选择启用日期',
+    },
+  },
+  {
+    prop: 'inspectionCycle',
+    label: '检测周期(天):',
+    component: 'ElInputNumber',
+    componentProps: {
+      min: 1,
+      precision: 0,
+      controlsPosition: 'right',
+    },
+  },
+  {
+    prop: 'useYears',
+    label: '使用年限(年):',
+    component: 'ElInputNumber',
+    componentProps: {
+      min: 1,
+      precision: 0,
+      controlsPosition: 'right',
+    },
+  },
+  {
+    prop: 'inspectionTime',
+    label: '检测时间:',
+    component: 'ElDatePicker',
+    componentProps: {
+      type: 'date',
+      valueFormat: 'YYYY-MM-DD',
+      placeholder: '请选择检测时间',
+    },
+  },
+  {
+    prop: 'deviceStatus',
+    label: '设备状态:',
+    slot: 'deviceStatus',
+  },
+  {
+    prop: 'isUseDepartment',
+    label: '是否使用部门:',
+    slot: 'isUseDepartment',
+  },
+  {
+    prop: 'nextInspectionDate',
+    label: '下次检测时间:',
+    component: 'ElDatePicker',
+    componentProps: {
+      type: 'date',
+      valueFormat: 'YYYY-MM-DD',
+      placeholder: '请选择下次检测时间',
+    },
+  },
+];
+
+export const SPECIAL_EQUIPMENT_FORM_DATA = {
+  deviceId: '',
+  assetId: '',
+  deviceName: '',
+  useUnit: '',
+  categoryName: '',
+  typeName: '',
+  registerCode: '',
+  licenseNo: '',
+  deviceCode: '',
+  safeLocation: '',
+  useDepartment: '',
+  responsibilityDeptId: undefined as number | undefined,
+  responsiblePerson: '',
+  jobNo: '',
+  factoryNo: '',
+  productionDate: '',
+  startUseDate: '',
+  inspectionCycle: undefined as number | undefined,
+  useYears: undefined as number | undefined,
+  inspectionTime: '',
+  deviceStatus: 1,
+  isUseDepartment: 1,
+  nextInspectionDate: '',
+};
+
+export const SPECIAL_EQUIPMENT_FORM_RULES = {
+  deviceName: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }],
+  useUnit: [{ required: true, message: '使用单位不能为空', trigger: 'blur' }],
+  deviceStatus: [{ required: true, message: '请选择设备状态', trigger: 'change' }],
+};
+
+

+ 75 - 0
src/views/production-safety/risk-identification-and-control/special-equipment-manage/configs/tables.ts

@@ -0,0 +1,75 @@
+import type { TableColumnProps } from '@/types/basic-table';
+
+// 特种设备设施管理 - 表格基础配置
+export const TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  loading: true,
+  maxHeight: 'calc(70vh - 150px)',
+};
+
+export const SPECIAL_EQUIPMENT_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '序号',
+    type: 'index',
+    align: 'center',
+    width: '80px',
+  },
+  {
+    label: '设备名称',
+    prop: 'deviceName',
+    align: 'left',
+    minWidth: '160px',
+  },
+  {
+    label: '使用单位',
+    prop: 'useUnit',
+    align: 'left',
+    minWidth: '160px',
+  },
+  {
+    label: '设备类别',
+    prop: 'categoryName',
+    align: 'left',
+    minWidth: '140px',
+  },
+  {
+    label: '设备种类',
+    prop: 'typeName',
+    align: 'left',
+    minWidth: '140px',
+  },
+  {
+    label: '安全地点',
+    prop: 'safeLocation',
+    align: 'left',
+    minWidth: '180px',
+  },
+  {
+    label: '使用部门',
+    prop: 'useDepartment',
+    align: 'left',
+    minWidth: '140px',
+  },
+  {
+    label: '责任部门',
+    prop: 'responsibilityDeptName',
+    align: 'left',
+    minWidth: '140px',
+  },
+  {
+    label: '状态',
+    prop: 'deviceStatus',
+    slot: 'deviceStatus',
+    align: 'center',
+    minWidth: '100px',
+  },
+  {
+    label: '操作',
+    prop: 'action',
+    slot: 'action',
+    fixed: 'right',
+    width: '220px',
+    align: 'left',
+  },
+];
+

+ 230 - 1
src/views/production-safety/risk-identification-and-control/special-equipment-manage/edit.vue

@@ -1 +1,230 @@
-<template>edit</template>
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">编辑特种设备设施</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <BasicForm
+        v-loading="loading"
+        ref="basicFormRef"
+        :formData="formData"
+        :formRules="formRules"
+        :formConfig="SPECIAL_EQUIPMENT_FORM_CONFIG"
+        class="equipment-form"
+      >
+        <template #deviceStatus>
+          <el-radio-group v-model="formData.deviceStatus">
+            <el-radio :label="1">在用</el-radio>
+            <el-radio :label="2">停用</el-radio>
+            <el-radio :label="3">报废</el-radio>
+          </el-radio-group>
+        </template>
+        <template #isUseDepartment>
+          <el-radio-group v-model="formData.isUseDepartment">
+            <el-radio :label="1">是</el-radio>
+            <el-radio :label="0">否</el-radio>
+          </el-radio-group>
+        </template>
+        <template #responsibilityDept>
+          <el-cascader
+            v-model="responsibilityDeptPath"
+            :options="deptOptions"
+            :props="deptCascaderProps"
+            :show-all-levels="false"
+            placeholder="请选择责任部门"
+            filterable
+            clearable
+            @change="handleDeptChange"
+          />
+        </template>
+      </BasicForm>
+    </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="router.back()">返回</el-button>
+      <el-button type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
+    </footer>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { reactive, ref, onMounted, computed } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import { querySpecialEquipmentDetail, updateSpecialEquipment, type SpecialEquipment } from '@/api/production-safety/special-equipment';
+  import { getAllDepartments } from '@/api/auth/dept';
+  import { formatDeptTree } from '@/views/disaster/utils/formatDeptTree';
+  import BasicForm from '@/components/BasicForm.vue';
+  import {
+    SPECIAL_EQUIPMENT_FORM_CONFIG,
+    SPECIAL_EQUIPMENT_FORM_DATA,
+    SPECIAL_EQUIPMENT_FORM_RULES,
+  } from './configs/form';
+
+  const router = useRouter();
+  const route = useRoute();
+  const loading = ref(false);
+  const submitting = ref(false);
+
+  const id = computed(() => Number(route.query.id));
+
+  const formData = reactive({
+    ...SPECIAL_EQUIPMENT_FORM_DATA,
+    id: undefined as number | undefined,
+  });
+
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+
+  // 部门树(queryAllDeptTree 样式)
+  const deptOptions = ref<any[]>([]);
+  const responsibilityDeptPath = ref<number[]>([]);
+  const deptCascaderProps = {
+    expandTrigger: 'click',
+    checkStrictly: true,
+    value: 'id',
+    label: 'deptName',
+  };
+
+  const formRules = SPECIAL_EQUIPMENT_FORM_RULES;
+
+  const loadDeptTree = async () => {
+    try {
+      const res = await getAllDepartments();
+      deptOptions.value = formatDeptTree(res);
+      // 根据已有 responsibilityDeptId 反推路径(简单遍历)
+      if ((formData as any).responsibilityDeptId) {
+        const path: number[] = [];
+        const dfs = (nodes: any[], currentPath: number[]): boolean => {
+          for (const n of nodes) {
+            const newPath = [...currentPath, n.id];
+            if (n.id === (formData as any).responsibilityDeptId) {
+              responsibilityDeptPath.value = newPath;
+              return true;
+            }
+            if (n.children && n.children.length && dfs(n.children, newPath)) {
+              return true;
+            }
+          }
+          return false;
+        };
+        dfs(deptOptions.value, []);
+      }
+    } catch (e) {
+      console.error('获取部门树失败:', e);
+      deptOptions.value = [];
+    }
+  };
+
+  const handleDeptChange = (val: number[]) => {
+    if (Array.isArray(val) && val.length) {
+      (formData as any).responsibilityDeptId = val[val.length - 1];
+    } else {
+      (formData as any).responsibilityDeptId = undefined;
+    }
+  };
+
+  const getDetail = async () => {
+    if (!id.value) {
+      ElMessage.error('缺少 id');
+      return;
+    }
+    loading.value = true;
+    try {
+      const res = await querySpecialEquipmentDetail(id.value);
+      if (res) {
+        (formData as any).id = res.id;
+        formData.deviceId = res.deviceId ?? '';
+        formData.deviceName = res.deviceName ?? '';
+        formData.useUnit = res.useUnit ?? '';
+        (formData as any).categoryId = res.categoryId;
+        (formData as any).typeId = res.typeId;
+        (formData as any).registerCode = res.registerCode ?? '';
+        (formData as any).deviceCode = res.deviceCode ?? '';
+        (formData as any).useDepartment = res.useDepartment ?? '';
+        (formData as any).responsiblePerson = res.responsiblePerson ?? '';
+        (formData as any).factoryNo = res.factoryNo ?? '';
+        formData.startUseDate = res.startUseDate ?? '';
+        formData.useYears = res.useYears ?? undefined;
+        formData.inspectionTime = res.inspectionTime ?? '';
+        formData.assetId = res.assetId ?? '';
+        formData.licenseNo = res.licenseNo ?? '';
+        formData.safeLocation = res.safeLocation ?? '';
+        (formData as any).responsibilityDeptId = res.responsibilityDeptId;
+        (formData as any).jobNo = res.jobNo ?? '';
+        formData.productionDate = res.productionDate ?? '';
+        formData.inspectionCycle = res.inspectionCycle ?? undefined;
+        formData.deviceStatus = res.deviceStatus ?? 1;
+        formData.isUseDepartment = res.isUseDepartment ?? 1;
+        formData.nextInspectionDate = res.nextInspectionDate ?? '';
+        formData.remark = res.remark ?? '';
+      }
+    } catch (e) {
+      console.error('获取特种设备设施详情失败:', e);
+      ElMessage.error('获取详情失败');
+    } finally {
+      loading.value = false;
+    }
+  };
+
+  const handleSubmit = async () => {
+    const valid = await basicFormRef.value?.validateForm().catch(() => false);
+    if (!valid) return;
+    if (!(formData as any).id) {
+      ElMessage.error('缺少 id');
+      return;
+    }
+    submitting.value = true;
+    try {
+      const payload: SpecialEquipment = {
+        ...(formData as any),
+        id: (formData as any).id,
+        deviceId: formData.deviceId || undefined,
+        deviceName: formData.deviceName || undefined,
+        useUnit: formData.useUnit || undefined,
+        registerCode: (formData as any).registerCode || undefined,
+        deviceCode: (formData as any).deviceCode || undefined,
+        useDepartment: (formData as any).useDepartment || undefined,
+        responsiblePerson: (formData as any).responsiblePerson || undefined,
+        factoryNo: (formData as any).factoryNo || undefined,
+        startUseDate: formData.startUseDate || undefined,
+        inspectionTime: formData.inspectionTime || undefined,
+        assetId: formData.assetId || undefined,
+        licenseNo: formData.licenseNo || undefined,
+        safeLocation: formData.safeLocation || undefined,
+        jobNo: (formData as any).jobNo || undefined,
+        productionDate: formData.productionDate || undefined,
+        nextInspectionDate: formData.nextInspectionDate || undefined,
+        remark: formData.remark || undefined,
+      };
+      await updateSpecialEquipment(payload);
+      ElMessage.success('保存成功');
+      router.back();
+    } catch (e) {
+      console.error('编辑特种设备设施失败:', e);
+      ElMessage.error('保存失败,请重试');
+    } finally {
+      submitting.value = false;
+    }
+  };
+
+  onMounted(() => {
+    getDetail().then(() => {
+      loadDeptTree();
+    });
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+  .equipment-form {
+    max-width: 700px;
+  }
+</style>

+ 218 - 239
src/views/production-safety/risk-identification-and-control/special-equipment-manage/list.vue

@@ -1,237 +1,263 @@
 <template>
   <div class="safety-platform-container">
     <header class="safety-platform-container__header">
-      <div class="breadcrumb-title"> 特种设备设施管理 </div>
+      <div class="breadcrumb-title">特种设备设施管理</div>
     </header>
     <main class="safety-platform-container__main">
-      <div class="search-form">
-        <el-form :inline="true">
-          <el-form-item label="搜索">
-            <el-input
-              v-model="queryParams.queryParam.mergeFiled"
-              placeholder="搜索楼号/楼宇/楼层/房间"
-              style="width: 170px"
-            />
-          </el-form-item>
-          <el-form-item label="状态">
-            <el-select v-model="queryParams.queryParam.status" clearable placeholder="状态" style="width: 170px">
-              <el-option :value="1" label="正常" />
-              <el-option :value="2" label="待确认" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="部门名称">
-            <el-cascader
-              v-model="queryParams.queryParam.responsibleDepartmentId"
-              style="width: 170px"
-              ref="cascaderRef"
-              :options="firstLevelDepts"
-              :props="cascaderProp"
-              :show-all-levels="false"
-              placeholder="部门名称"
-              filterable
-              @change="handleChangeDept"
-            />
-          </el-form-item>
-          <el-form-item label="风险类型">
-            <el-select
-              v-model="queryParams.queryParam.riskCategory"
-              clearable
-              placeholder="风险类型"
-              style="width: 170px"
+      <div class="search-table-container">
+        <header>
+          <div style="position: relative">
+            <el-button
+              type="primary"
+              class="search-table-container--button"
+              @click="$router.push({ name: 'specialEquipmentManageAdd' })"
             >
-              <el-option :value="1" label="III级危险点" />
-              <el-option :value="2" label="II级危险点" />
-              <el-option :value="3" label="I级危险点" />
-              <el-option :value="4" label="UPS" />
-              <el-option :value="5" label="电力设施(强电)" />
-              <el-option :value="6" label="高低温气体液体、高压气体" />
-              <el-option :value="7" label="试验设施设备" />
-              <el-option :value="8" label="特种设备" />
-              <el-option :value="9" label="危化品、易燃易爆固液气体" />
-              <el-option :value="10" label="有限空间" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="风险级别">
-            <el-select v-model="queryParams.queryParam.riskLevel" clearable placeholder="风险类型" style="width: 170px">
-              <el-option :value="1" label="B" />
-              <el-option :value="2" label="C" />
-            </el-select>
-          </el-form-item>
-        </el-form>
+              添加
+            </el-button>
+          </div>
 
-        <div>
-          <el-button type="primary" @click="$router.push({ name: 'riskManageAdd' })">添加 </el-button>
-          <el-button type="primary" @click="queryTableList">查询</el-button>
-          <el-button @click="handleRestParams">重置</el-button>
-        </div>
-      </div>
+          <div class="act-search">
+            <section class="select-box">
+              <div class="select-box--item">
+                <span>设备名称:</span>
+                <el-input
+                  v-model="queryParams.queryParam.deviceName"
+                  placeholder="请输入设备名称"
+                  class="act-search-input"
+                  clearable
+                />
+              </div>
+              <div class="select-box--item">
+                <span>状态:</span>
+                <el-select
+                  v-model="queryParams.queryParam.deviceStatus"
+                  placeholder="请选择状态"
+                  clearable
+                  class="act-search-input"
+                >
+                  <el-option label="在用" :value="1" />
+                  <el-option label="停用" :value="2" />
+                  <el-option label="报废" :value="3" />
+                </el-select>
+              </div>
+              <div class="select-box--item">
+                <span>是否使用部门:</span>
+                <el-select
+                  v-model="queryParams.queryParam.isUseDepartment"
+                  placeholder="是否使用部门"
+                  clearable
+                  class="act-search-input"
+                >
+                  <el-option label="是" :value="1" />
+                  <el-option label="否" :value="0" />
+                </el-select>
+              </div>
+              <div class="select-box--item">
+                <span>责任部门:</span>
+                <el-cascader
+                  v-model="responsibilityDeptPath"
+                  :options="deptOptions"
+                  :props="deptCascaderProps"
+                  :show-all-levels="false"
+                  placeholder="请选择责任部门"
+                  class="act-search-input"
+                  filterable
+                  clearable
+                  @change="handleDeptChange"
+                />
+              </div>
+              <div class="select-box--item">
+                <span>使用单位:</span>
+                <el-input
+                  v-model="queryParams.queryParam.useUnit"
+                  placeholder="请输入使用单位"
+                  class="act-search-input"
+                  clearable
+                />
+              </div>
+            </section>
+            <section class="search-btn">
+              <el-button type="primary" @click="queryTableList">查询</el-button>
+              <el-button @click="handleRestParams">重置</el-button>
+            </section>
+          </div>
+        </header>
 
-      <div class="table-content">
-        <el-table :data="tableData.data">
-          <el-table-column type="index" label="序号" width="80" />
-          <el-table-column label="楼号/区域" prop="buildingArea" width="180" />
-          <el-table-column label="楼宇名称" prop="buildingName" width="180" />
-          <el-table-column label="楼层/位置" prop="floorLocation" width="180" />
-          <el-table-column label="房间号(名称)" prop="roomName" width="180" />
-          <el-table-column label="安全责任人" prop="roomSafetyResponsibleName" width="180" />
-          <el-table-column label="是否存在风险点" prop="hasRiskPointName" width="180" />
-          <el-table-column label="风险点类别" prop="riskCategoryName" width="180" />
-          <el-table-column label="变更原因" prop="changeReason" width="170" />
-          <el-table-column label="状态" prop="statusName" width="100" />
-          <el-table-column fixed="right" min-width="240" label="操作">
-            <template #default="scope">
-              <el-button
-                type="primary"
-                link
-                @click="$router.push({ name: 'riskManageEdit', query: { id: scope.row.id } })"
-                >编辑</el-button
-              >
-              <el-button type="primary" link @click="handleConfirmDeleteRow(scope)">删除</el-button>
-              <el-button type="primary" link>查看</el-button>
-              <el-button
-                type="primary"
-                link
-                @click="$router.push({ name: 'riskManageChange', query: { id: scope.row.id } })"
-                >变更</el-button
-              >
-              <el-button type="primary" link @click="handleApprove(scope, 1)">确认</el-button>
-              <el-button type="primary" link @click="handleApprove(scope, 0)">拒绝</el-button>
-              <el-button type="primary" link @click="handleApprove(scope, 0)">撤回</el-button>
+        <div class="batch-table">
+          <BasicTable
+            ref="basicTableRef"
+            :tableData="tableData"
+            :tableConfig="tableConfig"
+            @update:pageSize="handleSizeChange"
+            @update:pageNumber="handleCurrentChange"
+          >
+            <template #deviceStatus="scope">
+              <span>
+                {{ getStatusText(scope.row.deviceStatus, scope.row.deviceStatusName) }}
+              </span>
             </template>
-          </el-table-column>
-        </el-table>
-      </div>
-      <div class="pagination-container" v-if="tableData.total > 0">
-        <el-pagination
-          background
-          :current-page="queryParams.pageNumber"
-          :page-size="queryParams.pageSize"
-          :total="tableData.total"
-          @size-change="handleSizeChange"
-          @current-change="handleCurrentChange"
-        />
+            <template #action="scope">
+              <div class="action-container--div" style="justify-content: left">
+                <ActionButton
+                  text="查看"
+                  @click="$router.push({ name: 'specialEquipmentManageView', query: { id: scope.row.id } })"
+                />
+                <ActionButton
+                  text="编辑"
+                  @click="$router.push({ name: 'specialEquipmentManageEdit', query: { id: scope.row.id } })"
+                />
+                <ActionButton
+                  text="删除"
+                  :popconfirm="{ title: '确定要删除该特种设备设施吗?' }"
+                  @confirm="handleDelete(scope.row)"
+                />
+              </div>
+            </template>
+          </BasicTable>
+        </div>
       </div>
     </main>
   </div>
 </template>
+
 <script lang="ts" setup>
   import { onMounted, reactive, ref } from 'vue';
-  import dayjs from 'dayjs';
   import { ElMessage } from 'element-plus';
-  import { useRouter } from 'vue-router';
+  import type { QueryPageRequest } from '@/types/basic-query';
   import {
-    safetyRiskListQueryPage,
-    safetyRiskListDelete,
-    safetyRiskListApprove,
-  } from '@/api/production-safety/responsibility-implementation';
-  import { omit } from 'lodash-es';
-  import { useUserInfoHook } from '@/hooks/useUserInfoHook';
-  import { unformatAttachment } from '@/components/UploadFiles/utils';
-  import { downloadFile } from '@/views/disaster/utils';
-  import { formatDeptTree } from '@/views/disaster/utils/formatDeptTree';
+    querySpecialEquipmentPage,
+    deleteSpecialEquipment,
+    type SpecialEquipment,
+    type SpecialEquipmentQueryParam,
+  } from '@/api/production-safety/special-equipment';
   import { getAllDepartments } from '@/api/auth/dept';
+  import { formatDeptTree } from '@/views/disaster/utils/formatDeptTree';
+  import BasicTable from '@/components/BasicTable.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import ActionButton from '@/components/ActionButton.vue';
+  import { TABLE_OPTIONS, SPECIAL_EQUIPMENT_TABLE_COLUMNS } from './configs/tables';
 
-  const router = useRouter();
-  const { id } = useUserInfoHook();
-  const firstLevelDepts = ref<any[]>([]);
-  const cascaderProp = {
-    expandTrigger: 'click',
-    checkStrictly: true,
-    // emitPath: false,
-    value: 'id',
-    label: 'deptName',
-  };
-  const queryParams = reactive<any>({
+  const loading = ref(false);
+
+  // BasicTable
+  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+  const { tableConfig, pagination } = useTableConfig(SPECIAL_EQUIPMENT_TABLE_COLUMNS, TABLE_OPTIONS);
+
+  const queryParams = reactive<QueryPageRequest<SpecialEquipmentQueryParam>>({
     pageNumber: 1,
     pageSize: 10,
     queryParam: {
-      mergeFiled: '',
-      status: '',
-      responsibleDepartment: '',
-      riskCategory: '',
-      riskLevel: '',
-      userId: id,
-      responsibleDepartmentId: [],
+      deviceName: '',
+      deviceStatus: undefined,
+      categoryId: undefined,
+      typeId: undefined,
+      isUseDepartment: undefined,
+      responsibilityDeptId: undefined,
+      useUnit: '',
     },
   });
-  const cascaderRef = ref();
 
-  const tableData = reactive({
-    data: [],
-    total: 0,
-  });
+  // 类别/种类名称(目前接口只支持按 ID 查,这里仅作为显示搜索占位)
+  const categoryName = ref('');
+  const typeName = ref('');
+
+  // 部门树(queryAllDeptTree 样式)
+  const deptOptions = ref<any[]>([]);
+  const responsibilityDeptPath = ref<number[]>([]);
+  const deptCascaderProps = {
+    expandTrigger: 'click',
+    checkStrictly: true,
+    value: 'id',
+    label: 'deptName',
+  };
+
+  const tableData = ref<SpecialEquipment[]>([]);
 
-  const handleSizeChange = (value) => {};
-  const handleCurrentChange = (value) => {
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    queryParams.pageSize = value;
+    queryParams.pageNumber = 1;
+    queryTableList();
+  };
+
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
     queryParams.pageNumber = value;
     queryTableList();
   };
 
-  const getDeptData = () => {
-    getAllDepartments().then((res) => {
-      firstLevelDepts.value = formatDeptTree(res);
-    });
+  const getStatusText = (status?: number, statusName?: string) => {
+    if (statusName) return statusName;
+    if (status === 1) return '在用';
+    if (status === 2) return '停用';
+    if (status === 3) return '报废';
+    return '-';
   };
-  const handleChangeDept = () => {
-    const deptInfo = cascaderRef.value?.getCheckedNodes();
-    if (deptInfo?.[0]) {
-      queryParams.queryParam.responsibleDepartment = deptInfo[0].label;
+
+  const loadDeptTree = async () => {
+    try {
+      const res = await getAllDepartments();
+      deptOptions.value = formatDeptTree(res);
+    } catch (e) {
+      console.error('获取部门树失败:', e);
+      deptOptions.value = [];
     }
   };
 
-  const handleApprove = (scope, approveType) => {
-    safetyRiskListApprove({
-      id: scope.row.id,
-      approveType,
-    }).then(() => {
-      ElMessage.success('操作成功!');
-      queryTableList();
-    });
+  const handleDeptChange = (val: number[]) => {
+    if (Array.isArray(val) && val.length) {
+      queryParams.queryParam.responsibilityDeptId = val[val.length - 1];
+    } else {
+      queryParams.queryParam.responsibilityDeptId = undefined;
+    }
   };
 
-  const handleDownloadLink = (scope) => {
-    const attachment = unformatAttachment(scope.row.attachment);
-    attachment?.forEach((item: any) => {
-      downloadFile(item.fileUrl, item.fileName);
-    });
-  };
-  const handleConfirmDeleteRow = (scope) => {
-    safetyRiskListDelete(scope.row.id).then(() => {
-      ElMessage.success('删除成功!');
-      queryTableList();
-    });
+  const queryTableList = () => {
+    loading.value = true;
+    tableConfig.loading = true;
+    querySpecialEquipmentPage(queryParams)
+      .then((res: any) => {
+        tableData.value = (res?.records ?? res?.list ?? []) as SpecialEquipment[];
+        pagination.total = res?.totalRow ?? res?.total ?? 0;
+      })
+      .finally(() => {
+        loading.value = false;
+        tableConfig.loading = false;
+      });
   };
 
-  const queryTableList = () => {
-    safetyRiskListQueryPage({
-      ...queryParams,
-      queryParam: {
-        ...omit(queryParams.queryParam, 'responsibleDepartmentId'),
-      },
-    }).then((res) => {
-      tableData.data = res.records;
-      tableData.total = res.totalRow;
-    });
+  const handleDelete = (row: SpecialEquipment) => {
+    if (!row.id) return;
+    deleteSpecialEquipment(row.id!)
+      .then(() => {
+        ElMessage.success('删除成功');
+        queryTableList();
+      })
+      .catch(() => {});
   };
+
   const handleRestParams = () => {
-    Object.assign(queryParams, {
-      pageNumber: 1,
-      pageSize: 10,
-      queryParam: {
-        ...queryParams.queryParam,
-        mergeFiled: '',
-        status: '',
-        responsibleDepartment: '',
-        riskCategory: '',
-        riskLevel: '',
-        responsibleDepartmentId: [],
-      },
-    });
+    pagination.pageNumber = 1;
+    pagination.pageSize = 10;
+    queryParams.pageNumber = 1;
+    queryParams.pageSize = 10;
+    queryParams.queryParam = {
+      deviceName: '',
+      deviceStatus: undefined,
+      categoryId: undefined,
+      typeId: undefined,
+      isUseDepartment: undefined,
+      responsibilityDeptId: undefined,
+      useUnit: '',
+    };
+    categoryName.value = '';
+    typeName.value = '';
+    responsibilityDeptPath.value = [];
     queryTableList();
   };
 
-  onMounted(async () => {
-    await getDeptData();
+  onMounted(() => {
+    loadDeptTree();
     queryTableList();
   });
 </script>
@@ -240,52 +266,5 @@
   @use '@/styles/page-details-layout.scss' as *;
   @use '@/styles/page-main-layout.scss' as *;
   @use '@/styles/basic-table-action.scss' as *;
-
-  :deep(.el-tabs__header) {
-    margin: 0;
-  }
-  :deep(.el-tabs__item) {
-    font-size: 14px !important;
-  }
-  :deep(.flexContent) {
-    display: flex;
-  }
-  :deep(.breadcrumb .title) {
-    margin-left: 0;
-  }
-
-  :deep(.el-form) {
-    flex: 1;
-    display: flex;
-    row-gap: 15px;
-    flex-wrap: wrap;
-  }
-  :deep(.el-form-item) {
-    margin-bottom: 0;
-  }
-  :deep(main) {
-    display: flex;
-    flex-direction: column;
-  }
-  .search-form {
-    min-width: 800px;
-    display: flex;
-
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 20px;
-  }
-
-  .button-content {
-    margin-bottom: 20px;
-  }
-  .table-content {
-    flex: 1;
-    overflow: hidden;
-    overflow-y: auto;
-  }
-  .page-content {
-    display: flex;
-    justify-content: flex-end;
-  }
+  @use '@/views/traffic/violation/style/act-search-table.scss' as *;
 </style>

+ 174 - 1
src/views/production-safety/risk-identification-and-control/special-equipment-manage/view.vue

@@ -1 +1,174 @@
-<template>view</template>
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">查看特种设备设施</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <BasicForm
+        v-loading="loading"
+        ref="basicFormRef"
+        :formData="formData"
+        :formConfig="viewFormConfig"
+      >
+        <template #deviceStatus>
+          <el-radio-group v-model="formData.deviceStatus" disabled>
+            <el-radio :label="1">在用</el-radio>
+            <el-radio :label="2">停用</el-radio>
+            <el-radio :label="3">报废</el-radio>
+          </el-radio-group>
+        </template>
+        <template #isUseDepartment>
+          <el-radio-group v-model="formData.isUseDepartment" disabled>
+            <el-radio :label="1">是</el-radio>
+            <el-radio :label="0">否</el-radio>
+          </el-radio-group>
+        </template>
+        <template #responsibilityDept>
+          <el-cascader
+            v-model="responsibilityDeptPath"
+            :options="deptOptions"
+            :props="deptCascaderProps"
+            :show-all-levels="false"
+            placeholder="请选择责任部门"
+            class="act-search-input"
+            filterable
+            clearable
+            disabled
+          />
+        </template>
+      </BasicForm>
+    </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="router.back()">返回</el-button>
+    </footer>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { reactive, ref, onMounted, computed } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import BasicForm from '@/components/BasicForm.vue';
+  import { querySpecialEquipmentDetail, type SpecialEquipment } from '@/api/production-safety/special-equipment';
+  import { SPECIAL_EQUIPMENT_FORM_CONFIG, SPECIAL_EQUIPMENT_FORM_DATA } from './configs/form';
+  import { getAllDepartments } from '@/api/auth/dept';
+  import { formatDeptTree } from '@/views/disaster/utils/formatDeptTree';
+
+  const router = useRouter();
+  const route = useRoute();
+  const loading = ref(false);
+
+  const id = computed(() => Number(route.query.id));
+
+  const formData = reactive({
+    ...SPECIAL_EQUIPMENT_FORM_DATA,
+  });
+
+  const viewFormConfig = ref(
+    SPECIAL_EQUIPMENT_FORM_CONFIG.map((item) => ({
+      ...item,
+      componentProps: {
+        ...(item.componentProps || {}),
+        disabled: true,
+      },
+    })),
+  );
+
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+
+  // 部门树(queryAllDeptTree 样式,仅展示)
+  const deptOptions = ref<any[]>([]);
+  const responsibilityDeptPath = ref<number[]>([]);
+  const deptCascaderProps = {
+    expandTrigger: 'click',
+    checkStrictly: true,
+    value: 'id',
+    label: 'deptName',
+  };
+
+  const getDetail = async () => {
+    if (!id.value) {
+      ElMessage.error('缺少 id');
+      return;
+    }
+    loading.value = true;
+    try {
+      const res = (await querySpecialEquipmentDetail(id.value)) as SpecialEquipment | undefined;
+      if (res) {
+        formData.deviceId = res.deviceId ?? '';
+        formData.assetId = res.assetId ?? '';
+        formData.deviceName = res.deviceName ?? '';
+        formData.useUnit = res.useUnit ?? '';
+        formData.categoryName = res.categoryName ?? '';
+        formData.typeName = res.typeName ?? '';
+        formData.registerCode = res.registerCode ?? '';
+        formData.licenseNo = res.licenseNo ?? '';
+        formData.deviceCode = res.deviceCode ?? '';
+        formData.safeLocation = res.safeLocation ?? '';
+        formData.useDepartment = res.useDepartment ?? '';
+        (formData as any).responsibilityDeptId = res.responsibilityDeptId ?? undefined;
+        formData.responsiblePerson = res.responsiblePerson ?? '';
+        formData.jobNo = res.jobNo ?? '';
+        formData.factoryNo = res.factoryNo ?? '';
+        formData.productionDate = res.productionDate ?? '';
+        formData.startUseDate = res.startUseDate ?? '';
+        formData.inspectionCycle = res.inspectionCycle ?? undefined;
+        formData.useYears = res.useYears ?? undefined;
+        formData.inspectionTime = res.inspectionTime ?? '';
+        formData.deviceStatus = res.deviceStatus ?? 1;
+        formData.isUseDepartment = res.isUseDepartment ?? 1;
+        formData.nextInspectionDate = res.nextInspectionDate ?? '';
+        formData.remark = res.remark ?? '';
+
+        // 加载部门树并回显路径
+        try {
+          const deptRes = await getAllDepartments();
+          deptOptions.value = formatDeptTree(deptRes);
+          const targetId = (formData as any).responsibilityDeptId;
+          if (targetId) {
+            const path: number[] = [];
+            const dfs = (nodes: any[], currentPath: number[]): boolean => {
+              for (const n of nodes) {
+                const newPath = [...currentPath, n.id];
+                if (n.id === targetId) {
+                  responsibilityDeptPath.value = newPath;
+                  return true;
+                }
+                if (n.children && n.children.length && dfs(n.children, newPath)) {
+                  return true;
+                }
+              }
+              return false;
+            };
+            dfs(deptOptions.value, []);
+          }
+        } catch (e) {
+          console.error('获取部门树失败:', e);
+          deptOptions.value = [];
+        }
+      }
+    } catch (e) {
+      console.error('获取特种设备设施详情失败:', e);
+      ElMessage.error('获取详情失败');
+    } finally {
+      loading.value = false;
+    }
+  };
+
+  onMounted(() => {
+    getDetail();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+</style>

+ 30 - 2
src/views/production-safety/safetyAssessment/evaluationDepartment/components/EvaluationDepartmentFeedback.vue

@@ -42,7 +42,10 @@
           <el-table-column label="考核内容" prop="evaluationContent" min-width="200" />
           <el-table-column label="评分方式" prop="scoringMethod" min-width="150" />
           <el-table-column label="加减分项" prop="scoreType" min-width="120" />
-          <el-table-column label="自评得分" prop="selfScore" min-width="180">
+          <el-table-column prop="selfScore" min-width="180">
+            <template #header>
+              <span class="required-star">*</span>自评得分
+            </template>
             <template #default="scope">
               <el-input-number
                 v-model="scope.row.selfScore"
@@ -55,7 +58,10 @@
               />
             </template>
           </el-table-column>
-          <el-table-column label="资料说明" prop="materialDescription" min-width="400">
+          <el-table-column prop="materialDescription" min-width="400">
+            <template #header>
+              <span class="required-star">*</span>资料说明
+            </template>
             <template #default="scope">
               <UploadFiles
                 label="上传附件"
@@ -318,6 +324,23 @@
         return;
       }
 
+      // 校验表格中“自评得分”和“资料说明”必填
+      const hasEmptyScore = evaluationItems.value.some(
+        (item, index) => item.selfScore === null || item.selfScore === undefined || item.selfScore === '',
+      );
+      if (hasEmptyScore) {
+        ElMessage.error('请填写所有考核项的自评得分');
+        return;
+      }
+
+      const hasEmptyAttachment = evaluationItems.value.some(
+        (item) => !Array.isArray(item.attachmentFileList) || item.attachmentFileList.length === 0,
+      );
+      if (hasEmptyAttachment) {
+        ElMessage.error('请为所有考核项上传资料说明附件');
+        return;
+      }
+
       // 使用详情原始数据,更新自评得分、加减分项及资料说明附件
       const updatedScores =
         (await Promise.all(
@@ -424,6 +447,11 @@
     width: 100%;
   }
 
+  .required-star {
+    color: #f56c6c;
+    margin-right: 2px;
+  }
+
   .upload-files-disabled {
     pointer-events: none;
     opacity: 0.6;

+ 4 - 3
src/views/production-safety/safetyAssessment/evaluationSystem/components/EvaluationSystemFeedback.vue

@@ -76,7 +76,7 @@
           <el-table-column label="复核得分" prop="reviewScore" min-width="180">
             <template #default="scope">
               <el-input-number
-                v-if="!isAudit && scope.row.isReviewInput"
+                v-if="scope.row.isReviewInput"
                 v-model="scope.row.reviewScore"
                 :min="0"
                 :max="99999"
@@ -91,7 +91,7 @@
           <el-table-column label="复核不通过原因" prop="reviewRejectReson" min-width="220">
             <template #default="scope">
               <el-input
-                v-if="scope.row.isReviewInput"
+                v-if="isSelfApproveButton"
                 v-model="scope.row.reviewRejectReson"
                 type="textarea"
                 :rows="2"
@@ -482,7 +482,8 @@
           reviewRejectReson: score.reviewRejectReson || '', // 复核不通过原因
           materialDescription: score.attachments || '', // 资料说明(使用附件字段,字符串)
           attachmentFileList: parseAttachmentsToFileList(score.attachments || ''), // 资料说明对应的附件文件列表
-          isReviewInput: detail.isSelfApproveButton == true, // 是否显示复核得分输入框
+          isReviewInput: score.isReviewInput, // 是否显示复核得分输入框
+          // reviewRejectResonShow: detail.isSelfApproveButton || '', // 复核不通过原因
         }));
       } else {
         evaluationItems.value = [];