Explorar el Código

Merge branch 'feat/production-safety-evaluation' into feat/production-safety

xiaweibo hace 3 meses
padre
commit
48af322797
Se han modificado 44 ficheros con 9140 adiciones y 3 borrados
  1. 13 1
      package.json
  2. 626 0
      src/api/evaluationSystem/index.ts
  3. 129 0
      src/api/inventory/index.ts
  4. 126 0
      src/api/pointDeduction/index.ts
  5. 164 0
      src/api/receiptRecord/index.ts
  6. 2 1
      src/components/batch-import/BatchImport.vue
  7. 4 0
      src/components/batch-import/types.ts
  8. 269 0
      src/router/routers/production-safety.ts
  9. 50 0
      src/views/production-safety/safetyAssessment/evaluationDepartment/EvaluationDepartmentItem.vue
  10. 767 0
      src/views/production-safety/safetyAssessment/evaluationDepartment/components/EvaluationDepartmentAdvancedPerson.vue
  11. 356 0
      src/views/production-safety/safetyAssessment/evaluationDepartment/components/EvaluationDepartmentFeedback.vue
  12. 19 0
      src/views/production-safety/safetyAssessment/evaluationDepartment/configs/status.ts
  13. 58 0
      src/views/production-safety/safetyAssessment/evaluationDepartment/configs/tables.ts
  14. 7 0
      src/views/production-safety/safetyAssessment/evaluationDepartment/constants.ts
  15. 317 0
      src/views/production-safety/safetyAssessment/evaluationDepartment/evaluationDepartment.vue
  16. 75 0
      src/views/production-safety/safetyAssessment/evaluationSystem/EvaluationSystemItem.vue
  17. 812 0
      src/views/production-safety/safetyAssessment/evaluationSystem/components/EvaluationSystemAdvancedPerson.vue
  18. 846 0
      src/views/production-safety/safetyAssessment/evaluationSystem/components/EvaluationSystemDetail.vue
  19. 501 0
      src/views/production-safety/safetyAssessment/evaluationSystem/components/EvaluationSystemFeedback.vue
  20. 774 0
      src/views/production-safety/safetyAssessment/evaluationSystem/components/EvaluationTarget.vue
  21. 39 0
      src/views/production-safety/safetyAssessment/evaluationSystem/configs/form.ts
  22. 22 0
      src/views/production-safety/safetyAssessment/evaluationSystem/configs/status.ts
  23. 71 0
      src/views/production-safety/safetyAssessment/evaluationSystem/configs/tables.ts
  24. 169 0
      src/views/production-safety/safetyAssessment/evaluationSystem/configs/targetTables.ts
  25. 7 0
      src/views/production-safety/safetyAssessment/evaluationSystem/constants.ts
  26. 710 0
      src/views/production-safety/safetyAssessment/evaluationSystem/evaluationSystem.vue
  27. 44 0
      src/views/production-safety/safetyAssessment/inventory/InventoryItem.vue
  28. 127 0
      src/views/production-safety/safetyAssessment/inventory/components/InventoryDetail.vue
  29. 58 0
      src/views/production-safety/safetyAssessment/inventory/configs/form.ts
  30. 62 0
      src/views/production-safety/safetyAssessment/inventory/configs/tables.ts
  31. 264 0
      src/views/production-safety/safetyAssessment/inventory/inventory.vue
  32. 43 0
      src/views/production-safety/safetyAssessment/pointDeduction/PointDeductionItem.vue
  33. 275 0
      src/views/production-safety/safetyAssessment/pointDeduction/components/PointDeductionDetail.vue
  34. 79 0
      src/views/production-safety/safetyAssessment/pointDeduction/configs/form.ts
  35. 11 0
      src/views/production-safety/safetyAssessment/pointDeduction/configs/status.ts
  36. 57 0
      src/views/production-safety/safetyAssessment/pointDeduction/configs/tables.ts
  37. 333 0
      src/views/production-safety/safetyAssessment/pointDeduction/pointDeduction.vue
  38. 337 0
      src/views/production-safety/safetyAssessment/receiptRecord/components/ReceiptRecordDetail.vue
  39. 82 0
      src/views/production-safety/safetyAssessment/receiptRecord/configs/form.ts
  40. 13 0
      src/views/production-safety/safetyAssessment/receiptRecord/configs/status.ts
  41. 68 0
      src/views/production-safety/safetyAssessment/receiptRecord/configs/tables.ts
  42. 307 0
      src/views/production-safety/safetyAssessment/receiptRecord/receiptRecord.vue
  43. 46 0
      src/views/production-safety/safetyAssessment/receiptRecord/receiptRecordItem.vue
  44. 1 1
      utils/devProxy/staff/proxy.ts

+ 13 - 1
package.json

@@ -170,6 +170,18 @@
         "rollup",
         "webpack"
       ]
-    }
+    },
+    "ignoredBuiltDependencies": [
+      "@vue-office/docx",
+      "@vue-office/excel",
+      "@vue-office/pdf",
+      "@vue-office/pptx",
+      "core-js",
+      "es5-ext",
+      "esbuild",
+      "postinstall-postinstall",
+      "vue-demi",
+      "vue-echarts"
+    ]
   }
 }

+ 626 - 0
src/api/evaluationSystem/index.ts

@@ -0,0 +1,626 @@
+import { http } from '@/utils/http/axios';
+import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
+
+/**
+ * 考核项目内容
+ */
+export interface EvaluationContent {
+  id: number;
+  psemId: number;
+  serialNum: number;
+  exProgram: string; // 考核项目
+  exContent: string; // 考核内容
+  scoringWay: string; // 评分方式
+  isAdd?: number; // 是否加分项(0-否,1-是)
+  reviewUserId?: number | null; // 复评人ID
+  reviewUserName?: string; // 复评人姓名
+}
+
+/**
+ * 安全考核管理返回对象
+ */
+export interface EvaluationSystemItem {
+  id: number;
+  exName: string; // 考核表名称
+  attachments: string; // 考核文档
+  ratingDescribe: string; // 评分说明
+  deptNames: string; // 下发部门
+  deptIds: string; // 下发部门ID
+  planStartTime: string; // 计划开始时间
+  planEndTime: string; // 计划结束时间
+  status: number; // 状态(0-未下发/2-待反馈/3-待评分/4-待审核/5-已作废/1-已完成)
+  statusName: string; // 状态名称
+  createdUserId: number;
+  createdUserName: string; // 创建人
+  createdAt: string; // 创建时间
+  updatedAt: string; // 更新时间
+  isDeleted: number;
+  exContents: EvaluationContent[]; // 考核项目列表
+}
+
+/**
+ * 安全考核管理查询参数
+ */
+export interface EvaluationSystemQueryParam {
+  psemId?: number;
+  exName?: string; // 考核表名称
+  status?: number; // 状态
+  deptName?: string; // 所属部门
+  userName?: string;
+  planStartTime?: string; // 计划开始时间
+  planEndTime?: string; // 计划结束时间
+}
+
+/**
+ * 查询安全考核管理列表
+ */
+export function querySecurityExamine(query: QueryPageRequest<EvaluationSystemQueryParam>) {
+  return http.request<QueryPageResponse<EvaluationSystemItem>>({
+    url: '/securityExamine/admin/querySecurityExamine',
+    method: 'post',
+    data: query,
+  });
+}
+
+/**
+ * 导入考核项目详情
+ */
+export function importSecurityExamineDet(formData: FormData) {
+  return http.request<EvaluationContent[]>({
+    url: '/securityExamine/admin/importSecurityExamineDet',
+    method: 'post',
+    data: formData,
+    headers: {
+      'Content-Type': 'multipart/form-data',
+    },
+  });
+}
+
+/**
+ * 保存安全考核管理请求参数
+ */
+export interface SaveSecurityExamineRequest {
+  id?: number; // 编辑时传ID,新增时为0或不传
+  exName: string; // 考核表名称
+  attachments: string; // 考核文档(附件URL字符串,多个用逗号分隔)
+  ratingDescribe: string; // 评分说明
+  deptNames?: string; // 下发部门
+  deptIds?: number[]; // 下发部门ID数组
+  getUserGroupId?: number;
+  planStartTime?: string; // 计划开始时间
+  planEndTime?: string; // 计划结束时间
+  status: number; // 状态(0-未下发)
+  createdUserId?: number; // 创建人ID
+  createdUserName?: string; // 创建人名称
+  exContents: EvaluationContent[]; // 考核项目列表
+}
+
+/**
+ * 保存安全考核管理(新增/编辑)
+ */
+export function saveSecurityExamine(data: SaveSecurityExamineRequest) {
+  return http.request({
+    url: '/securityExamine/admin/saveSecurityExamine',
+    method: 'post',
+    data,
+  });
+}
+
+/**
+ * 查询安全考核管理详情
+ */
+export function querySecurityExamineDetail(id: number) {
+  return http.request<EvaluationSystemItem>({
+    url: `/securityExamine/admin/querySecurityExamineDetail`,
+    method: 'get',
+    params: { id },
+  });
+}
+
+/**
+ * 删除安全考核管理
+ */
+export function deleteSecurityExamine(id: number) {
+  return http.request({
+    url: `/securityExamine/admin/deleteSecurityExamine?id=${id}`,
+    method: 'delete',
+    // params: { id },
+  });
+}
+
+/**
+ * 更新安全考核管理请求参数(编辑时使用)
+ */
+export interface UpdateSecurityExamineRequest {
+  id: number; // 必须传ID
+  exName: string; // 考核表名称
+  attachments: string; // 考核文档(附件URL字符串,多个用逗号分隔)
+  ratingDescribe: string; // 评分说明
+  deptNames?: string; // 下发部门
+  deptIds?: number[]; // 下发部门ID数组
+  getUserGroupId?: number;
+  planStartTime?: string; // 计划开始时间
+  planEndTime?: string; // 计划结束时间
+  status: number; // 状态
+  createdUserId?: number; // 创建人ID
+  createdUserName?: string; // 创建人名称
+  exContents: EvaluationContent[]; // 考核项目列表
+}
+
+/**
+ * 更新安全考核管理(编辑)
+ */
+export function updateSecurityExamine(data: UpdateSecurityExamineRequest) {
+  return http.request({
+    url: '/securityExamine/admin/updateSecurityExamine',
+    method: 'put',
+    data,
+  });
+}
+
+/**
+ * 下发安全考核表请求参数
+ */
+export interface SaveSecurityExamineIssueRequest {
+  id: number;
+  deptNames: string; // 部门名称(逗号分隔)
+  deptIds: number; // 部门ID(单个)
+  getUserGroupId?: number; // 分组ID
+  deptSelfApproveUserId?: number; // 部门自评审核人ID
+  planStartTime?: string; // 计划开始时间
+  planEndTime?: string; // 计划结束时间
+}
+
+/**
+ * 下发安全考核表
+ */
+export function saveSecurityExamineIssue(data: SaveSecurityExamineIssueRequest) {
+  return http.request({
+    url: '/securityExamine/admin/saveSecurityExamineIssue',
+    method: 'post',
+    data,
+  });
+}
+
+/**
+ * 考核对象评分项
+ */
+export interface EvaluationScoreItem {
+  id: number;
+  psemId: number;
+  psedId: number;
+  pseiId: number;
+  serialNum: number;
+  exProgram: string; // 考核项目
+  exContent: string; // 考核内容
+  scoringWay: string; // 评分方式
+  selfScore: number; // 自评分数
+  attachments: string; // 附件
+  reviewScore: number; // 审核分数
+  isAdd?: number; // 是否加分项(0-否,1-是)
+  createdAt: string;
+  updatedAt: string;
+  isDeleted: number;
+}
+
+/**
+ * 考核对象列表项
+ */
+export interface EvaluationTargetItem {
+  id: number;
+  psemId: number; // 考核表ID
+  deptName: string; // 部门名称
+  exName: string; // 考核表名称
+  attachments: string; // 考核文档
+  planEndTime: string; // 计划结束时间
+  deptId: number; // 部门ID
+  status: number; // 状态(0-未下发/2-待反馈/3-待评分/4-待审核/5-已作废/1-已完成)
+  statusName: string; // 状态名称
+  reviewRejectReson?: string; // 审核拒绝原因
+  approveRejectReson?: string; // 审批拒绝原因
+  deptUserId?: number; // 部门负责人ID
+  deptUserName?: string; // 部门负责人姓名
+  deptUserLink?: string; // 部门负责人联系方式
+  reviewUserId?: number; // 审核人ID
+  reviewUserName?: string; // 审核人姓名
+  createdAt: string;
+  updatedAt: string;
+  isDeleted: number;
+  scores?: EvaluationScoreItem[]; // 评分详情
+  scoreRank?: number; // 排名
+}
+
+/**
+ * 查询考核对象列表请求参数(queryParam)
+ */
+export interface QuerySecurityExamineIssueQueryParam {
+  psemId?: number; // 考核表ID(从路由参数获取)
+  planStartTime?: string; // 计划开始时间
+  planEndTime?: string; // 计划结束时间
+  status?: number; // 状态(0-未下发/2-待反馈/3-待评分/4-待审核/5-已作废/1-已完成)
+  deptName?: string; // 部门名称(考核对象)
+}
+
+/**
+ * 查询考核对象列表请求参数
+ */
+export interface QuerySecurityExamineIssueParams {
+  pageNumber: number; // 页号
+  pageSize: number; // 每页数量
+  queryParam: QuerySecurityExamineIssueQueryParam; // 查询参数
+}
+
+/**
+ * 查询考核对象列表
+ */
+export function querySecurityExamineIssue(params: QuerySecurityExamineIssueParams) {
+  return http.request<QueryPageResponse<EvaluationTargetItem>>({
+    url: '/securityExamine/admin/querySecurityExamineIssue',
+    method: 'post',
+    data: params,
+  });
+}
+
+/**
+ * 作废安全考核表
+ */
+export function updateSecurityExamineRepeal(id: number) {
+  return http.request({
+    url: `/securityExamine/admin/updateSecurityExamineRepeal?id=${id}`,
+    method: 'put',
+  });
+}
+
+/**
+ * 作废考核对象
+ */
+export function updateSecurityExamineIssueRepeal(id: number) {
+  return http.request({
+    url: `/securityExamine/admin/updateSecurityExamineIssueRepeal?id=${id}`,
+    method: 'put',
+  });
+}
+
+/**
+ * 删除考核对象
+ */
+export function deleteSecurityExamineIssue(id: number) {
+  return http.request({
+    url: `/securityExamine/admin/deleteSecurityExamineIssue?id=${id}`,
+    method: 'delete',
+  });
+}
+
+/**
+ * 查询部门考核列表请求参数(queryParam)
+ */
+export interface QuerySecurityExamineDeptQueryParam {
+  exName?: string; // 考核表名称
+  status?: number; // 状态
+  deptName?: string; // 部门名称
+  planStartTime?: string; // 计划开始时间
+  planEndTime?: string; // 计划结束时间
+}
+
+/**
+ * 查询部门考核列表请求参数
+ */
+export interface QuerySecurityExamineDeptParams {
+  pageNumber: number; // 页号
+  pageSize: number; // 每页数量
+  queryParam: QuerySecurityExamineDeptQueryParam; // 查询参数
+}
+
+/**
+ * 查询部门考核列表
+ */
+export function querySecurityExamineDept(params: QuerySecurityExamineDeptParams) {
+  return http.request<QueryPageResponse<EvaluationTargetItem>>({
+    url: '/securityExamine/dept/querySecurityExamineDept',
+    method: 'post',
+    data: params,
+  });
+}
+
+/**
+ * 查询部门考核详情
+ */
+export function querySecurityExamineIssueDeptDetail(id: number) {
+  return http.request<EvaluationTargetItem>({
+    url: `/securityExamine/dept/querySecurityExamineIssueDeptDetail?id=${id}`,
+    method: 'get',
+  });
+}
+
+/**
+ * 查询管理员端考核对象详情(用于评分)
+ */
+export function querySecurityExamineIssueDetail(id: number) {
+  return http.request<EvaluationTargetItem>({
+    url: `/securityExamine/admin/querySecurityExamineIssueDetail?id=${id}`,
+    method: 'get',
+  }); 
+}
+
+/**
+ * 提交部门考核反馈请求参数
+ */
+export interface UpdateSecurityExamineDeptSubmitParams {
+  id: number; // 考核表ID (psemId)
+  scores: Array<{
+    id?: number; // 评分项ID
+    selfScore: number; // 自评得分
+  }>;
+}
+
+/**
+ * 提交部门考核反馈
+ */
+export function updateSecurityExamineDeptSubmit(params: UpdateSecurityExamineDeptSubmitParams) {
+  return http.request({
+    url: '/securityExamine/dept/updateSecurityExamineDeptSubmit',
+    method: 'put',
+    data: params,
+  });
+}
+
+/**
+ * 导出部门考核详情
+ */
+export function exportSecurityExamineIssueDeptDetail(id: number) {
+  return http.request({
+    url: '/securityExamine/dept/exportSecurityExamineIssueDeptDetail',
+    method: 'post',
+    data: { id },
+    responseType: 'blob',
+  });
+}
+
+/**
+ * 导入部门考核详情
+ */
+export function importSecurityExamineIssueDeptDetail(params: { id: number; file: File }) {
+  const formData = new FormData();
+  formData.append('id', String(params.id));
+  formData.append('file', params.file);
+  return http.request({
+    url: '/securityExamine/dept/importSecurityExamineIssueDeptDetail',
+    method: 'post',
+    data: formData,
+    headers: {
+      'Content-Type': 'multipart/form-data',
+    },
+  });
+}
+
+/**
+ * 管理员端复核提交
+ */
+export function updateSecurityExamineIssueReviewSubmit(params: UpdateSecurityExamineDeptSubmitParams) {
+  return http.request({
+    url: '/securityExamine/admin/updateSecurityExamineIssueReviewSubmit',
+    method: 'put',
+    data: params,
+  });
+}
+
+/**
+ * 管理员端复核通过(提交)
+ */
+export function updateSecurityExamineIssueReviewAgree(params: UpdateSecurityExamineDeptSubmitParams) {
+  return http.request({
+    url: '/securityExamine/admin/updateSecurityExamineIssueReviewAgree',
+    method: 'put',
+    data: params,
+  });
+}
+
+/**
+ * 管理员端复核不通过
+ */
+export function updateSecurityExamineIssueReviewDisagree(id: number, reviewRejectReson?: string) {
+  return http.request({
+    url: '/securityExamine/admin/updateSecurityExamineIssueReviewDisagree',
+    method: 'put',
+    data: {
+      id,
+      reviewRejectReson,
+    },
+  });
+}
+
+/**
+ * 管理员端审核通过
+ */
+export function updateSecurityExamineIssueApproveAgree(id: number) {
+  return http.request({
+    url: `/securityExamine/admin/updateSecurityExamineIssueApproveAgree?id=${id}`,
+    method: 'put',
+  });
+}
+
+/**
+ * 管理员端审核不通过
+ */
+export function updateSecurityExamineIssueApproveDisagree(id: number, approveRejectReson?: string) {
+  return http.request({
+    url: '/securityExamine/admin/updateSecurityExamineIssueApproveDisagree',
+    method: 'put',
+    data: {
+      id,
+      approveRejectReson,
+    },
+  });
+}
+
+/**
+ * 查询先进集体
+ */
+export interface QuerySecurityExamineIssueAdvancedParams extends QueryPageRequest<any> {
+  queryParam: {
+    psemId?: number;
+    deptName?: string;
+    planStartTime?: string;
+    planEndTime?: string;
+  };
+}
+
+export function querySecurityExamineIssueAdvanced(params: QuerySecurityExamineIssueAdvancedParams) {
+  return http.request<QueryPageResponse<EvaluationTargetItem>>({
+    url: '/securityExamine/admin/querySecurityExamineIssueAdvanced',
+    method: 'post',
+    data: params,
+  });
+}
+
+/**
+ * 部门端先进个人查询请求参数(queryParam)
+ */
+export interface QuerySecurityExamineDeptAdvancedUserQueryParam {
+  psemId?: number; // 考核表ID
+  userName?: string; // 员工工号/名称
+  planStartTime?: string; // 计划开始时间
+  planEndTime?: string; // 计划结束时间
+}
+
+/**
+ * 部门端先进个人查询请求参数
+ */
+export interface QuerySecurityExamineDeptAdvancedUserParams {
+  pageNumber: number; // 页号
+  pageSize: number; // 每页数量
+  queryParam: QuerySecurityExamineDeptAdvancedUserQueryParam; // 查询参数
+}
+
+/**
+ * 部门端先进个人返回对象
+ */
+export interface EvaluationDeptAdvancedUserItem {
+  id?: number; // 记录ID
+  deptName: string; // 所属部门
+  deptId?: number; // 部门ID
+  deptUserId?: number; // 部门用户ID
+  deptUserName: string; // 部门负责人
+  deptUserLink: string; // 部门负责人联系方式
+  advUserStaffNo: string; // 员工工号
+  advUserName: string; // 先进个人姓名
+  advUserLink: string; // 先进个人联系方式(员工联系方式)
+  remark: string; // 先进个人描述
+  attachments: string; // 附件
+  planEndTime: string; // 上报日期(计划结束时间)
+}
+
+/**
+ * 查询部门端先进个人列表
+ */
+export function querySecurityExamineDeptAdvancedUser(params: QuerySecurityExamineDeptAdvancedUserParams) {
+  return http.request<QueryPageResponse<EvaluationDeptAdvancedUserItem>>({
+    url: '/securityExamine/dept/querySecurityExamineDeptAdvancedUser',
+    method: 'post',
+    data: params,
+  });
+}
+
+/**
+ * 管理员端先进个人查询请求参数(queryParam)
+ */
+export interface QuerySecurityExamineIssueAdvancedUserQueryParam {
+  psemId?: number; // 考核表ID
+  deptName?: string; // 部门名称
+  userName?: string; // 员工工号/名称
+  planStartTime?: string; // 计划开始时间
+  planEndTime?: string; // 计划结束时间
+}
+
+/**
+ * 管理员端先进个人查询请求参数
+ */
+export interface QuerySecurityExamineIssueAdvancedUserParams {
+  pageNumber: number; // 页号
+  pageSize: number; // 每页数量
+  queryParam: QuerySecurityExamineIssueAdvancedUserQueryParam; // 查询参数
+}
+
+/**
+ * 查询管理员端先进个人列表
+ */
+export function querySecurityExamineIssueAdvancedUser(params: QuerySecurityExamineIssueAdvancedUserParams) {
+  return http.request<QueryPageResponse<EvaluationDeptAdvancedUserItem>>({
+    url: '/securityExamine/admin/querySecurityExamineIssueAdvancedUser',
+    method: 'post',
+    data: params,
+  });
+}
+
+/**
+ * 保存部门端先进个人请求参数
+ */
+export interface SaveSecurityExamineDeptAdvUserRequest {
+  psemId: number; // 考核表ID
+  users: Array<{
+    deptId: number; // 部门ID
+    advUserStaffNo: string; // 员工工号
+    advUserName: string; // 先进个人姓名
+    advUserLink: string; // 先进个人联系方式
+    remark: string; // 先进个人描述
+  }>;
+}
+
+/**
+ * 保存部门端先进个人
+ */
+export function saveSecurityExamineDeptAdvUser(data: SaveSecurityExamineDeptAdvUserRequest) {
+  return http.request({
+    url: '/securityExamine/dept/saveSecurityExamineDeptAdvUser',
+    method: 'post',
+    data,
+  });
+}
+
+/**
+ * 查询部门端先进个人详情
+ */
+export function querySecurityExamineDeptAdvUserDetail(id: number) {
+  return http.request<EvaluationDeptAdvancedUserItem>({
+    url: `/securityExamine/dept/querySecurityExamineDeptAdvUser?id=${id}`,
+    method: 'get',
+  });
+}
+
+/**
+ * 更新部门端先进个人请求参数
+ */
+export interface UpdateSecurityExamineDeptAdvUserRequest {
+  id: number; // 记录ID
+  psemId: number; // 考核表ID
+  deptName: string; // 部门名称
+  deptId: number; // 部门ID
+  deptUserId: number; // 部门用户ID
+  deptUserName: string; // 部门负责人姓名
+  deptUserLink: string; // 部门负责人联系方式
+  advUserStaffNo: string; // 员工工号
+  advUserName: string; // 先进个人姓名
+  advUserLink: string; // 先进个人联系方式
+  remark: string; // 先进个人描述
+}
+
+/**
+ * 更新部门端先进个人
+ */
+export function updateSecurityExamineDeptAdvUser(data: UpdateSecurityExamineDeptAdvUserRequest) {
+  return http.request({
+    url: '/securityExamine/dept/updateSecurityExamineDeptAdvUser',
+    method: 'put',
+    data,
+  });
+}
+
+/**
+ * 删除部门端先进个人
+ */
+export function deleteSecurityExamineDeptAdvUser(id: number) {
+  return http.request({
+    url: `/securityExamine/dept/deleteSecurityExamineDeptAdvUser?id=${id}`,
+    method: 'delete',
+  });
+}

+ 129 - 0
src/api/inventory/index.ts

@@ -0,0 +1,129 @@
+  import { http } from '@/utils/http/axios';
+import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
+
+/**
+ * 查询物品信息返回对象
+ */
+export interface InventoryItem {
+  id: number;
+  stuffName: string; // 物品名称
+  inStoreTime: string; // 入库日期
+  stuffQty: number; // 物品数量
+  surplusQty: number; // 物品结余数量
+  remark: string; // 备注
+  createdUserName: string; // 经办人
+  status: boolean; // 状态(1-启用/0-禁用)
+  createdAt: string; // 创建时间
+  updatedAt: string; // 更新时间
+  isDeleted: number; // 0-未删除,大于0(时间戳)-已删除
+  statusName: string; // 状态名称
+}
+
+/**
+ * 查询物品信息请求参数
+ */
+export interface InventoryQueryParam {
+  stuffName?: string; // 物品名称
+  status?: boolean; // 状态
+  ids?: string[]; // 选择数据的ID
+}
+
+/**
+ * 查询物品库存管理列表
+ */
+export function queryInventoryManage(query: QueryPageRequest<InventoryQueryParam>) {
+  return http.request<QueryPageResponse<InventoryItem>>({
+    url: '/inventory/queryInventoryManage',
+    method: 'post',
+    data: query,
+  });
+}
+
+/**
+ * 保存物品信息请求参数
+ */
+export interface SaveInventoryRequest {
+  stuffName: string; // 物品名称
+  inStoreTime: string; // 入库日期 (ISO 格式)
+  stuffQty: number; // 物品数量
+  remark: string; // 备注
+}
+
+/**
+ * 保存物品库存(新增)
+ */
+export function saveInventory(data: SaveInventoryRequest) {
+  return http.request({
+    url: '/inventory/saveInventory',
+    method: 'post',
+    data,
+  });
+}
+
+/**
+ * 更新物品信息请求参数
+ */
+export interface UpdateInventoryRequest extends SaveInventoryRequest {
+  id: number; // 物品ID
+}
+
+/**
+ * 更新物品库存(编辑)
+ */
+export function updateInventory(data: UpdateInventoryRequest) {
+  return http.request({
+    url: '/inventory/updateInventory',
+    method: 'put',
+    data,
+  });
+}
+
+/**
+ * 删除物品库存
+ */
+export function deleteInventory(id: number) {
+  return http.request({
+    url: `/inventory/deleteInventory?id=${id}`,
+    method: 'delete',
+    // data: { id },
+  });
+}
+
+/**
+ * 查询物品库存详情
+ */
+export function queryInventoryDetail(id: number) {
+  return http.request<InventoryItem>({
+    url: `/inventory/queryInventoryDetail?id=${id}`,
+    method: 'get',
+  });
+}
+
+/**
+ * 导入物品库存
+ * 注意:导入功能使用 BatchImport 组件,直接通过 URL 上传文件
+ */
+
+/**
+ * 导出物品库存请求参数
+ */
+export interface ExportInventoryRequest {
+  stuffName?: string; // 物品名称
+  status?: boolean; // 状态
+  ids?: string[]; // 选择数据的ID
+}
+
+/**
+ * 导出物品库存
+ */
+export function exportInventory(query: ExportInventoryRequest) {
+  return http.request({
+    url: '/inventory/exportInventory',
+    method: 'post',
+    data: query,
+    responseType: 'blob',
+  }, {
+    isTransformResponse: false,
+  });
+}
+

+ 126 - 0
src/api/pointDeduction/index.ts

@@ -0,0 +1,126 @@
+import { http } from '@/utils/http/axios';
+import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
+
+/**
+ * 扣分原因项
+ */
+export interface DeductionReasonItem {
+  id: number;
+  pmdId: number;
+  serialNum: number;
+  deductionReason: string;
+  scoreVal: number;
+}
+
+/**
+ * 月度扣分返回对象
+ */
+export interface PointDeductionItem {
+  id: number;
+  deductionTitle: string; // 扣分标题
+  deductionDate: string; // 扣分日期 (YYYY-MM-DD)
+  deductionScore: number; // 扣分值
+  departmentName: string; // 部门名称
+  deductionReason?: string; // 扣分原因(已废弃,保留兼容)
+  dedResonList?: DeductionReasonItem[]; // 扣分原因列表
+  status: boolean; // 状态 (true-启用, false-禁用)
+  statusName: string; // 状态名称
+}
+
+/**
+ * 月度扣分查询参数
+ */
+export interface PointDeductionQueryParam {
+  deductionTitle?: string; // 扣分标题
+  status?: boolean; // 状态
+  选择数据的ID?: string[]; // 选择数据的ID
+}
+
+/**
+ * 查询月度扣分列表
+ */
+export function queryMonthlyDeduction(query: QueryPageRequest<PointDeductionQueryParam>) {
+  return http.request<QueryPageResponse<PointDeductionItem>>({
+    url: '/monthlyDeduction/queryMonthlyDeduction',
+    method: 'post',
+    data: query,
+  });
+}
+
+/**
+ * 保存月度扣分请求参数
+ */
+export interface SaveMonthlyDeductionRequest {
+  deductionTitle: string; // 扣分标题
+  deductionDate: string; // 扣分日期 (YYYY-MM-DD)
+  deductionScore: number; // 扣分值
+  departmentName: string; // 部门名称
+  deductionReason?: string; // 扣分原因(已废弃,保留兼容)
+  dedResonList: DeductionReasonItem[]; // 扣分原因列表
+  status: boolean; // 状态 (true-启用, false-禁用)
+  id?: number; // ID(编辑时需要)
+}
+
+/**
+ * 保存月度扣分(新增)
+ */
+export function saveMonthlyDeduction(data: SaveMonthlyDeductionRequest) {
+  return http.request({
+    url: '/monthlyDeduction/saveMonthlyDeduction',
+    method: 'post',
+    data,
+  });
+}
+
+/**
+ * 更新月度扣分(编辑)
+ */
+export function updateMonthlyDeduction(data: SaveMonthlyDeductionRequest) {
+  return http.request({
+    url: '/monthlyDeduction/updateMonthlyDeduction',
+    method: 'put',
+    data,
+  });
+}
+
+/**
+ * 删除月度扣分
+ */
+export function deleteDeduction(id: number) {
+  return http.request({
+    url: `/monthlyDeduction/deleteDeduction?id=${id}`,
+    method: 'delete',
+  });
+}
+
+/**
+ * 查询月度扣分详情
+ */
+export function queryDeductionDetail(id: number) {
+  return http.request<PointDeductionItem>({
+    url: `/monthlyDeduction/queryDeductionDetail?id=${id}`,
+    method: 'get',
+  });
+}
+
+/**
+ * 导出月度扣分请求参数
+ */
+export interface ExportMonthlyDeductionRequest {
+  deductionTitle?: string; // 扣分标题
+  status?: boolean; // 状态
+}
+
+/**
+ * 导出月度扣分
+ */
+export function exportMonthlyDeduction(query: ExportMonthlyDeductionRequest) {
+  return http.request({
+    url: '/monthlyDeduction/exportMonthlyDeduction',
+    method: 'post',
+    data: query,
+    responseType: 'blob',
+  }, {
+    isTransformResponse: false,
+  });
+}

+ 164 - 0
src/api/receiptRecord/index.ts

@@ -0,0 +1,164 @@
+import { http } from '@/utils/http/axios';
+import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
+import type { InventoryItem } from '@/api/inventory';
+
+/**
+ * 物品领取记录返回对象
+ */
+export interface ReceiptRecordItem {
+  id: number;
+  pimId: number; // 物品库存ID
+  stuffName: string; // 物品名称
+  outStoreTime: string; // 出库日期
+  claimQty: number; // 领取数量
+  surplusQty: number; // 结余数量
+  orderNumber: string; // 单号
+  deptName: string; // 部门名称
+  userName: string; // 领取人
+  remark: string; // 备注
+  status?: number; // 0 待审核, 1 审核通过, -1 审核不通过
+  createdUserName: string; // 创建人
+  createdAt: string; // 创建时间
+  updatedAt: string; // 更新时间
+  isDeleted: number; // 0-未删除,大于0(时间戳)-已删除
+}
+
+/**
+ * 物品领取记录查询参数
+ */
+export interface ReceiptRecordQueryParam {
+  stuffName?: string; // 物品名称
+  deptName?: string; // 部门名称
+  status?: number; // 0 待审核, 1 审核通过, -1 审核不通过
+}
+
+/**
+ * 查询物品领取记录列表
+ */
+export function queryClaimItemsLogs(query: QueryPageRequest<ReceiptRecordQueryParam>) {
+  return http.request<QueryPageResponse<ReceiptRecordItem>>({
+    url: '/claimItemsLog/queryClaimItemsLogs',
+    method: 'post',
+    data: query,
+  });
+}
+
+/**
+ * 保存物品领取记录请求参数
+ */
+export interface SaveClaimItemsLogRequest {
+  stuffName: string; // 物品名称
+  pimId: number; // 物品库存ID
+  outStoreTime: string; // 出库日期 (ISO 格式)
+  claimQty: number; // 领取数量
+  orderNumber: string; // 单号
+  deptName: string; // 部门名称
+  deptId?: number; // 部门ID
+  userName: string; // 领取人
+  userId?: number; // 领取人ID
+  remark: string; // 备注
+}
+
+/**
+ * 保存物品领取记录(新增)
+ */
+export function saveClaimItemsLog(data: SaveClaimItemsLogRequest) {
+  return http.request({
+    url: '/claimItemsLog/saveClaimItemsLog',
+    method: 'post',
+    data,
+  });
+}
+
+/**
+ * 更新物品领取记录请求参数
+ */
+export interface UpdateClaimItemsLogRequest extends SaveClaimItemsLogRequest {
+  id: number; // 记录ID
+}
+
+/**
+ * 更新物品领取记录(编辑)
+ */
+export function updateClaimItemsLog(data: UpdateClaimItemsLogRequest) {
+  return http.request({
+    url: '/claimItemsLog/updateClaimItemsLog',
+    method: 'put',
+    data,
+  });
+}
+
+/**
+ * 删除物品领取记录
+ */
+export function deleteClaimItemsLog(id: number) {
+  return http.request({
+    url: `/claimItemsLog/deleteClaimItemsLog?id=${id}`,
+    method: 'delete',
+  });
+}
+
+/**
+ * 查询物品领取记录详情
+ */
+export function queryClaimItemsLogDetail(id: number) {
+  return http.request<ReceiptRecordItem>({
+    url: `/claimItemsLog/queryClaimItemsLogDetail?id=${id}`,
+    method: 'get',
+  });
+}
+
+/**
+ * 导出物品领取记录请求参数
+ */
+export interface ExportClaimItemsLogRequest {
+  stuffName?: string; // 物品名称
+  deptName?: string; // 部门名称
+}
+
+/**
+ * 导出物品领取记录
+ */
+export function exportClaimItemsLog(query: ExportClaimItemsLogRequest) {
+  return http.request({
+    url: '/claimItemsLog/exportClaimItemsLog',
+    method: 'post',
+    data: query,
+    responseType: 'blob',
+  }, {
+    isTransformResponse: false,
+  });
+}
+
+/**
+ * 同意审核
+ * @param id 记录ID
+ */
+export function updateClaimItemsLogAgree(id: number) {
+  return http.request({
+    url: `/claimItemsLog/updateClaimItemsLogAgree?id=${id}`,
+    method: 'put',
+    // data: { id },
+  });
+}
+
+/**
+ * 拒绝审核
+ * @param id 记录ID
+ */
+export function updateClaimItemsLogReject(id: number) {
+  return http.request({
+    url: `/claimItemsLog/updateClaimItemsLogReject?id=${id}`,
+    method: 'put',
+  });
+}
+
+/**
+ * 查询物品库存管理列表(用于下拉框)
+ */
+export function querInventoryManageList() {
+  return http.request<InventoryItem[]>({
+    url: '/claimItemsLog/querInventoryManageList',
+    method: 'get',
+  });
+}

+ 2 - 1
src/components/batch-import/BatchImport.vue

@@ -37,7 +37,7 @@
           </div>
         </el-upload>
         <div style="margin-top: 72px; display: flex; justify-content: flex-end">
-          <el-button @click="handleDownloadTemplate">下载模板</el-button>
+          <el-button v-if="props.showTemplate" @click="handleDownloadTemplate">下载模板</el-button>
           <el-button type="primary" @click="handleImport" :disabled="isImportDisable">导入</el-button>
         </div>
       </div>
@@ -82,6 +82,7 @@
 
   const props = withDefaults(defineProps<BatchImportProps>(), {
     templateName: '下载模板',
+    showTemplate: true,
   });
 
   const emit = defineEmits<{

+ 4 - 0
src/components/batch-import/types.ts

@@ -18,6 +18,10 @@ export interface BatchImportProps {
    * 模板名称
    */
   templateName: string;
+  /**
+   * 是否显示下载模板按钮,默认为 true
+   */
+  showTemplate?: boolean;
 }
 
 /**

+ 269 - 0
src/router/routers/production-safety.ts

@@ -33,6 +33,27 @@ const productionSafetyRoutes = {
         component: '/production-safety/implement-safety-duty/responsibility-agree-manage',
         meta: {
           title: '安全责任书管理',
+    id: 9000,
+    parentId: -1,
+    name: 'WorkSafety',
+    path: '/work-safety',
+    component: 'MENU_LAYOUT',
+    meta: {
+      title: '生产安全',
+      icon: 'CameraOutlined',
+      isRoot: false,
+      hidden: false,
+      noCache: false,
+    },
+    children: [
+      {
+        id: 9001,
+        parentId: 9000,
+        name: 'responsibilityImplementation',
+        path: 'responsibility-implementation',
+        redirect: '',
+        meta: {
+          title: '安全责任落实',
           icon: 'OverviewIcon',
           isRoot: false,
           hidden: false,
@@ -48,6 +69,103 @@ const productionSafetyRoutes = {
         component: '/production-safety/implement-safety-duty/create-responsibility-agree',
         meta: {
           title: '创建安全责任书',
+        },
+        children: [{
+          id: 90000,
+          parentId: 9001,
+          name: 'responsibilityAgreeManage',
+          path: 'responsibility-agree-manage',
+          component: '/production-safety/implement-safety-duty/responsibility-agree-manage',
+          meta: {
+            title: '安全责任书管理',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: false,
+            noCache: false,
+          }
+  
+        },
+        {
+          id: 90001,
+          parentId: 9001,
+          name: 'createResponsibilityAgree',
+          path: 'create-responsibility-agree',
+          component: '/production-safety/implement-safety-duty/create-responsibility-agree',
+          meta: {
+            title: '创建安全责任书',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: false,
+            noCache: false,
+          }
+        },
+  
+        {
+          id: 90002,
+          parentId: 9001,
+          name: 'eidtResponsibilityAgree',
+          path: 'edit-responsibility-agree',
+          component: '/production-safety/implement-safety-duty/edit-responsibility-agree',
+          meta: {
+            title: '编辑安全责任书',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: false,
+            noCache: false,
+          }
+        },
+        {
+          id: 90003,
+          parentId: 9001,
+          name: 'viewRecipients',
+          path: 'view-recipients',
+          component: '/production-safety/implement-safety-duty/view-recipients',
+          meta: {
+            title: '查看下发对象',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: false,
+            noCache: false,
+          }
+        },
+        {
+          id: 90004,
+          parentId: 9001,
+          name: 'reviewResponsibilityAgree',
+          path: 'review-responsibility-agree',
+          component: '/production-safety/implement-safety-duty/review-responsibility-agree',
+          meta: {
+            title: '审核安全责任书',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: false,
+            noCache: false,
+          }
+        },
+        {
+          id: 90005,
+          parentId: 9001,
+          name: 'agreeDocumentReview',
+          path: 'agree-document-review',
+          component: '/production-safety/implement-safety-duty/agree-document-review',
+          meta: {
+            title: '安全责任书材料审核',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: false,
+            noCache: false,
+          }
+        }
+        ]
+      },
+      {
+        id: 9002,
+        parentId: 9000,
+        name: 'safetyAssessment',
+        path: 'safetyAssessment',
+        redirect: '',
+        meta: {
+          title: '安全奖惩与考核',
           icon: 'OverviewIcon',
           isRoot: false,
           hidden: false,
@@ -479,3 +597,154 @@ const productionSafetyRoutes = {
 };
 
 export default productionSafetyRoutes;
+        },
+        children: [{
+          id: 90020,
+          parentId: 9002,
+          name: 'Inventory',
+          path: 'inventory',
+          component: '/production-safety/safetyAssessment/inventory/inventory',
+          meta: {
+            title: '物品库存管理',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: false,
+            noCache: false,
+          }
+  
+        },
+        {
+          id: 9002001,
+          parentId: 9002,
+          name: 'InventoryItem',
+          path: 'inventory-item',
+          component: '/production-safety/safetyAssessment/inventory/InventoryItem',
+          meta: {
+            title: '物品库存详情',
+            // activeMenu: '/work-safety/safetyAssessment/inventory',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: true,
+            noCache: false,
+          }
+        },
+        {
+          id: 90021,
+          parentId: 9002,
+          name: 'ReceiptRecord',
+          path: 'receipt-record',
+          component: '/production-safety/safetyAssessment/receiptRecord/receiptRecord',
+          meta: {
+            title: '物品领取记录管理',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: false,
+            noCache: false,
+          }
+        },
+        {
+          id: 9002101,
+          parentId: 9002,
+          name: 'ReceiptRecordItem',
+          path: 'receipt-record-item',
+          component: '/production-safety/safetyAssessment/receiptRecord/receiptRecordItem',
+          meta: {
+            title: '物品领取记录详情',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: true,
+            noCache: false,
+          }
+        },
+  
+        {
+          id: 90022,
+          parentId: 9002,
+          name: 'PointDeduction',
+          path: 'point-deduction',
+          component: '/production-safety/safetyAssessment/pointDeduction/pointDeduction',
+          meta: {
+            title: '月度扣分管理',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: false,
+            noCache: false,
+          }
+        },
+        {
+          id: 9002201,
+          parentId: 9002,
+          name: 'PointDeductionItem',
+          path: 'point-deduction-item',
+          component: '/production-safety/safetyAssessment/pointDeduction/PointDeductionItem',
+          meta: {
+            title: '月度扣分详情',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: true,
+            noCache: false,
+          }
+        },
+        {
+          id: 90023,
+          parentId: 9002,
+          name: 'EvaluationDepartment',
+          path: 'evaluation-department',
+          component: '/production-safety/safetyAssessment/evaluationDepartment/evaluationDepartment',
+          meta: {
+            title: '安全考核管理(部)',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: false,
+            noCache: false,
+          }
+        },
+        {
+          id: 9002301,
+          parentId: 9002,
+          name: 'EvaluationDepartmentItem',
+          path: 'evaluation-department-item',
+          component: '/production-safety/safetyAssessment/evaluationDepartment/EvaluationDepartmentItem',
+          meta: {
+            title: '安全考核管理(部)详情',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: true,
+            activeMenu: 'EvaluationDepartment',
+            noCache: false,
+          }
+        },
+        {
+          id: 90024,
+          parentId: 9002,
+          name: 'EvaluationSystem',
+          path: 'evaluation-system',
+          component: '/production-safety/safetyAssessment/evaluationSystem/evaluationSystem',
+          meta: {
+            title: '安全考核管理(管)',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: false,
+            noCache: false,
+          }
+        },
+        {
+          id: 9002401,
+          parentId: 9002,
+          name: 'EvaluationSystemItem',
+          path: 'evaluation-system-item',
+          component: '/production-safety/safetyAssessment/evaluationSystem/EvaluationSystemItem',
+          meta: {
+            title: '安全考核管理(管)详情',
+            icon: 'OverviewIcon',
+            isRoot: false,
+            hidden: true,
+            noCache: false,
+          }
+        },
+        ]
+      }
+    ],
+  };
+  
+  export default productionSafetyRoutes;

+ 50 - 0
src/views/production-safety/safetyAssessment/evaluationDepartment/EvaluationDepartmentItem.vue

@@ -0,0 +1,50 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ headerTitle }}</span>
+    </header>
+    <component :is="dynamicComponent" :id="id" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed, defineAsyncComponent } from 'vue';
+  import { useRoute } from 'vue-router';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+
+  const route = useRoute();
+  const operate = computed(() => route.query.operate as string);
+  const id = computed(() => (route.query.id ? Number(route.query.id) : undefined));
+
+  const headerTitle = computed(() => {
+    if (operate.value === 'evaluationDepartment-feedback') {
+      return '反馈';
+    }
+    if (operate.value === 'evaluationDepartment-advanced-person') {
+      return '先进个人信息';
+    }
+    return '安全考核管理(部门)详情';
+  });
+
+  const dynamicComponent = computed(() => {
+    if (operate.value === 'evaluationDepartment-feedback') {
+      return defineAsyncComponent(() => import('./components/EvaluationDepartmentFeedback.vue'));
+    }
+    if (operate.value === 'evaluationDepartment-advanced-person') {
+      return defineAsyncComponent(() => import('./components/EvaluationDepartmentAdvancedPerson.vue'));
+    }
+    return null;
+  });
+</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>

+ 767 - 0
src/views/production-safety/safetyAssessment/evaluationDepartment/components/EvaluationDepartmentAdvancedPerson.vue

@@ -0,0 +1,767 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="evaluation-header">
+        <h1 class="evaluation-title">{{ evaluationDetail.exName || '先进个人信息' }}</h1>
+        <div class="evaluation-meta">
+          <span>考核部门: {{ evaluationDetail.deptNames || '-' }}</span>
+          <span>创建人: {{ evaluationDetail.createdUserName || '-' }}</span>
+          <span>创建时间: {{ formatDateTime(evaluationDetail.createdAt) || '-' }}</span>
+        </div>
+      </div>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header>
+          <div class="act-search">
+            <section class="select-box">
+              <div class="select-box--item">
+                <span>员工工号/名称:</span>
+                <el-input
+                  v-model="tableQuery.queryParam.userName"
+                  placeholder="搜索员工工号或名称"
+                  class="act-search-input"
+                />
+              </div>
+              <div class="select-box--item">
+                <span>时间范围:</span>
+                <el-date-picker
+                  v-model="tableQuery.queryParam.dateRange"
+                  type="daterange"
+                  range-separator="至"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                  value-format="YYYY-MM-DD"
+                  format="YYYY-MM-DD"
+                />
+              </div>
+            </section>
+            <section class="search-btn">
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+              <el-dropdown trigger="click" @command="selectDeptType">
+                <el-button type="primary" style="margin:0 10px;">
+                  添加
+                </el-button>
+                <template #dropdown>
+                  <el-dropdown-menu>
+                    <el-dropdown-item command="normal">普通部门</el-dropdown-item>
+                    <el-dropdown-item command="advanced">先进集体部门</el-dropdown-item>
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
+              <el-button plain class="search-table-container--button" @click="handleExport">
+                导出
+              </el-button>
+            </section>
+          </div>
+        </header>
+
+        <div class="batch-table">
+          <BasicTable
+            ref="basicTableRef"
+            :tableData="tableData"
+            :tableConfig="tableConfig"
+            @update:pageSize="handleSizeChange"
+            @update:pageNumber="handleCurrentChange"
+          >
+            <template #action="scope">
+              <div class="action-container--div" style="justify-content: left">
+                <ActionButton text="编辑" @click="handleEdit(scope.row)" />
+                <ActionButton text="删除" @click="handleDelete(scope.row)" />
+              </div>
+            </template>
+          </BasicTable>
+        </div>
+      </div>
+    </main>
+
+    <!-- 添加/编辑先进个人对话框 -->
+    <el-dialog
+      v-model="addDialogVisible"
+      :title="`${isEditMode ? '编辑' : '新增'}先进个人 (${deptType === 'normal' ? '普通部门' : '先进集体部门'})`"
+      width="800px"
+      :close-on-click-modal="false"
+      @close="handleDialogClose"
+    >
+      <div class="add-dialog-content">
+        <div class="description">
+          <span class="description-label">说明:</span>
+          <span class="description-text">
+            评为先进集体部门,先进个人可以申报两位员工。非先进集体部门,只能申报一位员工为先进个人。
+          </span>
+        </div>
+
+        <!-- 普通部门表单 -->
+        <el-form
+          v-if="deptType === 'normal'"
+          ref="normalFormRef"
+          :model="normalForm"
+          :rules="normalFormRules"
+          label-width="140px"
+        >
+          <el-form-item label="员工工号:" prop="employeeCode">
+            <el-input
+              v-model="normalForm.employeeCode"
+              placeholder="请输入员工工号"
+              maxlength="50"
+            />
+          </el-form-item>
+          <el-form-item label="员工姓名:" prop="employeeName">
+            <el-input
+              v-model="normalForm.employeeName"
+              placeholder="请输入员工姓名"
+              maxlength="50"
+            />
+          </el-form-item>
+          <el-form-item label="员工联系方式:" prop="employeeContact">
+            <el-input
+              v-model="normalForm.employeeContact"
+              placeholder="请输入11位手机号码"
+              maxlength="11"
+              @input="handlePhoneInput"
+            />
+          </el-form-item>
+          <el-form-item label="个人先进描述:" prop="remark">
+            <el-input
+              v-model="normalForm.remark"
+              type="textarea"
+              :rows="4"
+              placeholder="请填写个人先进获取内容描述。"
+              maxlength="300"
+              show-word-limit
+            />
+          </el-form-item>
+        </el-form>
+
+        <!-- 先进集体部门表格表单 -->
+        <div v-if="deptType === 'advanced'">
+          <el-table :data="advancedFormList" border style="width: 100%">
+            <el-table-column label="编号" width="80" align="center">
+              <template #default="{ $index }">
+                {{ $index + 1 }}
+              </template>
+            </el-table-column>
+            <el-table-column label="员工工号" prop="employeeCode">
+              <template #default="{ row, $index }">
+                <el-input
+                  v-model="row.employeeCode"
+                  placeholder="请输入..."
+                  maxlength="50"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column label="员工姓名" prop="employeeName">
+              <template #default="{ row }">
+                <el-input
+                  v-model="row.employeeName"
+                  placeholder="请输入..."
+                  maxlength="50"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column label="员工联系方式" prop="employeeContact">
+              <template #default="{ row }">
+                <el-input
+                  v-model="row.employeeContact"
+                  placeholder="请输入11位手机号码"
+                  maxlength="11"
+                  @input="(val) => handlePhoneInputForTable(row, val)"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column label="个人先进描述" prop="remark">
+              <template #default="{ row }">
+                <el-input
+                  v-model="row.remark"
+                  type="textarea"
+                  :rows="2"
+                  placeholder="请输入..."
+                  maxlength="300"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="150" align="center">
+              <template #default="{ $index }">
+                <el-button
+                  type="primary"
+                  link
+                  :disabled="advancedFormList.length >= 2"
+                  @click="handleAddAdvancedRow"
+                >
+                  新增
+                </el-button>
+                <el-button
+                  type="danger"
+                  link
+                  @click="handleRemoveAdvancedRow($index)"
+                >
+                  删除
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="handleDialogClose">取消</el-button>
+          <el-button type="primary" @click="handleSave">保存</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { computed, onMounted, reactive, ref } from 'vue';
+  import { useRoute } from 'vue-router';
+  import { ElMessage, ElMessageBox } from 'element-plus';
+  import type { FormInstance, FormRules } from 'element-plus';
+  import BasicTable from '@/components/BasicTable.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import ActionButton from '@/components/ActionButton.vue';
+  import { TABLE_OPTIONS } from '../../evaluationSystem/configs/tables';
+  import { EVALUATION_ADVANCED_PERSON_TABLE_COLUMNS } from '../../evaluationSystem/configs/targetTables';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import {
+    querySecurityExamineDeptAdvancedUser,
+    querySecurityExamineIssueDeptDetail,
+    saveSecurityExamineDeptAdvUser,
+    deleteSecurityExamineDeptAdvUser,
+    querySecurityExamineDeptAdvUserDetail,
+    updateSecurityExamineDeptAdvUser,
+  } from '@/api/evaluationSystem';
+  import type {
+    QuerySecurityExamineDeptAdvancedUserParams,
+    EvaluationDeptAdvancedUserItem,
+    EvaluationTargetItem,
+    UpdateSecurityExamineDeptAdvUserRequest,
+  } from '@/api/evaluationSystem';
+
+  const route = useRoute();
+
+  // 表格
+  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+
+  const { tableConfig, pagination } = useTableConfig(EVALUATION_ADVANCED_PERSON_TABLE_COLUMNS, TABLE_OPTIONS);
+
+  const tableData = ref<any[]>([]);
+
+  const tableQuery = reactive<QueryPageRequest<any>>({
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {
+      userName: '',
+      dateRange: null as any,
+      startTime: '',
+      endTime: '',
+    },
+  });
+
+  // 从路由获取考核对象ID(psemId)
+  const evaluationId = computed(() => {
+    const id = route.query.id;
+    return id ? Number(id) : undefined;
+  });
+
+  // 考核表详情
+  const evaluationDetail = ref<Partial<EvaluationTargetItem>>({});
+  const deptId = ref<number | undefined>(undefined); // 部门ID
+  const psemId = ref<number | undefined>(undefined); // 考核表ID(从接口获取)
+  const deptUserId = ref<number | undefined>(undefined); // 部门用户ID(从接口获取)
+
+  // 添加对话框相关
+  const addDialogVisible = ref(false); // 添加对话框
+  const deptType = ref<'normal' | 'advanced'>('normal'); // 当前部门类型
+  const isEditMode = ref(false); // 是否为编辑模式
+  const currentEditRow = ref<any>(null); // 当前编辑的行数据
+  const editDetailData = ref<EvaluationDeptAdvancedUserItem | null>(null); // 编辑时的详情数据
+
+  // 普通部门表单
+  const normalFormRef = ref<FormInstance>();
+  const normalForm = reactive({
+    employeeCode: '',
+    employeeName: '',
+    employeeContact: '',
+    remark: '',
+  });
+
+  const normalFormRules: FormRules = {
+    employeeCode: [
+      { required: true, message: '请输入员工工号', trigger: 'blur' },
+    ],
+    employeeName: [
+      { required: true, message: '请输入员工姓名', trigger: 'blur' },
+    ],
+    employeeContact: [
+      { required: true, message: '请输入员工联系方式', trigger: 'blur' },
+      { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的11位手机号码', trigger: 'blur' },
+    ],
+    remark: [
+      { required: true, message: '请输入个人先进描述', trigger: 'blur' },
+      { max: 300, message: '最多输入300个字符', trigger: 'blur' },
+    ],
+  };
+
+  // 先进集体部门表单列表
+  const advancedFormList = ref<Array<{
+    employeeCode: string;
+    employeeName: string;
+    employeeContact: string;
+    remark: string;
+  }>>([
+    {
+      employeeCode: '',
+      employeeName: '',
+      employeeContact: '',
+      remark: '',
+    },
+  ]);
+
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    tableQuery.pageSize = value;
+    getTableData();
+  };
+
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    tableQuery.pageNumber = value;
+    getTableData();
+  };
+
+  async function getTableData() {
+    tableConfig.loading = true;
+    try {
+      const params: QuerySecurityExamineDeptAdvancedUserParams = {
+        pageNumber: tableQuery.pageNumber,
+        pageSize: tableQuery.pageSize,
+        queryParam: {
+          psemId: evaluationId.value, // 考核表ID(从路由参数获取)
+          userName: tableQuery.queryParam.userName || undefined,
+          planStartTime: tableQuery.queryParam.startTime || undefined,
+          planEndTime: tableQuery.queryParam.endTime || undefined,
+        },
+      };
+
+      const res = await querySecurityExamineDeptAdvancedUser(params);
+      if (res) {
+        // 映射返回数据字段到表格字段(先进个人信息)
+        tableData.value = res.records.map((item: EvaluationDeptAdvancedUserItem) => ({
+          id: item.id, // 记录ID(用于删除等操作)
+          employeeCode: item.advUserStaffNo || '-', // 员工工号
+          employeeName: item.advUserName || '-', // 员工姓名
+          employeeContact: item.advUserLink || '-', // 员工联系方式
+          deptName: item.deptName || '-', // 所属部门
+          departmentLeader: item.deptUserName || '-', // 部门负责人
+          remark: item.remark || '-', // 先进个人描述
+          reportDate: item.planEndTime || '-', // 上报日期
+          // 保留原始数据,供编辑等操作使用
+          originalData: item, // 保存列表接口返回的完整原始数据
+        }));
+        pagination.total = res.totalRow;
+      }
+    } catch (e) {
+      console.error('获取先进个人信息列表失败:', e);
+      tableData.value = [];
+      pagination.total = 0;
+    } finally {
+      tableConfig.loading = false;
+    }
+  }
+
+  function handleSearch() {
+    if (tableQuery.queryParam.dateRange && tableQuery.queryParam.dateRange.length === 2) {
+      tableQuery.queryParam.startTime = tableQuery.queryParam.dateRange[0];
+      tableQuery.queryParam.endTime = tableQuery.queryParam.dateRange[1];
+    } else {
+      tableQuery.queryParam.startTime = '';
+      tableQuery.queryParam.endTime = '';
+    }
+    pagination.pageNumber = 1;
+    tableQuery.pageNumber = 1;
+    getTableData();
+  }
+
+  const handleReset = () => {
+    tableQuery.queryParam.userName = '';
+    tableQuery.queryParam.dateRange = null;
+    handleSearch();
+  };
+
+  // 选择部门类型(从下拉菜单触发)
+  const selectDeptType = (type: 'normal' | 'advanced') => {
+    deptType.value = type;
+    addDialogVisible.value = true;
+    
+    // 如果是先进集体部门,初始化表格数据(默认1行)
+    if (type === 'advanced') {
+      advancedFormList.value = [
+        {
+          employeeCode: '',
+          employeeName: '',
+          employeeContact: '',
+          remark: '',
+        },
+      ];
+    }
+  };
+
+  // 处理电话号码输入(只允许数字)
+  const handlePhoneInput = (value: string) => {
+    // 只保留数字
+    normalForm.employeeContact = value.replace(/\D/g, '');
+  };
+
+  // 处理表格中电话号码输入(只允许数字)
+  const handlePhoneInputForTable = (row: any, value: string) => {
+    // 只保留数字
+    row.employeeContact = value.replace(/\D/g, '');
+  };
+
+  // 添加先进集体部门行
+  const handleAddAdvancedRow = () => {
+    if (advancedFormList.value.length < 2) {
+      advancedFormList.value.push({
+        employeeCode: '',
+        employeeName: '',
+        employeeContact: '',
+        remark: '',
+      });
+    }
+  };
+
+  // 删除先进集体部门行
+  const handleRemoveAdvancedRow = (index: number) => {
+    if (advancedFormList.value.length > 1) {
+      advancedFormList.value.splice(index, 1);
+    } else {
+      ElMessage.warning('至少需要保留一条记录');
+    }
+  };
+
+  // 关闭对话框
+  const handleDialogClose = () => {
+    addDialogVisible.value = false;
+    isEditMode.value = false;
+    currentEditRow.value = null;
+    editDetailData.value = null;
+    // 重置表单
+    if (normalFormRef.value) {
+      normalFormRef.value.resetFields();
+    }
+    normalForm.employeeCode = '';
+    normalForm.employeeName = '';
+    normalForm.employeeContact = '';
+    normalForm.remark = '';
+    advancedFormList.value = [
+      {
+        employeeCode: '',
+        employeeName: '',
+        employeeContact: '',
+        remark: '',
+      },
+    ];
+    deptType.value = 'normal';
+  };
+
+  // 验证普通部门表单
+  const validateNormalForm = (): Promise<boolean> => {
+    return new Promise((resolve) => {
+      if (!normalFormRef.value) {
+        resolve(false);
+        return;
+      }
+      normalFormRef.value.validate((valid) => {
+        resolve(valid);
+      });
+    });
+  };
+
+  // 验证先进集体部门表单
+  const validateAdvancedForm = (): boolean => {
+    const phonePattern = /^1[3-9]\d{9}$/;
+    for (let i = 0; i < advancedFormList.value.length; i++) {
+      const item = advancedFormList.value[i];
+      if (!item.employeeCode || !item.employeeName || !item.employeeContact || !item.remark) {
+        ElMessage.warning(`请填写第${i + 1}行的所有必填项`);
+        return false;
+      }
+      if (!phonePattern.test(item.employeeContact)) {
+        ElMessage.warning(`第${i + 1}行的员工联系方式请输入正确的11位手机号码`);
+        return false;
+      }
+      if (item.remark.length > 300) {
+        ElMessage.warning(`第${i + 1}行的个人先进描述最多输入300个字符`);
+        return false;
+      }
+    }
+    return true;
+  };
+
+  // 保存
+  const handleSave = async () => {
+    if (!psemId.value || !deptId.value) {
+      ElMessage.error('缺少必要参数');
+      return;
+    }
+
+    let users: Array<{
+      deptId: number;
+      advUserStaffNo: string;
+      advUserName: string;
+      advUserLink: string;
+      remark: string;
+    }> = [];
+
+    if (deptType.value === 'normal') {
+      // 验证普通部门表单
+      const isValid = await validateNormalForm();
+      if (!isValid) {
+        return;
+      }
+      users = [
+        {
+          deptId: deptId.value,
+          advUserStaffNo: normalForm.employeeCode,
+          advUserName: normalForm.employeeName,
+          advUserLink: normalForm.employeeContact,
+          remark: normalForm.remark,
+        },
+      ];
+    } else {
+      // 验证先进集体部门表单
+      if (!validateAdvancedForm()) {
+        return;
+      }
+      // 过滤掉空行
+      users = advancedFormList.value
+        .filter(
+          (item) =>
+            item.employeeCode || item.employeeName || item.employeeContact || item.remark,
+        )
+        .map((item) => ({
+          deptId: deptId.value!,
+          advUserStaffNo: item.employeeCode,
+          advUserName: item.employeeName,
+          advUserLink: item.employeeContact,
+          remark: item.remark,
+        }));
+    }
+
+    if (users.length === 0) {
+      ElMessage.warning('请至少填写一条记录');
+      return;
+    }
+
+    try {
+      if (isEditMode.value && currentEditRow.value?.id && editDetailData.value) {
+        // 编辑模式:调用更新接口
+        // 验证表单
+        const isValid = await validateNormalForm();
+        if (!isValid) {
+          return;
+        }
+        if (!psemId.value) {
+          ElMessage.error('缺少必要参数');
+          return;
+        }
+        // 使用详情接口返回的数据作为基础,但使用表单中修改后的字段
+        const updateParams: UpdateSecurityExamineDeptAdvUserRequest = {
+          id: currentEditRow.value.id,
+          psemId: psemId.value,
+          deptName: editDetailData.value.deptName,
+          deptId: editDetailData.value.deptId || deptId.value || 0,
+          deptUserId: editDetailData.value.deptUserId || deptUserId.value || 0,
+          deptUserName: editDetailData.value.deptUserName,
+          deptUserLink: editDetailData.value.deptUserLink,
+          // 使用表单中修改后的字段
+          advUserStaffNo: normalForm.employeeCode,
+          advUserName: normalForm.employeeName,
+          advUserLink: normalForm.employeeContact,
+          remark: normalForm.remark,
+        };
+        await updateSecurityExamineDeptAdvUser(updateParams);
+        ElMessage.success('更新成功');
+      } else {
+        // 新增模式:调用保存接口
+        await saveSecurityExamineDeptAdvUser({
+          psemId: psemId.value,
+          users,
+        });
+        ElMessage.success('保存成功');
+      }
+      handleDialogClose();
+      getTableData();
+    } catch (e) {
+      console.error('保存失败:', e);
+      ElMessage.error(isEditMode.value ? '更新失败' : '保存失败');
+    }
+  };
+
+  const handleEdit = async (row: any) => {
+    if (!row.id) {
+      ElMessage.error('缺少记录ID,无法编辑');
+      return;
+    }
+
+    try {
+      // 调用详情接口获取数据
+      const detail = await querySecurityExamineDeptAdvUserDetail(row.id);
+      
+      // 保存当前编辑的行数据和详情数据
+      currentEditRow.value = row;
+      editDetailData.value = detail;
+      isEditMode.value = true;
+      deptType.value = 'normal'; // 编辑时固定为普通部门
+      addDialogVisible.value = true;
+      
+      // 回填表单数据(普通部门)- 使用详情数据
+      normalForm.employeeCode = detail.advUserStaffNo || '';
+      normalForm.employeeName = detail.advUserName || '';
+      normalForm.employeeContact = detail.advUserLink || '';
+      normalForm.remark = detail.remark || '';
+    } catch (e) {
+      console.error('获取详情失败:', e);
+      ElMessage.error('获取详情失败');
+    }
+  };
+
+  const handleDelete = async (row: any) => {
+    try {
+      await ElMessageBox.confirm('确定要删除这条记录吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      });
+      
+      if (!row.id) {
+        ElMessage.error('缺少记录ID,无法删除');
+        return;
+      }
+      
+      await deleteSecurityExamineDeptAdvUser(row.id);
+      ElMessage.success('删除成功');
+      getTableData();
+    } catch (e: any) {
+      // 用户取消删除或删除失败
+      if (e !== 'cancel') {
+        console.error('删除失败:', e);
+        ElMessage.error('删除失败');
+      }
+    }
+  };
+
+  const handleExport = () => {
+    // TODO: 导出当前筛选结果
+    console.log('export advanced person list', tableQuery);
+  };
+
+  // 格式化日期时间
+  const formatDateTime = (dateTimeStr: string | undefined): string => {
+    if (!dateTimeStr) return '';
+    try {
+      const date = new Date(dateTimeStr);
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      const hours = String(date.getHours()).padStart(2, '0');
+      const minutes = String(date.getMinutes()).padStart(2, '0');
+      const seconds = String(date.getSeconds()).padStart(2, '0');
+      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+    } catch (e) {
+      return dateTimeStr;
+    }
+  };
+
+  // 获取考核表详情(使用考核对象ID,即psemId)
+  const getEvaluationDetail = async () => {
+    // 路由参数中的id是考核对象ID(psemId),直接使用
+    if (!evaluationId.value) return;
+    
+    try {
+      const res = await querySecurityExamineIssueDeptDetail(evaluationId.value);
+      if (res) {
+        evaluationDetail.value = {
+          exName: res.exName,
+          deptNames: res.deptName, // 部门端接口返回的是 deptName,不是 deptNames
+          createdUserName: res.deptUserName || '-', // 部门端可能没有创建人字段,使用部门用户名
+          createdAt: res.createdAt,
+        };
+        // 保存部门ID、考核表ID和部门用户ID,用于提交时使用
+        deptId.value = res.deptId;
+        psemId.value = res.psemId;
+        deptUserId.value = res.deptUserId;
+      }
+    } catch (e) {
+      console.error('获取考核表详情失败:', e);
+    }
+  };
+
+  onMounted(() => {
+    getTableData();
+    getEvaluationDetail();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+  @use '@/styles/basic-table-file.scss' as *;
+  @use '@/views/traffic/violation/style/act-search-table.scss' as *;
+
+  .safety-platform-container__header {
+    padding-bottom: 0 !important;
+  }
+
+  .evaluation-header {
+    width: 100%;
+  }
+
+  .evaluation-title {
+    font-size: 20px;
+    font-weight: bold;
+    margin: 0 0 12px 0;
+    color: #333;
+  }
+
+  .evaluation-meta {
+    margin-bottom: 12px;
+    display: flex;
+    gap: 24px;
+    font-size: 14px;
+    color: #666;
+  }
+
+  .evaluation-meta span {
+    white-space: nowrap;
+  }
+
+  .add-dialog-content {
+    .description {
+      margin-bottom: 24px;
+      padding: 12px;
+      background-color: #f5f7fa;
+      border-radius: 4px;
+
+      .description-label {
+        font-weight: bold;
+        color: #333;
+      }
+
+      .description-text {
+        color: #666;
+        line-height: 1.6;
+      }
+    }
+  }
+
+
+  .dialog-footer {
+    text-align: right;
+  }
+</style>

+ 356 - 0
src/views/production-safety/safetyAssessment/evaluationDepartment/components/EvaluationDepartmentFeedback.vue

@@ -0,0 +1,356 @@
+<template>
+  <main class="safety-platform-container__main">
+    <el-form ref="formRef" :model="ruleFormData" :rules="formRules" label-width="auto" class="evaluation-form">
+      <el-form-item label="考核信息标题:" prop="evaluationTitle">
+        <el-input v-model="ruleFormData.evaluationTitle" placeholder="请输入考核信息标题" disabled />
+      </el-form-item>
+      <el-form-item label="上传附件文档:" prop="attachmentDocument">
+        <div class="upload-files-disabled">
+          <UploadFiles label="上传附件" :file-list="ruleFormData.attachmentDocument" @uploadSuccess="handleUploadSuccess" />
+        </div>
+      </el-form-item>
+      <el-form-item label="评分说明:" prop="scoringDescription">
+        <el-input v-model="ruleFormData.scoringDescription" type="textarea" :rows="5" placeholder="请输入评分说明" disabled />
+      </el-form-item>
+    </el-form>
+
+    <div class="evaluation-items-section">
+      <div class="section-header">
+        <el-button plain @click="handleDownloadTemplate" disabled>模板下载</el-button>
+        <el-button plain @click="handleImport">导入</el-button>
+        <el-button plain @click="handleExport">导出</el-button>
+        <input
+          ref="importFileInputRef"
+          type="file"
+          accept=".xlsx,.xls"
+          style="display: none"
+          @change="handleFileChange"
+        />
+      </div>
+      <div class="evaluation-items-table">
+        <el-table :data="evaluationItems" border show-summary :summary-method="getSummaries">
+          <el-table-column label="编号" type="index" width="80" align="center" />
+          <el-table-column label="考核项目" prop="evaluationItem" min-width="150" />
+          <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">
+            <template #default="scope">
+              <el-input-number
+                v-model="scope.row.selfScore"
+                :min="0"
+                :precision="0"
+                :step="1"
+                placeholder="请输入自评得分"
+                @blur="handleScoreBlur"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="资料说明" prop="materialDescription" min-width="200" />
+        </el-table>
+      </div>
+    </div>
+  </main>
+  <footer class="safety-platform-container__footer">
+    <el-button @click="router.back()">取消</el-button>
+    <el-button type="primary" @click="handleSubmit">提交</el-button>
+  </footer>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import { useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import type { FormInstance } from 'element-plus';
+  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import {
+    querySecurityExamineIssueDeptDetail,
+    updateSecurityExamineDeptSubmit,
+    exportSecurityExamineIssueDeptDetail,
+    importSecurityExamineIssueDeptDetail,
+  } from '@/api/evaluationSystem';
+  import type { FileItem } from '@/components/UploadFiles/types';
+
+  const props = defineProps<{
+    id: number;
+  }>();
+
+  const router = useRouter();
+
+  const formRef = ref<FormInstance>();
+  const ruleFormData = ref({
+    evaluationTitle: '',
+    attachmentDocument: [] as FileItem[],
+    scoringDescription: '',
+  });
+
+  const formRules = {
+    evaluationTitle: [{ required: true, message: '请输入考核信息标题', trigger: 'blur' }],
+    attachmentDocument: [{ required: true, message: '请上传附件文档', trigger: 'change' }],
+    // scoringDescription: [{ required: true, message: '请输入评分说明', trigger: 'blur' }],
+  };
+
+  const evaluationItems = ref<any[]>([]);
+  // 保存详情原始数据,用于提交
+  const detailData = ref<any>(null);
+
+  // 计算总计
+  const getTotalScore = () => {
+    return evaluationItems.value.reduce((sum, item) => {
+      return sum + (Number(item.selfScore) || 0);
+    }, 0);
+  };
+
+  // 自评得分失去焦点时触发(用于强制更新合计行)
+  const handleScoreBlur = () => {
+    // 触发响应式更新,让合计行重新计算
+    // 通过修改数组引用来触发更新
+    evaluationItems.value = [...evaluationItems.value];
+  };
+
+  // 表格合计行方法
+  const getSummaries = (param: any) => {
+    const { columns } = param;
+    const sums: string[] = [];
+    columns.forEach((column: any, index: number) => {
+      if (index === 0) {
+        // 编号列
+        sums[index] = '';
+      } else if (column.property === 'scoreType') {
+        // 加减分项列显示"总计:"
+        sums[index] = '总计:';
+      } else if (column.property === 'selfScore') {
+        // 自评得分列显示总分,即使为0也显示
+        const total = getTotalScore();
+        sums[index] = `${total}分`;
+      } else {
+        // 其他列显示空
+        sums[index] = '';
+      }
+    });
+    return sums;
+  };
+
+  const handleValidate = async () => {
+    if (!formRef.value) return;
+    return new Promise((resolve) => {
+      formRef.value?.validate((valid: boolean) => {
+        resolve(valid);
+      });
+    });
+  };
+
+  const handleUploadSuccess = (files: any[]) => {
+    ruleFormData.value.attachmentDocument = files;
+  };
+
+  const handleDownloadTemplate = () => {
+    // TODO: 下载模板
+    console.log('download template');
+  };
+
+  // 导入文件引用
+  const importFileInputRef = ref<HTMLInputElement>();
+
+  const handleImport = () => {
+    // 触发文件选择
+    importFileInputRef.value?.click();
+  };
+
+  const handleFileChange = async (event: Event) => {
+    const target = event.target as HTMLInputElement;
+    const file = target.files?.[0];
+    if (!file) return;
+
+    try {
+      await importSecurityExamineIssueDeptDetail({
+        id: props.id,
+        file,
+      });
+      ElMessage.success('导入成功');
+      // 重新加载详情数据
+      await getDetail();
+    } catch (e: any) {
+      console.error('导入失败:', e);
+      ElMessage.error(e?.message || '导入失败,请重试');
+    } finally {
+      // 清空文件选择
+      if (target) {
+        target.value = '';
+      }
+    }
+  };
+
+  const handleExport = async () => {
+    try {
+      const blob = await exportSecurityExamineIssueDeptDetail(props.id);
+      // 创建下载链接
+      const url = window.URL.createObjectURL(blob);
+      const link = document.createElement('a');
+      link.href = url;
+      link.download = `部门考核详情_${new Date().getTime()}.xlsx`;
+      document.body.appendChild(link);
+      link.click();
+      document.body.removeChild(link);
+      window.URL.revokeObjectURL(url);
+      ElMessage.success('导出成功');
+    } catch (e: any) {
+      console.error('导出失败:', e);
+      ElMessage.error(e?.message || '导出失败,请重试');
+    }
+  };
+
+  // 将逗号分隔的 URL 字符串转换为 FileItem[] 格式
+  const parseAttachmentsToFileList = (attachmentsStr: string | undefined): FileItem[] => {
+    if (!attachmentsStr || !attachmentsStr.trim()) {
+      return [];
+    }
+
+    // 按逗号分割URL
+    const urls = attachmentsStr.split(',').map(url => url.trim()).filter(url => url);
+
+    return urls.map((url, index) => {
+      // 从URL中提取文件名
+      const urlParts = url.split('/');
+      const fileName = urlParts[urlParts.length - 1] || `文件${index + 1}`;
+
+      // 根据文件扩展名判断文件类型
+      const extension = fileName.split('.').pop()?.toLowerCase() || '';
+      let fileType = 'pdf';
+      if (extension === 'doc' || extension === 'docx') {
+        fileType = 'word';
+      } else if (extension === 'xls' || extension === 'xlsx') {
+        fileType = 'excel';
+      } else if (extension === 'ppt' || extension === 'pptx') {
+        fileType = 'ppt';
+      }
+
+      return {
+        fileId: index + 1,
+        fileName,
+        fileType,
+        fileSize: '0', // 接口未返回文件大小,使用默认值
+        fileUrl: url,
+      };
+    });
+  };
+
+  const getDetail = async () => {
+    try {
+      const detail = await querySecurityExamineIssueDeptDetail(props.id);
+      if (!detail) return;
+
+      // 保存原始详情数据,用于提交
+      detailData.value = detail;
+
+      // 映射表单字段
+      ruleFormData.value.evaluationTitle = detail.exName || ''; // 考核表名称
+      ruleFormData.value.attachmentDocument = parseAttachmentsToFileList(detail.attachments); // 附件文档
+      ruleFormData.value.scoringDescription = ''; // 评分说明(接口暂无此字段,留空)
+
+      // 映射考核项目列表(scores 数组)
+      if (detail.scores && detail.scores.length > 0) {
+        evaluationItems.value = detail.scores.map((score) => ({
+          id: score.id, // 保留评分项ID,用于提交
+          isAdd: score.isAdd !== undefined ? score.isAdd : (score.selfScore >= 0 ? 1 : 0), // 是否加分项(0-否,1-是)
+          isAddName: score.isAdd === 1 ? '加分项' : '减分项', // 加减分项名称
+          scoreType: score.isAdd === 1 ? '加分项' : '减分项', // 加减分项(用于显示,兼容旧字段)
+          evaluationItem: score.exProgram || '', // 考核项目
+          evaluationContent: score.exContent || '', // 考核内容
+          scoringMethod: score.scoringWay || '', // 评分方式
+          selfScore: score.selfScore || 0, // 自评得分
+          materialDescription: score.attachments || '', // 资料说明(使用附件字段)
+        }));
+      } else {
+        evaluationItems.value = [];
+      }
+    } catch (e) {
+      console.error('获取部门考核详情失败:', e);
+      ElMessage.error('获取详情失败,请重试');
+    }
+  };
+
+  const handleSubmit = async () => {
+    const res = await handleValidate();
+    if (!res) return;
+    try {
+      if (!detailData.value) {
+        ElMessage.error('数据加载失败,请刷新后重试');
+        return;
+      }
+
+      // 使用详情原始数据,更新自评得分和isAdd字段
+      const submitData = {
+        ...detailData.value,
+        scores: detailData.value.scores?.map((score: any) => {
+          // 找到对应的自评得分和isAdd
+          const item = evaluationItems.value.find((item) => item.id === score.id);
+          return {
+            ...score,
+            selfScore: item ? Number(item.selfScore) || 0 : score.selfScore || 0,
+            isAdd: item ? (item.isAdd !== undefined ? item.isAdd : (item.selfScore >= 0 ? 1 : 0)) : (score.isAdd !== undefined ? score.isAdd : (score.selfScore >= 0 ? 1 : 0)),
+          };
+        }) || [],
+      };
+
+      await updateSecurityExamineDeptSubmit(submitData);
+      ElMessage.success('提交成功');
+      router.back();
+    } catch (e: any) {
+      console.error('提交失败:', e);
+      ElMessage.error(e?.message || '提交失败,请重试');
+    }
+  };
+
+  onMounted(() => {
+    getDetail();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+
+  .evaluation-form {
+    display: flex;
+    flex-direction: column;
+    width: 600px;
+    gap: 32px;
+
+    :deep(.el-form-item) {
+      margin-bottom: 0;
+    }
+
+    :deep(.el-form-item__label) {
+      padding: 0;
+    }
+  }
+
+  .evaluation-items-section {
+    margin-top: 32px;
+  }
+
+  .section-header {
+    display: flex;
+    gap: 10px;
+    margin-bottom: 20px;
+  }
+
+  .evaluation-items-table {
+    width: 100%;
+  }
+
+  .upload-files-disabled {
+    pointer-events: none;
+    opacity: 0.6;
+
+    :deep(.upload-button) {
+      cursor: not-allowed;
+      background-color: #f5f5f5;
+      color: #aaa;
+    }
+
+    :deep(.delete-button) {
+      display: none;
+    }
+  }
+</style>

+ 19 - 0
src/views/production-safety/safetyAssessment/evaluationDepartment/configs/status.ts

@@ -0,0 +1,19 @@
+// 安全考核管理(部门)状态配置 - 与安全考核管理(管)一致
+export const EVALUATION_DEPARTMENT_STATUS_OPTIONS = [
+  { label: '全部', value: '' },
+  { label: '未下发', value: 'NOT_ISSUED' },
+  { label: '签署中', value: 'SIGNING' },
+  { label: '待签署', value: 'PENDING_SIGN' },
+  { label: '待反馈材料', value: 'PENDING_FEEDBACK' },
+  { label: '已完成', value: 'COMPLETED' },
+  { label: '已作废', value: 'CANCELLED' },
+];
+
+export const EVALUATION_DEPARTMENT_STATUS_LABEL: Record<string, string> = {
+  NOT_ISSUED: '未下发',
+  SIGNING: '签署中',
+  PENDING_SIGN: '待签署',
+  PENDING_FEEDBACK: '待反馈材料',
+  COMPLETED: '已完成',
+  CANCELLED: '已作废',
+};

+ 58 - 0
src/views/production-safety/safetyAssessment/evaluationDepartment/configs/tables.ts

@@ -0,0 +1,58 @@
+import type { TableColumnProps } from '@/types/basic-table';
+
+// 基础表格样式配置
+export const TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  loading: true,
+  maxHeight: 'calc(70vh - 150px)',
+};
+
+export const EVALUATION_DEPARTMENT_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    prop: 'select',
+    label: '',
+    type: 'selection',
+    align: 'center',
+    width: '80px',
+  },
+  {
+    label: '编号',
+    type: 'index',
+    align: 'center',
+    width: '80px',
+  },
+  {
+    label: '考核表名称',
+    prop: 'evaluationTableName',
+    align: 'left',
+    minWidth: '150px',
+  },
+  {
+    label: '状态',
+    prop: 'status',
+    slot: 'status',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '考核文档',
+    prop: 'evaluationDocument',
+    slot: 'evaluationDocument',
+    align: 'left',
+    minWidth: '150px',
+  },
+  {
+    label: '计划完成时间',
+    prop: 'plannedCompletionTime',
+    align: 'left',
+    minWidth: '150px',
+  },
+  {
+    label: '操作',
+    prop: 'action',
+    slot: 'action',
+    fixed: 'right',
+    width: '200px',
+    align: 'center',
+  },
+];

+ 7 - 0
src/views/production-safety/safetyAssessment/evaluationDepartment/constants.ts

@@ -0,0 +1,7 @@
+// 安全考核管理(部门) tabs 配置
+export const EVALUATION_DEPARTMENT_TABS = [
+  { label: '全部', value: 'all' },
+  { label: '待反馈', value: 'pending_feedback' },
+  { label: '已完成', value: 'completed' },
+  { label: '已作废', value: 'cancelled' },
+];

+ 317 - 0
src/views/production-safety/safetyAssessment/evaluationDepartment/evaluationDepartment.vue

@@ -0,0 +1,317 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="breadcrumb-title"> 安全考核管理(部门) </div>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header>
+          <div class="act-search">
+            <section class="select-box">
+              <div class="select-box--item">
+                <span>考核表名称:</span>
+                <el-input
+                  v-model="tableQuery.queryParam.evaluationTableName"
+                  placeholder="搜索考核表名称"
+                  class="act-search-input"
+                />
+              </div>
+              <div class="select-box--item">
+                <span>状态:</span>
+                <el-select
+                  v-model="tableQuery.queryParam.status"
+                  placeholder="请选择状态"
+                  clearable
+                >
+                  <el-option
+                    v-for="item in EVALUATION_SYSTEM_STATUS_OPTIONS"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </el-select>
+              </div>
+              <div>
+                <span>计划日期范围:</span>
+                <el-date-picker
+                  v-model="tableQuery.queryParam.dateRange"
+                  type="daterange"
+                  range-separator="至"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                  value-format="YYYY-MM-DD"
+                  format="YYYY-MM-DD"
+                />
+              </div>
+            </section>
+            <section class="search-btn">
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+            </section>
+          </div>
+        </header>
+
+        <div class="batch-table">
+          <BasicTable
+            ref="basicTableRef"
+            :tableData="tableData"
+            :tableConfig="tableConfig"
+            @update:pageSize="handleSizeChange"
+            @update:pageNumber="handleCurrentChange"
+            @update:selection="handleSelectionChange"
+          >
+            <template #status="scope">
+              <span>
+                {{ EVALUATION_SYSTEM_STATUS_LABEL[String(scope.row.status)] || scope.row.statusName || '-' }}
+              </span>
+            </template>
+            <template #evaluationDocument="scope">
+              <div
+                class="file-container--div"
+                v-for="item in parseAttachments(scope.row.evaluationDocument)"
+                :key="item.fileUrl"
+              >
+                <img
+                  class="file-container--div__icon"
+                  @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
+                  :src="FILE_TYPE_ICON[item.fileType]"
+                />
+                <span
+                  class="file-container--div__name"
+                  @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
+                  >{{ item.fileName }}</span
+                >
+                <img
+                  class="file-container--div__download"
+                  :src="DownloadIcon"
+                  @click="downloadFile(item.fileUrl, item.fileName)"
+                />
+              </div>
+            </template>
+            <template #action="scope">
+              <div class="action-container--div" style="justify-content: left">
+                <!-- 待反馈(2):显示反馈 -->
+                <template v-if="Number(scope.row.status) === 2">
+                  <ActionButton text="反馈" @click="handleFeedback(scope.row.id)" />
+                </template>
+                <!-- 已完成(1):显示反馈和先进个人申报 -->
+                <template v-else-if="Number(scope.row.status) === 1">
+                  <ActionButton text="反馈" @click="handleFeedback(scope.row.id)" />
+                  <ActionButton text="先进个人申报" @click="handleAdvancedPerson(scope.row.id)" />
+                </template>
+                <!-- 已作废(5):显示反馈 -->
+                <template v-else-if="Number(scope.row.status) === 5">
+                  <ActionButton text="反馈" @click="handleFeedback(scope.row.id)" />
+                </template>
+              </div>
+            </template>
+          </BasicTable>
+        </div>
+      </div>
+    </main>
+    <PreviewOnline ref="previewOnlineRef" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { onMounted, reactive, ref } from 'vue';
+  import BasicTable from '@/components/BasicTable.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import ActionButton from '@/components/ActionButton.vue';
+  import { TABLE_OPTIONS, EVALUATION_DEPARTMENT_TABLE_COLUMNS } from './configs/tables';
+  import { EVALUATION_SYSTEM_STATUS_OPTIONS, EVALUATION_SYSTEM_STATUS_LABEL } from '../evaluationSystem/configs/status';
+  import { useRouter } from 'vue-router';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import DownloadIcon from '@/views/disaster/disaster-control/src/svg/download.svg';
+  import { downloadFile } from '@/views/disaster/utils';
+  import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
+  import { FILE_TYPE_ICON } from '@/components/UploadFiles/constants';
+  import { querySecurityExamineDept } from '@/api/evaluationSystem';
+  import type { QuerySecurityExamineDeptParams } from '@/api/evaluationSystem';
+
+  const router = useRouter();
+
+  // 表格
+  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+
+  const { tableConfig, pagination } = useTableConfig(EVALUATION_DEPARTMENT_TABLE_COLUMNS, TABLE_OPTIONS);
+
+  const tableData = ref<any[]>([]);
+
+  const tableQuery = reactive<QueryPageRequest<any>>({
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {
+      evaluationTableName: '',
+      status: undefined as number | undefined,
+      dateRange: null,
+      startTime: '',
+      endTime: '',
+    },
+  });
+
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    tableQuery.pageSize = value;
+    getTableData();
+  };
+
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    tableQuery.pageNumber = value;
+    getTableData();
+  };
+
+  const selectionItems = ref<any[]>([]);
+
+  const handleSelectionChange = (selection: any[]) => {
+    selectionItems.value = selection;
+  };
+
+  async function getTableData() {
+    tableConfig.loading = true;
+    try {
+      const params: QuerySecurityExamineDeptParams = {
+        pageNumber: tableQuery.pageNumber,
+        pageSize: tableQuery.pageSize,
+        queryParam: {
+          exName: tableQuery.queryParam.evaluationTableName || undefined,
+          status: tableQuery.queryParam.status !== undefined && tableQuery.queryParam.status !== null
+            ? tableQuery.queryParam.status
+            : undefined,
+          deptName: undefined, // 部门名称(如果需要可以添加搜索条件)
+          planStartTime: tableQuery.queryParam.startTime || undefined,
+          planEndTime: tableQuery.queryParam.endTime || undefined,
+        },
+      };
+
+      const res = await querySecurityExamineDept(params);
+      if (res) {
+        // 映射返回数据字段到表格字段
+        tableData.value = res.records.map((item) => ({
+          id: item.id,
+          evaluationTableName: item.exName, // 考核表名称
+          status: item.status, // 状态(使用数字状态码)
+          statusName: item.statusName, // 状态名称
+          evaluationDocument: item.attachments, // 考核文档
+          plannedCompletionTime: item.planEndTime || '-', // 计划完成时间(使用计划结束时间)
+          // 保留原始数据,供其他操作使用
+          psemId: item.psemId,
+          deptId: item.deptId,
+          deptName: item.deptName,
+          scores: item.scores,
+          scoreRank: item.scoreRank,
+        }));
+        pagination.total = res.totalRow;
+      }
+    } catch (e) {
+      console.error('获取部门考核列表失败:', e);
+      tableData.value = [];
+      pagination.total = 0;
+    } finally {
+      tableConfig.loading = false;
+    }
+  }
+
+  const handleSearch = () => {
+    // 处理日期范围
+    if (tableQuery.queryParam.dateRange && tableQuery.queryParam.dateRange.length === 2) {
+      tableQuery.queryParam.startTime = tableQuery.queryParam.dateRange[0];
+      tableQuery.queryParam.endTime = tableQuery.queryParam.dateRange[1];
+    } else {
+      tableQuery.queryParam.startTime = '';
+      tableQuery.queryParam.endTime = '';
+    }
+    pagination.pageNumber = 1;
+    tableQuery.pageNumber = 1;
+    getTableData();
+  };
+
+  const handleReset = () => {
+    tableQuery.queryParam.evaluationTableName = '';
+    tableQuery.queryParam.status = undefined;
+    tableQuery.queryParam.dateRange = null;
+    handleSearch();
+  };
+
+  const handleFeedback = (id: number) => {
+    router.push({
+      name: 'EvaluationDepartmentItem',
+      query: {
+        id,
+        operate: 'evaluationDepartment-feedback',
+      },
+    });
+  };
+
+  const handleAdvancedPerson = (id: number) => {
+    router.push({
+      name: 'EvaluationDepartmentItem',
+      query: {
+        id,
+        operate: 'evaluationDepartment-advanced-person',
+      },
+    });
+  };
+
+  // 预览
+  const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
+  const previewOnline = (url: string | undefined, type: keyof typeof FILE_TYPE_ICON) => {
+    if (url) {
+      previewOnlineRef.value?.open(url, type);
+    }
+  };
+
+  // 解析逗号分隔的URL字符串为文件列表
+  const parseAttachments = (attachmentsStr: string | undefined): Array<{
+    fileUrl: string;
+    fileName: string;
+    fileType: string;
+  }> => {
+    if (!attachmentsStr || !attachmentsStr.trim()) {
+      return [];
+    }
+
+    // 按逗号分割URL
+    const urls = attachmentsStr.split(',').map(url => url.trim()).filter(url => url);
+
+    return urls.map((url) => {
+      // 从URL中提取文件名
+      const urlParts = url.split('/');
+      const fileName = urlParts[urlParts.length - 1] || '未知文件';
+
+      // 根据文件扩展名判断文件类型
+      const extension = fileName.split('.').pop()?.toLowerCase() || '';
+      let fileType = 'pdf';
+      if (extension === 'doc' || extension === 'docx') {
+        fileType = 'word';
+      } else if (extension === 'xls' || extension === 'xlsx') {
+        fileType = 'excel';
+      } else if (extension === 'ppt' || extension === 'pptx') {
+        fileType = 'ppt';
+      }
+
+      return {
+        fileUrl: url,
+        fileName,
+        fileType,
+      };
+    });
+  };
+
+  onMounted(() => {
+    getTableData();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+  @use '@/styles/basic-table-file.scss' as *;
+  @use '@/views/traffic/violation/style/act-search-table.scss' as *;
+
+  .safety-platform-container__header {
+    padding-bottom: 0 !important;
+  }
+</style>

+ 75 - 0
src/views/production-safety/safetyAssessment/evaluationSystem/EvaluationSystemItem.vue

@@ -0,0 +1,75 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ headerTitle }}</span>
+    </header>
+    <component :is="dynamicComponent" :id="id" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed, defineAsyncComponent } from 'vue';
+  import { useRoute } from 'vue-router';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+
+  const route = useRoute();
+  const operate = computed(() => route.query.operate as string);
+  const id = computed(() => Number(route.query.id));
+
+  const headerTitle = computed(() => {
+    switch (operate.value) {
+      case 'evaluationSystem-create':
+        return '新增安全考核';
+      case 'evaluationSystem-edit':
+        return '编辑安全考核';
+      case 'evaluationSystem-view':
+        return '查看安全考核';
+      case 'evaluationSystem-target':
+        return '考核对象';
+      case 'evaluationSystem-advanced-group':
+        return '部门考核';
+      case 'evaluationSystem-advanced-person':
+        return '先进个人';
+      case 'evaluationSystem-feedback':
+        return '评分';
+      case 'evaluationSystem-feedback-view':
+        return '部门考核详情';
+      case 'evaluationSystem-audit':
+        return '审核';
+      default:
+        return '未知操作';
+    }
+  });
+
+  const dynamicComponent = computed(() => {
+    switch (operate.value) {
+      case 'evaluationSystem-create':
+      case 'evaluationSystem-edit':
+      case 'evaluationSystem-view':
+        return defineAsyncComponent(() => import('./components/EvaluationSystemDetail.vue'));
+      case 'evaluationSystem-target':
+      case 'evaluationSystem-advanced-group':
+        return defineAsyncComponent(() => import('./components/EvaluationTarget.vue'));
+      case 'evaluationSystem-advanced-person':
+        return defineAsyncComponent(() => import('./components/EvaluationSystemAdvancedPerson.vue'));
+      case 'evaluationSystem-feedback':
+      case 'evaluationSystem-feedback-view':
+      case 'evaluationSystem-audit':
+        return defineAsyncComponent(() => import('./components/EvaluationSystemFeedback.vue'));
+      default:
+        return '';
+    }
+  });
+</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>

+ 812 - 0
src/views/production-safety/safetyAssessment/evaluationSystem/components/EvaluationSystemAdvancedPerson.vue

@@ -0,0 +1,812 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="evaluation-header">
+        <h1 class="evaluation-title">{{ evaluationDetail.exName || '先进个人信息' }}</h1>
+        <div class="evaluation-meta">
+          <span>考核部门: {{ evaluationDetail.deptNames || '-' }}</span>
+          <span>创建人: {{ evaluationDetail.createdUserName || '-' }}</span>
+          <span>创建时间: {{ formatDateTime(evaluationDetail.createdAt) || '-' }}</span>
+        </div>
+      </div>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header>
+          <div class="act-search">
+            <section class="select-box">
+              <div class="select-box--item">
+                <span>部门:</span>
+                <el-cascader
+                  ref="deptCascaderRef"
+                  v-model="deptId"
+                  :options="deptTree"
+                  :props="cascaderDeptProp"
+                  :show-all-levels="false"
+                  placeholder="请选择部门"
+                  filterable
+                  clearable
+                  @change="handleDeptChange"
+                />
+              </div>
+              <div class="select-box--item">
+                <span>员工工号/名称:</span>
+                <el-input
+                  v-model="tableQuery.queryParam.userName"
+                  placeholder="搜索员工工号或名称"
+                  class="act-search-input"
+                />
+              </div>
+              <div class="select-box--item">
+                <span>时间范围:</span>
+                <el-date-picker
+                  v-model="tableQuery.queryParam.dateRange"
+                  type="daterange"
+                  range-separator="至"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                  value-format="YYYY-MM-DD"
+                  format="YYYY-MM-DD"
+                />
+              </div>
+            </section>
+            <section class="search-btn">
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+              <el-dropdown trigger="click" @command="selectDeptType">
+                <el-button type="primary" style="margin:0 10px;">
+                  添加
+                </el-button>
+                <template #dropdown>
+                  <el-dropdown-menu>
+                    <el-dropdown-item command="normal">普通部门</el-dropdown-item>
+                    <el-dropdown-item command="advanced">先进集体部门</el-dropdown-item>
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
+              <el-button plain class="search-table-container--button" @click="handleExport">
+                导出
+              </el-button>
+            </section>
+          </div>
+        </header>
+
+        <div class="batch-table">
+          <BasicTable
+            ref="basicTableRef"
+            :tableData="tableData"
+            :tableConfig="tableConfig"
+            @update:pageSize="handleSizeChange"
+            @update:pageNumber="handleCurrentChange"
+          >
+            <template #action="scope">
+              <div class="action-container--div" style="justify-content: left">
+                <ActionButton text="编辑" @click="handleEdit(scope.row)" />
+                <ActionButton text="删除" @click="handleDelete(scope.row)" />
+              </div>
+            </template>
+          </BasicTable>
+        </div>
+      </div>
+    </main>
+
+    <!-- 添加/编辑先进个人对话框 -->
+    <el-dialog
+      v-model="addDialogVisible"
+      :title="`${isEditMode ? '编辑' : '新增'}先进个人 (${deptType === 'normal' ? '普通部门' : '先进集体部门'})`"
+      width="800px"
+      :close-on-click-modal="false"
+      @close="handleDialogClose"
+    >
+      <div class="add-dialog-content">
+        <div class="description">
+          <span class="description-label">说明:</span>
+          <span class="description-text">
+            评为先进集体部门,先进个人可以申报两位员工。非先进集体部门,只能申报一位员工为先进个人。
+          </span>
+        </div>
+
+        <!-- 普通部门表单 -->
+        <el-form
+          v-if="deptType === 'normal'"
+          ref="normalFormRef"
+          :model="normalForm"
+          :rules="normalFormRules"
+          label-width="140px"
+        >
+          <el-form-item label="员工工号:" prop="employeeCode">
+            <el-input
+              v-model="normalForm.employeeCode"
+              placeholder="请输入员工工号"
+              maxlength="50"
+            />
+          </el-form-item>
+          <el-form-item label="员工姓名:" prop="employeeName">
+            <el-input
+              v-model="normalForm.employeeName"
+              placeholder="请输入员工姓名"
+              maxlength="50"
+            />
+          </el-form-item>
+          <el-form-item label="员工联系方式:" prop="employeeContact">
+            <el-input
+              v-model="normalForm.employeeContact"
+              placeholder="请输入11位手机号码"
+              maxlength="11"
+              @input="handlePhoneInput"
+            />
+          </el-form-item>
+          <el-form-item label="个人先进描述:" prop="remark">
+            <el-input
+              v-model="normalForm.remark"
+              type="textarea"
+              :rows="4"
+              placeholder="请填写个人先进获取内容描述。"
+              maxlength="300"
+              show-word-limit
+            />
+          </el-form-item>
+        </el-form>
+
+        <!-- 先进集体部门表格表单 -->
+        <div v-if="deptType === 'advanced'">
+          <el-table :data="advancedFormList" border style="width: 100%">
+            <el-table-column label="编号" width="80" align="center">
+              <template #default="{ $index }">
+                {{ $index + 1 }}
+              </template>
+            </el-table-column>
+            <el-table-column label="员工工号" prop="employeeCode">
+              <template #default="{ row, $index }">
+                <el-input
+                  v-model="row.employeeCode"
+                  placeholder="请输入..."
+                  maxlength="50"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column label="员工姓名" prop="employeeName">
+              <template #default="{ row }">
+                <el-input
+                  v-model="row.employeeName"
+                  placeholder="请输入..."
+                  maxlength="50"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column label="员工联系方式" prop="employeeContact">
+              <template #default="{ row }">
+                <el-input
+                  v-model="row.employeeContact"
+                  placeholder="请输入11位手机号码"
+                  maxlength="11"
+                  @input="(val) => handlePhoneInputForTable(row, val)"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column label="个人先进描述" prop="remark">
+              <template #default="{ row }">
+                <el-input
+                  v-model="row.remark"
+                  type="textarea"
+                  :rows="2"
+                  placeholder="请输入..."
+                  maxlength="300"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="150" align="center">
+              <template #default="{ $index }">
+                <el-button
+                  type="primary"
+                  link
+                  :disabled="advancedFormList.length >= 2"
+                  @click="handleAddAdvancedRow"
+                >
+                  新增
+                </el-button>
+                <el-button
+                  type="danger"
+                  link
+                  @click="handleRemoveAdvancedRow($index)"
+                >
+                  删除
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="handleDialogClose">取消</el-button>
+          <el-button type="primary" @click="handleSave">保存</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { computed, onMounted, reactive, ref } from 'vue';
+  import { useRoute } from 'vue-router';
+  import { ElMessage, ElMessageBox } from 'element-plus';
+  import type { FormInstance, FormRules } from 'element-plus';
+  import BasicTable from '@/components/BasicTable.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import ActionButton from '@/components/ActionButton.vue';
+  import { TABLE_OPTIONS } from '../../evaluationSystem/configs/tables';
+  import { EVALUATION_ADVANCED_PERSON_TABLE_COLUMNS } from '../../evaluationSystem/configs/targetTables';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import {
+    querySecurityExamineIssueAdvancedUser,
+    querySecurityExamineDetail,
+    saveSecurityExamineDeptAdvUser,
+    deleteSecurityExamineDeptAdvUser,
+    querySecurityExamineDeptAdvUserDetail,
+    updateSecurityExamineDeptAdvUser,
+  } from '@/api/evaluationSystem';
+  import type {
+    QuerySecurityExamineIssueAdvancedUserParams,
+    EvaluationDeptAdvancedUserItem,
+    EvaluationSystemItem,
+    UpdateSecurityExamineDeptAdvUserRequest,
+  } from '@/api/evaluationSystem';
+  import { getAllDepartments } from '@/api/auth/dept';
+  import type { DeptTree } from '@/types/dept/type';
+
+  const route = useRoute();
+
+  // 表格
+  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+
+  const { tableConfig, pagination } = useTableConfig(EVALUATION_ADVANCED_PERSON_TABLE_COLUMNS, TABLE_OPTIONS);
+
+  const tableData = ref<any[]>([]);
+
+  const tableQuery = reactive<QueryPageRequest<any>>({
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {
+      deptName: '',
+      userName: '',
+      dateRange: null as any,
+      startTime: '',
+      endTime: '',
+    },
+  });
+
+  // 部门树(复用物品领取记录的部门下拉框逻辑)
+  const deptCascaderRef = ref();
+  const deptTree = ref<DeptTree[]>([]);
+  const deptId = ref<number | null>(null);
+  const cascaderDeptProp = {
+    checkStrictly: true,
+    expandTrigger: 'hover' as const,
+    value: 'id',
+    label: 'deptName',
+    emitPath: false,
+  };
+
+  const getDeptTreeData = async () => {
+    try {
+      const res = await getAllDepartments();
+      deptTree.value = res?.[0]?.children ?? [];
+    } catch (e) {
+      console.error('获取部门树失败:', e);
+    }
+  };
+
+  const handleDeptChange = () => {
+    const nodes = deptCascaderRef.value?.getCheckedNodes?.();
+    tableQuery.queryParam.deptName = nodes?.[0]?.label ?? '';
+  };
+
+  // 从路由获取考核对象ID(psemId)
+  const evaluationId = computed(() => {
+    const id = route.query.id;
+    return id ? Number(id) : undefined;
+  });
+
+  // 考核表详情
+  const evaluationDetail = ref<Partial<EvaluationSystemItem>>({});
+  const psemId = ref<number | undefined>(undefined); // 考核表ID(从接口获取)
+  const deptUserId = ref<number | undefined>(undefined); // 部门用户ID(从接口获取)
+  const currentDeptId = ref<number | undefined>(undefined); // 当前部门ID(用于保存操作)
+
+  // 添加对话框相关
+  const addDialogVisible = ref(false); // 添加对话框
+  const deptType = ref<'normal' | 'advanced'>('normal'); // 当前部门类型
+  const isEditMode = ref(false); // 是否为编辑模式
+  const currentEditRow = ref<any>(null); // 当前编辑的行数据
+  const editDetailData = ref<EvaluationDeptAdvancedUserItem | null>(null); // 编辑时的详情数据
+
+  // 普通部门表单
+  const normalFormRef = ref<FormInstance>();
+  const normalForm = reactive({
+    employeeCode: '',
+    employeeName: '',
+    employeeContact: '',
+    remark: '',
+  });
+
+  const normalFormRules: FormRules = {
+    employeeCode: [
+      { required: true, message: '请输入员工工号', trigger: 'blur' },
+    ],
+    employeeName: [
+      { required: true, message: '请输入员工姓名', trigger: 'blur' },
+    ],
+    employeeContact: [
+      { required: true, message: '请输入员工联系方式', trigger: 'blur' },
+      { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的11位手机号码', trigger: 'blur' },
+    ],
+    remark: [
+      { required: true, message: '请输入个人先进描述', trigger: 'blur' },
+      { max: 300, message: '最多输入300个字符', trigger: 'blur' },
+    ],
+  };
+
+  // 先进集体部门表单列表
+  const advancedFormList = ref<Array<{
+    employeeCode: string;
+    employeeName: string;
+    employeeContact: string;
+    remark: string;
+  }>>([
+    {
+      employeeCode: '',
+      employeeName: '',
+      employeeContact: '',
+      remark: '',
+    },
+  ]);
+
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    tableQuery.pageSize = value;
+    getTableData();
+  };
+
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    tableQuery.pageNumber = value;
+    getTableData();
+  };
+
+  async function getTableData() {
+    tableConfig.loading = true;
+    try {
+      const params: QuerySecurityExamineIssueAdvancedUserParams = {
+        pageNumber: tableQuery.pageNumber,
+        pageSize: tableQuery.pageSize,
+        queryParam: {
+          psemId: evaluationId.value, // 考核表ID(从路由参数获取)
+          deptName: tableQuery.queryParam.deptName || undefined,
+          userName: tableQuery.queryParam.userName || undefined,
+          planStartTime: tableQuery.queryParam.startTime || undefined,
+          planEndTime: tableQuery.queryParam.endTime || undefined,
+        },
+      };
+
+      const res = await querySecurityExamineIssueAdvancedUser(params);
+      if (res) {
+        // 映射返回数据字段到表格字段(先进个人信息)
+        tableData.value = res.records.map((item: EvaluationDeptAdvancedUserItem) => ({
+          id: item.id, // 记录ID(用于删除等操作)
+          employeeCode: item.advUserStaffNo || '-', // 员工工号
+          employeeName: item.advUserName || '-', // 员工姓名
+          employeeContact: item.advUserLink || '-', // 员工联系方式
+          deptName: item.deptName || '-', // 所属部门
+          departmentLeader: item.deptUserName || '-', // 部门负责人
+          remark: item.remark || '-', // 先进个人描述
+          reportDate: item.planEndTime || '-', // 上报日期
+          // 保留原始数据,供编辑等操作使用
+          originalData: item, // 保存列表接口返回的完整原始数据
+        }));
+        pagination.total = res.totalRow;
+      }
+    } catch (e) {
+      console.error('获取先进个人信息列表失败:', e);
+      tableData.value = [];
+      pagination.total = 0;
+    } finally {
+      tableConfig.loading = false;
+    }
+  }
+
+  function handleSearch() {
+    if (tableQuery.queryParam.dateRange && tableQuery.queryParam.dateRange.length === 2) {
+      tableQuery.queryParam.startTime = tableQuery.queryParam.dateRange[0];
+      tableQuery.queryParam.endTime = tableQuery.queryParam.dateRange[1];
+    } else {
+      tableQuery.queryParam.startTime = '';
+      tableQuery.queryParam.endTime = '';
+    }
+    pagination.pageNumber = 1;
+    tableQuery.pageNumber = 1;
+    getTableData();
+  }
+
+  const handleReset = () => {
+    tableQuery.queryParam.deptName = '';
+    tableQuery.queryParam.userName = '';
+    tableQuery.queryParam.dateRange = null;
+    deptId.value = null;
+    handleSearch();
+  };
+
+  // 选择部门类型(从下拉菜单触发)
+  const selectDeptType = (type: 'normal' | 'advanced') => {
+    deptType.value = type;
+    addDialogVisible.value = true;
+    
+    // 如果是先进集体部门,初始化表格数据(默认1行)
+    if (type === 'advanced') {
+      advancedFormList.value = [
+        {
+          employeeCode: '',
+          employeeName: '',
+          employeeContact: '',
+          remark: '',
+        },
+      ];
+    }
+  };
+
+  // 处理电话号码输入(只允许数字)
+  const handlePhoneInput = (value: string) => {
+    // 只保留数字
+    normalForm.employeeContact = value.replace(/\D/g, '');
+  };
+
+  // 处理表格中电话号码输入(只允许数字)
+  const handlePhoneInputForTable = (row: any, value: string) => {
+    // 只保留数字
+    row.employeeContact = value.replace(/\D/g, '');
+  };
+
+  // 添加先进集体部门行
+  const handleAddAdvancedRow = () => {
+    if (advancedFormList.value.length < 2) {
+      advancedFormList.value.push({
+        employeeCode: '',
+        employeeName: '',
+        employeeContact: '',
+        remark: '',
+      });
+    }
+  };
+
+  // 删除先进集体部门行
+  const handleRemoveAdvancedRow = (index: number) => {
+    if (advancedFormList.value.length > 1) {
+      advancedFormList.value.splice(index, 1);
+    } else {
+      ElMessage.warning('至少需要保留一条记录');
+    }
+  };
+
+  // 关闭对话框
+  const handleDialogClose = () => {
+    addDialogVisible.value = false;
+    isEditMode.value = false;
+    currentEditRow.value = null;
+    editDetailData.value = null;
+    // 重置表单
+    if (normalFormRef.value) {
+      normalFormRef.value.resetFields();
+    }
+    normalForm.employeeCode = '';
+    normalForm.employeeName = '';
+    normalForm.employeeContact = '';
+    normalForm.remark = '';
+    advancedFormList.value = [
+      {
+        employeeCode: '',
+        employeeName: '',
+        employeeContact: '',
+        remark: '',
+      },
+    ];
+    deptType.value = 'normal';
+  };
+
+  // 验证普通部门表单
+  const validateNormalForm = (): Promise<boolean> => {
+    return new Promise((resolve) => {
+      if (!normalFormRef.value) {
+        resolve(false);
+        return;
+      }
+      normalFormRef.value.validate((valid) => {
+        resolve(valid);
+      });
+    });
+  };
+
+  // 验证先进集体部门表单
+  const validateAdvancedForm = (): boolean => {
+    const phonePattern = /^1[3-9]\d{9}$/;
+    for (let i = 0; i < advancedFormList.value.length; i++) {
+      const item = advancedFormList.value[i];
+      if (!item.employeeCode || !item.employeeName || !item.employeeContact || !item.remark) {
+        ElMessage.warning(`请填写第${i + 1}行的所有必填项`);
+        return false;
+      }
+      if (!phonePattern.test(item.employeeContact)) {
+        ElMessage.warning(`第${i + 1}行的员工联系方式请输入正确的11位手机号码`);
+        return false;
+      }
+      if (item.remark.length > 300) {
+        ElMessage.warning(`第${i + 1}行的个人先进描述最多输入300个字符`);
+        return false;
+      }
+    }
+    return true;
+  };
+
+  // 保存
+  const handleSave = async () => {
+    if (!psemId.value || !currentDeptId.value) {
+      ElMessage.error('缺少必要参数');
+      return;
+    }
+
+    let users: Array<{
+      deptId: number;
+      advUserStaffNo: string;
+      advUserName: string;
+      advUserLink: string;
+      remark: string;
+    }> = [];
+
+    if (deptType.value === 'normal') {
+      // 验证普通部门表单
+      const isValid = await validateNormalForm();
+      if (!isValid) {
+        return;
+      }
+      users = [
+        {
+          deptId: currentDeptId.value,
+          advUserStaffNo: normalForm.employeeCode,
+          advUserName: normalForm.employeeName,
+          advUserLink: normalForm.employeeContact,
+          remark: normalForm.remark,
+        },
+      ];
+    } else {
+      // 验证先进集体部门表单
+      if (!validateAdvancedForm()) {
+        return;
+      }
+      // 过滤掉空行
+      users = advancedFormList.value
+        .filter(
+          (item) =>
+            item.employeeCode || item.employeeName || item.employeeContact || item.remark,
+        )
+        .map((item) => ({
+          deptId: currentDeptId.value!,
+          advUserStaffNo: item.employeeCode,
+          advUserName: item.employeeName,
+          advUserLink: item.employeeContact,
+          remark: item.remark,
+        }));
+    }
+
+    if (users.length === 0) {
+      ElMessage.warning('请至少填写一条记录');
+      return;
+    }
+
+    try {
+      if (isEditMode.value && currentEditRow.value?.id && editDetailData.value) {
+        // 编辑模式:调用更新接口
+        // 验证表单
+        const isValid = await validateNormalForm();
+        if (!isValid) {
+          return;
+        }
+        if (!psemId.value) {
+          ElMessage.error('缺少必要参数');
+          return;
+        }
+        // 使用详情接口返回的数据作为基础,但使用表单中修改后的字段
+        const updateParams: UpdateSecurityExamineDeptAdvUserRequest = {
+          id: currentEditRow.value.id,
+          psemId: psemId.value,
+          deptName: editDetailData.value.deptName,
+          deptId: editDetailData.value.deptId || currentDeptId.value || 0,
+          deptUserId: editDetailData.value.deptUserId || deptUserId.value || 0,
+          deptUserName: editDetailData.value.deptUserName,
+          deptUserLink: editDetailData.value.deptUserLink,
+          // 使用表单中修改后的字段
+          advUserStaffNo: normalForm.employeeCode,
+          advUserName: normalForm.employeeName,
+          advUserLink: normalForm.employeeContact,
+          remark: normalForm.remark,
+        };
+        await updateSecurityExamineDeptAdvUser(updateParams);
+        ElMessage.success('更新成功');
+      } else {
+        // 新增模式:调用保存接口
+        await saveSecurityExamineDeptAdvUser({
+          psemId: psemId.value,
+          users,
+        });
+        ElMessage.success('保存成功');
+      }
+      handleDialogClose();
+      getTableData();
+    } catch (e) {
+      console.error('保存失败:', e);
+      ElMessage.error(isEditMode.value ? '更新失败' : '保存失败');
+    }
+  };
+
+  const handleEdit = async (row: any) => {
+    if (!row.id) {
+      ElMessage.error('缺少记录ID,无法编辑');
+      return;
+    }
+
+    try {
+      // 调用详情接口获取数据
+      const detail = await querySecurityExamineDeptAdvUserDetail(row.id);
+      
+      // 保存当前编辑的行数据和详情数据
+      currentEditRow.value = row;
+      editDetailData.value = detail;
+      isEditMode.value = true;
+      deptType.value = 'normal'; // 编辑时固定为普通部门
+      addDialogVisible.value = true;
+      
+      // 回填表单数据(普通部门)- 使用详情数据
+      normalForm.employeeCode = detail.advUserStaffNo || '';
+      normalForm.employeeName = detail.advUserName || '';
+      normalForm.employeeContact = detail.advUserLink || '';
+      normalForm.remark = detail.remark || '';
+    } catch (e) {
+      console.error('获取详情失败:', e);
+      ElMessage.error('获取详情失败');
+    }
+  };
+
+  const handleDelete = async (row: any) => {
+    try {
+      await ElMessageBox.confirm('确定要删除这条记录吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      });
+      
+      if (!row.id) {
+        ElMessage.error('缺少记录ID,无法删除');
+        return;
+      }
+      
+      await deleteSecurityExamineDeptAdvUser(row.id);
+      ElMessage.success('删除成功');
+      getTableData();
+    } catch (e: any) {
+      // 用户取消删除或删除失败
+      if (e !== 'cancel') {
+        console.error('删除失败:', e);
+        ElMessage.error('删除失败');
+      }
+    }
+  };
+
+  const handleExport = () => {
+    // TODO: 导出当前筛选结果
+    console.log('export advanced person list', tableQuery);
+  };
+
+  // 格式化日期时间
+  const formatDateTime = (dateTimeStr: string | undefined): string => {
+    if (!dateTimeStr) return '';
+    try {
+      const date = new Date(dateTimeStr);
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      const hours = String(date.getHours()).padStart(2, '0');
+      const minutes = String(date.getMinutes()).padStart(2, '0');
+      const seconds = String(date.getSeconds()).padStart(2, '0');
+      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+    } catch (e) {
+      return dateTimeStr;
+    }
+  };
+
+  // 获取考核表详情
+  const getEvaluationDetail = async () => {
+    // 路由参数中的id是考核表ID(psemId),直接使用
+    if (!evaluationId.value) return;
+    
+    try {
+      const res = await querySecurityExamineDetail(evaluationId.value);
+      if (res) {
+        evaluationDetail.value = {
+          exName: res.exName,
+          deptNames: res.deptNames,
+          createdUserName: res.createdUserName,
+          createdAt: res.createdAt,
+        };
+        // 保存考核表ID,用于提交时使用
+        psemId.value = res.id;
+      }
+    } catch (e) {
+      console.error('获取考核表详情失败:', e);
+    }
+  };
+
+  onMounted(() => {
+    getDeptTreeData();
+    getTableData();
+    getEvaluationDetail();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+  @use '@/styles/basic-table-file.scss' as *;
+  @use '@/views/traffic/violation/style/act-search-table.scss' as *;
+
+  .safety-platform-container__header {
+    padding-bottom: 0 !important;
+  }
+
+  .evaluation-header {
+    width: 100%;
+  }
+
+  .evaluation-title {
+    font-size: 20px;
+    font-weight: bold;
+    margin: 0 0 12px 0;
+    color: #333;
+  }
+
+  .evaluation-meta {
+    margin-bottom: 12px;
+    display: flex;
+    gap: 24px;
+    font-size: 14px;
+    color: #666;
+  }
+
+  .evaluation-meta span {
+    white-space: nowrap;
+  }
+
+  .add-dialog-content {
+    .description {
+      margin-bottom: 24px;
+      padding: 12px;
+      background-color: #f5f7fa;
+      border-radius: 4px;
+
+      .description-label {
+        font-weight: bold;
+        color: #333;
+      }
+
+      .description-text {
+        color: #666;
+        line-height: 1.6;
+      }
+    }
+  }
+
+
+  .dialog-footer {
+    text-align: right;
+  }
+</style>

+ 846 - 0
src/views/production-safety/safetyAssessment/evaluationSystem/components/EvaluationSystemDetail.vue

@@ -0,0 +1,846 @@
+<template>
+  <main class="safety-platform-container__main">
+    <el-form
+      ref="formRef"
+      :model="ruleFormData"
+      :rules="formRules"
+      label-width="auto"
+      class="evaluation-form"
+    >
+      <el-form-item label="考核表名称:" prop="evaluationTableName">
+        <el-input
+          v-model="ruleFormData.evaluationTableName"
+          placeholder="请输入考核表名称"
+          :disabled="isViewMode"
+        />
+      </el-form-item>
+      <el-form-item label="上传附件文档:" prop="attachmentDocument">
+        <UploadFiles
+          label="上传附件"
+          :file-list="ruleFormData.attachmentDocument"
+          @uploadSuccess="handleUploadSuccess"
+        />
+      </el-form-item>
+      <el-form-item label="评分说明:" prop="scoringDescription">
+        <el-input
+          v-model="ruleFormData.scoringDescription"
+          type="textarea"
+          :rows="5"
+          placeholder="请输入评分说明"
+          :disabled="isViewMode"
+        />
+      </el-form-item>
+    </el-form>
+
+    <div class="evaluation-items-section">
+      <div class="section-header">
+        <!-- <el-button plain @click="handleDownloadTemplate">模板下载</el-button> -->
+        <el-button plain @click="handleImport">导入</el-button>
+      </div>
+      <div class="evaluation-items-table">
+        <el-table :data="evaluationItems" border :span-method="handleSpanMethod">
+          <el-table-column label="编号" type="index" width="80" align="center" />
+          <el-table-column label="考核项目" min-width="150">
+            <template #header>
+              <span>考核项目<span style="color: red">*</span></span>
+            </template>
+            <template #default="scope">
+              <el-input
+                :model-value="getCurrentEvaluationItem(scope.$index)"
+                @update:model-value="(val) => handleEvaluationItemInput(scope.$index, val)"
+                @blur="handleEvaluationItemBlur(scope.$index)"
+                placeholder="请输入考核项目"
+                :disabled="isViewMode"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="考核内容" min-width="200">
+            <template #header>
+              <span>考核内容<span style="color: red">*</span></span>
+            </template>
+            <template #default="scope">
+              <el-input
+                v-model="scope.row.evaluationContent"
+                placeholder="请输入考核内容"
+                :disabled="isViewMode"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="评分方式" min-width="150">
+            <template #default="scope">
+              <el-input
+                v-model="scope.row.scoringMethod"
+                placeholder="请输入评分方式"
+                :disabled="isViewMode"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="加减分项" min-width="150" align="center">
+            <template #default="scope">
+              <el-select
+                v-model="scope.row.isAdd"
+                placeholder="请选择"
+                :disabled="isViewMode"
+                style="width: 100%"
+              >
+                <el-option label="加分项" :value="1" />
+                <el-option label="减分项" :value="0" />
+              </el-select>
+            </template>
+          </el-table-column>
+          <el-table-column label="复评人" min-width="150">
+            <template #default="scope">
+              <el-select
+                v-model="scope.row.reviewUserId"
+                placeholder="请选择复评人"
+                filterable
+                clearable
+                :disabled="isViewMode"
+                style="width: 100%"
+                @change="(val) => handleReviewUserChange(scope.$index, val)"
+              >
+                <el-option
+                  v-for="user in reviewUserList"
+                  :key="user.id"
+                  :label="user.realname"
+                  :value="user.id"
+                />
+              </el-select>
+            </template>
+          </el-table-column>
+          <el-table-column v-if="!isViewMode" label="操作" fixed="right" width="350" align="center">
+            <template #default="scope">
+              <el-button type="primary" link @click="handleAddItem(scope.$index)">新增</el-button>
+              <el-button type="primary" link @click="handleMoveUp(scope.$index)">向上插入分类</el-button>
+              <el-button type="primary" link @click="handleMoveDown(scope.$index)">向下插入分类</el-button>
+              <el-button type="danger" link @click="handleDeleteItem(scope.$index)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+
+    <!-- 导入弹窗 -->
+    <el-dialog v-model="importDialogVisible" title="导入考核项目" width="500px" destroy-on-close>
+      <el-upload
+        :file-list="importFileList"
+        :auto-upload="false"
+        :on-change="handleFileChange"
+        :on-remove="handleFileRemove"
+        :limit="1"
+        drag
+        accept=".xlsx,.xls"
+      >
+        <el-icon class="el-icon--upload"><Document /></el-icon>
+        <div class="el-upload__text">
+          <div style="font-size: 12px; color: red; margin-bottom: 5px">请下载模板并按要求填写后上传</div>
+          <div style="font-size: 16px">点击或将文件拖拽到这里上传</div>
+          <div style="font-size: 12px; color: rgba(0, 0, 0, 0.45); margin-top: 5px">
+            文件支持.xlsx .xls格式,仅支持上传一个文件
+          </div>
+        </div>
+      </el-upload>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="importDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="handleConfirmImport" :disabled="importFileList.length === 0">
+            导入
+          </el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </main>
+  <footer class="safety-platform-container__footer">
+    <el-button @click="router.back()">取消</el-button>
+    <el-button v-if="!isViewMode" type="primary" @click="handleSubmit">
+      {{ isCreateMode ? '提交' : '保存' }}
+    </el-button>
+  </footer>
+</template>
+
+<script setup lang="ts">
+  import { computed, nextTick, onMounted, ref } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import type { FormInstance } from 'element-plus';
+  import { Document } from '@element-plus/icons-vue';
+  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import { EVALUATION_SYSTEM_FORM_DATA, EVALUATION_SYSTEM_FORM_RULES } from '../configs/form';
+  import { importSecurityExamineDet, saveSecurityExamine, querySecurityExamineDetail, updateSecurityExamine } from '@/api/evaluationSystem';
+  import type { EvaluationContent } from '@/api/evaluationSystem';
+  import type { FileItem } from '@/components/UploadFiles/types';
+  import { useUserInfoHook } from '@/hooks/useUserInfoHook';
+  import { formatAttachmentList } from '@/components/UploadFiles/utils';
+  import { getUserList } from '@/api/system/user-operate';
+
+  const props = defineProps<{
+    id?: number;
+  }>();
+
+  const router = useRouter();
+  const route = useRoute();
+
+  const operate = computed(() => (route.query.operate as string) || 'evaluationSystem-create');
+  const isCreateMode = computed(() => operate.value === 'evaluationSystem-create');
+  const isEditMode = computed(() => operate.value === 'evaluationSystem-edit');
+  const isViewMode = computed(() => operate.value === 'evaluationSystem-view');
+
+  const formRef = ref<FormInstance>();
+  const ruleFormData = ref({ ...EVALUATION_SYSTEM_FORM_DATA });
+  const formRules = EVALUATION_SYSTEM_FORM_RULES;
+
+  // 获取当前用户信息
+  const { id: userId, realname: userName } = useUserInfoHook();
+
+  // 初始化一条空白数据
+  const evaluationItems = ref<any[]>([
+    {
+      id: 0,
+      psemId: 0,
+      evaluationItem: '',
+      evaluationContent: '',
+      scoringMethod: '',
+      isAdd: 1, // 是否加分项(0-否,1-是),默认为1(加分项)
+      reviewUserId: null as number | null, // 复评人ID
+      reviewUserName: '', // 复评人姓名
+    },
+  ]);
+
+  // 复评人用户列表
+  const reviewUserList = ref<UserLisItem[]>([]);
+  const getReviewUserList = async () => {
+    try {
+      const res = await getUserList({
+        pageNumber: 1,
+        pageSize: 9999,
+        queryParam: {}, // 不传递 deptId 参数
+      });
+      reviewUserList.value = res?.records || [];
+    } catch (e) {
+      console.error('获取复评人列表失败:', e);
+      reviewUserList.value = [];
+    }
+  };
+
+  // 处理复评人选择变化
+  const handleReviewUserChange = (index: number, userId: number | null) => {
+    if (userId) {
+      const selectedUser = reviewUserList.value.find((user) => user.id === userId);
+      if (selectedUser) {
+        evaluationItems.value[index].reviewUserName = selectedUser.realname || '';
+      }
+    } else {
+      evaluationItems.value[index].reviewUserName = '';
+    }
+  };
+
+  // 用于存储输入过程中的临时值,避免实时合并
+  const tempEvaluationItems = ref<Record<number, string>>({});
+
+  const handleValidate = async () => {
+    if (!formRef.value) return;
+    return new Promise((resolve) => {
+      formRef.value?.validate((valid: boolean) => {
+        resolve(valid);
+      });
+    });
+  };
+
+  const handleUploadSuccess = (files: any[]) => {
+    ruleFormData.value.attachmentDocument = files;
+  };
+
+  const handleDownloadTemplate = () => {
+    // TODO: 下载模板
+    console.log('download template');
+  };
+
+  // 导入弹窗相关
+  const importDialogVisible = ref(false);
+  const importFileList = ref<any[]>([]);
+
+  const handleImport = () => {
+    importDialogVisible.value = true;
+    importFileList.value = [];
+  };
+
+  // 处理导入成功
+  const handleImportSuccess = async (response: any) => {
+    try {
+      // 接口返回的数据格式:{ code: 0, message: "string", data: EvaluationContent[] }
+      if (response && response.data && Array.isArray(response.data)) {
+        // 将导入的数据映射到 evaluationItems 格式
+        // 导入的数据视为新增数据,不带后端已保存的 id / psemId
+        evaluationItems.value = response.data.map((item: any) => ({
+          id: 0,
+          psemId: 0,
+          evaluationItem: item.exProgram || '', // 考核项目
+          evaluationContent: item.exContent || '', // 考核内容
+          scoringMethod: item.scoringWay || '', // 评分方式
+          isAdd: item.isAdd !== undefined ? item.isAdd : 1, // 是否加分项(0-否,1-是),默认为1
+          reviewUserId: item.reviewUserId || null, // 复评人ID
+          reviewUserName: item.reviewUserName || '', // 复评人姓名
+        }));
+
+        // 如果导入后列表为空,至少保留一条空白数据
+        if (evaluationItems.value.length === 0) {
+          evaluationItems.value = [
+            {
+              evaluationItem: '',
+              evaluationContent: '',
+              scoringMethod: '',
+            },
+          ];
+        }
+
+        ElMessage.success(`导入成功,共导入 ${response.data.length} 条数据`);
+        importDialogVisible.value = false;
+      } else {
+        ElMessage.error('导入失败:返回数据格式错误');
+      }
+    } catch (e) {
+      console.error('导入处理失败:', e);
+      ElMessage.error('导入处理失败');
+    }
+  };
+
+  // 处理文件上传
+  const handleFileChange = (file: any) => {
+    importFileList.value = [file];
+  };
+
+  // 处理文件移除
+  const handleFileRemove = () => {
+    importFileList.value = [];
+  };
+
+  // 确认导入
+  const handleConfirmImport = async () => {
+    if (importFileList.value.length === 0) {
+      ElMessage.warning('请先选择要导入的文件');
+      return;
+    }
+
+    const file = importFileList.value[0].raw || importFileList.value[0];
+    if (!file) {
+      ElMessage.warning('文件不存在');
+      return;
+    }
+
+    // 检查文件类型
+    const isExcel = /\.(xlsx|xls)$/.test(file.name.toLowerCase());
+    if (!isExcel) {
+      ElMessage.error('仅支持上传.xlsx .xls格式文件');
+      return;
+    }
+
+    try {
+      const formData = new FormData();
+      formData.append('file', file);
+
+      const res = await importSecurityExamineDet(formData);
+      if (res) {
+        handleImportSuccess({ data: res });
+      }
+    } catch (e: any) {
+      console.error('导入失败:', e);
+      ElMessage.error(e?.message || '导入失败,请重试');
+    }
+  };
+
+  // 获取当前行的考核项目值(考虑临时输入值)
+  const getCurrentEvaluationItem = (rowIndex: number): string => {
+    // 如果正在输入(有临时值),使用临时值;否则使用实际值
+    if (tempEvaluationItems.value[rowIndex] !== undefined) {
+      return tempEvaluationItems.value[rowIndex];
+    }
+    return evaluationItems.value[rowIndex]?.evaluationItem || '';
+  };
+
+  // 处理单元格合并(合并考核项目列)
+  const handleSpanMethod = ({ row, column, rowIndex, columnIndex }: any) => {
+    // 只合并考核项目列(columnIndex === 1,因为编号列是 0)
+    if (columnIndex === 1) {
+      const currentItem = row.evaluationItem;
+      if (!currentItem) {
+        // 如果当前行的考核项目为空,不合并
+        return {
+          rowspan: 1,
+          colspan: 1,
+        };
+      }
+
+      // 查找相同考核项目的连续行
+      let rowspan = 1;
+      for (let i = rowIndex + 1; i < evaluationItems.value.length; i++) {
+        if (evaluationItems.value[i].evaluationItem === currentItem) {
+          rowspan++;
+        } else {
+          break;
+        }
+      }
+
+      // 检查是否是同一组的第一行
+      if (rowIndex > 0 && evaluationItems.value[rowIndex - 1].evaluationItem === currentItem) {
+        // 不是第一行,不显示(被合并)
+        return {
+          rowspan: 0,
+          colspan: 0,
+        };
+      }
+
+      return {
+        rowspan,
+        colspan: 1,
+      };
+    }
+    // 其他列不合并
+    return {
+      rowspan: 1,
+      colspan: 1,
+    };
+  };
+
+  // 检查是否是相邻行的相同内容(用于合并)
+  const isAdjacentSameValue = (index: number, value: string): boolean => {
+    if (!value) return false;
+    
+    // 检查上一行或下一行是否有相同的值
+    const prevRow = evaluationItems.value[index - 1];
+    const nextRow = evaluationItems.value[index + 1];
+    
+    return (
+      (prevRow && prevRow.evaluationItem === value) ||
+      (nextRow && nextRow.evaluationItem === value)
+    );
+  };
+
+  // 检查是否有跨行的相同考核项目(非相邻的)
+  const checkNonConsecutiveDuplicate = (index: number, value: string): boolean => {
+    if (!value) return false;
+
+    // 检查是否有非相邻行的相同值
+    for (let i = 0; i < evaluationItems.value.length; i++) {
+      if (i === index) continue; // 跳过当前行
+
+      if (evaluationItems.value[i].evaluationItem === value) {
+        // 检查是否是相邻行
+        const isAdjacent = i === index - 1 || i === index + 1;
+        
+        if (!isAdjacent) {
+          return true; // 发现跨行的重复
+        }
+      }
+    }
+    return false;
+  };
+
+  // 考核项目输入时的处理(只更新临时值,不触发合并)
+  const handleEvaluationItemInput = (index: number, value: string) => {
+    // 只更新临时值,不更新实际数据,避免实时合并
+    tempEvaluationItems.value[index] = value;
+  };
+
+  // 考核项目失去焦点时的处理
+  const handleEvaluationItemBlur = async (index: number) => {
+    // 获取临时输入值
+    const tempValue = tempEvaluationItems.value[index];
+    const finalValue = tempValue !== undefined ? tempValue : evaluationItems.value[index].evaluationItem;
+    
+    // 清除临时值
+    delete tempEvaluationItems.value[index];
+
+    // 如果值为空,直接更新并返回
+    if (!finalValue || !finalValue.trim()) {
+      evaluationItems.value[index].evaluationItem = '';
+      await nextTick();
+      return;
+    }
+
+    // 先检查是否是相邻行的相同内容(允许合并)
+    const isAdjacent = isAdjacentSameValue(index, finalValue);
+    
+    if (isAdjacent) {
+      // 相邻行相同,允许合并,直接更新数据
+      evaluationItems.value[index].evaluationItem = finalValue;
+      await nextTick();
+      return;
+    }
+
+    // 检查是否有跨行的相同考核项目(非相邻的)
+    if (checkNonConsecutiveDuplicate(index, finalValue)) {
+      ElMessage.warning('考核项目一致,请使用连续的相同考核项目');
+      evaluationItems.value[index].evaluationItem = '';
+      await nextTick();
+      return;
+    }
+
+    // 没有冲突,更新实际数据
+    evaluationItems.value[index].evaluationItem = finalValue;
+
+    // 等待 DOM 更新后,强制表格重新计算合并
+    await nextTick();
+  };
+
+  // 新增:在当前行下面插入一条新数据,如果当前行有考核项目值,新行也使用相同的值
+  const handleAddItem = (index: number) => {
+    const currentItem = evaluationItems.value[index];
+      evaluationItems.value.splice(index + 1, 0, {
+        id: 0,
+        psemId: 0,
+        evaluationItem: currentItem.evaluationItem || '', // 合并考核项目字段
+        evaluationContent: '',
+        scoringMethod: '',
+        isAdd: currentItem.isAdd !== undefined ? currentItem.isAdd : 1, // 是否加分项,继承当前行的值
+        reviewUserId: null, // 复评人ID
+        reviewUserName: '', // 复评人姓名
+      });
+  };
+
+  // 删除当前行
+  const handleDeleteItem = (index: number) => {
+    if (evaluationItems.value.length <= 1) {
+      ElMessage.warning('至少需要保留一条数据');
+      return;
+    }
+    evaluationItems.value.splice(index, 1);
+  };
+
+  // 查找当前行所在合并组的起始位置
+  const findGroupStartIndex = (index: number): number => {
+    const currentItem = evaluationItems.value[index].evaluationItem;
+    if (!currentItem) {
+      return index; // 如果当前行考核项目为空,直接返回当前索引
+    }
+
+    // 向上查找,找到相同考核项目的第一行
+    let startIndex = index;
+    for (let i = index - 1; i >= 0; i--) {
+      if (evaluationItems.value[i].evaluationItem === currentItem) {
+        startIndex = i;
+      } else {
+        break;
+      }
+    }
+    return startIndex;
+  };
+
+  // 查找当前行所在合并组的结束位置
+  const findGroupEndIndex = (index: number): number => {
+    const currentItem = evaluationItems.value[index].evaluationItem;
+    if (!currentItem) {
+      return index; // 如果当前行考核项目为空,直接返回当前索引
+    }
+
+    // 向下查找,找到相同考核项目的最后一行
+    let endIndex = index;
+    for (let i = index + 1; i < evaluationItems.value.length; i++) {
+      if (evaluationItems.value[i].evaluationItem === currentItem) {
+        endIndex = i;
+      } else {
+        break;
+      }
+    }
+    return endIndex;
+  };
+
+  // 向上插入分类:在当前行所在合并组的第一行之前插入一条空白数据
+  const handleMoveUp = (index: number) => {
+    const groupStartIndex = findGroupStartIndex(index);
+    evaluationItems.value.splice(groupStartIndex, 0, {
+      id: 0,
+      psemId: 0,
+      evaluationItem: '',
+      evaluationContent: '',
+      scoringMethod: '',
+      isAdd: 1, // 是否加分项(0-否,1-是),默认为1
+      reviewUserId: null, // 复评人ID
+      reviewUserName: '', // 复评人姓名
+    });
+  };
+
+  // 向下插入分类:在当前行所在合并组的最后一行之后插入一条空白数据
+  const handleMoveDown = (index: number) => {
+    const groupEndIndex = findGroupEndIndex(index);
+    evaluationItems.value.splice(groupEndIndex + 1, 0, {
+      id: 0,
+      psemId: 0,
+      evaluationItem: '',
+      evaluationContent: '',
+      scoringMethod: '',
+      isAdd: 1, // 是否加分项(0-否,1-是),默认为1
+      reviewUserId: null, // 复评人ID
+      reviewUserName: '', // 复评人姓名
+    });
+  };
+
+  // 将逗号分隔的URL字符串转换为FileItem数组
+  const convertAttachmentsToFileItems = (attachmentsStr: string): FileItem[] => {
+    if (!attachmentsStr || !attachmentsStr.trim()) {
+      return [];
+    }
+    
+    // 按逗号分割URL
+    const urls = attachmentsStr.split(',').map(url => url.trim()).filter(url => url);
+    
+    return urls.map((url, index) => {
+      // 从URL中提取文件名
+      const urlParts = url.split('/');
+      const fileName = urlParts[urlParts.length - 1] || `附件${index + 1}`;
+      
+      // 根据文件扩展名判断文件类型
+      const extension = fileName.split('.').pop()?.toLowerCase() || '';
+      let fileType = 'pdf';
+      if (extension === 'doc' || extension === 'docx') {
+        fileType = 'word';
+      } else if (extension === 'xls' || extension === 'xlsx') {
+        fileType = 'excel';
+      } else if (extension === 'ppt' || extension === 'pptx') {
+        fileType = 'ppt';
+      }
+      
+      return {
+        fileId: 0,
+        fileName,
+        fileType,
+        fileSize: '0',
+        fileUrl: url,
+      };
+    });
+  };
+
+  const getDetail = async () => {
+    if (!props.id) return;
+    
+    try {
+      const detail = await querySecurityExamineDetail(props.id);
+      if (!detail) return;
+      
+      // 填充表单数据
+      ruleFormData.value.evaluationTableName = detail.exName || '';
+      ruleFormData.value.scoringDescription = detail.ratingDescribe || '';
+      
+      // 转换附件文档:将逗号分隔的URL字符串转换为FileItem数组
+      ruleFormData.value.attachmentDocument = convertAttachmentsToFileItems(detail.attachments || '');
+      
+      // 填充考核项目列表
+      if (detail.exContents && detail.exContents.length > 0) {
+        // 详情接口返回的数据视为已持久化的数据,保留 id / psemId
+        evaluationItems.value = detail.exContents.map((item: any) => ({
+          id: item.id || 0,
+          psemId: item.psemId || 0,
+          evaluationItem: item.exProgram || '',
+          evaluationContent: item.exContent || '',
+          scoringMethod: item.scoringWay || '',
+          isAdd: item.isAdd !== undefined ? item.isAdd : 1, // 是否加分项(0-否,1-是),默认为1
+          reviewUserId: item.reviewUserId || null, // 复评人ID
+          reviewUserName: item.reviewUserName || '', // 复评人姓名
+        }));
+      } else {
+        // 如果没有数据,至少保留一条空白数据
+        evaluationItems.value = [
+          {
+            id: 0,
+            psemId: 0,
+            evaluationItem: '',
+            evaluationContent: '',
+            scoringMethod: '',
+            isAdd: 1, // 是否加分项(0-否,1-是),默认为1
+            reviewUserId: null, // 复评人ID
+            reviewUserName: '', // 复评人姓名
+          },
+        ];
+      }
+    } catch (e: any) {
+      console.error('获取详情失败:', e);
+      ElMessage.error(e?.message || '获取详情失败,请重试');
+    }
+  };
+
+  // 验证考核项目列表
+  const validateEvaluationItems = (): boolean => {
+    for (let i = 0; i < evaluationItems.value.length; i++) {
+      const item = evaluationItems.value[i];
+      if (!item.evaluationItem || !item.evaluationItem.trim()) {
+        ElMessage.warning(`第 ${i + 1} 行的考核项目不能为空`);
+        return false;
+      }
+      if (!item.evaluationContent || !item.evaluationContent.trim()) {
+        ElMessage.warning(`第 ${i + 1} 行的考核内容不能为空`);
+        return false;
+      }
+    }
+    return true;
+  };
+
+  const handleSubmit = async () => {
+    const res = await handleValidate();
+    if (!res) return;
+
+    // 验证考核项目列表
+    if (!validateEvaluationItems()) {
+      return;
+    }
+
+    try {
+      // 处理附件文档:先上传文件获取 URL,然后提取 fileUrl,多个用逗号分隔
+      let attachments = '';
+      if (ruleFormData.value.attachmentDocument && ruleFormData.value.attachmentDocument.length > 0) {
+        // 分离已有URL的文件和新上传的文件
+        const existingFiles: string[] = [];
+        const newFiles: any[] = [];
+        
+        ruleFormData.value.attachmentDocument.forEach((file: any) => {
+          // 如果文件已经有 fileUrl 且没有 file 对象,说明是已有文件
+          if (file.fileUrl && !file.file) {
+            existingFiles.push(file.fileUrl);
+          } else {
+            // 否则是需要上传的新文件
+            newFiles.push(file);
+          }
+        });
+
+        // 上传新文件
+        let uploadedUrls: string[] = [];
+        if (newFiles.length > 0) {
+          const uploadedFiles = await formatAttachmentList(newFiles);
+          uploadedUrls = uploadedFiles
+            .map((file: any) => file.fileUrl || file.url || '')
+            .filter((url: string) => url);
+        }
+
+        // 合并已有URL和新上传的URL
+        attachments = [...existingFiles, ...uploadedUrls].filter((url: string) => url).join(',');
+      }
+
+      // 映射考核项目列表,添加序号
+      const exContents: EvaluationContent[] = evaluationItems.value.map((item, index) => {
+        const base: any = {
+          serialNum: index + 1, // 序号从1开始
+          exProgram: item.evaluationItem || '', // 考核项目
+          exContent: item.evaluationContent || '', // 考核内容
+          scoringWay: item.scoringMethod || '', // 评分方式
+          isAdd: item.isAdd !== undefined ? item.isAdd : 1, // 是否加分项(0-否,1-是)
+          reviewUserId: item.reviewUserId || null, // 复评人ID
+          reviewUserName: item.reviewUserName || '', // 复评人姓名
+        };
+
+        // 只有详情接口返回的已存在数据才携带 id / psemId
+        if (item.id && item.psemId) {
+          base.id = item.id;
+          base.psemId = item.psemId;
+        }
+
+        return base as EvaluationContent;
+      });
+
+      if (isEditMode.value && props.id) {
+        // 编辑模式:调用更新接口
+        const payload = {
+          id: props.id, // 编辑时必须传ID
+          exName: ruleFormData.value.evaluationTableName || '', // 考核表名称
+          attachments, // 考核文档
+          ratingDescribe: ruleFormData.value.scoringDescription || '', // 评分说明
+          deptNames: '', // 下发部门(编辑时为空)
+          deptIds: [], // 下发部门ID数组(编辑时为空)
+          getUserGroupId: 0,
+          planStartTime: '', // 计划开始时间(编辑时为空)
+          planEndTime: '', // 计划结束时间(编辑时为空)
+          status: 0, // 状态:0-未下发
+          createdUserId: userId || 0, // 创建人ID
+          createdUserName: userName || '', // 创建人名称
+          exContents, // 考核项目列表
+        };
+
+        await updateSecurityExamine(payload);
+        ElMessage.success('保存成功');
+      } else {
+        // 创建模式:调用保存接口
+        const payload = {
+          id: 0, // 新增时为0
+          exName: ruleFormData.value.evaluationTableName || '', // 考核表名称
+          attachments, // 考核文档
+          ratingDescribe: ruleFormData.value.scoringDescription || '', // 评分说明
+          deptNames: '', // 下发部门(创建时为空)
+          deptIds: [], // 下发部门ID数组(创建时为空)
+          getUserGroupId: 0,
+          planStartTime: '', // 计划开始时间(创建时为空)
+          planEndTime: '', // 计划结束时间(创建时为空)
+          status: 0, // 状态:0-未下发
+          createdUserId: userId || 0, // 创建人ID
+          createdUserName: userName || '', // 创建人名称
+          exContents, // 考核项目列表
+        };
+
+        await saveSecurityExamine(payload);
+        ElMessage.success('创建成功');
+      }
+      
+      router.back();
+    } catch (e: any) {
+      console.error('保存失败:', e);
+      ElMessage.error(e?.message || '保存失败,请重试');
+    }
+  };
+
+  onMounted(() => {
+    // 获取复评人列表
+    getReviewUserList();
+    
+    if (isEditMode.value || isViewMode.value) {
+      getDetail();
+    } else {
+      // 创建模式下,确保附件文档字段为空
+      ruleFormData.value.attachmentDocument = [];
+      // 创建模式下,确保至少有一条空白数据
+      if (evaluationItems.value.length === 0) {
+        evaluationItems.value = [
+          {
+            id: 0,
+            psemId: 0,
+            evaluationItem: '',
+            evaluationContent: '',
+            scoringMethod: '',
+            isAdd: 1, // 是否加分项(0-否,1-是),默认为1
+            reviewUserId: null, // 复评人ID
+            reviewUserName: '', // 复评人姓名
+          },
+        ];
+      }
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+
+  .evaluation-form {
+    display: flex;
+    flex-direction: column;
+    width: 600px;
+    gap: 32px;
+
+    :deep(.el-form-item) {
+      margin-bottom: 0;
+    }
+
+    :deep(.el-form-item__label) {
+      padding: 0;
+    }
+  }
+
+  .evaluation-items-section {
+    margin-top: 32px;
+  }
+
+  .section-header {
+    display: flex;
+    gap: 10px;
+    margin-bottom: 20px;
+  }
+
+  .evaluation-items-table {
+    width: 100%;
+  }
+</style>
+

+ 501 - 0
src/views/production-safety/safetyAssessment/evaluationSystem/components/EvaluationSystemFeedback.vue

@@ -0,0 +1,501 @@
+<template>
+  <main class="safety-platform-container__main">
+    <el-form ref="formRef" :model="ruleFormData" :rules="formRules" label-width="auto" class="evaluation-form">
+      <el-form-item label="考核信息标题:" prop="evaluationTitle">
+        <el-input v-model="ruleFormData.evaluationTitle" placeholder="请输入考核信息标题" disabled />
+      </el-form-item>
+      <el-form-item label="上传附件文档:" prop="attachmentDocument">
+        <div class="upload-files-disabled">
+          <UploadFiles label="上传附件" :file-list="ruleFormData.attachmentDocument" @uploadSuccess="handleUploadSuccess" />
+        </div>
+      </el-form-item>
+      <el-form-item label="评分说明:" prop="scoringDescription">
+        <el-input v-model="ruleFormData.scoringDescription" type="textarea" :rows="5" placeholder="请输入评分说明" disabled />
+      </el-form-item>
+    </el-form>
+
+    <div class="evaluation-items-section">
+      <div class="section-header">
+        <el-button plain @click="handleDownloadTemplate" disabled>模板下载</el-button>
+        <el-button plain @click="handleImport">导入</el-button>
+        <el-button plain @click="handleExport">导出</el-button>
+        <input
+          ref="importFileInputRef"
+          type="file"
+          accept=".xlsx,.xls"
+          style="display: none"
+          @change="handleFileChange"
+        />
+      </div>
+      <div class="evaluation-items-table">
+        <el-table :data="evaluationItems" border show-summary :summary-method="getSummaries">
+          <el-table-column label="编号" type="index" width="80" align="center" />
+          <el-table-column label="考核项目" prop="evaluationItem" min-width="150" />
+          <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="120" />
+          <el-table-column label="资料说明" prop="materialDescription" min-width="200" />
+          <el-table-column label="复核人姓名" prop="reviewUserName" min-width="120" />
+          <el-table-column label="复核得分" prop="reviewScore" min-width="180">
+            <template #default="scope">
+              <el-input-number
+                v-if="!isAudit && scope.row.isReviewInput"
+                v-model="scope.row.reviewScore"
+                :min="0"
+                :precision="0"
+                :step="1"
+                placeholder="请输入复核得分"
+                @blur="handleScoreBlur"
+              />
+              <span v-else>{{ scope.row.reviewScore || 0 }}</span>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+  </main>
+  <footer class="safety-platform-container__footer">
+    <el-button @click="router.back()">取消</el-button>
+    <el-button v-if="!isAudit && isSelfApproveButton" @click="openRejectDialog('review')">复核不通过</el-button>
+    <el-button v-if="isAudit" @click="openRejectDialog('approve')">审核不通过</el-button>
+    <el-button v-if="!isFromDeptView" type="primary" @click="handleSubmit">{{ getSubmitButtonText }}</el-button>
+  </footer>
+
+  <!-- 拒绝原因弹窗 -->
+  <el-dialog
+    v-model="rejectDialogVisible"
+    :title="rejectDialogTitle"
+    width="600px"
+    :close-on-click-modal="false"
+  >
+    <el-input
+      v-model="rejectReason"
+      type="textarea"
+      :rows="6"
+      :maxlength="300"
+      :show-word-limit="true"
+      :placeholder="rejectDialogPlaceholder"
+    />
+    <template #footer>
+      <el-button @click="closeRejectDialog">取消</el-button>
+      <el-button type="primary" @click="confirmReject">确定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+  import { computed, onMounted, ref, watch } from 'vue';
+  import { useRouter, useRoute } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import type { FormInstance } from 'element-plus';
+  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import {
+    querySecurityExamineIssueDetail,
+    updateSecurityExamineIssueReviewSubmit,
+    updateSecurityExamineIssueReviewAgree,
+    updateSecurityExamineIssueReviewDisagree,
+    updateSecurityExamineIssueApproveAgree,
+    updateSecurityExamineIssueApproveDisagree,
+    exportSecurityExamineIssueDeptDetail,
+    importSecurityExamineIssueDeptDetail,
+  } from '@/api/evaluationSystem';
+  import type { FileItem } from '@/components/UploadFiles/types';
+
+  const props = defineProps<{
+    id: number;
+  }>();
+
+  const router = useRouter();
+  const route = useRoute();
+
+  // 判断是评分还是审核
+  const isAudit = computed(() => route.query.operate === 'evaluationSystem-audit');
+
+  // 判断是否从部门考核结果视图进入(如果是,则不显示提交按钮)
+  const isFromDeptView = computed(() => route.query.fromDeptView === 'true');
+
+  // 提交按钮文字
+  const getSubmitButtonText = computed(() => {
+    if (isAudit.value) {
+      return '审核通过';
+    }
+    // 如果是评分模式,根据 isSelfApproveButton 决定按钮文字
+    if (isSelfApproveButton.value) {
+      return '复核同意';
+    }
+    return '复核提交';
+  });
+
+  // 拒绝原因弹窗相关
+  const rejectDialogVisible = ref(false);
+  const rejectReason = ref('');
+  const rejectType = ref<'review' | 'approve'>('review'); // 当前拒绝类型
+
+  const rejectDialogTitle = computed(() => {
+    return rejectType.value === 'review'
+      ? '复核评分不通过的记录,需要填写驳回原因'
+      : '审核不通过的记录,需要填写驳回原因';
+  });
+
+  const rejectDialogPlaceholder = computed(() => {
+    return rejectType.value === 'review'
+      ? '请填写驳回审批原因'
+      : '请填写驳回审批原因';
+  });
+
+  const openRejectDialog = (type: 'review' | 'approve') => {
+    rejectType.value = type;
+    rejectReason.value = '';
+    rejectDialogVisible.value = true;
+  };
+
+  const closeRejectDialog = () => {
+    rejectDialogVisible.value = false;
+    rejectReason.value = '';
+  };
+
+  const formRef = ref<FormInstance>();
+  const ruleFormData = ref({
+    evaluationTitle: '',
+    attachmentDocument: [] as FileItem[],
+    scoringDescription: '',
+  });
+
+  const formRules = {
+    evaluationTitle: [{ required: true, message: '请输入考核信息标题', trigger: 'blur' }],
+    attachmentDocument: [{ required: true, message: '请上传附件文档', trigger: 'change' }],
+    // scoringDescription: [{ required: true, message: '请输入评分说明', trigger: 'blur' }],
+  };
+
+  const evaluationItems = ref<any[]>([]);
+  // 保存详情原始数据,用于提交
+  const detailData = ref<any>(null);
+  // 是否显示复核不通过按钮(从详情接口获取)
+  const isSelfApproveButton = ref(false);
+
+  // 计算自评得分总计
+  const getTotalScore = () => {
+    return evaluationItems.value.reduce((sum, item) => {
+      return sum + (Number(item.selfScore) || 0);
+    }, 0);
+  };
+
+  // 计算复核得分总计
+  const getTotalReviewScore = () => {
+    return evaluationItems.value.reduce((sum, item) => {
+      return sum + (Number(item.reviewScore) || 0);
+    }, 0);
+  };
+
+  // 自评得分失去焦点时触发(用于强制更新合计行)
+  const handleScoreBlur = () => {
+    // 触发响应式更新,让合计行重新计算
+    // 通过修改数组引用来触发更新
+    evaluationItems.value = [...evaluationItems.value];
+  };
+
+  // 表格合计行方法
+  const getSummaries = (param: any) => {
+    const { columns } = param;
+    const sums: string[] = [];
+    columns.forEach((column: any, index: number) => {
+      if (index === 0) {
+        // 编号列
+        sums[index] = '';
+      } else if (column.property === 'scoreType') {
+        // 加减分项列显示"总计:"
+        sums[index] = '总计:';
+      } else if (column.property === 'reviewScore') {
+        // 复核得分列显示总分,即使为0也显示
+        const total = getTotalReviewScore();
+        sums[index] = `${total}分`;
+      } else {
+        // 其他列显示空
+        sums[index] = '';
+      }
+    });
+    return sums;
+  };
+
+  const handleValidate = async () => {
+    if (!formRef.value) return;
+    return new Promise((resolve) => {
+      formRef.value?.validate((valid: boolean) => {
+        resolve(valid);
+      });
+    });
+  };
+
+  const handleUploadSuccess = (files: any[]) => {
+    ruleFormData.value.attachmentDocument = files;
+  };
+
+  const handleDownloadTemplate = () => {
+    // TODO: 下载模板
+    console.log('download template');
+  };
+
+  // 导入文件引用
+  const importFileInputRef = ref<HTMLInputElement>();
+
+  const handleImport = () => {
+    // 触发文件选择
+    importFileInputRef.value?.click();
+  };
+
+  const handleFileChange = async (event: Event) => {
+    const target = event.target as HTMLInputElement;
+    const file = target.files?.[0];
+    if (!file) return;
+
+    try {
+      await importSecurityExamineIssueDeptDetail({
+        id: props.id,
+        file,
+      });
+      ElMessage.success('导入成功');
+      // 重新加载详情数据
+      await getDetail();
+    } catch (e: any) {
+      console.error('导入失败:', e);
+      ElMessage.error(e?.message || '导入失败,请重试');
+    } finally {
+      // 清空文件选择
+      if (target) {
+        target.value = '';
+      }
+    }
+  };
+
+  const handleExport = async () => {
+    try {
+      const blob = await exportSecurityExamineIssueDeptDetail(props.id);
+      // 创建下载链接
+      const url = window.URL.createObjectURL(blob);
+      const link = document.createElement('a');
+      link.href = url;
+      link.download = `部门考核详情_${new Date().getTime()}.xlsx`;
+      document.body.appendChild(link);
+      link.click();
+      document.body.removeChild(link);
+      window.URL.revokeObjectURL(url);
+      ElMessage.success('导出成功');
+    } catch (e: any) {
+      console.error('导出失败:', e);
+      ElMessage.error(e?.message || '导出失败,请重试');
+    }
+  };
+
+  // 将逗号分隔的 URL 字符串转换为 FileItem[] 格式
+  const parseAttachmentsToFileList = (attachmentsStr: string | undefined): FileItem[] => {
+    if (!attachmentsStr || !attachmentsStr.trim()) {
+      return [];
+    }
+
+    // 按逗号分割URL
+    const urls = attachmentsStr.split(',').map(url => url.trim()).filter(url => url);
+
+    return urls.map((url, index) => {
+      // 从URL中提取文件名
+      const urlParts = url.split('/');
+      const fileName = urlParts[urlParts.length - 1] || `文件${index + 1}`;
+
+      // 根据文件扩展名判断文件类型
+      const extension = fileName.split('.').pop()?.toLowerCase() || '';
+      let fileType = 'pdf';
+      if (extension === 'doc' || extension === 'docx') {
+        fileType = 'word';
+      } else if (extension === 'xls' || extension === 'xlsx') {
+        fileType = 'excel';
+      } else if (extension === 'ppt' || extension === 'pptx') {
+        fileType = 'ppt';
+      }
+
+      return {
+        fileId: index + 1,
+        fileName,
+        fileType,
+        fileSize: '0', // 接口未返回文件大小,使用默认值
+        fileUrl: url,
+      };
+    });
+  };
+
+  const getDetail = async () => {
+    if (!props.id || isNaN(props.id) || props.id <= 0) {
+      console.error('无效的ID:', props.id);
+      ElMessage.error('缺少有效的考核对象ID');
+      return;
+    }
+    try {
+      console.log('调用详情接口,ID:', props.id);
+      const detail = await querySecurityExamineIssueDetail(props.id);
+      if (!detail) {
+        console.warn('详情接口返回空数据');
+        return;
+      }
+
+      // 保存原始详情数据,用于提交
+      detailData.value = detail;
+
+      // 获取是否显示复核不通过按钮
+      isSelfApproveButton.value = detail.isSelfApproveButton === true;
+
+      // 映射表单字段
+      ruleFormData.value.evaluationTitle = detail.exName || ''; // 考核表名称
+      ruleFormData.value.attachmentDocument = parseAttachmentsToFileList(detail.attachments); // 附件文档
+      ruleFormData.value.scoringDescription = ''; // 评分说明(接口暂无此字段,留空)
+
+      // 映射考核项目列表(scores 数组)
+      if (detail.scores && detail.scores.length > 0) {
+        evaluationItems.value = detail.scores.map((score) => ({
+          id: score.id, // 保留评分项ID,用于提交
+          isAdd: score.isAdd !== undefined ? score.isAdd : (score.selfScore >= 0 ? 1 : 0), // 是否加分项(0-否,1-是)
+          isAddName: score.isAdd === 1 ? '加分项' : '减分项', // 加减分项名称
+          scoreType: score.isAdd === 1 ? '加分项' : '减分项', // 加减分项(用于显示,兼容旧字段)
+          evaluationItem: score.exProgram || '', // 考核项目
+          evaluationContent: score.exContent || '', // 考核内容
+          scoringMethod: score.scoringWay || '', // 评分方式
+          selfScore: score.selfScore || 0, // 自评得分
+          reviewUserName: score.reviewUserName || '-', // 复核人姓名(从详情顶层获取)
+          reviewScore: score.reviewScore || 0, // 复核得分
+          materialDescription: score.attachments || '', // 资料说明(使用附件字段)
+          isReviewInput: score.isReviewInput == true, // 是否显示复核得分输入框
+        }));
+      } else {
+        evaluationItems.value = [];
+      }
+    } catch (e) {
+      console.error('获取考核对象详情失败:', e);
+      ElMessage.error('获取详情失败,请重试');
+    }
+  };
+
+  const handleSubmit = async () => {
+    const res = await handleValidate();
+    if (!res) return;
+    try {
+      if (!detailData.value) {
+        ElMessage.error('数据加载失败,请刷新后重试');
+        return;
+      }
+
+      // 使用详情原始数据,更新复核得分和isAdd字段
+      const submitData = {
+        ...detailData.value,
+        scores: detailData.value.scores?.map((score: any) => {
+          // 找到对应的复核得分和isAdd
+          const item = evaluationItems.value.find((item) => item.id === score.id);
+          return {
+            ...score,
+            reviewScore: item ? Number(item.reviewScore) || 0 : score.reviewScore || 0,
+            isAdd: item ? (item.isAdd !== undefined ? item.isAdd : (item.selfScore >= 0 ? 1 : 0)) : (score.isAdd !== undefined ? score.isAdd : (score.selfScore >= 0 ? 1 : 0)),
+          };
+        }) || [],
+      };
+
+      if (isAudit.value) {
+        // 审核通过
+        await updateSecurityExamineIssueApproveAgree(props.id);
+        ElMessage.success('审核通过操作成功');
+      } else {
+        // 评分模式
+        if (isSelfApproveButton.value) {
+          // isSelfApproveButton 为 true,调用复核同意接口
+          await updateSecurityExamineIssueReviewAgree(submitData);
+          ElMessage.success('复核同意操作成功');
+        } else {
+          // isSelfApproveButton 为 false,调用提交接口
+          await updateSecurityExamineIssueReviewSubmit(submitData);
+          ElMessage.success('复核提交成功');
+        }
+      }
+      router.back();
+    } catch (e: any) {
+      console.error('提交失败:', e);
+      ElMessage.error(e?.message || '提交失败,请重试');
+    }
+  };
+
+  const confirmReject = async () => {
+    if (!rejectReason.value || !rejectReason.value.trim()) {
+      ElMessage.warning('请填写驳回原因');
+      return;
+    }
+
+    try {
+      if (rejectType.value === 'review') {
+        await updateSecurityExamineIssueReviewDisagree(props.id, rejectReason.value.trim());
+        ElMessage.success('复核不通过操作成功');
+      } else {
+        await updateSecurityExamineIssueApproveDisagree(props.id, rejectReason.value.trim());
+        ElMessage.success('审核不通过操作成功');
+      }
+      closeRejectDialog();
+      router.back();
+    } catch (e: any) {
+      console.error('操作失败:', e);
+      ElMessage.error(e?.message || '操作失败,请重试');
+    }
+  };
+
+  // 监听 props.id 变化,重新加载数据
+  watch(
+    () => props.id,
+    (newId) => {
+      if (newId && !isNaN(newId) && newId > 0) {
+        getDetail();
+      }
+    },
+    { immediate: true },
+  );
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+
+  .evaluation-form {
+    display: flex;
+    flex-direction: column;
+    width: 600px;
+    gap: 32px;
+
+    :deep(.el-form-item) {
+      margin-bottom: 0;
+    }
+
+    :deep(.el-form-item__label) {
+      padding: 0;
+    }
+  }
+
+  .evaluation-items-section {
+    margin-top: 32px;
+  }
+
+  .section-header {
+    display: flex;
+    gap: 10px;
+    margin-bottom: 20px;
+  }
+
+  .evaluation-items-table {
+    width: 100%;
+  }
+
+  .upload-files-disabled {
+    pointer-events: none;
+    opacity: 0.6;
+
+    :deep(.upload-button) {
+      cursor: not-allowed;
+      background-color: #f5f5f5;
+      color: #aaa;
+    }
+
+    :deep(.delete-button) {
+      display: none;
+    }
+  }
+</style>

+ 774 - 0
src/views/production-safety/safetyAssessment/evaluationSystem/components/EvaluationTarget.vue

@@ -0,0 +1,774 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="evaluation-header">
+        <h1 class="evaluation-title">{{ evaluationDetail.exName || '考核对象' }}</h1>
+        <div class="evaluation-meta">
+          <span>考核部门: {{ evaluationDetail.deptNames || '-' }}</span>
+          <span>创建人: {{ evaluationDetail.createdUserName || '-' }}</span>
+          <span>创建时间: {{ formatDateTime(evaluationDetail.createdAt) || '-' }}</span>
+        </div>
+      </div>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="search-table-container">
+        <!-- tabs 放在搜索条件上面 -->
+        <div class="header-tabs-wrapper" v-if="showViewTab">
+          <!-- 顶部切换:按状态 / 按部门考核结果(先进集体排名) / 先进个人 -->
+          <el-tabs v-model="viewTab">
+            <el-tab-pane v-if="operate !== 'evaluationSystem-advanced-group'" label="状态" name="status" />
+            <el-tab-pane label="部门考核结果" name="dept" />
+            <el-tab-pane v-if="operate !== 'evaluationSystem-advanced-group'" label="先进个人" name="person" />
+          </el-tabs>
+          <!-- 状态模式下,展示原有状态 tabs -->
+          <el-tabs
+            v-if="viewTab === 'status'"
+            v-model="activeTab"
+            @tab-change="handleTabChange"
+          >
+            <template v-if="showAllTabs">
+              <el-tab-pane label="全部" name="ALL" />
+              <el-tab-pane
+                v-for="item in EVALUATION_SYSTEM_STATUS_OPTIONS"
+                :key="item.value"
+                :label="item.label"
+                :name="String(item.value)"
+              />
+            </template>
+            <template v-else>
+              <el-tab-pane :label="singleTabLabel" :name="singleStatus" />
+            </template>
+          </el-tabs>
+        </div>
+        <!-- 从考核对象入口进来(非先进集体/先进个人),仅展示状态 tabs -->
+        <div class="header-tabs-wrapper" v-else>
+          <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+            <template v-if="showAllTabs">
+              <el-tab-pane label="全部" name="ALL" />
+              <el-tab-pane
+                v-for="item in EVALUATION_SYSTEM_STATUS_OPTIONS"
+                :key="item.value"
+                :label="item.label"
+                :name="String(item.value)"
+              />
+            </template>
+            <template v-else>
+              <el-tab-pane :label="singleTabLabel" :name="singleStatus" />
+            </template>
+          </el-tabs>
+        </div>
+
+        <header>
+          <div class="act-search">
+            <section class="select-box">
+              <!-- 部门考核结果视图:显示部门和部门负责人输入框 -->
+              <template v-if="viewTab === 'dept' && operate === 'evaluationSystem-advanced-group'">
+                <div class="select-box--item">
+                  <span>部门:</span>
+                  <el-input
+                    v-model="tableQuery.queryParam.deptName"
+                    placeholder="请输入部门名称"
+                    clearable
+                  />
+                </div>
+                <div class="select-box--item">
+                  <span>部门负责人:</span>
+                  <el-input
+                    v-model="tableQuery.queryParam.userName"
+                    placeholder="请输入部门负责人"
+                    clearable
+                  />
+                </div>
+                <!-- <div class="select-box--item">
+                  <span>时间范围:</span>
+                  <el-date-picker
+                    v-model="tableQuery.queryParam.dateRange"
+                    type="daterange"
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    value-format="YYYY-MM-DD"
+                    format="YYYY-MM-DD"
+                  />
+                </div> -->
+              </template>
+              <!-- 其他视图:显示原有搜索条件 -->
+              <template v-else>
+                <!-- 状态筛选仅在"全部"tab 下显示(包含文字和下拉框) -->
+                <div
+                  class="select-box--item"
+                  v-if="(showViewTab && viewTab === 'status' && activeTab === 'ALL') || (!showViewTab && activeTab === 'ALL')"
+                >
+                  <span>状态:</span>
+                  <el-select
+                    v-model="tableQuery.queryParam.status"
+                    placeholder="请选择状态"
+                    clearable
+                  >
+                    <el-option
+                      v-for="item in EVALUATION_SYSTEM_STATUS_OPTIONS"
+                      :key="item.value"
+                      :label="item.label"
+                      :value="item.value"
+                    />
+                  </el-select>
+                </div>
+                <div class="select-box--item">
+                  <span>考核对象:</span>
+                  <el-cascader
+                    ref="targetDeptCascaderRef"
+                    v-model="targetDeptId"
+                    :options="deptTree"
+                    :props="cascaderDeptProp"
+                    :show-all-levels="false"
+                    placeholder="请选择考核对象部门"
+                    filterable
+                    @change="handleTargetDeptChange"
+                  />
+                </div>
+                <div class="select-box--item">
+                  <span>时间范围:</span>
+                  <el-date-picker
+                    v-model="tableQuery.queryParam.dateRange"
+                    type="daterange"
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    value-format="YYYY-MM-DD"
+                    format="YYYY-MM-DD"
+                  />
+                </div>
+              </template>
+            </section>
+            <section class="search-btn">
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+              <el-button plain class="search-table-container--button" @click="handleExport">
+                导出
+              </el-button>
+              <!-- 先进集体排名 / 先进个人模式下,顶部按钮只保留“导出” -->
+              <!-- <el-button
+                v-if="viewTab === 'status'"
+                plain
+                class="search-table-container--button"
+                @click="handleBatchDelete"
+              >
+                删除
+              </el-button> -->
+            </section>
+          </div>
+        </header>
+
+        <div class="batch-table">
+          <BasicTable
+            ref="basicTableRef"
+            :tableData="tableData"
+            :tableConfig="tableConfig"
+            @update:pageSize="handleSizeChange"
+            @update:pageNumber="handleCurrentChange"
+          >
+            <template #status="scope">
+              <span>
+                {{ EVALUATION_SYSTEM_STATUS_LABEL[String(scope.row.status)] || '-' }}
+              </span>
+            </template>
+            <template #isAdvancedGroup="scope">
+              <span>{{ scope.row.isAdvancedGroup ? '是' : '否' }}</span>
+            </template>
+            <template #evaluationDocument="scope">
+              <div
+                class="file-container--div"
+                v-for="item in parseAttachments(scope.row.evaluationDocument)"
+                :key="item.fileUrl"
+              >
+                <img
+                  class="file-container--div__icon"
+                  @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
+                  :src="FILE_TYPE_ICON[item.fileType]"
+                />
+                <span
+                  class="file-container--div__name"
+                  @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
+                  >{{ item.fileName }}</span
+                >
+                <img
+                  class="file-container--div__download"
+                  :src="DownloadIcon"
+                  @click="downloadFile(item.fileUrl, item.fileName)"
+                />
+              </div>
+            </template>
+            <template #action="scope">
+              <div class="action-container--div" style="justify-content: left">
+                <!-- 先进集体视图:只显示详情 -->
+                <template v-if="viewTab === 'dept' && operate === 'evaluationSystem-advanced-group'">
+                  <ActionButton text="详情" @click="handleViewDetail(scope.row.id)" />
+                </template>
+                <!-- 待反馈:删除 / 作废 -->
+                <template v-else-if="Number(scope.row.status) === 2">
+                  <ActionButton
+                    text="删除"
+                    :popconfirm="{
+                      title: '确定要删除?',
+                    }"
+                    @confirm="handleDelete(scope.row.id)"
+                  />
+                  <ActionButton
+                    text="作废"
+                    :popconfirm="{
+                      title: '确定要作废?',
+                    }"
+                    @confirm="handleCancel(scope.row.id)"
+                  />
+                </template>
+
+                <!-- 待评分:评分 / 删除 / 作废 -->
+                <template v-else-if="Number(scope.row.status) === 3">
+                  <ActionButton text="评分" @click="handleScore(scope.row.id)" />
+                  <ActionButton
+                    text="删除"
+                    :popconfirm="{
+                      title: '确定要删除?',
+                    }"
+                    @confirm="handleDelete(scope.row.id)"
+                  />
+                  <ActionButton
+                    text="作废"
+                    :popconfirm="{
+                      title: '确定要作废?',
+                    }"
+                    @confirm="handleCancel(scope.row.id)"
+                  />
+                </template>
+
+                <!-- 待审核:审核 / 删除 / 作废 -->
+                <template v-else-if="Number(scope.row.status) === 4">
+                  <ActionButton text="审核" @click="handleAudit(scope.row.id)" />
+                  <ActionButton
+                    text="删除"
+                    :popconfirm="{
+                      title: '确定要删除?',
+                    }"
+                    @confirm="handleDelete(scope.row.id)"
+                  />
+                  <ActionButton
+                    text="作废"
+                    :popconfirm="{
+                      title: '确定要作废?',
+                    }"
+                    @confirm="handleCancel(scope.row.id)"
+                  />
+                </template>
+
+                <!-- 已作废:删除 -->
+                <template v-else-if="Number(scope.row.status) === 5">
+                  <ActionButton
+                    text="删除"
+                    :popconfirm="{
+                      title: '确定要删除?',
+                    }"
+                    @confirm="handleDelete(scope.row.id)"
+                  />
+                </template>
+
+                <!-- 已完成:删除 -->
+                <template v-else-if="Number(scope.row.status) === 1">
+                  <ActionButton
+                    text="删除"
+                    :popconfirm="{
+                      title: '确定要删除?',
+                    }"
+                    @confirm="handleDelete(scope.row.id)"
+                  />
+                </template>
+              </div>
+            </template>
+          </BasicTable>
+        </div>
+      </div>
+    </main>
+    <PreviewOnline ref="previewOnlineRef" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { computed, onMounted, reactive, ref, watch } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import BasicTable from '@/components/BasicTable.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import ActionButton from '@/components/ActionButton.vue';
+  import { TABLE_OPTIONS } from '../configs/tables';
+  import {
+    EVALUATION_TARGET_TABLE_COLUMNS,
+    EVALUATION_ADVANCED_GROUP_TABLE_COLUMNS,
+    EVALUATION_ADVANCED_PERSON_TABLE_COLUMNS,
+  } from '../configs/targetTables';
+  import { EVALUATION_SYSTEM_STATUS_OPTIONS, EVALUATION_SYSTEM_STATUS_LABEL } from '../configs/status';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import { getAllDepartments } from '@/api/auth/dept';
+  import type { DeptTree } from '@/types/dept/type';
+  import {
+    querySecurityExamineIssue,
+    querySecurityExamineDetail,
+    updateSecurityExamineIssueRepeal,
+    deleteSecurityExamineIssue,
+    querySecurityExamineIssueAdvanced,
+  } from '@/api/evaluationSystem';
+  import type { QuerySecurityExamineIssueParams, EvaluationSystemItem } from '@/api/evaluationSystem';
+  import { ElMessage } from 'element-plus';
+  import DownloadIcon from '@/views/disaster/disaster-control/src/svg/download.svg';
+  import { downloadFile } from '@/views/disaster/utils';
+  import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
+  import { FILE_TYPE_ICON } from '@/components/UploadFiles/constants';
+
+  const route = useRoute();
+  const router = useRouter();
+  const operate = route.query.operate as string | undefined;
+
+  // 考核表详情
+  const evaluationDetail = ref<Partial<EvaluationSystemItem>>({});
+
+  // 是否展示“状态 / 部门考核结果 / 先进个人”顶层 tabs
+  const showViewTab = computed(
+    () =>
+      operate === 'evaluationSystem-advanced-group' ||
+      operate === 'evaluationSystem-advanced-person',
+  );
+
+  // 顶部 tabs:按状态 / 按部门考核结果(先进集体排名)/ 先进个人
+  const viewTab = ref<'status' | 'dept' | 'person'>(
+    operate === 'evaluationSystem-advanced-group'
+      ? 'dept'
+      : operate === 'evaluationSystem-advanced-person'
+        ? 'person'
+        : 'status',
+  );
+  // 如果从上级页传入 singleStatus(例如 3:待评分),则只展示对应状态的 tab
+  const singleStatus = (route.query.singleStatus as string | undefined) || '';
+  const showAllTabs = computed(() => !singleStatus);
+
+  const singleTabLabel = computed(() => {
+    if (!singleStatus) return '全部';
+    const found = EVALUATION_SYSTEM_STATUS_OPTIONS.find(
+      (item) => String(item.value) === String(singleStatus),
+    );
+    return found?.label || '全部';
+  });
+
+  // tabs(ALL:全部,其它为对应状态码字符串)
+  const activeTab = ref<'ALL' | '0' | '2' | '3' | '4' | '5' | '1'>(
+    (singleStatus as any) || 'ALL',
+  );
+
+  // 表格
+  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+
+  const { tableConfig, pagination } = useTableConfig(EVALUATION_TARGET_TABLE_COLUMNS, TABLE_OPTIONS);
+
+  const tableData = ref<any[]>([]);
+
+  // 部门树(复用下发考核表的部门下拉逻辑)
+  const targetDeptCascaderRef = ref();
+  const deptTree = ref<DeptTree[]>([]);
+  const targetDeptId = ref<number | null>(null);
+  const cascaderDeptProp = {
+    checkStrictly: true,
+    expandTrigger: 'hover' as const,
+    value: 'id',
+    label: 'deptName',
+    emitPath: false,
+  };
+
+  const tableQuery = reactive<QueryPageRequest<any>>({
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {
+      status: '',
+      target: '',
+      deptName: '', // 部门名称(用于部门考核结果视图)
+      userName: '', // 部门负责人(用于部门考核结果视图)
+      dateRange: null as any,
+      startTime: '',
+      endTime: '',
+    },
+  });
+
+  // 从路由获取考核表ID(如果有)
+  const evaluationId = computed(() => {
+    const id = route.query.id;
+    return id ? Number(id) : undefined;
+  });
+
+  // 如果是单状态模式(例如待评分),初始化筛选条件
+  if (singleStatus) {
+    tableQuery.queryParam.status = Number(singleStatus);
+  }
+
+  // 根据顶部 tabs 切换表格列配置
+  watch(
+    viewTab,
+    (val) => {
+      if (val === 'status') {
+        tableConfig.columns = EVALUATION_TARGET_TABLE_COLUMNS;
+      } else if (val === 'dept') {
+        tableConfig.columns = EVALUATION_ADVANCED_GROUP_TABLE_COLUMNS;
+      } else {
+        tableConfig.columns = EVALUATION_ADVANCED_PERSON_TABLE_COLUMNS;
+      }
+      // 切换视图时重查
+      handleSearch();
+    },
+    { immediate: true },
+  );
+
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    tableQuery.pageSize = value;
+    getTableData();
+  };
+
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    tableQuery.pageNumber = value;
+    getTableData();
+  };
+
+  async function getTableData() {
+    tableConfig.loading = true;
+    try {
+      // 如果是先进集体(部门考核结果视图),调用先进集体接口
+      if (viewTab.value === 'dept' && operate === 'evaluationSystem-advanced-group') {
+        const params = {
+          pageNumber: tableQuery.pageNumber,
+          pageSize: tableQuery.pageSize,
+          queryParam: {
+            psemId: evaluationId.value, // 考核表ID(从路由参数获取)
+            planStartTime: tableQuery.queryParam.startTime || undefined,
+            planEndTime: tableQuery.queryParam.endTime || undefined,
+            deptName: tableQuery.queryParam.deptName || undefined, // 部门名称
+            userName: tableQuery.queryParam.userName || undefined, // 部门负责人
+          },
+        };
+
+        const res = await querySecurityExamineIssueAdvanced(params);
+        if (res) {
+          // 映射返回数据字段到表格字段(先进集体排名表格)
+          tableData.value = res.records.map((item) => {
+            // 计算总分数、加分项分数、减分项分数
+            let totalScore = 0;
+            let addScore = 0;
+            let subtractScore = 0;
+
+            if (item.scores && item.scores.length > 0) {
+              item.scores.forEach((score: any) => {
+                const scoreValue = Number(score.reviewScore) || 0;
+                totalScore += scoreValue;
+                if (scoreValue >= 0) {
+                  addScore += scoreValue;
+                } else {
+                  subtractScore += Math.abs(scoreValue);
+                }
+              });
+            }
+
+            return {
+              id: item.id,
+              departmentName: item.deptName, // 部门考核结果
+              isAdvancedGroup: item.isAdvancedGroup || false, // 是否先进集体(需要接口返回,暂时使用 false)
+              departmentSort: item.scoreRank || 0, // 部门排序(使用排名)
+              departmentLeader: item.deptUserName || '-', // 部门负责人
+              totalScore, // 总分数
+              addScore, // 加分项分数
+              subtractScore, // 减分项分数
+              // 保留原始数据
+              psemId: item.psemId,
+              deptId: item.deptId,
+              scores: item.scores,
+              scoreRank: item.scoreRank,
+            };
+          });
+          pagination.total = res.totalRow;
+        }
+      } else if (viewTab.value === 'status') {
+        // 状态视图下调用原有接口
+        const params: QuerySecurityExamineIssueParams = {
+          pageNumber: tableQuery.pageNumber,
+          pageSize: tableQuery.pageSize,
+          queryParam: {
+            psemId: evaluationId.value, // 考核表ID(从路由参数获取)
+            planStartTime: tableQuery.queryParam.startTime || undefined,
+            planEndTime: tableQuery.queryParam.endTime || undefined,
+            deptName: tableQuery.queryParam.target || undefined,
+            status:
+              tableQuery.queryParam.status !== '' && tableQuery.queryParam.status !== null
+                ? Number(tableQuery.queryParam.status)
+                : undefined,
+          },
+        };
+
+        const res = await querySecurityExamineIssue(params);
+        if (res) {
+          // 映射返回数据字段到表格字段
+          tableData.value = res.records.map((item) => ({
+            id: item.id,
+            evaluationTableName: item.exName, // 考核表名称
+            status: item.status, // 状态
+            statusName: item.statusName, // 状态名称
+            issueDepartment: item.deptName, // 下发部门(部门考核结果)
+            departmentLeader: item.deptUserName || '-', // 部门负责人
+            contactPhone: item.deptUserLink || '-', // 联系方式
+            evaluationDocument: item.attachments, // 考核文档
+            plannedCompletionTime: item.planEndTime || '-', // 计划完成时间(使用计划结束时间)
+            // 保留原始数据,供其他操作使用
+            psemId: item.psemId,
+            deptId: item.deptId,
+            scores: item.scores,
+            scoreRank: item.scoreRank,
+          }));
+          pagination.total = res.totalRow;
+        }
+      } else {
+        // 其他视图(如先进个人)暂时为空
+        tableData.value = [];
+        pagination.total = 0;
+      }
+    } catch (e) {
+      console.error('获取列表失败:', e);
+      tableData.value = [];
+      pagination.total = 0;
+    } finally {
+      tableConfig.loading = false;
+    }
+  }
+
+  const getDeptTreeData = async () => {
+    try {
+      const res = await getAllDepartments();
+      deptTree.value = res?.[0]?.children ?? [];
+    } catch (e) {
+      console.error('获取部门树失败:', e);
+    }
+  };
+
+  const handleTargetDeptChange = () => {
+    const nodes = targetDeptCascaderRef.value?.getCheckedNodes?.();
+    tableQuery.queryParam.target = nodes?.[0]?.label ?? '';
+  };
+
+  function handleSearch() {
+    if (tableQuery.queryParam.dateRange && tableQuery.queryParam.dateRange.length === 2) {
+      tableQuery.queryParam.startTime = tableQuery.queryParam.dateRange[0];
+      tableQuery.queryParam.endTime = tableQuery.queryParam.dateRange[1];
+    } else {
+      tableQuery.queryParam.startTime = '';
+      tableQuery.queryParam.endTime = '';
+    }
+    pagination.pageNumber = 1;
+    tableQuery.pageNumber = 1;
+    getTableData();
+  }
+
+  const handleReset = () => {
+    tableQuery.queryParam.status = '';
+    tableQuery.queryParam.target = '';
+    tableQuery.queryParam.deptName = '';
+    tableQuery.queryParam.userName = '';
+    tableQuery.queryParam.dateRange = null;
+    targetDeptId.value = null;
+    handleSearch();
+  };
+
+  const handleTabChange = (name: string | number) => {
+    if (name === 'ALL') {
+      tableQuery.queryParam.status = '';
+    } else {
+      tableQuery.queryParam.status = Number(name);
+    }
+    handleSearch();
+  };
+
+  const handleExport = () => {
+    // TODO: 导出当前筛选结果
+    console.log('export evaluation target list', tableQuery);
+  };
+
+  const handleBatchDelete = () => {
+    // TODO: 批量删除
+    console.log('batch delete evaluation targets');
+  };
+
+  const handleDelete = async (id: number) => {
+    try {
+      await deleteSecurityExamineIssue(id);
+      ElMessage.success('删除成功');
+      getTableData();
+    } catch (e: any) {
+      console.error('删除失败:', e);
+      ElMessage.error(e?.message || '删除失败,请重试');
+    }
+  };
+
+  const handleCancel = async (id: number) => {
+    try {
+      await updateSecurityExamineIssueRepeal(id);
+      ElMessage.success('作废成功');
+      getTableData();
+    } catch (e: any) {
+      console.error('作废失败:', e);
+      ElMessage.error(e?.message || '作废失败,请重试');
+    }
+  };
+
+  const handleScore = (id: number) => {
+    router.push({
+      name: 'EvaluationSystemItem',
+      query: {
+        id,
+        operate: 'evaluationSystem-feedback',
+      },
+    });
+  };
+
+  const handleAudit = (id: number) => {
+    router.push({
+      name: 'EvaluationSystemItem',
+      query: {
+        id,
+        operate: 'evaluationSystem-audit',
+      },
+    });
+  };
+
+  const handleViewDetail = (id: number) => {
+    router.push({
+      name: 'EvaluationSystemItem',
+      query: {
+        id,
+        operate: 'evaluationSystem-feedback-view',
+        fromDeptView: viewTab.value === 'dept' && operate === 'evaluationSystem-advanced-group' ? 'true' : undefined,
+      },
+    });
+  };
+
+  // 预览
+  const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
+  const previewOnline = (url: string | undefined, type: keyof typeof FILE_TYPE_ICON) => {
+    if (url) {
+      previewOnlineRef.value?.open(url, type);
+    }
+  };
+
+  // 解析逗号分隔的URL字符串为文件列表
+  const parseAttachments = (attachmentsStr: string | undefined): Array<{
+    fileUrl: string;
+    fileName: string;
+    fileType: string;
+  }> => {
+    if (!attachmentsStr || !attachmentsStr.trim()) {
+      return [];
+    }
+
+    // 按逗号分割URL
+    const urls = attachmentsStr.split(',').map(url => url.trim()).filter(url => url);
+
+    return urls.map((url) => {
+      // 从URL中提取文件名
+      const urlParts = url.split('/');
+      const fileName = urlParts[urlParts.length - 1] || '未知文件';
+
+      // 根据文件扩展名判断文件类型
+      const extension = fileName.split('.').pop()?.toLowerCase() || '';
+      let fileType = 'pdf';
+      if (extension === 'doc' || extension === 'docx') {
+        fileType = 'word';
+      } else if (extension === 'xls' || extension === 'xlsx') {
+        fileType = 'excel';
+      } else if (extension === 'ppt' || extension === 'pptx') {
+        fileType = 'ppt';
+      }
+
+      return {
+        fileUrl: url,
+        fileName,
+        fileType,
+      };
+    });
+  };
+
+  // 格式化日期时间
+  const formatDateTime = (dateTimeStr: string | undefined): string => {
+    if (!dateTimeStr) return '';
+    try {
+      const date = new Date(dateTimeStr);
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      const hours = String(date.getHours()).padStart(2, '0');
+      const minutes = String(date.getMinutes()).padStart(2, '0');
+      const seconds = String(date.getSeconds()).padStart(2, '0');
+      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+    } catch (e) {
+      return dateTimeStr;
+    }
+  };
+
+  // 获取考核表详情
+  const getEvaluationDetail = async () => {
+    if (!evaluationId.value) return;
+    try {
+      const res = await querySecurityExamineDetail(evaluationId.value);
+      if (res) {
+        evaluationDetail.value = {
+          exName: res.exName,
+          deptNames: res.deptNames,
+          createdUserName: res.createdUserName,
+          createdAt: res.createdAt,
+        };
+      }
+    } catch (e) {
+      console.error('获取考核表详情失败:', e);
+    }
+  };
+
+  onMounted(() => {
+    getDeptTreeData();
+    getTableData();
+    getEvaluationDetail();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+  @use '@/styles/basic-table-file.scss' as *;
+  @use '@/views/traffic/violation/style/act-search-table.scss' as *;
+
+  .safety-platform-container__header {
+    padding-bottom: 0 !important;
+  }
+
+  .evaluation-header {
+    width: 100%;
+  }
+
+  .evaluation-title {
+    font-size: 20px;
+    font-weight: bold;
+    margin: 0 0 12px 0;
+    color: #333;
+  }
+
+  .evaluation-meta {
+    margin-bottom: 12px;
+    display: flex;
+    gap: 24px;
+    font-size: 14px;
+    color: #666;
+  }
+
+  .evaluation-meta span {
+    white-space: nowrap;
+  }
+</style>
+

+ 39 - 0
src/views/production-safety/safetyAssessment/evaluationSystem/configs/form.ts

@@ -0,0 +1,39 @@
+import { FormConfig } from '@/types/basic-form';
+
+export const EVALUATION_SYSTEM_FORM_CONFIG: FormConfig[] = [
+  {
+    prop: 'evaluationTableName',
+    label: '考核表名称:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入考核表名称',
+    },
+  },
+  {
+    prop: 'attachmentDocument',
+    label: '上传附件文档:',
+    slot: 'attachmentDocument',
+  },
+  {
+    prop: 'scoringDescription',
+    label: '评分说明:',
+    component: 'ElInput',
+    componentProps: {
+      type: 'textarea',
+      rows: 5,
+      placeholder: '请输入评分说明',
+    },
+  },
+];
+
+export const EVALUATION_SYSTEM_FORM_DATA = {
+  evaluationTableName: '',
+  attachmentDocument: [],
+  scoringDescription: '',
+};
+
+export const EVALUATION_SYSTEM_FORM_RULES = {
+  evaluationTableName: [{ required: true, message: '请输入考核表名称', trigger: 'blur' }],
+  attachmentDocument: [{ required: true, message: '请上传附件文档', trigger: 'change' }],
+  scoringDescription: [{ required: true, message: '请输入评分说明', trigger: 'blur' }],
+};

+ 22 - 0
src/views/production-safety/safetyAssessment/evaluationSystem/configs/status.ts

@@ -0,0 +1,22 @@
+// 安全考核管理状态配置(搜索条件使用数值状态码)
+// 0-未下发 / 2-待反馈 / 3-待评分 / 4-待审核 / 5-已作废 / 1-已完成
+export const EVALUATION_SYSTEM_STATUS_OPTIONS = [
+  { label: '未下发', value: 0 },
+  { label: '待反馈', value: 2 },
+  { label: '待评分', value: 3 },
+  { label: '待审核', value: 4 },
+  { label: '已作废', value: 5 },
+  { label: '已完成', value: 1 },
+];
+
+export const EVALUATION_SYSTEM_STATUS_LABEL: Record<string, string> = {
+  '0': '未下发',
+  '2': '待反馈',
+  '3': '待评分',
+  '4': '待审核',
+  '5': '已作废',
+  '1': '已完成',
+};
+
+// 所属部门由输入框搜索,不再使用下拉配置
+export const EVALUATION_SYSTEM_DEPARTMENT_OPTIONS: never[] = [];

+ 71 - 0
src/views/production-safety/safetyAssessment/evaluationSystem/configs/tables.ts

@@ -0,0 +1,71 @@
+import type { TableColumnProps } from '@/types/basic-table';
+
+// 基础表格样式配置
+export const TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  loading: true,
+  maxHeight: 'calc(70vh - 150px)',
+};
+
+export const EVALUATION_SYSTEM_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '考核表名称',
+    // prop: 'evaluationTableName',
+    slot: 'evaluationTableName',
+    align: 'left',
+    minWidth: '150px',
+    showOverflowTooltip: true,
+  },
+  {
+    label: '状态',
+    prop: 'status',
+    slot: 'status',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '下发部门',
+    prop: 'issueDepartment',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '下发数',
+    prop: 'issueCount',
+    align: 'center',
+    minWidth: '100px',
+  },
+  {
+    label: '反馈数',
+    prop: 'feedbackCount',
+    align: 'center',
+    minWidth: '100px',
+  },
+  {
+    label: '反馈比例',
+    prop: 'feedbackRatio',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '考核文档',
+    // prop: 'evaluationDocument',
+    slot: 'evaluationDocument',
+    align: 'left',
+    minWidth: '150px',
+  },
+  {
+    label: '计划完成时间',
+    prop: 'plannedCompletionTime',
+    align: 'left',
+    minWidth: '150px',
+  },
+  {
+    label: '操作',
+    prop: 'action',
+    slot: 'action',
+    fixed: 'right',
+    minWidth: '300px',
+    align: 'left',
+  },
+];

+ 169 - 0
src/views/production-safety/safetyAssessment/evaluationSystem/configs/targetTables.ts

@@ -0,0 +1,169 @@
+import type { TableColumnProps } from '@/types/basic-table';
+
+export const EVALUATION_TARGET_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '考核表名称',
+    prop: 'evaluationTableName',
+    align: 'left',
+    minWidth: '150px',
+  },
+  {
+    label: '状态',
+    prop: 'status',
+    slot: 'status',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '下发部门',
+    prop: 'issueDepartment',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '部门负责人',
+    prop: 'departmentLeader',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '联系方式',
+    prop: 'contactPhone',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '考核文档',
+    prop: 'evaluationDocument',
+    slot: 'evaluationDocument',
+    align: 'left',
+    minWidth: '150px',
+  },
+  {
+    label: '计划完成时间',
+    prop: 'plannedCompletionTime',
+    align: 'left',
+    minWidth: '150px',
+  },
+  {
+    label: '操作',
+    // prop: 'action',
+    slot: 'action',
+    fixed: 'right',
+    minWidth: '300px',
+    align: 'left',
+  },
+];
+
+// 先进集体排名表格列
+export const EVALUATION_ADVANCED_GROUP_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '编号',
+    type: 'index',
+    align: 'center',
+    width: '80px',
+  },
+  {
+    label: '部门名称',
+    prop: 'departmentName',
+    align: 'left',
+    minWidth: '150px',
+  },
+  {
+    label: '是否先进集体',
+    prop: 'isAdvancedGroup',
+    slot: 'isAdvancedGroup',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '部门排序',
+    prop: 'departmentSort',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '部门负责人',
+    prop: 'departmentLeader',
+    align: 'left',
+    minWidth: '140px',
+  },
+  {
+    label: '总分数',
+    prop: 'totalScore',
+    align: 'center',
+    minWidth: '100px',
+  },
+  {
+    label: '加分项分数',
+    prop: 'addScore',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '减分项分数',
+    prop: 'subtractScore',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '操作',
+    slot: 'action',
+    fixed: 'right',
+    minWidth: '100px',
+    align: 'left',
+  },
+];
+
+// 先进个人信息表格列(部门端)
+export const EVALUATION_ADVANCED_PERSON_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '员工工号',
+    prop: 'employeeCode',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '员工姓名',
+    prop: 'employeeName',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '员工联系方式',
+    prop: 'employeeContact',
+    align: 'left',
+    minWidth: '140px',
+  },
+  {
+    label: '所属部门',
+    prop: 'deptName',
+    align: 'left',
+    minWidth: '150px',
+  },
+  {
+    label: '部门负责人',
+    prop: 'departmentLeader',
+    align: 'left',
+    minWidth: '140px',
+  },
+  {
+    label: '先进个人描述',
+    prop: 'remark',
+    align: 'left',
+    minWidth: '160px',
+  },
+  {
+    label: '上报日期',
+    prop: 'reportDate',
+    align: 'left',
+    minWidth: '150px',
+  },
+  {
+    label: '操作',
+    slot: 'action',
+    fixed: 'right',
+    minWidth: '150px',
+    align: 'left',
+  },
+];

+ 7 - 0
src/views/production-safety/safetyAssessment/evaluationSystem/constants.ts

@@ -0,0 +1,7 @@
+// 安全考核管理 tabs 配置
+export const EVALUATION_SYSTEM_TABS = [
+  { label: '全部', value: 'all' },
+  { label: '院领导', value: 'leadership' },
+  { label: '安全管理部', value: 'safety-management' },
+  { label: '质量管理部', value: 'quality-management' },
+];

+ 710 - 0
src/views/production-safety/safetyAssessment/evaluationSystem/evaluationSystem.vue

@@ -0,0 +1,710 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="breadcrumb-title"> 安全考核管理(管) </div>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header>
+          <div style="position: relative">
+            <el-button type="primary" class="search-table-container--button" @click="handleCreate">
+              添加
+            </el-button>
+          </div>
+
+          <div class="act-search">
+            <section class="select-box">
+              <div class="select-box--item">
+                <span>安全考核表名称:</span>
+                <el-input
+                  v-model="tableQuery.queryParam.exName"
+                  placeholder="搜索安全考核表名称"
+                  class="act-search-input"
+                />
+              </div>
+              <div class="select-box--item">
+                <span>状态:</span>
+                <el-select
+                  v-model="tableQuery.queryParam.status"
+                  placeholder="请选择状态"
+                  clearable
+                >
+                  <el-option
+                    v-for="item in EVALUATION_SYSTEM_STATUS_OPTIONS"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </el-select>
+              </div>
+              <div class="select-box--item">
+                <span>所属部门:</span>
+                <el-input
+                  v-model="tableQuery.queryParam.deptName"
+                  placeholder="请输入所属部门"
+                  class="act-search-input"
+                />
+              </div>
+              <div>
+                <span>计划日期范围:</span>
+                <el-date-picker
+                  v-model="dateRange"
+                  type="daterange"
+                  range-separator="至"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                  value-format="YYYY-MM-DD"
+                  format="YYYY-MM-DD"
+                />
+              </div>
+            </section>
+            <section class="search-btn">
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+            </section>
+          </div>
+        </header>
+
+        <div class="batch-table">
+          <BasicTable
+            ref="basicTableRef"
+            :tableData="tableData"
+            :tableConfig="tableConfig"
+            @update:pageSize="handleSizeChange"
+            @update:pageNumber="handleCurrentChange"
+          >
+            <template #evaluationTableName="scope">
+              <span class="evaluation-table-name">{{ scope.row.evaluationTableName }}</span>
+            </template>
+            <template #evaluationDocument="scope">
+              <div
+                class="file-container--div"
+                v-for="item in parseAttachments(scope.row.evaluationDocument)"
+                :key="item.fileUrl"
+              >
+                <img
+                  class="file-container--div__icon"
+                  @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
+                  :src="FILE_TYPE_ICON[item.fileType]"
+                />
+                <span
+                  class="file-container--div__name"
+                  @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
+                  >{{ item.fileName }}</span
+                >
+                <img
+                  class="file-container--div__download"
+                  :src="DownloadIcon"
+                  @click="downloadFile(item.fileUrl, item.fileName)"
+                />
+              </div>
+            </template>
+            <template #status="scope">
+              <span>
+                {{ EVALUATION_SYSTEM_STATUS_LABEL[String(scope.row.status)] || '-' }}
+              </span>
+            </template>
+            <template #action="scope">
+              <div class="action-container--div" style="justify-content: left">
+                <!-- 未下发:编辑 / 删除 / 下发 -->
+                <template v-if="Number(scope.row.status) === 0">
+                  <ActionButton text="编辑" @click="handleEdit(scope.row.id)" />
+                  <ActionButton
+                    text="删除"
+                    :popconfirm="{
+                      title: '确定要删除?',
+                    }"
+                    @confirm="handleDelete(scope.row.id)"
+                  />
+                  <ActionButton text="下发" @click="handleIssue(scope.row.id)" />
+                </template>
+
+                <!-- 待反馈:作废 / 考核对象 -->
+                <template v-else-if="Number(scope.row.status) === 2">
+                  <ActionButton
+                    text="作废"
+                    :popconfirm="{
+                      title: '确定要作废?',
+                    }"
+                    @confirm="handleCancel(scope.row.id)"
+                  />
+                  <ActionButton text="考核对象" @click="handleTarget(scope.row.id)" />
+                </template>
+
+                <!-- 待评分:评分 / 作废 / 考核对象 -->
+                <template v-else-if="Number(scope.row.status) === 3">
+                  <ActionButton text="评分" @click="handleScore(scope.row.id)" />
+                  <ActionButton
+                    text="作废"
+                    :popconfirm="{
+                      title: '确定要作废?',
+                    }"
+                    @confirm="handleCancel(scope.row.id)"
+                  />
+                  <ActionButton text="考核对象" @click="handleTarget(scope.row.id)" />
+                </template>
+
+                <!-- 待审核:审核 / 作废 / 考核对象 -->
+                <template v-else-if="Number(scope.row.status) === 4">
+                  <ActionButton text="审核" @click="handleAudit(scope.row.id)" />
+                  <ActionButton
+                    text="作废"
+                    :popconfirm="{
+                      title: '确定要作废?',
+                    }"
+                    @confirm="handleCancel(scope.row.id)"
+                  />
+                  <ActionButton text="考核对象" @click="handleTarget(scope.row.id)" />
+                </template>
+
+                <!-- 已作废:删除 / 考核对象 -->
+                <template v-else-if="Number(scope.row.status) === 5">
+                  <ActionButton
+                    text="删除"
+                    :popconfirm="{
+                      title: '确定要删除?',
+                    }"
+                    @confirm="handleDelete(scope.row.id)"
+                  />
+                  <ActionButton text="考核对象" @click="handleTarget(scope.row.id)" />
+                </template>
+
+                <!-- 已完成:删除 / 考核对象 / 部门排序 / 先进个人 -->
+                <template v-else-if="Number(scope.row.status) === 1">
+                  <ActionButton
+                    text="删除"
+                    :popconfirm="{
+                      title: '确定要删除?',
+                    }"
+                    @confirm="handleDelete(scope.row.id)"
+                  />
+                  <ActionButton text="考核对象" @click="handleTarget(scope.row.id)" />
+                  <ActionButton text="部门排序" @click="handleAdvancedGroup(scope.row.id)" />
+                  <ActionButton text="先进个人" @click="handleAdvancedIndividual(scope.row.id)" />
+                </template>
+              </div>
+            </template>
+          </BasicTable>
+        </div>
+      </div>
+
+      <!-- 下发弹窗 -->
+      <el-dialog v-model="issueDialogVisible" title="下发考核表" width="480px" destroy-on-close>
+        <el-form :model="issueForm" label-width="130px" class="issue-dialog-form">
+          <el-form-item label="部门名称:">
+            <el-cascader
+              ref="issueDeptCascaderRef"
+              v-model="issueDeptId"
+              :options="deptTree"
+              :props="cascaderDeptProp"
+              :show-all-levels="false"
+              placeholder="请选择部门"
+              filterable
+              style="width: 100%"
+              @change="handleIssueDeptChange"
+            />
+          </el-form-item>
+          <el-form-item label="用户名称:">
+            <el-select
+              v-model="issueForm.userGroupId"
+              placeholder="请选择用户组"
+              filterable
+              clearable
+              style="width: 100%"
+            >
+              <el-option
+                v-for="item in userGroupOptions"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="部门自评审核人:">
+            <el-select
+              v-model="issueForm.deptSelfApproveUserId"
+              placeholder="请选择部门自评审核人"
+              filterable
+              clearable
+              style="width: 100%"
+            >
+              <el-option
+                v-for="user in deptSelfApproveUserList"
+                :key="user.id"
+                :label="user.realname"
+                :value="user.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="计划开始日期:">
+            <el-date-picker
+              v-model="issueForm.startDate"
+              type="date"
+              placeholder="请选择计划开始日期"
+              value-format="YYYY-MM-DD"
+              format="YYYY-MM-DD"
+              style="width: 100%"
+              :disabled-date="(date: Date) => {
+                if (issueForm.endDate) {
+                  return date > new Date(issueForm.endDate);
+                }
+                return false;
+              }"
+            />
+          </el-form-item>
+          <el-form-item label="计划结束日期:">
+            <el-date-picker
+              v-model="issueForm.endDate"
+              type="date"
+              placeholder="请选择计划结束日期"
+              value-format="YYYY-MM-DD"
+              format="YYYY-MM-DD"
+              style="width: 100%"
+              :disabled-date="(date: Date) => {
+                if (issueForm.startDate) {
+                  return date < new Date(issueForm.startDate);
+                }
+                return false;
+              }"
+            />
+          </el-form-item>
+        </el-form>
+        <template #footer>
+          <span class="dialog-footer">
+            <el-button @click="issueDialogVisible = false">取消</el-button>
+            <el-button type="primary" @click="handleIssueConfirm">确定</el-button>
+          </span>
+        </template>
+      </el-dialog>
+    </main>
+    <PreviewOnline ref="previewOnlineRef" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { onMounted, reactive, ref } from 'vue';
+  import BasicTable from '@/components/BasicTable.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import ActionButton from '@/components/ActionButton.vue';
+  import { TABLE_OPTIONS, EVALUATION_SYSTEM_TABLE_COLUMNS } from './configs/tables';
+  import { EVALUATION_SYSTEM_STATUS_OPTIONS, EVALUATION_SYSTEM_STATUS_LABEL } from './configs/status';
+  import { useRouter } from 'vue-router';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import {
+    querySecurityExamine,
+    deleteSecurityExamine,
+    saveSecurityExamineIssue,
+    updateSecurityExamineRepeal,
+  } from '@/api/evaluationSystem';
+  import type { EvaluationSystemQueryParam } from '@/api/evaluationSystem';
+  import { ElMessage } from 'element-plus';
+  import DownloadIcon from '@/views/disaster/disaster-control/src/svg/download.svg';
+  import { downloadFile } from '@/views/disaster/utils';
+  import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
+  import { FILE_TYPE_ICON } from '@/components/UploadFiles/constants';
+  import { getAllDepartments } from '@/api/auth/dept';
+  import type { DeptTree } from '@/types/dept/type';
+  import { queryUserGroupPage } from '@/api/system/person-group';
+  import type { PersonGroupListItem } from '@/types/person-group/type';
+  import { getUserList } from '@/api/system/user-operate';
+  import type { UserLisItem } from '@/api/system/user-operate';
+
+  const router = useRouter();
+
+  // 表格
+  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+
+  const { tableConfig, pagination } = useTableConfig(EVALUATION_SYSTEM_TABLE_COLUMNS, TABLE_OPTIONS);
+
+  const tableData = ref<any[]>([]);
+
+  // 日期范围(用于日期选择器)
+  const dateRange = ref<[string, string] | null>(null);
+
+  // 下发弹窗相关
+  const issueDialogVisible = ref(false);
+  const currentIssueId = ref<number | null>(null);
+  const issueForm = reactive({
+    departmentName: '',
+    startDate: '',
+    endDate: '',
+    userGroupId: undefined as number | undefined,
+    deptSelfApproveUserId: undefined as number | undefined, // 部门自评审核人ID
+  });
+  const issueDeptId = ref<number | null>(null);
+
+  // 用户组(用于选择用户名称)
+  const userGroupOptions = ref<PersonGroupListItem[]>([]);
+
+  // 部门自评审核人用户列表
+  const deptSelfApproveUserList = ref<UserLisItem[]>([]);
+
+  // 部门树(复用物品领取记录的部门字段下拉框逻辑)
+  const issueDeptCascaderRef = ref();
+  const deptTree = ref<DeptTree[]>([]);
+  const cascaderDeptProp = {
+    checkStrictly: true,
+    expandTrigger: 'hover' as const,
+    value: 'id',
+    label: 'deptName',
+    emitPath: false,
+  };
+
+  const getDeptTreeData = async () => {
+    try {
+      const res = await getAllDepartments();
+      deptTree.value = res?.[0]?.children ?? [];
+    } catch (e) {
+      console.error('获取部门树失败:', e);
+    }
+  };
+
+  // 获取用户组列表(用于下发时选择用户名称)
+  const getUserGroupOptions = async () => {
+    try {
+      const res = await queryUserGroupPage({
+        pageNumber: 1,
+        pageSize: 9999,
+        queryParam: '',
+      });
+      userGroupOptions.value = res?.records ?? [];
+    } catch (e) {
+      console.error('获取用户组列表失败:', e);
+      userGroupOptions.value = [];
+    }
+  };
+
+  // 获取部门自评审核人用户列表
+  const getDeptSelfApproveUserList = async () => {
+    try {
+      const res = await getUserList({
+        pageNumber: 1,
+        pageSize: 9999,
+        queryParam: {}, // 不传递 deptId 参数
+      });
+      deptSelfApproveUserList.value = res?.records || [];
+    } catch (e) {
+      console.error('获取部门自评审核人列表失败:', e);
+      deptSelfApproveUserList.value = [];
+    }
+  };
+
+  const tableQuery = reactive<QueryPageRequest<EvaluationSystemQueryParam>>({
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {
+      exName: '', // 考核表名称
+      status: undefined, // 状态(数字或 undefined)
+      deptName: '', // 所属部门
+      planStartTime: '', // 计划开始时间
+      planEndTime: '', // 计划结束时间
+    },
+  });
+
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    tableQuery.pageSize = value;
+    getTableData();
+  };
+
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    tableQuery.pageNumber = value;
+    getTableData();
+  };
+
+  async function getTableData() {
+    tableConfig.loading = true;
+    try {
+      const res = await querySecurityExamine(tableQuery);
+      if (res) {
+        // 映射返回数据字段到表格字段
+        tableData.value = res.records.map((item) => ({
+          id: item.id,
+          evaluationTableName: item.exName, // 考核表名称
+          status: item.status, // 状态
+          statusName: item.statusName, // 状态名称
+          issueDepartment: item.deptNames, // 下发部门
+          issueCount: 0, // 下发数(接口未返回,暂时设为0)
+          feedbackCount: 0, // 反馈数(接口未返回,暂时设为0)
+          feedbackRatio: '0%', // 反馈比例(接口未返回,暂时设为0%)
+          evaluationDocument: item.attachments, // 考核文档
+          plannedCompletionTime: item.planStartTime && item.planEndTime
+            ? `${item.planStartTime} 至 ${item.planEndTime}`
+            : item.planStartTime || item.planEndTime || '-', // 计划完成时间
+        }));
+        pagination.total = res.totalRow;
+      }
+    } catch (e) {
+      console.error('获取安全考核管理列表失败:', e);
+      tableData.value = [];
+      pagination.total = 0;
+    } finally {
+      tableConfig.loading = false;
+    }
+  }
+
+  const handleSearch = () => {
+    // 处理日期范围
+    if (dateRange.value && dateRange.value.length === 2) {
+      tableQuery.queryParam.planStartTime = dateRange.value[0];
+      tableQuery.queryParam.planEndTime = dateRange.value[1];
+    } else {
+      tableQuery.queryParam.planStartTime = '';
+      tableQuery.queryParam.planEndTime = '';
+    }
+    pagination.pageNumber = 1;
+    tableQuery.pageNumber = 1;
+    getTableData();
+  };
+
+  const handleReset = () => {
+    tableQuery.queryParam.exName = '';
+    tableQuery.queryParam.status = undefined;
+    tableQuery.queryParam.deptName = '';
+    tableQuery.queryParam.planStartTime = '';
+    tableQuery.queryParam.planEndTime = '';
+    dateRange.value = null;
+    handleSearch();
+  };
+
+  const handleCreate = () => {
+    router.push({
+      name: 'EvaluationSystemItem',
+      query: {
+        operate: 'evaluationSystem-create',
+      },
+    });
+  };
+
+  const handleEdit = (id: number) => {
+    router.push({
+      name: 'EvaluationSystemItem',
+      query: {
+        id,
+        operate: 'evaluationSystem-edit',
+      },
+    });
+  };
+
+  const handleDelete = async (id: number) => {
+    try {
+      await deleteSecurityExamine(id);
+      ElMessage.success('删除成功');
+      getTableData();
+    } catch (e: any) {
+      console.error('删除失败:', e);
+      ElMessage.error(e?.message || '删除失败,请重试');
+    }
+  };
+
+  // 下发
+  const handleIssue = (id: number) => {
+    currentIssueId.value = id;
+    issueForm.departmentName = '';
+    issueForm.startDate = '';
+    issueForm.endDate = '';
+    issueForm.userGroupId = undefined;
+    issueForm.deptSelfApproveUserId = undefined;
+    issueDeptId.value = null;
+    issueDialogVisible.value = true;
+  };
+
+  const handleIssueDeptChange = () => {
+    const nodes = issueDeptCascaderRef.value?.getCheckedNodes?.();
+    issueForm.departmentName = nodes?.[0]?.label ?? '';
+  };
+
+  const handleIssueConfirm = async () => {
+    if (!currentIssueId.value) {
+      ElMessage.error('缺少考核表ID');
+      return;
+    }
+
+    // 验证日期:开始日期不能大于结束日期
+    if (issueForm.startDate && issueForm.endDate) {
+      const startDate = new Date(issueForm.startDate);
+      const endDate = new Date(issueForm.endDate);
+      if (startDate > endDate) {
+        ElMessage.error('计划开始日期不能大于计划结束日期');
+        return;
+      }
+    }
+
+    try {
+      const payload = {
+        id: currentIssueId.value,
+        deptNames: issueForm.departmentName,
+        deptIds: issueDeptId.value || 0,
+        getUserGroupId: issueForm.userGroupId,
+        deptSelfApproveUserId: issueForm.deptSelfApproveUserId,
+        planStartTime: issueForm.startDate || undefined,
+        planEndTime: issueForm.endDate || undefined,
+      };
+      await saveSecurityExamineIssue(payload);
+      ElMessage.success('下发成功');
+      issueDialogVisible.value = false;
+      getTableData();
+    } catch (e) {
+      console.error('下发失败:', e);
+      ElMessage.error('下发失败,请重试');
+    }
+  };
+
+  // 作废
+  const handleCancel = async (id: number) => {
+    try {
+      await updateSecurityExamineRepeal(id);
+      ElMessage.success('作废成功');
+      getTableData();
+    } catch (e: any) {
+      console.error('作废失败:', e);
+      ElMessage.error(e?.message || '作废失败,请重试');
+    }
+  };
+
+  // 评分:跳转到考核对象列表,并仅展示“待评分”tab
+  const handleScore = (id: number) => {
+    router.push({
+      name: 'EvaluationSystemItem',
+      query: {
+        id,
+        operate: 'evaluationSystem-target',
+        singleStatus: '3', // 3 表示“待评分”
+      },
+    });
+  };
+
+  // 审核:跳转到考核对象列表,并仅展示“待审核”tab
+  const handleAudit = (id: number) => {
+    router.push({
+      name: 'EvaluationSystemItem',
+      query: {
+        id,
+        operate: 'evaluationSystem-target',
+        singleStatus: '4', // 4 表示“待审核”
+      },
+    });
+  };
+
+  // 考核对象
+  const handleTarget = (id: number) => {
+    router.push({
+      name: 'EvaluationSystemItem',
+      query: {
+        id,
+        operate: 'evaluationSystem-target',
+      },
+    });
+  };
+
+  // 部门排序
+  const handleAdvancedGroup = (id: number) => {
+    router.push({
+      name: 'EvaluationSystemItem',
+      query: {
+        id,
+        operate: 'evaluationSystem-advanced-group',
+      },
+    });
+  };
+
+  // 先进个人
+  const handleAdvancedIndividual = (id: number) => {
+    router.push({
+      name: 'EvaluationSystemItem',
+      query: {
+        id,
+        operate: 'evaluationSystem-advanced-person',
+      },
+    });
+  };
+
+  const handleView = (id: number) => {
+    router.push({
+      name: 'EvaluationSystemItem',
+      query: {
+        id,
+        operate: 'evaluationSystem-view',
+      },
+    });
+  };
+
+  // 预览
+  const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
+  const previewOnline = (url: string | undefined, type: keyof typeof FILE_TYPE_ICON) => {
+    if (url) {
+      previewOnlineRef.value?.open(url, type);
+    }
+  };
+
+  // 解析逗号分隔的URL字符串为文件列表
+  const parseAttachments = (attachmentsStr: string | undefined): Array<{
+    fileUrl: string;
+    fileName: string;
+    fileType: string;
+  }> => {
+    if (!attachmentsStr || !attachmentsStr.trim()) {
+      return [];
+    }
+
+    // 按逗号分割URL
+    const urls = attachmentsStr.split(',').map(url => url.trim()).filter(url => url);
+
+    return urls.map((url) => {
+      // 从URL中提取文件名
+      const urlParts = url.split('/');
+      const fileName = urlParts[urlParts.length - 1] || '未知文件';
+
+      // 根据文件扩展名判断文件类型
+      const extension = fileName.split('.').pop()?.toLowerCase() || '';
+      let fileType = 'pdf';
+      if (extension === 'doc' || extension === 'docx') {
+        fileType = 'word';
+      } else if (extension === 'xls' || extension === 'xlsx') {
+        fileType = 'excel';
+      } else if (extension === 'ppt' || extension === 'pptx') {
+        fileType = 'ppt';
+      }
+
+      return {
+        fileUrl: url,
+        fileName,
+        fileType,
+      };
+    });
+  };
+
+  onMounted(() => {
+    getTableData();
+    getDeptTreeData();
+    getUserGroupOptions();
+    getDeptSelfApproveUserList();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+  @use '@/styles/basic-table-file.scss' as *;
+  @use '@/views/traffic/violation/style/act-search-table.scss' as *;
+
+  .safety-platform-container__header {
+    padding-bottom: 0 !important;
+  }
+
+  .evaluation-table-name {
+    color: #1777ff;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: block;
+  }
+
+  .issue-dialog-form {
+    :deep(.el-form-item__content) {
+      width: 100%;
+    }
+  }
+</style>

+ 44 - 0
src/views/production-safety/safetyAssessment/inventory/InventoryItem.vue

@@ -0,0 +1,44 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ headerTitle }}</span>
+    </header>
+    <InventoryDetail />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed } from 'vue';
+  import { useRoute } from 'vue-router';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import InventoryDetail from './components/InventoryDetail.vue';
+
+  const route = useRoute();
+  const operate = route.query.operate as string;
+
+  const headerTitle = computed(() => {
+    switch (operate) {
+      case 'inventory-create':
+        return '新增物品';
+      case 'inventory-edit':
+        return '编辑物品';
+      case 'inventory-view':
+        return '查看物品';
+      default:
+        return '未知操作';
+    }
+  });
+</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>
+

+ 127 - 0
src/views/production-safety/safetyAssessment/inventory/components/InventoryDetail.vue

@@ -0,0 +1,127 @@
+<template>
+  <main class="safety-platform-container__main">
+    <BasicForm
+      ref="basicFormRef"
+      :formData="ruleFormData"
+      :formRules="isViewMode ? undefined : formRules"
+      :formConfig="computedFormConfig"
+    />
+  </main>
+  <footer class="safety-platform-container__footer">
+    <el-button @click="router.back()">返回</el-button>
+    <el-button v-if="!isViewMode" type="primary" @click="handleSubmit">
+      {{ isCreateMode ? '提交' : '保存' }}
+    </el-button>
+  </footer>
+</template>
+
+<script setup lang="ts">
+  import { computed, onMounted, ref } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import BasicForm from '@/components/BasicForm.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { INVENTORY_FORM_CONFIG, INVENTORY_FORM_DATA, INVENTORY_FORM_RULES } from '../configs/form';
+  import { queryInventoryDetail, saveInventory, updateInventory } from '@/api/inventory';
+
+  const router = useRouter();
+  const route = useRoute();
+
+  const operate = computed(() => (route.query.operate as string) || 'inventory-create');
+  const currentId = computed(() => Number(route.query.id));
+
+  const isCreateMode = computed(() => operate.value === 'inventory-create');
+  const isEditMode = computed(() => operate.value === 'inventory-edit');
+  const isViewMode = computed(() => operate.value === 'inventory-view');
+
+  const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } =
+    useFormConfigHook(INVENTORY_FORM_CONFIG, INVENTORY_FORM_DATA, INVENTORY_FORM_RULES);
+
+  // 查看模式下,所有字段设为只读
+  const viewFormConfig = ref(
+    INVENTORY_FORM_CONFIG.map((item) => ({
+      ...item,
+      componentProps: {
+        ...item.componentProps,
+        disabled: true,
+      },
+    })),
+  );
+
+  const computedFormConfig = computed(() => {
+    if (isViewMode.value) {
+      return viewFormConfig.value;
+    }
+    return ruleFormConfig.value;
+  });
+
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+
+  const handleValidate = async () => {
+    if (!basicFormRef.value) return;
+    const res = await basicFormRef.value.validateForm();
+    return res;
+  };
+
+  const getDetail = async () => {
+    if (!currentId.value) return;
+    try {
+      const res = await queryInventoryDetail(currentId.value);
+      if (res) {
+        // 映射接口字段到表单字段
+        ruleFormData.itemName = res.stuffName; // 物品名称
+        ruleFormData.warehouseDate = res.inStoreTime ? res.inStoreTime.split('T')[0] : ''; // 入库日期(YYYY-MM-DD)
+        ruleFormData.itemQuantity = res.stuffQty; // 物品数量
+        ruleFormData.remarks = res.remark || ''; // 备注
+      }
+      cloneRuleFormData();
+    } catch (e) {
+      console.error('获取物品库存详情失败:', e);
+      ElMessage.error('获取详情失败');
+    }
+  };
+
+  const handleSubmit = async () => {
+    const res = await handleValidate();
+    if (!res) return;
+    try {
+      const basePayload = {
+        stuffName: ruleFormData.itemName,
+        inStoreTime: ruleFormData.warehouseDate
+          ? new Date(ruleFormData.warehouseDate).toISOString()
+          : '',
+        stuffQty: ruleFormData.itemQuantity,
+        remark: ruleFormData.remarks || '',
+      };
+
+      if (isCreateMode.value) {
+        await saveInventory(basePayload);
+        ElMessage.success('创建成功');
+      } else if (isEditMode.value && currentId.value) {
+        await updateInventory({
+          id: currentId.value,
+          ...basePayload,
+        });
+        ElMessage.success('保存成功');
+      }
+
+      router.back();
+    } catch (e) {
+      console.error('保存物品库存失败:', e);
+      ElMessage.error('保存失败,请重试');
+    }
+  };
+
+  onMounted(() => {
+    cloneRuleFormData();
+    beforeRouteLeave();
+    if (isEditMode.value || isViewMode.value) {
+      getDetail();
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+</style>
+

+ 58 - 0
src/views/production-safety/safetyAssessment/inventory/configs/form.ts

@@ -0,0 +1,58 @@
+import { FormConfig } from '@/types/basic-form';
+
+export const INVENTORY_FORM_CONFIG: FormConfig[] = [
+  {
+    prop: 'itemName',
+    label: '物品名称:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入物品名称',
+    },
+  },
+  {
+    prop: 'warehouseDate',
+    label: '入库日期:',
+    component: 'ElDatePicker',
+    componentProps: {
+      type: 'date',
+      placeholder: '请选择入库日期',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  {
+    prop: 'itemQuantity',
+    label: '物品数量:',
+    component: 'ElInputNumber',
+    componentProps: {
+      min: 1,
+      precision: 0, // 不允许小数点,只能输入整数
+      placeholder: '请输入物品数量',
+    },
+  },
+  {
+    label: '备注:',
+    prop: 'remarks',
+    component: 'ElInput',
+    componentProps: {
+      type: 'textarea',
+      rows: 5,
+      placeholder: '请输入备注',
+    },
+  },
+];
+
+export const INVENTORY_FORM_DATA = {
+  itemName: '',
+  warehouseDate: '',
+  itemQuantity: 1, // 最小值为1
+  remarks: '',
+};
+
+export const INVENTORY_FORM_RULES = {
+  itemName: [{ required: true, message: '请输入物品名称', trigger: 'blur' }],
+  warehouseDate: [{ required: true, message: '请选择入库日期', trigger: 'change' }],
+  itemQuantity: [
+    { required: true, message: '请输入物品数量', trigger: 'blur' },
+    { type: 'number', min: 1, message: '物品数量不能小于1', trigger: 'blur' },
+  ],
+};

+ 62 - 0
src/views/production-safety/safetyAssessment/inventory/configs/tables.ts

@@ -0,0 +1,62 @@
+import type { TableColumnProps } from '@/types/basic-table';
+
+// 基础表格样式配置
+export const TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  loading: true,
+  maxHeight: 'calc(70vh - 150px)',
+};
+
+export const INVENTORY_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '编号',
+    type: 'index',
+    align: 'center',
+    width: '80px',
+  },
+  {
+    label: '物品名称',
+    prop: 'itemName',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '入库日期',
+    prop: 'warehouseDate',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '物品数量',
+    prop: 'itemQuantity',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '经办人',
+    prop: 'handler',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '备注',
+    prop: 'remarks',
+    align: 'left',
+    minWidth: '150px',
+  },
+  {
+    label: '状态',
+    prop: 'status',
+    slot: 'status',
+    align: 'center',
+    minWidth: '100px',
+  },
+  {
+    label: '操作',
+    prop: 'action',
+    slot: 'action',
+    fixed: 'right',
+    width: '180px',
+    align: 'left',
+  },
+];

+ 264 - 0
src/views/production-safety/safetyAssessment/inventory/inventory.vue

@@ -0,0 +1,264 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="breadcrumb-title"> 物品库存管理 </div>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header>
+          <div style="position: relative">
+            <el-button type="primary" class="search-table-container--button" @click="handleCreate">
+              添加
+            </el-button>
+            <el-button plain class="search-table-container--button" @click="handleImport">
+              导入
+            </el-button>
+            <el-button plain class="search-table-container--button" @click="handleDownload">
+              导出
+            </el-button>
+          </div>
+
+          <div class="act-search">
+            <section class="select-box">
+              <div class="select-box--item">
+                <span>物品名称:</span>
+                <el-input
+                  v-model="tableQuery.queryParam.stuffName"
+                  placeholder="搜索物品名称"
+                  class="act-search-input"
+                />
+              </div>
+              <div class="select-box--item">
+                <span>状态:</span>
+                <el-select
+                  v-model="tableQuery.queryParam.status"
+                  placeholder="请选择状态"
+                  clearable
+                >
+                  <el-option label="启用" :value="true" />
+                  <el-option label="禁用" :value="false" />
+                </el-select>
+              </div>
+            </section>
+            <section class="search-btn">
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+            </section>
+          </div>
+        </header>
+
+        <div class="batch-table">
+          <BasicTable
+            ref="basicTableRef"
+            :tableData="tableData"
+            :tableConfig="tableConfig"
+            @update:pageSize="handleSizeChange"
+            @update:pageNumber="handleCurrentChange"
+          >
+            <template #status="scope">
+              <span>
+                {{ scope.row.statusName || (scope.row.status === true || scope.row.status === 'true' ? '启用' : scope.row.status === false || scope.row.status === 'false' ? '禁用' : '-') }}
+              </span>
+            </template>
+            <template #action="scope">
+              <div class="action-container--div" style="justify-content: left">
+                <ActionButton text="编辑" @click="handleEdit(scope.row.id)" />
+                <ActionButton
+                  text="删除"
+                  :popconfirm="{
+                    title: '确定要删除?',
+                  }"
+                  @confirm="handleDelete(scope.row.id)"
+                />
+                <ActionButton text="查看" @click="handleView(scope.row.id)" />
+              </div>
+            </template>
+          </BasicTable>
+        </div>
+      </div>
+    </main>
+    <BatchImport
+      v-if="batchImportVisible"
+      :visible="batchImportVisible"
+      :import-api-url="importApiUrl"
+      :template-url="templateUrl"
+      template-name="下载模板"
+      :show-template="false"
+      @close="batchImportVisible = false"
+      @update="handleUpdate"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, reactive, ref } from 'vue';
+  import { ElMessage } from 'element-plus';
+  import BasicTable from '@/components/BasicTable.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import ActionButton from '@/components/ActionButton.vue';
+  import { TABLE_OPTIONS, INVENTORY_TABLE_COLUMNS } from './configs/tables';
+  import { useRouter } from 'vue-router';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import { queryInventoryManage, deleteInventory, exportInventory } from '@/api/inventory';
+  import { downloadByData } from '@/utils/file/download';
+  import BatchImport from '@/components/batch-import/BatchImport.vue';
+  import { useGlobSetting } from '@/hooks/setting';
+  import urlJoin from 'url-join';
+
+  const router = useRouter();
+
+  // 表格
+  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+
+  const { tableConfig, pagination } = useTableConfig(INVENTORY_TABLE_COLUMNS, TABLE_OPTIONS);
+
+  const tableData = ref<any[]>([]);
+
+  const tableQuery = reactive<QueryPageRequest<any>>({
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {
+      stuffName: '', // 物品名称
+      status: true, // 状态,默认启用
+      ids: [], // 选择数据的ID
+    },
+  });
+
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    tableQuery.pageSize = value;
+    getTableData();
+  };
+
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    tableQuery.pageNumber = value;
+    getTableData();
+  };
+
+
+  async function getTableData() {
+    tableConfig.loading = true;
+    try {
+      const res = await queryInventoryManage(tableQuery);
+      if (res) {
+        // 映射返回数据字段到表格字段
+        tableData.value = res.records.map((item) => ({
+          id: item.id,
+          itemName: item.stuffName, // 物品名称
+          warehouseDate: item.inStoreTime, // 入库日期
+          itemQuantity: item.stuffQty, // 物品数量
+          handler: item.createdUserName, // 经办人
+          remarks: item.remark, // 备注
+          status: item.status, // 状态:true-启用,false-禁用
+          statusName: item.statusName, // 状态名称
+        }));
+        pagination.total = res.totalRow;
+      }
+    } catch (e) {
+      console.error('获取物品库存列表失败:', e);
+      tableData.value = [];
+      pagination.total = 0;
+    } finally {
+      tableConfig.loading = false;
+    }
+  }
+
+  const handleSearch = () => {
+    pagination.pageNumber = 1;
+    tableQuery.pageNumber = 1;
+    getTableData();
+  };
+
+  const handleReset = () => {
+    tableQuery.queryParam.stuffName = '';
+    tableQuery.queryParam.status = true; // 重置为默认启用状态
+    tableQuery.queryParam.ids = [];
+    handleSearch();
+  };
+
+  // 批量导入
+  const batchImportVisible = ref(false);
+  const { urlPrefix } = useGlobSetting();
+  const importApiUrl = ref(urlJoin(urlPrefix, '/inventory/importInventory'));
+  const templateUrl = ref('./skyeye-file-upload/sfysecurity/TEMPLATE/import-inventory-template.xlsx');
+
+  const handleImport = () => {
+    batchImportVisible.value = true;
+  };
+
+  const handleUpdate = () => {
+    batchImportVisible.value = false;
+    getTableData();
+  };
+
+  const handleDownload = async () => {
+    try {
+      const exportParams = {
+        stuffName: tableQuery.queryParam.stuffName || undefined,
+        status: tableQuery.queryParam.status,
+        ids: tableQuery.queryParam.ids.length > 0 ? tableQuery.queryParam.ids : undefined,
+      };
+      const response = await exportInventory(exportParams);
+      if (response) {
+        const fileName = `物品库存管理_${new Date().toISOString().split('T')[0]}.xlsx`;
+        downloadByData(response, fileName);
+        ElMessage.success('导出成功');
+      }
+    } catch (e) {
+      console.error('导出物品库存失败:', e);
+      ElMessage.error('导出失败,请重试');
+    }
+  };
+
+  const handleCreate = () => {
+    router.push({
+      name: 'InventoryItem',
+      query: {
+        operate: 'inventory-create',
+      },
+    });
+  };
+
+  const handleEdit = (id: number) => {
+    router.push({
+      name: 'InventoryItem',
+      query: {
+        id,
+        operate: 'inventory-edit',
+      },
+    });
+  };
+
+  const handleDelete = async (id: number) => {
+    try {
+      await deleteInventory(id);
+      ElMessage.success('删除成功');
+      getTableData();
+    } catch (e) {
+      console.error('删除物品库存失败:', e);
+      ElMessage.error('删除失败,请重试');
+    }
+  };
+
+  const handleView = (id: number) => {
+    router.push({
+      name: 'InventoryItem',
+      query: {
+        id,
+        operate: 'inventory-view',
+      },
+    });
+  };
+
+  onMounted(() => {
+    getTableData();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+  @use '@/views/traffic/violation/style/act-search-table.scss' as *;
+</style>

+ 43 - 0
src/views/production-safety/safetyAssessment/pointDeduction/PointDeductionItem.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ headerTitle }}</span>
+    </header>
+    <PointDeductionDetail />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed } from 'vue';
+  import { useRoute } from 'vue-router';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import PointDeductionDetail from './components/PointDeductionDetail.vue';
+
+  const route = useRoute();
+  const operate = route.query.operate as string;
+
+  const headerTitle = computed(() => {
+    switch (operate) {
+      case 'pointDeduction-create':
+        return '新增月度扣分';
+      case 'pointDeduction-edit':
+        return '编辑月度扣分';
+      case 'pointDeduction-view':
+        return '查看月度扣分';
+      default:
+        return '未知操作';
+    }
+  });
+</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>

+ 275 - 0
src/views/production-safety/safetyAssessment/pointDeduction/components/PointDeductionDetail.vue

@@ -0,0 +1,275 @@
+<template>
+  <main class="safety-platform-container__main">
+    <BasicForm
+      ref="basicFormRef"
+      :formData="ruleFormData"
+      :formRules="isViewMode ? undefined : formRules"
+      :formConfig="computedFormConfig"
+    >
+      <template #status>
+        <el-radio-group v-model="ruleFormData.status" :disabled="isViewMode">
+          <el-radio label="ENABLE">启用</el-radio>
+          <el-radio label="DISABLE">禁用</el-radio>
+        </el-radio-group>
+      </template>
+      <template #dedResonList>
+        <div class="deduction-reason-table">
+          <el-table :data="ruleFormData.dedResonList" border style="width: 100%">
+            <el-table-column prop="serialNum" label="编号" width="100" align="center">
+              <template #default="{ row }">
+                <span>{{ row.serialNum }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="deductionReason" label="扣分项" min-width="200">
+              <template #default="{ row }">
+                <el-input
+                  v-if="!isViewMode"
+                  v-model="row.deductionReason"
+                  placeholder="请输入扣分项"
+                  size="small"
+                />
+                <span v-else>{{ row.deductionReason }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="scoreVal" label="分值" width="120" align="center">
+              <template #default="{ row }">
+                <el-input-number
+                  v-if="!isViewMode"
+                  v-model="row.scoreVal"
+                  :min="1"
+                  :precision="0"
+                  size="small"
+                  style="width: 100%"
+                  @change="calculateTotalScore"
+                />
+                <span v-else>{{ row.scoreVal }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="180" align="center" v-if="!isViewMode">
+              <template #default="{ $index }">
+                <el-button type="primary" link size="small" @click="handleAddReasonAfter($index)">新增</el-button>
+                <el-button type="danger" link size="small" @click="handleDeleteReason($index)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </template>
+    </BasicForm>
+  </main>
+  <footer class="safety-platform-container__footer">
+    <el-button @click="router.back()">返回</el-button>
+    <el-button v-if="!isViewMode" type="primary" @click="handleSubmit">
+      {{ isCreateMode ? '提交' : '保存' }}
+    </el-button>
+  </footer>
+</template>
+
+<script setup lang="ts">
+  import { computed, onMounted, ref } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import BasicForm from '@/components/BasicForm.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import {
+    POINT_DEDUCTION_FORM_CONFIG,
+    POINT_DEDUCTION_FORM_DATA,
+    POINT_DEDUCTION_FORM_RULES,
+  } from '../configs/form';
+  import {
+    saveMonthlyDeduction,
+    updateMonthlyDeduction,
+    queryDeductionDetail,
+  } from '@/api/pointDeduction';
+
+  const router = useRouter();
+  const route = useRoute();
+
+  const operate = computed(() => (route.query.operate as string) || 'pointDeduction-create');
+  const currentId = computed(() => Number(route.query.id));
+
+  const isCreateMode = computed(() => operate.value === 'pointDeduction-create');
+  const isEditMode = computed(() => operate.value === 'pointDeduction-edit');
+  const isViewMode = computed(() => operate.value === 'pointDeduction-view');
+
+  const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } =
+    useFormConfigHook(POINT_DEDUCTION_FORM_CONFIG, POINT_DEDUCTION_FORM_DATA, POINT_DEDUCTION_FORM_RULES);
+
+  // 查看模式下,所有字段设为只读
+  const viewFormConfig = ref(
+    POINT_DEDUCTION_FORM_CONFIG.map((item) => ({
+      ...item,
+      componentProps: {
+        ...item.componentProps,
+        disabled: true,
+      },
+    })),
+  );
+
+  const computedFormConfig = computed(() => {
+    if (isViewMode.value) {
+      return viewFormConfig.value;
+    }
+    return ruleFormConfig.value;
+  });
+
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+
+  // 自动排序编号
+  const updateSerialNumbers = () => {
+    ruleFormData.dedResonList.forEach((item, index) => {
+      item.serialNum = index + 1;
+    });
+  };
+
+  // 自动计算总扣分值
+  const calculateTotalScore = () => {
+    const totalScore = ruleFormData.dedResonList.reduce((sum, item) => sum + (item.scoreVal || 0), 0);
+    ruleFormData.deductionValue = totalScore;
+  };
+
+  // 扣分原因列表操作 - 在指定位置后新增
+  const handleAddReasonAfter = (index: number) => {
+    ruleFormData.dedResonList.splice(index + 1, 0, {
+      id: 0,
+      pmdId: currentId.value || 0,
+      serialNum: 0, // 会自动排序
+      deductionReason: '',
+      scoreVal: 1, // 默认值为1
+    });
+    updateSerialNumbers();
+    calculateTotalScore();
+  };
+
+  // 删除扣分原因
+  const handleDeleteReason = (index: number) => {
+    ruleFormData.dedResonList.splice(index, 1);
+    updateSerialNumbers();
+    calculateTotalScore();
+  };
+
+  const handleValidate = async () => {
+    if (!basicFormRef.value) return;
+    const res = await basicFormRef.value.validateForm();
+    if (!res) return false;
+    
+    // 验证扣分原因列表
+    if (!ruleFormData.dedResonList || ruleFormData.dedResonList.length === 0) {
+      ElMessage.error('请至少添加一条扣分原因');
+      return false;
+    }
+    
+    for (let i = 0; i < ruleFormData.dedResonList.length; i++) {
+      const item = ruleFormData.dedResonList[i];
+      if (!item.deductionReason || item.deductionReason.trim() === '') {
+        ElMessage.error(`第${i + 1}条扣分项的扣分项不能为空`);
+        return false;
+      }
+      if (!item.scoreVal || item.scoreVal <= 0) {
+        ElMessage.error(`第${i + 1}条扣分项的分值必须大于0`);
+        return false;
+      }
+    }
+    
+    // 计算总扣分值
+    calculateTotalScore();
+    
+    return true;
+  };
+
+  const getDetail = async () => {
+    if (!currentId.value) return;
+    try {
+      const res = await queryDeductionDetail(currentId.value);
+      if (res) {
+        ruleFormData.deductionTitle = res.deductionTitle;
+        ruleFormData.deductionDate = res.deductionDate;
+        ruleFormData.deductionValue = res.deductionScore;
+        ruleFormData.departmentName = res.departmentName;
+        ruleFormData.status = res.status ? 'ENABLE' : 'DISABLE';
+        ruleFormData.dedResonList = res.dedResonList || [];
+        // 确保编号排序和分值默认值
+        ruleFormData.dedResonList.forEach((item) => {
+          if (!item.scoreVal || item.scoreVal === 0) {
+            item.scoreVal = 1;
+          }
+        });
+        updateSerialNumbers();
+        calculateTotalScore();
+      }
+      cloneRuleFormData();
+    } catch (e) {
+      console.error('获取月度扣分详情失败:', e);
+      ElMessage.error('获取详情失败');
+    }
+  };
+
+  const handleSubmit = async () => {
+    const res = await handleValidate();
+    if (!res) return;
+    try {
+      const basePayload = {
+        deductionTitle: ruleFormData.deductionTitle,
+        deductionDate: ruleFormData.deductionDate,
+        deductionScore: ruleFormData.deductionValue,
+        departmentName: ruleFormData.departmentName,
+        status: ruleFormData.status === 'ENABLE',
+        dedResonList: ruleFormData.dedResonList.map((item) => {
+          // 如果是新增的数据(id为0),不传递id和pmdId;如果是后端已有的数据,保留所有字段
+          if (item.id === 0) {
+            const { id, pmdId, ...rest } = item;
+            return rest;
+          }
+          return item;
+        }),
+      };
+
+      if (isCreateMode.value) {
+        await saveMonthlyDeduction(basePayload);
+        ElMessage.success('创建成功');
+      } else if (isEditMode.value && currentId.value) {
+        await updateMonthlyDeduction({
+          id: currentId.value,
+          ...basePayload,
+        });
+        ElMessage.success('保存成功');
+      }
+
+      router.back();
+    } catch (e) {
+      console.error('保存月度扣分失败:', e);
+      ElMessage.error('保存失败,请重试');
+    }
+  };
+
+  onMounted(() => {
+    cloneRuleFormData();
+    beforeRouteLeave();
+    // 创建模式下,初始化一条空的扣分原因记录
+    if (isCreateMode.value && (!ruleFormData.dedResonList || ruleFormData.dedResonList.length === 0)) {
+      ruleFormData.dedResonList = [
+        {
+          id: 0,
+          pmdId: 0,
+          serialNum: 1,
+          deductionReason: '',
+          scoreVal: 1, // 默认值为1
+        },
+      ];
+    } else {
+      // 确保编号排序
+      updateSerialNumbers();
+    }
+    if (isEditMode.value || isViewMode.value) {
+      getDetail();
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+
+  .deduction-reason-table {
+    width: 100%;
+  }
+</style>
+

+ 79 - 0
src/views/production-safety/safetyAssessment/pointDeduction/configs/form.ts

@@ -0,0 +1,79 @@
+import { FormConfig } from '@/types/basic-form';
+
+export const POINT_DEDUCTION_FORM_CONFIG: FormConfig[] = [
+  {
+    prop: 'deductionTitle',
+    label: '扣分标题:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入扣分标题',
+    },
+  },
+  {
+    prop: 'deductionDate',
+    label: '扣分日期:',
+    component: 'ElDatePicker',
+    componentProps: {
+      type: 'date',
+      placeholder: '请选择扣分日期',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  {
+    prop: 'deductionValue',
+    label: '扣分值:',
+    component: 'ElInputNumber',
+    componentProps: {
+      min: 1,
+      precision: 0, // 不允许小数点,只能输入整数
+      placeholder: '请输入扣分值',
+      disabled: true, // 禁用扣分值字段
+    },
+  },
+  {
+    prop: 'departmentName',
+    label: '部门名称:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入部门名称',
+    },
+  },
+  {
+    prop: 'dedResonList',
+    label: '扣分原因:',
+    slot: 'dedResonList',
+  },
+  {
+    prop: 'status',
+    label: '状态:',
+    slot: 'status',
+  },
+];
+
+export interface DeductionReasonItem {
+  id: number;
+  pmdId: number;
+  serialNum: number;
+  deductionReason: string;
+  scoreVal: number;
+}
+
+export const POINT_DEDUCTION_FORM_DATA = {
+  deductionTitle: '',
+  deductionDate: '',
+  deductionValue: 1, // 最小值为1
+  departmentName: '',
+  dedResonList: [] as DeductionReasonItem[],
+  status: 'ENABLE',
+};
+
+export const POINT_DEDUCTION_FORM_RULES = {
+  deductionTitle: [{ required: true, message: '请输入扣分标题', trigger: 'blur' }],
+  deductionDate: [{ required: true, message: '请选择扣分日期', trigger: 'change' }],
+  deductionValue: [
+    { required: true, message: '请输入扣分值', trigger: 'blur' },
+    { type: 'number', min: 1, message: '扣分值不能小于1', trigger: 'blur' },
+  ],
+  departmentName: [{ required: true, message: '请输入部门名称', trigger: 'blur' }],
+  status: [{ required: true, message: '请选择状态', trigger: 'change' }],
+};

+ 11 - 0
src/views/production-safety/safetyAssessment/pointDeduction/configs/status.ts

@@ -0,0 +1,11 @@
+// 月度扣分状态配置
+export const POINT_DEDUCTION_STATUS_OPTIONS = [
+  { label: '全部', value: '' },
+  { label: '启用', value: 'ENABLE' },
+  { label: '禁用', value: 'DISABLE' },
+];
+
+export const POINT_DEDUCTION_STATUS_LABEL: Record<string, string> = {
+  ENABLE: '启用',
+  DISABLE: '禁用',
+};

+ 57 - 0
src/views/production-safety/safetyAssessment/pointDeduction/configs/tables.ts

@@ -0,0 +1,57 @@
+import type { TableColumnProps } from '@/types/basic-table';
+
+// 基础表格样式配置
+export const TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  loading: true,
+  maxHeight: 'calc(70vh - 150px)',
+};
+
+export const POINT_DEDUCTION_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '扣分标题',
+    prop: 'deductionTitle',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '扣分日期',
+    prop: 'deductionDate',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '扣分值',
+    prop: 'deductionValue',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '部门名称',
+    prop: 'departmentName',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '扣分原因',
+    prop: 'dedResonList',
+    slot: 'dedResonList',
+    align: 'left',
+    minWidth: '300px',
+  },
+  {
+    label: '状态',
+    prop: 'status',
+    slot: 'status',
+    align: 'center',
+    minWidth: '100px',
+  },
+  {
+    label: '操作',
+    prop: 'action',
+    slot: 'action',
+    fixed: 'right',
+    width: '180px',
+    align: 'left',
+  },
+];

+ 333 - 0
src/views/production-safety/safetyAssessment/pointDeduction/pointDeduction.vue

@@ -0,0 +1,333 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="breadcrumb-title"> 月度扣分管理 </div>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header>
+          <div style="position: relative">
+            <el-button type="primary" class="search-table-container--button" @click="handleCreate">
+              添加
+            </el-button>
+            <el-button plain class="search-table-container--button" @click="handleImport">
+              导入
+            </el-button>
+            <el-button plain class="search-table-container--button" @click="handleDownload">
+              导出
+            </el-button>
+          </div>
+
+          <div class="act-search">
+            <section class="select-box">
+              <div class="select-box--item">
+                <span>扣分标题:</span>
+                <el-input
+                  v-model="tableQuery.queryParam.deductionTitle"
+                  placeholder="搜索扣分标题"
+                  class="act-search-input"
+                />
+              </div>
+              <div class="select-box--item">
+                <span>状态:</span>
+                <el-select
+                  v-model="tableQuery.queryParam.status"
+                  placeholder="请选择状态"
+                  clearable
+                >
+                  <el-option label="启用" :value="true" />
+                  <el-option label="禁用" :value="false" />
+                </el-select>
+              </div>
+            </section>
+            <section class="search-btn">
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+            </section>
+          </div>
+        </header>
+
+        <div class="batch-table">
+          <BasicTable
+            ref="basicTableRef"
+            :tableData="tableData"
+            :tableConfig="tableConfig"
+            @update:pageSize="handleSizeChange"
+            @update:pageNumber="handleCurrentChange"
+          >
+            <template #status="scope">
+              <span>
+                {{ scope.row.statusName || (scope.row.status === true ? '启用' : scope.row.status === false ? '禁用' : '-') }}
+              </span>
+            </template>
+            <template #dedResonList="scope">
+              <div class="deduction-reason-list">
+                <template v-if="scope.row.deductionReason">
+                  <template
+                    v-for="(item, index) in (() => {
+                      const list = getDeductionReasonList(scope.row.deductionReason);
+                      return list.map((text, idx) => ({ text, index: idx, total: list.length }));
+                    })()"
+                    :key="index"
+                  >
+                    <div class="deduction-reason-item">
+                      <div class="reason-content">
+                        <span class="reason-text">{{ item.text }}</span>
+                      </div>
+                      <!-- 只有多条数据时才显示下划线,且不是最后一条 -->
+                      <div v-if="item.total > 1 && item.index < item.total - 1" class="reason-divider"></div>
+                    </div>
+                  </template>
+                </template>
+                <div v-else class="no-reason">-</div>
+              </div>
+            </template>
+            <template #action="scope">
+              <div class="action-container--div" style="justify-content: left">
+                <ActionButton text="编辑" @click="handleEdit(scope.row.id)" />
+                <ActionButton
+                  text="删除"
+                  :popconfirm="{
+                    title: '确定要删除?',
+                  }"
+                  @confirm="handleDelete(scope.row.id)"
+                />
+                <ActionButton text="查看" @click="handleView(scope.row.id)" />
+              </div>
+            </template>
+          </BasicTable>
+        </div>
+      </div>
+    </main>
+    <BatchImport
+      v-if="batchImportVisible"
+      :visible="batchImportVisible"
+      :import-api-url="importApiUrl"
+      :template-url="templateUrl"
+      template-name="下载模板"
+      :show-template="false"
+      @close="batchImportVisible = false"
+      @update="handleUpdate"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { onMounted, reactive, ref } from 'vue';
+  import BasicTable from '@/components/BasicTable.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import ActionButton from '@/components/ActionButton.vue';
+  import { TABLE_OPTIONS, POINT_DEDUCTION_TABLE_COLUMNS } from './configs/tables';
+  import { POINT_DEDUCTION_STATUS_OPTIONS } from './configs/status';
+  import { useRouter } from 'vue-router';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import { queryMonthlyDeduction, deleteDeduction, exportMonthlyDeduction } from '@/api/pointDeduction';
+  import { downloadByData } from '@/utils/file/download';
+  import BatchImport from '@/components/batch-import/BatchImport.vue';
+  import { useGlobSetting } from '@/hooks/setting';
+  import urlJoin from 'url-join';
+  import { ElMessage } from 'element-plus';
+
+  const router = useRouter();
+
+  // 处理扣分原因字符串,按换行符分割
+  const getDeductionReasonList = (deductionReason: string): string[] => {
+    if (!deductionReason) return [];
+    // 按 \r\n 或 \n 分割,并过滤空字符串
+    return deductionReason.split(/\r\n|\n/).filter((item) => item.trim() !== '');
+  };
+
+  // 表格
+  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+
+  const { tableConfig, pagination } = useTableConfig(POINT_DEDUCTION_TABLE_COLUMNS, TABLE_OPTIONS);
+
+  const tableData = ref<any[]>([]);
+
+  const tableQuery = reactive<QueryPageRequest<any>>({
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {
+      deductionTitle: '', // 扣分标题
+      status: true, // 状态,默认启用
+    },
+  });
+
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    tableQuery.pageSize = value;
+    getTableData();
+  };
+
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    tableQuery.pageNumber = value;
+    getTableData();
+  };
+
+  async function getTableData() {
+    tableConfig.loading = true;
+    try {
+      const res = await queryMonthlyDeduction(tableQuery);
+      if (res) {
+        // 映射返回数据字段到表格字段
+        tableData.value = res.records.map((item) => ({
+          id: item.id,
+          deductionTitle: item.deductionTitle, // 扣分标题
+          deductionDate: item.deductionDate, // 扣分日期
+          deductionValue: item.deductionScore, // 扣分值(API 返回的是 deductionScore)
+          departmentName: item.departmentName, // 部门名称
+          deductionReason: item.deductionReason, // 扣分原因(兼容旧数据)
+          dedResonList: item.dedResonList || [], // 扣分原因列表
+          status: item.status, // 状态(boolean)
+          statusName: item.statusName, // 状态名称
+        }));
+        pagination.total = res.totalRow;
+      }
+    } catch (e) {
+      console.error('获取月度扣分列表失败:', e);
+      tableData.value = [];
+      pagination.total = 0;
+    } finally {
+      tableConfig.loading = false;
+    }
+  }
+
+  const handleSearch = () => {
+    pagination.pageNumber = 1;
+    tableQuery.pageNumber = 1;
+    getTableData();
+  };
+
+  const handleReset = () => {
+    tableQuery.queryParam.deductionTitle = '';
+    tableQuery.queryParam.status = true; // 重置为默认启用状态
+    handleSearch();
+  };
+
+  // 批量导入
+  const batchImportVisible = ref(false);
+  const { urlPrefix } = useGlobSetting();
+  const importApiUrl = ref(urlJoin(urlPrefix, '/monthlyDeduction/importMonthlyDeduction'));
+  const templateUrl = ref('./skyeye-file-upload/sfysecurity/TEMPLATE/import-point-deduction-template.xlsx');
+
+  const handleImport = () => {
+    batchImportVisible.value = true;
+  };
+
+  const handleUpdate = () => {
+    batchImportVisible.value = false;
+    getTableData();
+  };
+
+  const handleDownload = async () => {
+    try {
+      const exportParams = {
+        deductionTitle: tableQuery.queryParam.deductionTitle || undefined,
+        status: tableQuery.queryParam.status,
+      };
+      const response = await exportMonthlyDeduction(exportParams);
+      if (response) {
+        const fileName = `月度扣分_${new Date().toISOString().split('T')[0]}.xlsx`;
+        downloadByData(response, fileName);
+        ElMessage.success('导出成功');
+      }
+    } catch (e) {
+      console.error('导出月度扣分失败:', e);
+      ElMessage.error('导出失败,请重试');
+    }
+  };
+
+  const handleCreate = () => {
+    router.push({
+      name: 'PointDeductionItem',
+      query: {
+        operate: 'pointDeduction-create',
+      },
+    });
+  };
+
+  const handleEdit = (id: number) => {
+    router.push({
+      name: 'PointDeductionItem',
+      query: {
+        id,
+        operate: 'pointDeduction-edit',
+      },
+    });
+  };
+
+  const handleDelete = async (id: number) => {
+    try {
+      await deleteDeduction(id);
+      ElMessage.success('删除成功');
+      getTableData();
+    } catch (e) {
+      console.error('删除月度扣分失败:', e);
+      ElMessage.error('删除失败,请重试');
+    }
+  };
+
+  const handleView = (id: number) => {
+    router.push({
+      name: 'PointDeductionItem',
+      query: {
+        id,
+        operate: 'pointDeduction-view',
+      },
+    });
+  };
+
+  onMounted(() => {
+    getTableData();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+  @use '@/views/traffic/violation/style/act-search-table.scss' as *;
+
+  .deduction-reason-list {
+    width: 100%;
+    /* padding: 8px 0; */
+  }
+
+  .deduction-reason-item {
+    /* padding: 4px 0; */
+  }
+
+  .reason-content {
+    display: flex;
+    align-items: flex-start;
+    gap: 4px;
+  }
+
+  .reason-serial {
+    font-weight: 500;
+    color: #606266;
+  }
+
+  .reason-text {
+    flex: 1;
+    color: #303133;
+  }
+
+  .reason-score {
+    color: #909399;
+    font-size: 12px;
+  }
+
+  .reason-divider {
+    height: 1px;
+    width: 100%;
+    background-color: #dcdfe6;
+    margin: 8px 0;
+    display: block;
+  }
+
+  .no-reason {
+    color: #909399;
+  }
+</style>

+ 337 - 0
src/views/production-safety/safetyAssessment/receiptRecord/components/ReceiptRecordDetail.vue

@@ -0,0 +1,337 @@
+<template>
+  <main class="safety-platform-container__main">
+    <BasicForm
+      ref="basicFormRef"
+      :formData="ruleFormData"
+      :formRules="isViewMode || isAuditMode ? undefined : formRules"
+      :formConfig="computedFormConfig"
+    >
+      <template #itemId>
+        <el-select
+          v-model="ruleFormData.itemId"
+          placeholder="请选择物品名称"
+          filterable
+          clearable
+          :disabled="isViewMode || isAuditMode"
+        >
+          <el-option
+            v-for="item in inventoryList"
+            :key="item.id"
+            :label="item.stuffName"
+            :value="item.id"
+          />
+        </el-select>
+      </template>
+      <template #department>
+        <el-cascader
+          ref="cascaderRef"
+          v-model="ruleFormData.deptId"
+          :options="deptTree"
+          :props="cascaderDeptProp"
+          :show-all-levels="false"
+          placeholder="请选择部门"
+          filterable
+          :disabled="isViewMode || isAuditMode"
+          @change="handleDeptChange"
+        />
+      </template>
+      <template #recipient>
+        <el-select
+          v-model="ruleFormData.recipientUserId"
+          placeholder="请选择领取人"
+          filterable
+          clearable
+          :disabled="isViewMode || isAuditMode"
+          @change="handleRecipientChange"
+        >
+          <el-option
+            v-for="user in recipientUserList"
+            :key="user.id"
+            :label="user.realname"
+            :value="user.id"
+          />
+        </el-select>
+      </template>
+    </BasicForm>
+  </main>
+  <footer class="safety-platform-container__footer">
+    <el-button @click="router.back()">返回</el-button>
+    <template v-if="isAuditMode">
+      <el-button type="primary" @click="handleAuditPass">同意</el-button>
+      <el-button type="danger" @click="handleAuditReject">拒绝</el-button>
+    </template>
+    <el-button v-else-if="!isViewMode" type="primary" @click="handleSubmit">
+      {{ isCreateMode ? '提交' : '保存' }}
+    </el-button>
+  </footer>
+</template>
+
+<script setup lang="ts">
+  import { computed, onMounted, ref } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import BasicForm from '@/components/BasicForm.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import {
+    RECEIPT_RECORD_FORM_CONFIG,
+    RECEIPT_RECORD_FORM_DATA,
+    RECEIPT_RECORD_FORM_RULES,
+  } from '../configs/form';
+  import {
+    saveClaimItemsLog,
+    updateClaimItemsLog,
+    queryClaimItemsLogDetail,
+    querInventoryManageList,
+    updateClaimItemsLogAgree,
+    updateClaimItemsLogReject,
+  } from '@/api/receiptRecord';
+  import type { InventoryItem } from '@/api/inventory';
+  import { getAllDepartments } from '@/api/auth/dept';
+  import type { DeptTree } from '@/types/dept/type';
+  import { getUserList } from '@/api/system/user-operate';
+  import type { UserLisItem } from '@/api/system/user-operate';
+
+  const router = useRouter();
+  const route = useRoute();
+
+  const operate = computed(() => (route.query.operate as string) || 'receiptRecord-create');
+  const currentId = computed(() => Number(route.query.id));
+
+  const isCreateMode = computed(() => operate.value === 'receiptRecord-create');
+  const isEditMode = computed(() => operate.value === 'receiptRecord-edit');
+  const isViewMode = computed(() => operate.value === 'receiptRecord-view');
+  const isAuditMode = computed(() => operate.value === 'receiptRecord-audit');
+
+  // 部门树(复用添加应急预案的制定部门数据源)
+  const cascaderRef = ref();
+  const deptTree = ref<DeptTree[]>([]);
+  const cascaderDeptProp = {
+    checkStrictly: true,
+    expandTrigger: 'hover' as const,
+    value: 'id',
+    label: 'deptName',
+    emitPath: false,
+  };
+  const getDeptTreeData = async () => {
+    try {
+      const res = await getAllDepartments();
+      deptTree.value = res?.[0]?.children ?? [];
+    } catch (e) {
+      console.error('获取部门树失败:', e);
+    }
+  };
+  const handleDeptChange = () => {
+    const nodes = cascaderRef.value?.getCheckedNodes();
+    ruleFormData.department = nodes?.[0]?.label ?? '';
+    // 部门变化时,重新获取该部门的用户列表
+    if (ruleFormData.deptId) {
+      getRecipientUserList(ruleFormData.deptId);
+    } else {
+      recipientUserList.value = [];
+      ruleFormData.recipientUserId = null;
+    }
+  };
+  const findDeptIdByName = (nodes: DeptTree[] | undefined, name: string): number | undefined => {
+    if (!nodes?.length) return undefined;
+    for (const n of nodes) {
+      if (n.deptName === name) return n.id ?? undefined;
+      const found = findDeptIdByName(n.children, name);
+      if (found != null) return found;
+    }
+    return undefined;
+  };
+
+  // 领取人用户列表
+  const recipientUserList = ref<UserLisItem[]>([]);
+  const getRecipientUserList = async (deptId: number) => {
+    try {
+      const res = await getUserList({
+        pageNumber: 1,
+        pageSize: 9999,
+        queryParam: {
+          deptId,
+        },
+      });
+      if (res && res.records) {
+        recipientUserList.value = res.records;
+      } else {
+        recipientUserList.value = [];
+      }
+    } catch (e) {
+      console.error('获取用户列表失败:', e);
+      recipientUserList.value = [];
+    }
+  };
+
+  const handleRecipientChange = () => {
+    const selectedUser = recipientUserList.value.find((user) => user.id === ruleFormData.recipientUserId);
+    ruleFormData.recipient = selectedUser?.realname ?? '';
+  };
+
+  const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } =
+    useFormConfigHook(RECEIPT_RECORD_FORM_CONFIG, RECEIPT_RECORD_FORM_DATA, RECEIPT_RECORD_FORM_RULES);
+
+  // 物品库存列表
+  const inventoryList = ref<InventoryItem[]>([]);
+
+  // 查看模式下,所有字段设为只读
+  const viewFormConfig = ref(
+    RECEIPT_RECORD_FORM_CONFIG.map((item) => ({
+      ...item,
+      componentProps: {
+        ...item.componentProps,
+        disabled: true,
+      },
+    })),
+  );
+
+  const computedFormConfig = computed(() => {
+    if (isViewMode.value || isAuditMode.value) {
+      return viewFormConfig.value;
+    }
+    return ruleFormConfig.value;
+  });
+
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+
+  // 获取物品库存列表
+  const getInventoryList = async () => {
+    try {
+      const res = await querInventoryManageList();
+      if (res) {
+        if (Array.isArray(res)) {
+          inventoryList.value = res;
+        } else if (res && typeof res === 'object' && 'data' in res && Array.isArray(res.data)) {
+          inventoryList.value = res.data;
+        } else {
+          inventoryList.value = res as InventoryItem[];
+        }
+      }
+    } catch (e) {
+      console.error('获取物品库存列表失败:', e);
+    }
+  };
+
+  const handleValidate = async () => {
+    if (!basicFormRef.value) return;
+    const res = await basicFormRef.value.validateForm();
+    return res;
+  };
+
+  const getDetail = async () => {
+    if (!currentId.value) return;
+    try {
+      const res = await queryClaimItemsLogDetail(currentId.value);
+      if (res) {
+        ruleFormData.itemId = res.pimId;
+        ruleFormData.outboundDate = res.outStoreTime ? res.outStoreTime.split('T')[0] : '';
+        ruleFormData.outboundQuantity = res.claimQty;
+        ruleFormData.receiptNumber = res.orderNumber;
+        ruleFormData.department = res.deptName ?? '';
+        ruleFormData.deptId = findDeptIdByName(deptTree.value, res.deptName ?? '') ?? null;
+        ruleFormData.recipient = res.userName ?? '';
+        ruleFormData.pimId = res.pimId;
+        
+        // 如果部门ID存在,先获取该部门的用户列表,然后根据用户名查找用户ID
+        if (ruleFormData.deptId) {
+          await getRecipientUserList(ruleFormData.deptId);
+          const foundUser = recipientUserList.value.find((user) => user.realname === res.userName);
+          ruleFormData.recipientUserId = foundUser?.id ?? null;
+        } else {
+          ruleFormData.recipientUserId = null;
+        }
+      }
+      cloneRuleFormData();
+    } catch (e) {
+      console.error('获取物品领取记录详情失败:', e);
+      ElMessage.error('获取详情失败');
+    }
+  };
+
+  const handleSubmit = async () => {
+    const res = await handleValidate();
+    if (!res) return;
+    try {
+      const selectedInventory = inventoryList.value.find((item) => item.id === ruleFormData.itemId);
+      if (!selectedInventory) {
+        ElMessage.error('请选择有效的物品名称');
+        return;
+      }
+
+      const selectedUser = recipientUserList.value.find((user) => user.id === ruleFormData.recipientUserId);
+      if (!selectedUser) {
+        ElMessage.error('请选择有效的领取人');
+        return;
+      }
+
+      const basePayload = {
+        stuffName: selectedInventory.stuffName,
+        pimId: selectedInventory.id,
+        outStoreTime: ruleFormData.outboundDate
+          ? new Date(ruleFormData.outboundDate).toISOString()
+          : '',
+        claimQty: ruleFormData.outboundQuantity,
+        orderNumber: ruleFormData.receiptNumber,
+        deptName: ruleFormData.department,
+        deptId: ruleFormData.deptId ?? undefined,
+        userName: selectedUser.realname,
+        userId: selectedUser.id, // 领取人用户ID
+        remark: '',
+      };
+
+      if (isCreateMode.value) {
+        await saveClaimItemsLog(basePayload);
+        ElMessage.success('创建成功');
+      } else if (isEditMode.value && currentId.value) {
+        await updateClaimItemsLog({
+          id: currentId.value,
+          ...basePayload,
+        });
+        ElMessage.success('保存成功');
+      }
+
+      router.back();
+    } catch (e) {
+      console.error('保存物品领取记录失败:', e);
+      ElMessage.error('保存失败,请重试');
+    }
+  };
+
+  const handleAuditPass = async () => {
+    if (!currentId.value) return;
+    try {
+      await updateClaimItemsLogAgree(currentId.value);
+      ElMessage.success('审核通过');
+      router.back();
+    } catch (e) {
+      console.error('审核失败:', e);
+      ElMessage.error('审核失败,请重试');
+    }
+  };
+
+  const handleAuditReject = async () => {
+    if (!currentId.value) return;
+    try {
+      await updateClaimItemsLogReject(currentId.value);
+      ElMessage.success('已审核不通过');
+      router.back();
+    } catch (e) {
+      console.error('审核失败:', e);
+      ElMessage.error('审核失败,请重试');
+    }
+  };
+
+  onMounted(async () => {
+    cloneRuleFormData();
+    beforeRouteLeave();
+    await Promise.all([getDeptTreeData(), getInventoryList()]);
+    if (isEditMode.value || isViewMode.value || isAuditMode.value) {
+      await getDetail();
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+</style>
+

+ 82 - 0
src/views/production-safety/safetyAssessment/receiptRecord/configs/form.ts

@@ -0,0 +1,82 @@
+import { FormConfig } from '@/types/basic-form';
+
+export const RECEIPT_RECORD_FORM_CONFIG: FormConfig[] = [
+  {
+    prop: 'itemId',
+    label: '物品名称:',
+    component: 'ElSelect',
+    slot: 'itemId', // 使用自定义插槽
+    componentProps: {
+      placeholder: '请选择物品名称',
+      filterable: true,
+    },
+  },
+  {
+    prop: 'outboundDate',
+    label: '出库日期:',
+    component: 'ElDatePicker',
+    componentProps: {
+      type: 'date',
+      placeholder: '请选择出库日期',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  {
+    prop: 'outboundQuantity',
+    label: '取出数量:',
+    component: 'ElInputNumber',
+    componentProps: {
+      min: 1,
+      precision: 0, // 不允许小数点,只能输入整数
+      placeholder: '请输入取出数量',
+    },
+  },
+  {
+    prop: 'receiptNumber',
+    label: '单号:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入单号',
+    },
+  },
+  {
+    prop: 'department',
+    label: '部门:',
+    slot: 'department', // 复用添加应急预案的制定部门下拉(el-cascader)
+    componentProps: {
+      placeholder: '请选择部门',
+    },
+  },
+  {
+    prop: 'recipient',
+    label: '领取人:',
+    slot: 'recipient', // 使用用户列表接口(getUserList + el-select)
+    componentProps: {
+      placeholder: '请选择领取人',
+    },
+  },
+];
+
+export const RECEIPT_RECORD_FORM_DATA = {
+  itemId: undefined as number | undefined, // 物品库存ID(用于下拉框选中)
+  outboundDate: '',
+  outboundQuantity: 1, // 最小值为1
+  receiptNumber: '',
+  deptId: null as number | null, // 部门ID(部门级联,与应急预案制定部门同源)
+  department: '', // 部门名称,提交给接口的 deptName
+  recipientUserId: null as number | null, // 领取人用户ID(通过getUserList接口获取)
+  recipient: '', // 领取人姓名,提交给接口的 userName
+  pimId: 0, // 物品库存ID(用于提交时使用,兼容旧数据)
+};
+
+export const RECEIPT_RECORD_FORM_RULES = {
+  itemId: [{ required: true, message: '请选择物品名称', trigger: 'change' }],
+  outboundDate: [{ required: true, message: '请选择出库日期', trigger: 'change' }],
+  outboundQuantity: [
+    { required: true, message: '请输入取出数量', trigger: 'blur' },
+    { type: 'number', min: 1, message: '取出数量不能小于1', trigger: 'blur' },
+  ],
+  receiptNumber: [{ required: true, message: '请输入单号', trigger: 'blur' }],
+  department: [{ required: true, message: '请选择部门', trigger: 'change' }],
+  recipient: [{ required: true, message: '请选择领取人', trigger: 'change' }],
+};

+ 13 - 0
src/views/production-safety/safetyAssessment/receiptRecord/configs/status.ts

@@ -0,0 +1,13 @@
+// 物品领取记录状态配置
+// 0 待审核, 1 审核通过, -1 审核不通过
+export const RECEIPT_RECORD_STATUS_OPTIONS = [
+  { label: '待审核', value: 0 },
+  { label: '审核通过', value: 1 },
+  { label: '审核不通过', value: -1 },
+];
+
+export const RECEIPT_RECORD_STATUS_LABEL: Record<string, string> = {
+  '0': '待审核',
+  '1': '审核通过',
+  '-1': '审核不通过',
+};

+ 68 - 0
src/views/production-safety/safetyAssessment/receiptRecord/configs/tables.ts

@@ -0,0 +1,68 @@
+import type { TableColumnProps } from '@/types/basic-table';
+
+// 基础表格样式配置
+export const TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  loading: true,
+  maxHeight: 'calc(70vh - 150px)',
+};
+
+export const RECEIPT_RECORD_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '物品名称',
+    prop: 'itemName',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '出库日期',
+    prop: 'outboundDate',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '取出数量',
+    prop: 'outboundQuantity',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '结余数量',
+    prop: 'remainingQuantity',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '单号',
+    prop: 'receiptNumber',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '部门',
+    prop: 'department',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '领取人',
+    prop: 'recipient',
+    align: 'left',
+    minWidth: '120px',
+  },
+  {
+    label: '状态',
+    prop: 'status',
+    slot: 'status',
+    align: 'center',
+    minWidth: '100px',
+  },
+  {
+    label: '操作',
+    prop: 'action',
+    slot: 'action',
+    fixed: 'right',
+    width: '180px',
+    align: 'left',
+  },
+];

+ 307 - 0
src/views/production-safety/safetyAssessment/receiptRecord/receiptRecord.vue

@@ -0,0 +1,307 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="breadcrumb-title"> 物品领取记录管理 </div>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header>
+          <div style="position: relative">
+            <el-button type="primary" class="search-table-container--button" @click="handleCreate">
+              添加
+            </el-button>
+            <el-button plain class="search-table-container--button" @click="handleImport">
+              导入
+            </el-button>
+            <el-button plain class="search-table-container--button" @click="handleDownload">
+              导出
+            </el-button>
+          </div>
+
+          <div class="act-search">
+            <section class="select-box">
+              <div class="select-box--item">
+                <span>物品名称:</span>
+                <el-input
+                  v-model="tableQuery.queryParam.stuffName"
+                  placeholder="搜索物品名称"
+                  class="act-search-input"
+                />
+              </div>
+              <div class="select-box--item">
+                <span>部门名称:</span>
+                <el-input
+                  v-model="tableQuery.queryParam.deptName"
+                  placeholder="搜索部门名称"
+                  class="act-search-input"
+                />
+              </div>
+              <div class="select-box--item">
+                <span>状态:</span>
+                <el-select
+                  v-model="tableQuery.queryParam.status"
+                  placeholder="请选择状态"
+                  clearable
+                >
+                  <el-option
+                    v-for="item in RECEIPT_RECORD_STATUS_OPTIONS"
+                    :key="String(item.value)"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </el-select>
+              </div>
+            </section>
+            <section class="search-btn">
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+            </section>
+          </div>
+        </header>
+
+        <div class="batch-table">
+          <BasicTable
+            ref="basicTableRef"
+            :tableData="tableData"
+            :tableConfig="tableConfig"
+            @update:pageSize="handleSizeChange"
+            @update:pageNumber="handleCurrentChange"
+          >
+            <template #status="scope">
+              <span>{{ RECEIPT_RECORD_STATUS_LABEL[String(scope.row.status)] ?? '-' }}</span>
+            </template>
+            <template #action="scope">
+              <div class="action-container--div" style="justify-content: left">
+                <!-- 0 待审核:审核 -->
+                <template v-if="Number(scope.row.status) === 0">
+                  <ActionButton text="审核" @click="handleAudit(scope.row.id)" />
+                </template>
+                <!-- 1 审核通过:删除、查看 -->
+                <template v-else-if="Number(scope.row.status) === 1">
+                  <ActionButton
+                    text="删除"
+                    :popconfirm="{ title: '确定要删除?' }"
+                    @confirm="handleDelete(scope.row.id)"
+                  />
+                  <ActionButton text="查看" @click="handleView(scope.row.id)" />
+                </template>
+                <!-- -1 审核不通过:编辑、删除、查看 -->
+                <template v-else-if="Number(scope.row.status) === -1">
+                  <ActionButton text="编辑" @click="handleEdit(scope.row.id)" />
+                  <ActionButton
+                    text="删除"
+                    :popconfirm="{ title: '确定要删除?' }"
+                    @confirm="handleDelete(scope.row.id)"
+                  />
+                  <ActionButton text="查看" @click="handleView(scope.row.id)" />
+                </template>
+                <!-- 其他/未知状态:编辑、删除、查看 -->
+                <template v-else>
+                  <ActionButton text="编辑" @click="handleEdit(scope.row.id)" />
+                  <ActionButton
+                    text="删除"
+                    :popconfirm="{ title: '确定要删除?' }"
+                    @confirm="handleDelete(scope.row.id)"
+                  />
+                  <ActionButton text="查看" @click="handleView(scope.row.id)" />
+                </template>
+              </div>
+            </template>
+          </BasicTable>
+        </div>
+      </div>
+
+    </main>
+    <BatchImport
+      v-if="batchImportVisible"
+      :visible="batchImportVisible"
+      :import-api-url="importApiUrl"
+      :template-url="templateUrl"
+      template-name="下载模板"
+      :show-template="false"
+      @close="batchImportVisible = false"
+      @update="handleUpdate"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { onMounted, reactive, ref } from 'vue';
+  import BasicTable from '@/components/BasicTable.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import ActionButton from '@/components/ActionButton.vue';
+  import { TABLE_OPTIONS, RECEIPT_RECORD_TABLE_COLUMNS } from './configs/tables';
+  import { RECEIPT_RECORD_STATUS_OPTIONS, RECEIPT_RECORD_STATUS_LABEL } from './configs/status';
+  import { useRouter } from 'vue-router';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import { queryClaimItemsLogs, deleteClaimItemsLog, exportClaimItemsLog } from '@/api/receiptRecord';
+  import { downloadByData } from '@/utils/file/download';
+  import BatchImport from '@/components/batch-import/BatchImport.vue';
+  import { useGlobSetting } from '@/hooks/setting';
+  import urlJoin from 'url-join';
+  import { ElMessage } from 'element-plus';
+
+  const router = useRouter();
+
+  // 表格
+  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+
+  const { tableConfig, pagination } = useTableConfig(RECEIPT_RECORD_TABLE_COLUMNS, TABLE_OPTIONS);
+
+  const tableData = ref<any[]>([]);
+
+  const tableQuery = reactive<QueryPageRequest<any>>({
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {
+      stuffName: '',
+      deptName: '',
+      status: undefined as number | undefined, // 0 待审核, 1 审核通过, -1 审核不通过
+    },
+  });
+
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    tableQuery.pageSize = value;
+    getTableData();
+  };
+
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    tableQuery.pageNumber = value;
+    getTableData();
+  };
+
+  async function getTableData() {
+    tableConfig.loading = true;
+    try {
+      const res = await queryClaimItemsLogs(tableQuery);
+      if (res) {
+        // 映射返回数据字段到表格字段
+        tableData.value = res.records.map((item) => ({
+          id: item.id,
+          itemName: item.stuffName, // 物品名称
+          outboundDate: item.outStoreTime, // 出库日期
+          outboundQuantity: item.claimQty, // 取出数量
+          remainingQuantity: item.surplusQty, // 结余数量
+          receiptNumber: item.orderNumber, // 单号
+          department: item.deptName, // 部门
+          recipient: item.userName, // 领取人
+          status: item.status, // 0 待审核 1 审核通过 -1 审核不通过
+        }));
+        pagination.total = res.totalRow;
+      }
+    } catch (e) {
+      console.error('获取物品领取记录列表失败:', e);
+      tableData.value = [];
+      pagination.total = 0;
+    } finally {
+      tableConfig.loading = false;
+    }
+  }
+
+  const handleSearch = () => {
+    pagination.pageNumber = 1;
+    tableQuery.pageNumber = 1;
+    getTableData();
+  };
+
+  const handleReset = () => {
+    tableQuery.queryParam.stuffName = '';
+    tableQuery.queryParam.deptName = '';
+    tableQuery.queryParam.status = undefined;
+    handleSearch();
+  };
+
+  // 批量导入
+  const batchImportVisible = ref(false);
+  const { urlPrefix } = useGlobSetting();
+  const importApiUrl = ref(urlJoin(urlPrefix, '/claimItemsLog/importClaimItemsLog'));
+  const templateUrl = ref('./skyeye-file-upload/sfysecurity/TEMPLATE/import-receipt-record-template.xlsx');
+
+  const handleImport = () => {
+    batchImportVisible.value = true;
+  };
+
+  const handleUpdate = () => {
+    batchImportVisible.value = false;
+    getTableData();
+  };
+
+  const handleDownload = async () => {
+    try {
+      const exportParams = {
+        stuffName: tableQuery.queryParam.stuffName || undefined,
+        deptName: tableQuery.queryParam.deptName || undefined,
+        status: tableQuery.queryParam.status,
+      };
+      const response = await exportClaimItemsLog(exportParams);
+      if (response) {
+        const fileName = `物品领取记录_${new Date().toISOString().split('T')[0]}.xlsx`;
+        downloadByData(response, fileName);
+        ElMessage.success('导出成功');
+      }
+    } catch (e) {
+      console.error('导出物品领取记录失败:', e);
+      ElMessage.error('导出失败,请重试');
+    }
+  };
+
+  const handleCreate = () => {
+    router.push({
+      name: 'ReceiptRecordItem',
+      query: {
+        operate: 'receiptRecord-create',
+      },
+    });
+  };
+
+  const handleEdit = (id: number) => {
+    router.push({
+      name: 'ReceiptRecordItem',
+      query: {
+        id,
+        operate: 'receiptRecord-edit',
+      },
+    });
+  };
+
+  const handleDelete = async (id: number) => {
+    try {
+      await deleteClaimItemsLog(id);
+      ElMessage.success('删除成功');
+      getTableData();
+    } catch (e) {
+      console.error('删除物品领取记录失败:', e);
+      ElMessage.error('删除失败,请重试');
+    }
+  };
+
+  const handleView = (id: number) => {
+    router.push({
+      name: 'ReceiptRecordItem',
+      query: {
+        id,
+        operate: 'receiptRecord-view',
+      },
+    });
+  };
+
+  const handleAudit = (id: number) => {
+    router.push({
+      name: 'ReceiptRecordItem',
+      query: { id, operate: 'receiptRecord-audit' },
+    });
+  };
+
+  onMounted(() => {
+    getTableData();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+  @use '@/views/traffic/violation/style/act-search-table.scss' as *;
+</style>

+ 46 - 0
src/views/production-safety/safetyAssessment/receiptRecord/receiptRecordItem.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ headerTitle }}</span>
+    </header>
+    <ReceiptRecordDetail />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { computed } from 'vue';
+  import { useRoute } from 'vue-router';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import ReceiptRecordDetail from './components/ReceiptRecordDetail.vue';
+
+  const route = useRoute();
+  const operate = route.query.operate as string;
+
+  const headerTitle = computed(() => {
+    switch (operate) {
+      case 'receiptRecord-create':
+        return '新增物品领取记录';
+      case 'receiptRecord-edit':
+        return '编辑物品领取记录';
+      case 'receiptRecord-view':
+        return '查看物品领取记录';
+      case 'receiptRecord-audit':
+        return '审核物品领取记录';
+      default:
+        return '未知操作';
+    }
+  });
+</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>
+

+ 1 - 1
utils/devProxy/staff/proxy.ts

@@ -3,7 +3,7 @@ import path from 'path';
 
 // staff环境
 const proxyStaff: PROXY_TYPE = {
-  serverHost: 'http://192.168.6.42:8802/',
+  serverHost: 'http://192.168.22.116:8802/',
   // serverHost: 'http://192.168.20.4:8802/',
   skyeyeLoginHost: 'http://192.168.6.42:7000/skyeye-login/#/',
   skyeyePlatformHost: 'http://192.168.6.42:7000/skyeye-pc/#/',