Quellcode durchsuchen

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

xiaweibo vor 2 Monaten
Ursprung
Commit
2fcb8d5656
17 geänderte Dateien mit 2069 neuen und 582 gelöschten Zeilen
  1. 117 0
      src/api/production-safety/personal-protective-equipment-purchase-apply.ts
  2. 82 0
      src/api/production-safety/personal-protective-equipment-receive.ts
  3. 75 0
      src/api/production-safety/personal-protective-equipment.ts
  4. 10 8
      src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagementDept/components/areaCheckPlanManagementDeptDetail.vue
  5. 119 1
      src/views/production-safety/risk-identification-and-control/labor-products-manage/add.vue
  6. 162 1
      src/views/production-safety/risk-identification-and-control/labor-products-manage/edit.vue
  7. 107 173
      src/views/production-safety/risk-identification-and-control/labor-products-manage/list.vue
  8. 124 1
      src/views/production-safety/risk-identification-and-control/labor-products-manage/view.vue
  9. 2 0
      src/views/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage/Item.vue
  10. 375 84
      src/views/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage/components/detail.vue
  11. 18 24
      src/views/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage/configs/tables.ts
  12. 83 111
      src/views/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage/list.vue
  13. 211 1
      src/views/production-safety/risk-identification-and-control/labor-products-requisition-manage/add.vue
  14. 275 1
      src/views/production-safety/risk-identification-and-control/labor-products-requisition-manage/edit.vue
  15. 93 175
      src/views/production-safety/risk-identification-and-control/labor-products-requisition-manage/list.vue
  16. 215 1
      src/views/production-safety/risk-identification-and-control/labor-products-requisition-manage/view.vue
  17. 1 1
      utils/devProxy/staff/proxy.ts

+ 117 - 0
src/api/production-safety/personal-protective-equipment-purchase-apply.ts

@@ -0,0 +1,117 @@
+import { http } from '@/utils/http/axios';
+import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
+
+/**
+ * 劳防用品采购申请 - 实体与接口
+ * 状态:0-待审核,1-审核通过,-1-审核不通过
+ */
+
+/** 采购申请明细行(劳防用品项) */
+export interface PurchaseApplyItem {
+  id?: number;
+  ppeName?: string; // 劳防用品名称
+  ppeId?: number; // 劳防用品ID(下拉选择)
+  stylePhoto?: string; // 样式照片(附件URL或JSON)
+  sizeOrShoeSize?: string; // 物品尺寸/鞋码
+  requiredQty?: number | string; // 需求数量
+  specModelType?: string; // 规格、型号、类型
+  unitPrice?: number | string; // 单价/元
+  productNo?: string; // 商品编号
+  remark?: string; // 备注
+  [key: string]: any;
+}
+
+export interface PersonalProtectiveEquipmentPurchaseApply {
+  id?: number;
+  applyNo?: string; // 申请单号
+  applicantName?: string; // 申请人
+  applicantDeptCode?: string; // 申请人部门编码
+  applicantDeptName?: string; // 申请人部门
+  ppeName?: string; // 用品名称(主表冗余,列表用)
+  status?: number; // 0-待审核,1-审核通过,-1-审核不通过
+  statusName?: string; // 状态名称
+  currentNodeName?: string; // 当前流程节点
+  auditDeptCode?: string; // 审核部门编码
+  auditDeptName?: string; // 审核部门名称
+  remark?: string;
+  /** 明细列表(劳防用品项) */
+  itemList?: PurchaseApplyItem[];
+  isDeleted?: string | number;
+  createdAt?: string;
+  updatedAt?: string;
+  [key: string]: any;
+}
+
+/** 分页查询请求 - 查询条件 */
+export interface QueryPurchaseApplyPageReq {
+  /** 用品名称 或 申请人(模糊) */
+  keyword?: string;
+  /** 状态:0-待审核,1-审核通过,-1-审核不通过 */
+  status?: number;
+  /** 审核部门编码或名称 */
+  auditDeptCode?: string;
+  auditDeptName?: string;
+}
+
+/** 分页查询劳防用品采购申请列表 */
+export function queryPurchaseApplyList(
+  query: QueryPageRequest<QueryPurchaseApplyPageReq>,
+) {
+  return http.request<QueryPageResponse<PersonalProtectiveEquipmentPurchaseApply>>({
+    url: '/personalProtectiveEquipmentPurchaseApply/queryPurchaseApplyList',
+    method: 'post',
+    data: query,
+  });
+}
+
+/** 查询劳防用品采购申请详情 */
+export function queryPurchaseApplyDetail(id: number) {
+  return http.request<PersonalProtectiveEquipmentPurchaseApply>({
+    url: '/personalProtectiveEquipmentPurchaseApply/queryPurchaseApplyDetail',
+    method: 'post',
+    params: { id },
+  });
+}
+
+/** 新增劳防用品采购申请 */
+export function savePurchaseApply(data: PersonalProtectiveEquipmentPurchaseApply) {
+  return http.request({
+    url: '/personalProtectiveEquipmentPurchaseApply/savePurchaseApply',
+    method: 'post',
+    data,
+  });
+}
+
+/** 编辑劳防用品采购申请 */
+export function updatePurchaseApply(data: PersonalProtectiveEquipmentPurchaseApply) {
+  return http.request({
+    url: '/personalProtectiveEquipmentPurchaseApply/updatePurchaseApply',
+    method: 'put',
+    data,
+  });
+}
+
+/** 删除劳防用品采购申请 */
+export function deletePurchaseApply(id: number) {
+  return http.request({
+    url: `/personalProtectiveEquipmentPurchaseApply/deletePurchaseApply?id=${id}`,
+    method: 'delete',
+  });
+}
+
+/** 审核参数 */
+export interface AuditPurchaseApplyParam {
+  id: number;
+  /** 1-审核通过,-1-审核不通过 */
+  status: number;
+  rejectReason?: string; // 审核不通过原因
+}
+
+/** 审核劳防用品采购申请 */
+export function auditPurchaseApply(data: AuditPurchaseApplyParam) {
+  return http.request({
+    url: '/personalProtectiveEquipmentPurchaseApply/auditPurchaseApply',
+    method: 'post',
+    data,
+  });
+}

+ 82 - 0
src/api/production-safety/personal-protective-equipment-receive.ts

@@ -0,0 +1,82 @@
+import { http } from '@/utils/http/axios';
+import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
+
+/**
+ * 劳防用品领用管理 - 实体与接口
+ * 基础路径: /api/personalProtectiveEquipmentReceive
+ */
+
+/** 劳防用品领用记录实体 */
+export interface PersonalProtectiveEquipmentReceive {
+  id?: number; // 领用记录唯一ID(自增)
+  equipmentId?: string | number; // 劳防用品ID
+  version?: string; // 型号
+  ppeName?: string; // 防护用品名称
+  hazardFactor?: string; // 职业危害因素
+  issuerCode?: string; // 发放人CODE
+  issuerName?: string; // 发放人名称
+  issueTime?: string; // 发放时间
+  receiveNum?: number | string; // 领用数量
+  receiverCode?: string; // 领用人CODE
+  receiverName?: string; // 领用人名称
+  remark?: string; // 领用备注
+  status?: boolean; // 用品状态:true-启用,false-禁用
+  isDeleted?: string | number;
+  createdAt?: string;
+  updatedAt?: string;
+}
+
+/** 分页查询请求 - 查询条件 */
+export interface QueryPersonalProtectiveEquipmentReceivePageReq {
+  name?: string; // 标题
+  status?: boolean; // 状态:true-启用,false-禁用
+}
+
+/** 分页查询劳防用品领用列表 */
+export function queryPersonalProtectiveEquipmentReceiveList(
+  query: QueryPageRequest<QueryPersonalProtectiveEquipmentReceivePageReq>,
+) {
+  return http.request<QueryPageResponse<PersonalProtectiveEquipmentReceive>>({
+    url: '/personalProtectiveEquipmentReceive/queryPersonalProtectiveEquipmentReceiveList',
+    method: 'post',
+    data: query,
+  });
+}
+
+/** 新增劳防用品领用(id 不传) */
+export function savePersonalProtectiveEquipmentReceive(
+  data: PersonalProtectiveEquipmentReceive,
+) {
+  return http.request({
+    url: '/personalProtectiveEquipmentReceive/savePersonalProtectiveEquipmentReceive',
+    method: 'post',
+    data,
+  });
+}
+
+/** 编辑劳防用品领用(id 必传) */
+export function updatePersonalProtectiveEquipmentReceive(
+  data: PersonalProtectiveEquipmentReceive,
+) {
+  return http.request({
+    url: '/personalProtectiveEquipmentReceive/updatePersonalProtectiveEquipmentReceive',
+    method: 'put',
+    data,
+  });
+}
+
+/** 查询劳防用品领用详情(id 必传,RequestParam) */
+export function queryPersonalProtectiveEquipmentReceiveDetail(id: number) {
+  return http.request<PersonalProtectiveEquipmentReceive>({
+    url: `/personalProtectiveEquipmentReceive/queryPersonalProtectiveEquipmentReceiveDetail?id=${id}`,
+    method: 'post',
+  });
+}
+
+/** 删除劳防用品领用(POST,传 id) */
+export function deletePersonalProtectiveEquipmentReceive(id: number) {
+  return http.request({
+    url: `/personalProtectiveEquipmentReceive/deletePersonalProtectiveEquipmentReceive?id=${id}`,
+    method: 'delete',
+  });
+}

+ 75 - 0
src/api/production-safety/personal-protective-equipment.ts

@@ -0,0 +1,75 @@
+import { http } from '@/utils/http/axios';
+import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
+
+/**
+ * 劳防用品管理 - 实体与接口
+ * 基础路径: /api/personalProtectiveEquipment
+ */
+
+/** 劳防用品实体 */
+export interface PersonalProtectiveEquipment {
+  id?: number;
+  jobPost?: string; // 作业(岗位)分类
+  ppeCategory?: string; // 物品分类
+  ppeName?: string; // 物品名称
+  serviceMonths?: string; // 使用期限(月)
+  status?: boolean; // 用品状态:true-启用,false-禁用
+  remark?: string; // 备注
+  isDeleted?: string | number; // 0-未删除,大于0(时间戳)-已删除
+  createdAt?: string; // 创建时间
+  updatedAt?: string; // 更新时间
+}
+
+/** 分页查询请求体 - 查询条件 */
+export interface QueryPersonalProtectiveEquipmentCommonPageReq {
+  name?: string; // 标题
+  status?: boolean; // 状态:true-启用,false-禁用
+  applyDeptCode?: string; // 申请人部门CODE/申请部门编码
+  applyDeptName?: string; // 申请人部门名称
+}
+
+/** 分页查询劳防用品列表 */
+export function queryPersonalProtectiveEquipmentList(
+  query: QueryPageRequest<QueryPersonalProtectiveEquipmentCommonPageReq>,
+) {
+  return http.request<QueryPageResponse<PersonalProtectiveEquipment>>({
+    url: '/personalProtectiveEquipment/queryPersonalProtectiveEquipmentList',
+    method: 'post',
+    data: query,
+  });
+}
+
+/** 新增劳防用品(id 不传) */
+export function savePersonalProtectiveEquipment(data: PersonalProtectiveEquipment) {
+  return http.request({
+    url: '/personalProtectiveEquipment/savePersonalProtectiveEquipment',
+    method: 'post',
+    data,
+  });
+}
+
+/** 编辑劳防用品(id 必传) */
+export function updatePersonalProtectiveEquipment(data: PersonalProtectiveEquipment) {
+  return http.request({
+    url: '/personalProtectiveEquipment/updatePersonalProtectiveEquipment',
+    method: 'put',
+    data,
+  });
+}
+
+/** 查询劳防用品详情(id 必传,RequestParam) */
+export function queryPersonalProtectiveEquipmentDetail(id: number) {
+  return http.request<PersonalProtectiveEquipment>({
+    url: '/personalProtectiveEquipment/queryPersonalProtectiveEquipmentDetail',
+    method: 'post',
+    params: { id },
+  });
+}
+
+/** 删除劳防用品(POST,传 id) */
+export function deletePersonalProtectiveEquipment(id: number) {
+  return http.request({
+    url: `/personalProtectiveEquipment/deletePersonalProtectiveEquipment?id=${id}`,
+    method: 'delete',
+  });
+}

+ 10 - 8
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/areaCheckPlanManagementDept/components/areaCheckPlanManagementDeptDetail.vue

@@ -5,8 +5,8 @@
       <div class="view-summary__title">一级危险点:{{ viewDetail.checkVenue || '-' }}</div>
       <div class="view-summary__meta">
         <span>检查类别:{{ viewDetail.venueCategoryName || '-' }}</span>
-        <span>创建人:{{ viewDetail. createdPersonName || '李小红' }}</span>
-        <span>创建时间:{{ viewDetail.createdAt || '2026-01-06 12:20:30' }}</span>
+        <span>创建人:{{ viewDetail.createdPersonName || '-' }}</span>
+        <span>创建时间:{{ viewDetail.createdAt || '-' }}</span>
       </div>
     </section>
 
@@ -29,7 +29,7 @@
         <div class="row">
           <div class="col">
             <div class="label">主责部门:</div>
-            <div class="value">{{ viewDetail.mainDeptName || '-' }}</div>
+            <div class="value">{{ viewDetail.primaryResponsibleDeptName || '-' }}</div>
           </div>
           <div class="col">
             <div class="label">自查频率:</div>
@@ -89,7 +89,7 @@
         <div class="row">
           <div class="col">
             <div class="label">检查单所属类别名称:</div>
-            <div class="value">{{ viewDetail.checklistCategoryName || '-' }}</div>
+            <div class="value">{{ viewDetail.categoryName || '-' }}</div>
           </div>
           <div class="col">
             <div class="label">检查单模版名称:</div>
@@ -127,7 +127,7 @@
           </div>
           <div class="col">
             <div class="label">业务工作:</div>
-            <div class="value">{{ viewDetail.businessWork || '动火作业' }}</div>
+            <div class="value">{{ viewDetail.businessWork || '-' }}</div>
           </div>
         </div>
       </div>
@@ -364,6 +364,7 @@
       venueCategoryName: d?.venueCategoryName ?? '-',
       checkVenue: d?.checkVenue ?? '-',
       mainDeptName: d?.mainDeptName ?? '-',
+      primaryResponsibleDeptName: (d?.primaryResponsibleDeptName ?? d?.mainDeptName) ?? '-',
       selfCheckFrequency: d?.selfCheckFrequency ?? '-',
       mainDeptExecutorGroupName: d?.mainDeptExecutorGroupName ?? '-',
       mainDeptResponsiblePerson: d?.mainDeptResponsiblePerson ?? '-',
@@ -376,14 +377,15 @@
       hospitalLeaderExecutorGroupName: d?.hospitalLeaderExecutorGroupName ?? '-',
       hospitalLeaderResponsiblePerson: d?.hospitalLeaderResponsiblePerson ?? '-',
       checklistCategoryName: d?.checklistCategoryName ?? '-',
+      categoryName: (d?.categoryName ?? d?.checklistCategoryName) ?? '-',
       checklistTemplateName: d?.checklistTemplateName ?? '-',
       needOverallDesc: d?.needOverallDesc,
       needInspectedSign: d?.needInspectedSign,
       planStartTime: d?.planStartTime ?? '-',
       planEndTime: d?.planEndTime ?? '-',
-      createdPersonName: d?. createdPersonName ?? '李小红',
-      createdAt: d?.createdAt ?? '2026-01-06 12:20:30',
-      businessWork: d?.businessWork ?? '动火作业',
+      createdPersonName: (d?.createdPersonName ?? '') || '-',
+      createdAt: (d?.createdAt ?? '') || '-',
+      businessWork: (d?.businessWork ?? '') || '-',
     };
   });
 

+ 119 - 1
src/views/production-safety/risk-identification-and-control/labor-products-manage/add.vue

@@ -1 +1,119 @@
-<template>add</template>
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">新增劳防用品</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <el-form
+        ref="formRef"
+        :model="form"
+        :rules="formRules"
+        label-width="120px"
+        class="ppe-form"
+      >
+        <el-form-item label="作业(岗位)分类" prop="jobPost">
+          <el-input v-model="form.jobPost" placeholder="请输入作业(岗位)分类" clearable />
+        </el-form-item>
+        <el-form-item label="物品分类" prop="ppeCategory">
+          <el-input v-model="form.ppeCategory" placeholder="请输入物品分类" clearable />
+        </el-form-item>
+        <el-form-item label="物品名称" prop="ppeName">
+          <el-input v-model="form.ppeName" placeholder="请输入物品名称" clearable />
+        </el-form-item>
+        <el-form-item label="使用期限(月)" prop="serviceMonths">
+          <el-input-number
+            v-model="form.serviceMonths"
+            :min="1"
+            :max="9999"
+            :precision="0"
+            placeholder="1-9999"
+            controls-position="right"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="用品状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio :label="true">启用</el-radio>
+            <el-radio :label="false">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input
+            v-model="form.remark"
+            type="textarea"
+            placeholder="请输入备注"
+            :rows="3"
+            clearable
+          />
+        </el-form-item>
+      </el-form>
+    </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="router.back()">返回</el-button>
+      <el-button type="primary" :loading="submitting" @click="handleSubmit">提交</el-button>
+    </footer>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive } from 'vue';
+  import { useRouter } from 'vue-router';
+  import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import { savePersonalProtectiveEquipment } from '@/api/production-safety/personal-protective-equipment';
+
+  const router = useRouter();
+  const formRef = ref<FormInstance>();
+  const submitting = ref(false);
+
+  const form = reactive({
+    jobPost: '',
+    ppeCategory: '',
+    ppeName: '',
+    serviceMonths: undefined as number | undefined, // 1-9999 整数
+    status: true as boolean, // true-启用,false-禁用,默认启用
+    remark: '',
+  });
+
+  const formRules: FormRules = {
+    // 按需可加必填规则,接口未标注必填这里不强制
+  };
+
+  const handleSubmit = async () => {
+    const valid = await formRef.value?.validate().then(() => true).catch(() => false);
+    if (!valid) return;
+    submitting.value = true;
+    try {
+      await savePersonalProtectiveEquipment({
+        jobPost: form.jobPost || undefined,
+        ppeCategory: form.ppeCategory || undefined,
+        ppeName: form.ppeName || undefined,
+        serviceMonths: form.serviceMonths != null ? String(form.serviceMonths) : undefined,
+        status: form.status,
+        remark: form.remark || undefined,
+      });
+      ElMessage.success('新增成功');
+      router.back();
+    } catch (e) {
+      console.error('新增劳防用品失败:', e);
+      ElMessage.error('新增失败,请重试');
+    } finally {
+      submitting.value = false;
+    }
+  };
+</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;
+  }
+  .ppe-form {
+    max-width: 600px;
+  }
+</style>

+ 162 - 1
src/views/production-safety/risk-identification-and-control/labor-products-manage/edit.vue

@@ -1 +1,162 @@
-<template>edit</template>
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">编辑劳防用品</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <el-form
+        v-loading="loading"
+        ref="formRef"
+        :model="form"
+        :rules="formRules"
+        label-width="120px"
+        class="ppe-form"
+      >
+        <el-form-item label="作业(岗位)分类" prop="jobPost">
+          <el-input v-model="form.jobPost" placeholder="请输入作业(岗位)分类" clearable />
+        </el-form-item>
+        <el-form-item label="物品分类" prop="ppeCategory">
+          <el-input v-model="form.ppeCategory" placeholder="请输入物品分类" clearable />
+        </el-form-item>
+        <el-form-item label="物品名称" prop="ppeName">
+          <el-input v-model="form.ppeName" placeholder="请输入物品名称" clearable />
+        </el-form-item>
+        <el-form-item label="使用期限(月)" prop="serviceMonths">
+          <el-input-number
+            v-model="form.serviceMonths"
+            :min="1"
+            :max="9999"
+            :precision="0"
+            placeholder="1-9999"
+            controls-position="right"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="用品状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio :label="true">启用</el-radio>
+            <el-radio :label="false">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input
+            v-model="form.remark"
+            type="textarea"
+            placeholder="请输入备注"
+            :rows="3"
+            clearable
+          />
+        </el-form-item>
+      </el-form>
+    </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="router.back()">返回</el-button>
+      <el-button type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
+    </footer>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, onMounted, computed } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import {
+    queryPersonalProtectiveEquipmentDetail,
+    updatePersonalProtectiveEquipment,
+  } from '@/api/production-safety/personal-protective-equipment';
+
+  const router = useRouter();
+  const route = useRoute();
+  const formRef = ref<FormInstance>();
+  const loading = ref(false);
+  const submitting = ref(false);
+
+  const id = computed(() => Number(route.query.id));
+
+  const form = reactive({
+    id: undefined as number | undefined,
+    jobPost: '',
+    ppeCategory: '',
+    ppeName: '',
+    serviceMonths: undefined as number | undefined, // 1-9999 整数
+    status: true as boolean, // true-启用,false-禁用
+    remark: '',
+  });
+
+  const formRules: FormRules = {};
+
+  const getDetail = async () => {
+    if (!id.value) {
+      ElMessage.error('缺少 id');
+      return;
+    }
+    loading.value = true;
+    try {
+      const res = await queryPersonalProtectiveEquipmentDetail(id.value);
+      if (res) {
+        form.id = res.id;
+        form.jobPost = res.jobPost ?? '';
+        form.ppeCategory = res.ppeCategory ?? '';
+        form.ppeName = res.ppeName ?? '';
+        const sm = res.serviceMonths;
+        form.serviceMonths =
+          sm !== '' && sm != null && !Number.isNaN(Number(sm)) ? Number(sm) : undefined;
+        form.status = res.status === true || res.status === 'true';
+        form.remark = res.remark ?? '';
+      }
+    } catch (e) {
+      console.error('获取劳防用品详情失败:', e);
+      ElMessage.error('获取详情失败');
+    } finally {
+      loading.value = false;
+    }
+  };
+
+  const handleSubmit = async () => {
+    const valid = await formRef.value?.validate().then(() => true).catch(() => false);
+    if (!valid) return;
+    if (!form.id) {
+      ElMessage.error('缺少 id');
+      return;
+    }
+    submitting.value = true;
+    try {
+      await updatePersonalProtectiveEquipment({
+        id: form.id,
+        jobPost: form.jobPost || undefined,
+        ppeCategory: form.ppeCategory || undefined,
+        ppeName: form.ppeName || undefined,
+        serviceMonths: form.serviceMonths != null ? String(form.serviceMonths) : undefined,
+        status: form.status,
+        remark: form.remark || undefined,
+      });
+      ElMessage.success('保存成功');
+      router.back();
+    } catch (e) {
+      console.error('编辑劳防用品失败:', e);
+      ElMessage.error('保存失败,请重试');
+    } finally {
+      submitting.value = false;
+    }
+  };
+
+  onMounted(() => {
+    getDetail();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+  .ppe-form {
+    max-width: 600px;
+  }
+</style>

+ 107 - 173
src/views/production-safety/risk-identification-and-control/labor-products-manage/list.vue

@@ -1,102 +1,85 @@
 <template>
   <div class="safety-platform-container">
     <header class="safety-platform-container__header">
-      <div class="breadcrumb-title"> 劳防用品管理 </div>
+      <div class="breadcrumb-title">劳防用品管理</div>
     </header>
     <main class="safety-platform-container__main">
       <div class="search-form">
         <el-form :inline="true">
-          <el-form-item label="搜索">
+          <el-form-item label="标题">
             <el-input
-              v-model="queryParams.queryParam.mergeFiled"
-              placeholder="搜索楼号/楼宇/楼层/房间"
+              v-model="queryParams.queryParam.name"
+              placeholder="请输入标题"
+              clearable
               style="width: 170px"
             />
           </el-form-item>
           <el-form-item label="状态">
-            <el-select v-model="queryParams.queryParam.status" clearable placeholder="状态" style="width: 170px">
-              <el-option :value="1" label="正常" />
-              <el-option :value="2" label="待确认" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="部门名称">
-            <el-cascader
-              v-model="queryParams.queryParam.responsibleDepartmentId"
-              style="width: 170px"
-              ref="cascaderRef"
-              :options="firstLevelDepts"
-              :props="cascaderProp"
-              :show-all-levels="false"
-              placeholder="部门名称"
-              filterable
-              @change="handleChangeDept"
-            />
-          </el-form-item>
-          <el-form-item label="风险类型">
             <el-select
-              v-model="queryParams.queryParam.riskCategory"
+              v-model="queryParams.queryParam.status"
+              placeholder="请选择状态"
               clearable
-              placeholder="风险类型"
               style="width: 170px"
             >
-              <el-option :value="1" label="III级危险点" />
-              <el-option :value="2" label="II级危险点" />
-              <el-option :value="3" label="I级危险点" />
-              <el-option :value="4" label="UPS" />
-              <el-option :value="5" label="电力设施(强电)" />
-              <el-option :value="6" label="高低温气体液体、高压气体" />
-              <el-option :value="7" label="试验设施设备" />
-              <el-option :value="8" label="特种设备" />
-              <el-option :value="9" label="危化品、易燃易爆固液气体" />
-              <el-option :value="10" label="有限空间" />
+              <el-option label="启用" :value="true" />
+              <el-option label="禁用" :value="false" />
             </el-select>
           </el-form-item>
-          <el-form-item label="风险级别">
-            <el-select v-model="queryParams.queryParam.riskLevel" clearable placeholder="风险类型" style="width: 170px">
-              <el-option :value="1" label="B" />
-              <el-option :value="2" label="C" />
-            </el-select>
+          <!-- <el-form-item label="申请部门编码">
+            <el-input
+              v-model="queryParams.queryParam.applyDeptCode"
+              placeholder="申请人部门CODE"
+              clearable
+              style="width: 170px"
+            />
           </el-form-item>
+          <el-form-item label="申请部门名称">
+            <el-input
+              v-model="queryParams.queryParam.applyDeptName"
+              placeholder="申请人部门名称"
+              clearable
+              style="width: 170px"
+            />
+          </el-form-item> -->
         </el-form>
-
         <div>
-          <el-button type="primary" @click="$router.push({ name: 'riskManageAdd' })">添加 </el-button>
+          <el-button type="primary" @click="$router.push({ name: 'laborProductsManageAdd' })">
+            添加
+          </el-button>
           <el-button type="primary" @click="queryTableList">查询</el-button>
           <el-button @click="handleRestParams">重置</el-button>
         </div>
       </div>
 
       <div class="table-content">
-        <el-table :data="tableData.data">
-          <el-table-column type="index" label="序号" width="80" />
-          <el-table-column label="楼号/区域" prop="buildingArea" width="180" />
-          <el-table-column label="楼宇名称" prop="buildingName" width="180" />
-          <el-table-column label="楼层/位置" prop="floorLocation" width="180" />
-          <el-table-column label="房间号(名称)" prop="roomName" width="180" />
-          <el-table-column label="安全责任人" prop="roomSafetyResponsibleName" width="180" />
-          <el-table-column label="是否存在风险点" prop="hasRiskPointName" width="180" />
-          <el-table-column label="风险点类别" prop="riskCategoryName" width="180" />
-          <el-table-column label="变更原因" prop="changeReason" width="170" />
-          <el-table-column label="状态" prop="statusName" width="100" />
-          <el-table-column fixed="right" min-width="240" label="操作">
+        <el-table v-loading="loading" :data="tableData.data">
+          <el-table-column label="作业(岗位)分类" prop="jobPost" min-width="120" />
+          <el-table-column label="物品分类" prop="ppeCategory" min-width="120" />
+          <el-table-column label="物品名称" prop="ppeName" min-width="120" />
+          <el-table-column label="使用期限(月)" prop="serviceMonths" width="120" />
+          <el-table-column label="用品状态" prop="status" width="100">
+            <template #default="{ row }">
+              {{ row.status === true || row.status === 'true' ? '启用' : row.status === false || row.status === 'false' ? '禁用' : '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column label="备注" prop="remark" min-width="150" show-overflow-tooltip />
+          <el-table-column fixed="right" label="操作" width="260">
             <template #default="scope">
               <el-button
                 type="primary"
                 link
-                @click="$router.push({ name: 'riskManageEdit', query: { id: scope.row.id } })"
-                >编辑</el-button
+                @click="$router.push({ name: 'laborProductsManageView', query: { id: scope.row.id } })"
               >
-              <el-button type="primary" link @click="handleConfirmDeleteRow(scope)">删除</el-button>
-              <el-button type="primary" link>查看</el-button>
+                查看
+              </el-button>
               <el-button
                 type="primary"
                 link
-                @click="$router.push({ name: 'riskManageChange', query: { id: scope.row.id } })"
-                >变更</el-button
+                @click="$router.push({ name: 'laborProductsManageEdit', query: { id: scope.row.id } })"
               >
-              <el-button type="primary" link @click="handleApprove(scope, 1)">确认</el-button>
-              <el-button type="primary" link @click="handleApprove(scope, 0)">拒绝</el-button>
-              <el-button type="primary" link @click="handleApprove(scope, 0)">撤回</el-button>
+                编辑
+              </el-button>
+              <el-button type="primary" link @click="handleDelete(scope.row)">删除</el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -107,6 +90,8 @@
           :current-page="queryParams.pageNumber"
           :page-size="queryParams.pageSize"
           :total="tableData.total"
+          :page-sizes="[10, 20, 50]"
+          layout="total, sizes, prev, pager, next"
           @size-change="handleSizeChange"
           @current-change="handleCurrentChange"
         />
@@ -114,124 +99,90 @@
     </main>
   </div>
 </template>
+
 <script lang="ts" setup>
   import { onMounted, reactive, ref } from 'vue';
-  import dayjs from 'dayjs';
-  import { ElMessage } from 'element-plus';
-  import { useRouter } from 'vue-router';
+  import { ElMessage, ElMessageBox } from 'element-plus';
   import {
-    safetyRiskListQueryPage,
-    safetyRiskListDelete,
-    safetyRiskListApprove,
-  } from '@/api/production-safety/responsibility-implementation';
-  import { omit } from 'lodash-es';
-  import { useUserInfoHook } from '@/hooks/useUserInfoHook';
-  import { unformatAttachment } from '@/components/UploadFiles/utils';
-  import { downloadFile } from '@/views/disaster/utils';
-  import { formatDeptTree } from '@/views/disaster/utils/formatDeptTree';
-  import { getAllDepartments } from '@/api/auth/dept';
+    queryPersonalProtectiveEquipmentList,
+    deletePersonalProtectiveEquipment,
+    type QueryPersonalProtectiveEquipmentCommonPageReq,
+    type PersonalProtectiveEquipment,
+  } from '@/api/production-safety/personal-protective-equipment';
+  import type { QueryPageRequest } from '@/types/basic-query';
 
-  const router = useRouter();
-  const { id } = useUserInfoHook();
-  const firstLevelDepts = ref<any[]>([]);
-  const cascaderProp = {
-    expandTrigger: 'click',
-    checkStrictly: true,
-    // emitPath: false,
-    value: 'id',
-    label: 'deptName',
-  };
-  const queryParams = reactive<any>({
+  const loading = ref(false);
+  const queryParams = reactive<QueryPageRequest<QueryPersonalProtectiveEquipmentCommonPageReq>>({
     pageNumber: 1,
     pageSize: 10,
     queryParam: {
-      mergeFiled: '',
-      status: '',
-      responsibleDepartment: '',
-      riskCategory: '',
-      riskLevel: '',
-      userId: id,
-      responsibleDepartmentId: [],
+      name: '',
+      status: undefined as boolean | undefined,
+      applyDeptCode: '',
+      applyDeptName: '',
     },
   });
-  const cascaderRef = ref();
 
-  const tableData = reactive({
+  /** 列表数据与接口返回 Page<PersonalProtectiveEquipment> 一致 */
+  const tableData = reactive<{ data: PersonalProtectiveEquipment[]; total: number }>({
     data: [],
     total: 0,
   });
 
-  const handleSizeChange = (value) => {};
-  const handleCurrentChange = (value) => {
-    queryParams.pageNumber = value;
+  const handleSizeChange = (value: number) => {
+    queryParams.pageSize = value;
+    queryParams.pageNumber = 1;
     queryTableList();
   };
 
-  const getDeptData = () => {
-    getAllDepartments().then((res) => {
-      firstLevelDepts.value = formatDeptTree(res);
-    });
-  };
-  const handleChangeDept = () => {
-    const deptInfo = cascaderRef.value?.getCheckedNodes();
-    if (deptInfo?.[0]) {
-      queryParams.queryParam.responsibleDepartment = deptInfo[0].label;
-    }
+  const handleCurrentChange = (value: number) => {
+    queryParams.pageNumber = value;
+    queryTableList();
   };
 
-  const handleApprove = (scope, approveType) => {
-    safetyRiskListApprove({
-      id: scope.row.id,
-      approveType,
-    }).then(() => {
-      ElMessage.success('操作成功!');
-      queryTableList();
-    });
+  const queryTableList = () => {
+    loading.value = true;
+    queryPersonalProtectiveEquipmentList(queryParams)
+      .then((res: any) => {
+        // 与接口返回 WebResult<Page<PersonalProtectiveEquipment>> 对齐:records + totalRow(兼容 total/list)
+        tableData.data = (res?.records ?? res?.list ?? []) as PersonalProtectiveEquipment[];
+        tableData.total = res?.totalRow ?? res?.total ?? 0;
+      })
+      .finally(() => {
+        loading.value = false;
+      });
   };
 
-  const handleDownloadLink = (scope) => {
-    const attachment = unformatAttachment(scope.row.attachment);
-    attachment?.forEach((item: any) => {
-      downloadFile(item.fileUrl, item.fileName);
-    });
-  };
-  const handleConfirmDeleteRow = (scope) => {
-    safetyRiskListDelete(scope.row.id).then(() => {
-      ElMessage.success('删除成功!');
-      queryTableList();
-    });
+  const handleDelete = (row: PersonalProtectiveEquipment) => {
+    if (row.id == null) return;
+    ElMessageBox.confirm('确定要删除该劳防用品吗?', '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    })
+      .then(() => {
+        return deletePersonalProtectiveEquipment(row.id!);
+      })
+      .then(() => {
+        ElMessage.success('删除成功');
+        queryTableList();
+      })
+      .catch(() => {});
   };
 
-  const queryTableList = () => {
-    safetyRiskListQueryPage({
-      ...queryParams,
-      queryParam: {
-        ...omit(queryParams.queryParam, 'responsibleDepartmentId'),
-      },
-    }).then((res) => {
-      tableData.data = res.records;
-      tableData.total = res.totalRow;
-    });
-  };
   const handleRestParams = () => {
-    Object.assign(queryParams, {
-      pageNumber: 1,
-      pageSize: 10,
-      queryParam: {
-        ...queryParams.queryParam,
-        mergeFiled: '',
-        status: '',
-        responsibleDepartment: '',
-        riskCategory: '',
-        riskLevel: '',
-        responsibleDepartmentId: [],
-      },
-    });
+    queryParams.pageNumber = 1;
+    queryParams.pageSize = 10;
+    queryParams.queryParam = {
+      name: '',
+      status: undefined,
+      applyDeptCode: '',
+      applyDeptName: '',
+    };
     queryTableList();
   };
 
-  onMounted(async () => {
-    await getDeptData();
+  onMounted(() => {
     queryTableList();
   });
 </script>
@@ -241,19 +192,6 @@
   @use '@/styles/page-main-layout.scss' as *;
   @use '@/styles/basic-table-action.scss' as *;
 
-  :deep(.el-tabs__header) {
-    margin: 0;
-  }
-  :deep(.el-tabs__item) {
-    font-size: 14px !important;
-  }
-  :deep(.flexContent) {
-    display: flex;
-  }
-  :deep(.breadcrumb .title) {
-    margin-left: 0;
-  }
-
   :deep(.el-form) {
     flex: 1;
     display: flex;
@@ -270,21 +208,17 @@
   .search-form {
     min-width: 800px;
     display: flex;
-
     justify-content: space-between;
     align-items: center;
     margin-bottom: 20px;
   }
-
-  .button-content {
-    margin-bottom: 20px;
-  }
   .table-content {
     flex: 1;
     overflow: hidden;
     overflow-y: auto;
   }
-  .page-content {
+  .pagination-container {
+    margin-top: 16px;
     display: flex;
     justify-content: flex-end;
   }

+ 124 - 1
src/views/production-safety/risk-identification-and-control/labor-products-manage/view.vue

@@ -1 +1,124 @@
-<template>view</template>
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">查看劳防用品</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <el-form v-loading="loading" :model="form" label-width="120px" class="ppe-form">
+        <el-form-item label="作业(岗位)分类" prop="jobPost">
+          <el-input v-model="form.jobPost" disabled />
+        </el-form-item>
+        <el-form-item label="物品分类" prop="ppeCategory">
+          <el-input v-model="form.ppeCategory" disabled />
+        </el-form-item>
+        <el-form-item label="物品名称" prop="ppeName">
+          <el-input v-model="form.ppeName" disabled />
+        </el-form-item>
+        <el-form-item label="使用期限(月)" prop="serviceMonths">
+          <el-input-number
+            v-model="form.serviceMonths"
+            :min="1"
+            :max="9999"
+            :precision="0"
+            disabled
+            controls-position="right"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="用品状态" prop="status">
+          <el-radio-group v-model="form.status" disabled>
+            <el-radio :label="true">启用</el-radio>
+            <el-radio :label="false">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" :rows="3" disabled />
+        </el-form-item>
+      </el-form>
+    </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="router.back()">返回</el-button>
+    </footer>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, onMounted, computed } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import { queryPersonalProtectiveEquipmentDetail } from '@/api/production-safety/personal-protective-equipment';
+
+  const router = useRouter();
+  const route = useRoute();
+  const loading = ref(false);
+
+  const id = computed(() => Number(route.query.id));
+
+  const form = reactive<{
+    jobPost: string;
+    ppeCategory: string;
+    ppeName: string;
+    serviceMonths?: number;
+    status: boolean;
+    remark: string;
+    createdAt: string;
+    updatedAt: string;
+  }>({
+    jobPost: '',
+    ppeCategory: '',
+    ppeName: '',
+    serviceMonths: undefined,
+    status: true,
+    remark: '',
+    createdAt: '',
+    updatedAt: '',
+  });
+
+  const getDetail = async () => {
+    if (!id.value) {
+      ElMessage.error('缺少 id');
+      return;
+    }
+    loading.value = true;
+    try {
+      const res = await queryPersonalProtectiveEquipmentDetail(id.value);
+      if (res) {
+        form.jobPost = res.jobPost ?? '';
+        form.ppeCategory = res.ppeCategory ?? '';
+        form.ppeName = res.ppeName ?? '';
+        const sm = res.serviceMonths;
+        form.serviceMonths =
+          sm !== '' && sm != null && !Number.isNaN(Number(sm)) ? Number(sm) : undefined;
+        form.status = res.status === true || res.status === 'true';
+        form.remark = res.remark ?? '';
+        form.createdAt = res.createdAt ?? '';
+        form.updatedAt = res.updatedAt ?? '';
+      }
+    } catch (e) {
+      console.error('获取劳防用品详情失败:', e);
+      ElMessage.error('获取详情失败');
+    } finally {
+      loading.value = false;
+    }
+  };
+
+  onMounted(() => {
+    getDetail();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+  .ppe-form {
+    max-width: 600px;
+  }
+</style>

+ 2 - 0
src/views/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage/Item.vue

@@ -25,6 +25,8 @@
         return '编辑劳防用品采购申请';
       case 'inventory-view':
         return '查看劳防用品采购申请';
+      case 'audit':
+        return '审核劳防用品采购申请';
       default:
         return '劳防用品采购申请详情';
     }

+ 375 - 84
src/views/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage/components/detail.vue

@@ -1,38 +1,217 @@
 <template>
   <main class="safety-platform-container__main">
-    <BasicForm
-      ref="basicFormRef"
-      :formData="ruleFormData"
-      :formRules="isViewMode ? undefined : formRules"
-      :formConfig="computedFormConfig"
+    <el-form
+      ref="formRef"
+      :model="form"
+      :rules="formRules"
+      label-width="120px"
+      class="purchase-apply-form"
     >
-      <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>
+      <!-- 申请单号:仅非新增时显示;查看/审核为纯文本 -->
+      <el-form-item v-if="!isCreateMode" label="申请单号" prop="applyNo" required>
+        <span v-if="isViewMode || isAuditMode" class="apply-no-text">{{ form.applyNo || '-' }}</span>
+        <el-input
+          v-else
+          v-model="form.applyNo"
+          placeholder="申请单号"
+          readonly
+          class="apply-no-input"
+        />
+      </el-form-item>
+
+      <!-- 劳防用品明细表 -->
+      <div class="table-section">
+        <el-table :data="form.itemList" border size="default" class="items-table">
+          <el-table-column type="index" label="编号" width="100" align="center" />
+          <el-table-column label="劳防用品名称" min-width="140">
+            <template #default="{ row }">
+              <span v-if="isViewMode || isAuditMode">{{ row.ppeName || '-' }}</span>
+              <el-select
+                v-else
+                v-model="row.ppeName"
+                placeholder="请选择"
+                filterable
+                clearable
+                style="width: 100%"
+                @change="onPpeSelect(row, $event)"
+              >
+                <el-option
+                  v-for="item in ppeOptions"
+                  :key="item.id"
+                  :label="item.ppeName"
+                  :value="item.ppeName"
+                />
+              </el-select>
+            </template>
+          </el-table-column>
+          <el-table-column label="样式照片" min-width="420" align="center">
+            <template #default="{ row }">
+              <span v-if="isViewMode || isAuditMode">{{ getStylePhotoDisplay(row) }}</span>
+              <UploadFiles
+                v-else
+                label="选择附件"
+                :file-list="getRowFileList(row)"
+                :max-count="1"
+                @uploadSuccess="(list: FileItem[]) => onStylePhotoSuccess(row, list)"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="物品尺寸/鞋码" min-width="150">
+            <template #default="{ row }">
+              <span v-if="isViewMode || isAuditMode">{{ row.sizeOrShoeSize || '-' }}</span>
+              <el-input
+                v-else
+                v-model="row.sizeOrShoeSize"
+                placeholder="请输入..."
+                clearable
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="需求数量" min-width="150">
+            <template #default="{ row }">
+              <span v-if="isViewMode || isAuditMode">{{ row.requiredQty ?? '-' }}</span>
+              <el-input-number
+                v-else
+                v-model="row.requiredQty"
+                :min="1"
+                :precision="0"
+                placeholder="请输入..."
+                controls-position="right"
+                style="width: 100%"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="规格、型号、类型" min-width="200">
+            <template #default="{ row }">
+              <span v-if="isViewMode || isAuditMode">{{ row.specModelType || '-' }}</span>
+              <el-input
+                v-else
+                v-model="row.specModelType"
+                placeholder="请输入..."
+                clearable
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="单价/元" min-width="150">
+            <template #default="{ row }">
+              <span v-if="isViewMode || isAuditMode">{{ row.unitPrice ?? '-' }}</span>
+              <el-input
+                v-else
+                v-model="row.unitPrice"
+                placeholder="请输入..."
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="商品编号" min-width="150">
+            <template #default="{ row }">
+              <span v-if="isViewMode || isAuditMode">{{ row.productNo || '-' }}</span>
+              <el-input
+                v-else
+                v-model="row.productNo"
+                placeholder="请输入..."
+                clearable
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="备注" min-width="150">
+            <template #default="{ row }">
+              <span v-if="isViewMode || isAuditMode">{{ row.remark || '-' }}</span>
+              <el-input
+                v-else
+                v-model="row.remark"
+                placeholder="请输入..."
+                clearable
+              />
+            </template>
+          </el-table-column>
+          <el-table-column v-if="!isViewMode && !isAuditMode" label="操作" width="150" fixed="right" align="center">
+            <template #default="{ $index }">
+              <el-button type="primary" circle size="small" @click="addRow">
+                <el-icon><Plus /></el-icon>
+              </el-button>
+              <el-button
+                type="danger"
+                circle
+                size="small"
+                :disabled="form.itemList.length <= 1"
+                @click="removeRow($index)"
+              >
+                <el-icon><Minus /></el-icon>
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </el-form>
+
+    <footer class="safety-platform-container__footer">
+      <el-button @click="router.back()">返回</el-button>
+      <template v-if="isCreateMode || isEditMode">
+        <el-button type="primary" :loading="submitting" @click="handleSubmit">
+          {{ isCreateMode ? '提交' : '保存' }}
+        </el-button>
+      </template>
+      <template v-if="isAuditMode">
+        <el-button type="success" :loading="auditSubmitting" @click="handleAuditPass">
+          审核通过
+        </el-button>
+        <el-button type="danger" :loading="auditSubmitting" @click="showRejectDialog = true">
+          审核不通过
+        </el-button>
+      </template>
+    </footer>
+
+    <!-- 审核不通过原因 -->
+    <el-dialog
+      v-model="showRejectDialog"
+      title="审核不通过"
+      width="400px"
+      destroy-on-close
+      @close="rejectReason = ''"
+    >
+      <el-input
+        v-model="rejectReason"
+        type="textarea"
+        :rows="3"
+        placeholder="请输入审核不通过原因"
+      />
+      <template #footer>
+        <el-button @click="showRejectDialog = false">取消</el-button>
+        <el-button type="danger" :loading="auditSubmitting" @click="handleAuditReject">
+          确定
+        </el-button>
       </template>
-    </BasicForm>
+    </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, onMounted, ref } from 'vue';
+  import { computed, onMounted, ref, reactive } 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';
+  import type { FormInstance, FormRules } from 'element-plus';
+  import { Plus, Minus } from '@element-plus/icons-vue';
+  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import type { FileItem } from '@/components/UploadFiles/types';
+  import { queryPersonalProtectiveEquipmentList } from '@/api/production-safety/personal-protective-equipment';
+  import type { PersonalProtectiveEquipment } from '@/api/production-safety/personal-protective-equipment';
+  import {
+    queryPurchaseApplyDetail,
+    savePurchaseApply,
+    updatePurchaseApply,
+    auditPurchaseApply,
+    type PersonalProtectiveEquipmentPurchaseApply,
+    type PurchaseApplyItem,
+  } from '@/api/production-safety/personal-protective-equipment-purchase-apply';
+  import type { QueryPageRequest } from '@/types/basic-query';
 
   const router = useRouter();
   const route = useRoute();
+  const formRef = ref<FormInstance>();
+  const submitting = ref(false);
+  const auditSubmitting = ref(false);
+  const showRejectDialog = ref(false);
+  const rejectReason = ref('');
 
   const operate = computed(() => (route.query.operate as string) || 'inventory-create');
   const currentId = computed(() => Number(route.query.id));
@@ -40,97 +219,209 @@
   const isCreateMode = computed(() => operate.value === 'inventory-create');
   const isEditMode = computed(() => operate.value === 'inventory-edit');
   const isViewMode = computed(() => operate.value === 'inventory-view');
+  const isAuditMode = computed(() => operate.value === 'audit');
 
-  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 ppeOptions = ref<PersonalProtectiveEquipment[]>([]);
+
+  const defaultItem = (): PurchaseApplyItem => ({
+    ppeName: '',
+    ppeId: undefined,
+    stylePhoto: '',
+    sizeOrShoeSize: '',
+    requiredQty: undefined,
+    specModelType: '',
+    unitPrice: undefined,
+    productNo: '',
+    remark: '',
   });
 
-  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+  const form = reactive<PersonalProtectiveEquipmentPurchaseApply & { itemList: PurchaseApplyItem[] }>({
+    applyNo: '',
+    itemList: [defaultItem()],
+  });
 
-  const handleValidate = async () => {
-    if (!basicFormRef.value) return;
-    const res = await basicFormRef.value.validateForm();
-    return res;
+  const formRules: FormRules = {
+    applyNo: [{ required: true, message: '申请单号不能为空', trigger: 'blur' }],
   };
 
-  const getDetail = async () => {
+  function getRowFileList(row: PurchaseApplyItem): FileItem[] {
+    if (!row.stylePhoto) return [];
+    try {
+      const parsed = typeof row.stylePhoto === 'string' ? JSON.parse(row.stylePhoto) : row.stylePhoto;
+      const arr = Array.isArray(parsed) ? parsed : [parsed];
+      return arr.map((f: any) => ({
+        fileId: f.fileId ?? 0,
+        fileName: f.fileName ?? '',
+        fileType: f.fileType ?? 'pdf',
+        fileSize: f.fileSize ?? '',
+        fileUrl: f.fileUrl,
+      }));
+    } catch {
+      return [];
+    }
+  }
+
+  /** 查看/审核时样式照片列仅显示文件名文本 */
+  function getStylePhotoDisplay(row: PurchaseApplyItem): string {
+    const list = getRowFileList(row);
+    if (!list.length) return '-';
+    return list.map((f) => f.fileName).filter(Boolean).join('、') || '-';
+  }
+
+  function onStylePhotoSuccess(row: PurchaseApplyItem, list: FileItem[]) {
+    row.stylePhoto = JSON.stringify(
+      list.map((f) => ({ fileUrl: f.fileUrl, fileName: f.fileName, fileId: f.fileId, fileType: f.fileType, fileSize: f.fileSize })),
+    );
+  }
+
+  function onPpeSelect(row: PurchaseApplyItem, ppeName: string) {
+    const found = ppeOptions.value.find((p) => p.ppeName === ppeName);
+    if (found?.id) row.ppeId = found.id;
+  }
+
+  function addRow() {
+    form.itemList.push(defaultItem());
+  }
+
+  function removeRow(index: number) {
+    if (form.itemList.length <= 1) return;
+    form.itemList.splice(index, 1);
+  }
+
+  async function loadPpeOptions() {
+    try {
+      const res = await queryPersonalProtectiveEquipmentList({
+        pageNumber: 1,
+        pageSize: 500,
+        queryParam: {  },
+      } as QueryPageRequest<any>);
+      const list = res?.records ?? res?.list ?? [];
+      ppeOptions.value = list as PersonalProtectiveEquipment[];
+    } catch (e) {
+      console.error('加载劳防用品列表失败:', e);
+    }
+  }
+
+  async function getDetail() {
     if (!currentId.value) return;
     try {
-      const res = await queryInventoryDetail(currentId.value);
+      const res = await queryPurchaseApplyDetail(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.status = res.status ? 'ENABLE' : 'DISABLE'; // 状态
-        ruleFormData.remarks = res.remark || ''; // 备注
+        form.applyNo = res.applyNo ?? '';
+        form.itemList = (res.itemList && res.itemList.length) ? res.itemList.map((it) => ({ ...defaultItem(), ...it })) : [defaultItem()];
       }
-      cloneRuleFormData();
     } catch (e) {
-      console.error('获取物品库存详情失败:', 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,
-        status: ruleFormData.status === 'ENABLE',
-        remark: ruleFormData.remarks || '',
-      };
+  function buildSubmitPayload() {
+    const itemList = form.itemList.map((it) => ({
+      ppeName: it.ppeName,
+      ppeId: it.ppeId,
+      stylePhoto: it.stylePhoto,
+      sizeOrShoeSize: it.sizeOrShoeSize,
+      requiredQty: it.requiredQty,
+      specModelType: it.specModelType,
+      unitPrice: it.unitPrice,
+      productNo: it.productNo,
+      remark: it.remark,
+    }));
+    return {
+      id: currentId.value || undefined,
+      applyNo: form.applyNo || undefined,
+      itemList,
+    };
+  }
 
+  async function handleSubmit() {
+    const valid = await formRef.value?.validate().catch(() => false);
+    if (!valid) return;
+    if (!form.itemList.length || form.itemList.every((it) => !it.ppeName)) {
+      ElMessage.warning('请至少填写一条劳防用品明细');
+      return;
+    }
+    submitting.value = true;
+    try {
+      const payload = buildSubmitPayload();
       if (isCreateMode.value) {
-        await saveInventory(basePayload);
-        ElMessage.success('创建成功');
-      } else if (isEditMode.value && currentId.value) {
-        await updateInventory({
-          id: currentId.value,
-          ...basePayload,
-        });
+        await savePurchaseApply(payload as PersonalProtectiveEquipmentPurchaseApply);
+        ElMessage.success('提交成功');
+      } else {
+        await updatePurchaseApply(payload as PersonalProtectiveEquipmentPurchaseApply);
         ElMessage.success('保存成功');
       }
-
       router.back();
     } catch (e) {
-      console.error('保存物品库存失败:', e);
+      console.error('保存失败:', e);
       ElMessage.error('保存失败,请重试');
+    } finally {
+      submitting.value = false;
     }
-  };
+  }
+
+  async function handleAuditPass() {
+    if (!currentId.value) return;
+    auditSubmitting.value = true;
+    try {
+      await auditPurchaseApply({ id: currentId.value, status: 1 });
+      ElMessage.success('审核通过');
+      router.back();
+    } catch (e) {
+      console.error('审核失败:', e);
+      ElMessage.error('审核失败,请重试');
+    } finally {
+      auditSubmitting.value = false;
+    }
+  }
+
+  async function handleAuditReject() {
+    if (!rejectReason.value?.trim()) {
+      ElMessage.warning('请输入审核不通过原因');
+      return;
+    }
+    if (!currentId.value) return;
+    auditSubmitting.value = true;
+    try {
+      await auditPurchaseApply({ id: currentId.value, status: -1, rejectReason: rejectReason.value.trim() });
+      ElMessage.success('已提交审核不通过');
+      showRejectDialog.value = false;
+      router.back();
+    } catch (e) {
+      console.error('审核失败:', e);
+      ElMessage.error('审核失败,请重试');
+    } finally {
+      auditSubmitting.value = false;
+    }
+  }
 
   onMounted(() => {
-    cloneRuleFormData();
-    beforeRouteLeave();
-    if (isEditMode.value || isViewMode.value) {
+    loadPpeOptions();
+    if (isEditMode.value || isViewMode.value || isAuditMode.value) {
       getDetail();
+    } else {
+      form.itemList = [defaultItem()];
     }
   });
 </script>
 
 <style scoped lang="scss">
   @use '@/styles/page-details-layout.scss' as *;
-</style>
 
+  .purchase-apply-form {
+    max-width: 100%;
+  }
+  .apply-no-input {
+    max-width: 320px;
+  }
+  .apply-no-text {
+    color: var(--el-text-color-regular);
+  }
+  .table-section {
+    margin-top: 16px;
+  }
+  .items-table {
+    width: 100%;
+  }
+</style>

+ 18 - 24
src/views/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage/configs/tables.ts

@@ -1,13 +1,13 @@
 import type { TableColumnProps } from '@/types/basic-table';
 
-// 基础表格样式配置
 export const TABLE_OPTIONS = {
   emptyText: '暂无数据',
   loading: true,
   maxHeight: 'calc(70vh - 150px)',
 };
 
-export const INVENTORY_TABLE_COLUMNS: TableColumnProps[] = [
+/** 劳防用品采购申请列表列:申请单号、申请人、申请人部门、状态、当前流程节点、操作 */
+export const PURCHASE_APPLY_TABLE_COLUMNS: TableColumnProps[] = [
   {
     label: '编号',
     type: 'index',
@@ -15,34 +15,22 @@ export const INVENTORY_TABLE_COLUMNS: TableColumnProps[] = [
     width: '80px',
   },
   {
-    label: '物品名称',
-    prop: 'itemName',
+    label: '申请单号',
+    prop: 'applyNo',
     align: 'left',
-    minWidth: '120px',
+    minWidth: '140px',
   },
   {
-    label: '入库日期',
-    prop: 'warehouseDate',
+    label: '申请人',
+    prop: 'applicantName',
     align: 'left',
-    minWidth: '120px',
-  },
-  {
-    label: '物品数量',
-    prop: 'itemQuantity',
-    align: 'center',
-    minWidth: '120px',
-  },
-  {
-    label: '经办人',
-    prop: 'handler',
-    align: 'left',
-    minWidth: '120px',
+    minWidth: '100px',
   },
   {
-    label: '备注',
-    prop: 'remarks',
+    label: '申请人部门',
+    prop: 'applicantDeptName',
     align: 'left',
-    minWidth: '150px',
+    minWidth: '140px',
   },
   {
     label: '状态',
@@ -51,12 +39,18 @@ export const INVENTORY_TABLE_COLUMNS: TableColumnProps[] = [
     align: 'center',
     minWidth: '100px',
   },
+  {
+    label: '当前流程节点',
+    prop: 'currentNodeName',
+    align: 'left',
+    minWidth: '140px',
+  },
   {
     label: '操作',
     prop: 'action',
     slot: 'action',
     fixed: 'right',
-    width: '180px',
+    width: '220px',
     align: 'left',
   },
 ];

+ 83 - 111
src/views/production-safety/risk-identification-and-control/labor-products-purchase-apply-manage/list.vue

@@ -10,22 +10,17 @@
             <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>
+                <span>用品名称/申请人:</span>
                 <el-input
-                  v-model="tableQuery.queryParam.stuffName"
-                  placeholder="搜索物品名称"
+                  v-model="tableQuery.queryParam.keyword"
+                  placeholder="请输入用品名称或申请人"
                   class="act-search-input"
+                  clearable
                 />
               </div>
               <div class="select-box--item">
@@ -34,11 +29,22 @@
                   v-model="tableQuery.queryParam.status"
                   placeholder="请选择状态"
                   clearable
+                  class="act-search-input"
                 >
-                  <el-option label="启用" :value="true" />
-                  <el-option label="禁用" :value="false" />
+                  <el-option label="待审核" :value="0" />
+                  <el-option label="审核通过" :value="1" />
+                  <el-option label="审核不通过" :value="-1" />
                 </el-select>
               </div>
+              <div class="select-box--item">
+                <span>审核部门:</span>
+                <el-input
+                  v-model="tableQuery.queryParam.auditDeptName"
+                  placeholder="请输入审核部门"
+                  class="act-search-input"
+                  clearable
+                />
+              </div>
             </section>
             <section class="search-btn">
               <el-button type="primary" @click="handleSearch">查询</el-button>
@@ -57,36 +63,39 @@
           >
             <template #status="scope">
               <span>
-                {{ scope.row.statusName || (scope.row.status === true || scope.row.status === 'true' ? '启用' : scope.row.status === false || scope.row.status === 'false' ? '禁用' : '-') }}
+                {{ getStatusText(scope.row.status) }}
               </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)" />
+                <!-- 待审核:查看、审核 -->
+                <template v-if="scope.row.status === 0 || scope.row.status === '0'">
+                  <ActionButton text="查看" @click="handleView(scope.row.id)" />
+                  <ActionButton text="审核" @click="handleAudit(scope.row.id)" />
+                </template>
+                <!-- 审核通过:查看 -->
+                <template v-else-if="scope.row.status === 1 || scope.row.status === '1'">
+                  <ActionButton text="查看" @click="handleView(scope.row.id)" />
+                </template>
+                <!-- 审核不通过:编辑、删除、查看 -->
+                <template v-else-if="scope.row.status === -1 || 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="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>
 
@@ -96,34 +105,43 @@
   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 { TABLE_OPTIONS, PURCHASE_APPLY_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';
+  import {
+    queryPurchaseApplyList,
+    deletePurchaseApply,
+    type PersonalProtectiveEquipmentPurchaseApply,
+    type QueryPurchaseApplyPageReq,
+  } from '@/api/production-safety/personal-protective-equipment-purchase-apply';
 
   const router = useRouter();
-
-  // 表格
   const basicTableRef = ref<InstanceType<typeof BasicTable>>();
 
-  const { tableConfig, pagination } = useTableConfig(INVENTORY_TABLE_COLUMNS, TABLE_OPTIONS);
+  const { tableConfig, pagination } = useTableConfig(
+    PURCHASE_APPLY_TABLE_COLUMNS,
+    TABLE_OPTIONS,
+  );
 
-  const tableData = ref<any[]>([]);
+  const tableData = ref<PersonalProtectiveEquipmentPurchaseApply[]>([]);
 
-  const tableQuery = reactive<QueryPageRequest<any>>({
+  const tableQuery = reactive<QueryPageRequest<QueryPurchaseApplyPageReq>>({
     pageNumber: pagination.pageNumber,
     pageSize: pagination.pageSize,
     queryParam: {
-      stuffName: '', // 物品名称
-      status: true, // 状态,默认启用
-      ids: [], // 选择数据的ID
+      keyword: '',
+      status: undefined,
+      auditDeptName: '',
     },
   });
 
+  function getStatusText(status: number | string | undefined) {
+    if (status === 0 || status === '0') return '待审核';
+    if (status === 1 || status === '1') return '审核通过';
+    if (status === -1 || status === '-1') return '审核不通过';
+    return '-';
+  }
+
   const handleSizeChange = (value: number) => {
     pagination.pageSize = value;
     tableQuery.pageSize = value;
@@ -136,27 +154,16 @@
     getTableData();
   };
 
-
   async function getTableData() {
     tableConfig.loading = true;
     try {
-      const res = await queryInventoryManage(tableQuery);
+      const res = await queryPurchaseApplyList(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;
+        tableData.value = (res?.records ?? res?.list ?? []) as PersonalProtectiveEquipmentPurchaseApply[];
+        pagination.total = res?.totalRow ?? res?.total ?? 0;
       }
     } catch (e) {
-      console.error('获取物品库存列表失败:', e);
+      console.error('获取劳防用品采购申请列表失败:', e);
       tableData.value = [];
       pagination.total = 0;
     } finally {
@@ -171,72 +178,33 @@
   };
 
   const handleReset = () => {
-    tableQuery.queryParam.stuffName = '';
-    tableQuery.queryParam.status = true; // 重置为默认启用状态
-    tableQuery.queryParam.ids = [];
+    tableQuery.queryParam.keyword = '';
+    tableQuery.queryParam.status = undefined;
+    tableQuery.queryParam.auditDeptName = '';
     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: 'laborProductsPurchaseApplyManageItem',
-      query: {
-        operate: 'inventory-create',
-      },
+      query: { operate: 'inventory-create' },
     });
   };
 
   const handleEdit = (id: number) => {
     router.push({
       name: 'laborProductsPurchaseApplyManageItem',
-      query: {
-        id,
-        operate: 'inventory-edit',
-      },
+      query: { id: String(id), operate: 'inventory-edit' },
     });
   };
 
   const handleDelete = async (id: number) => {
     try {
-      await deleteInventory(id);
+      await deletePurchaseApply(id);
       ElMessage.success('删除成功');
       getTableData();
     } catch (e) {
-      console.error('删除物品库存失败:', e);
+      console.error('删除失败:', e);
       ElMessage.error('删除失败,请重试');
     }
   };
@@ -244,10 +212,14 @@
   const handleView = (id: number) => {
     router.push({
       name: 'laborProductsPurchaseApplyManageItem',
-      query: {
-        id,
-        operate: 'inventory-view',
-      },
+      query: { id: String(id), operate: 'inventory-view' },
+    });
+  };
+
+  const handleAudit = (id: number) => {
+    router.push({
+      name: 'laborProductsPurchaseApplyManageItem',
+      query: { id: String(id), operate: 'audit' },
     });
   };
 
@@ -261,4 +233,4 @@
   @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>
+</style>

+ 211 - 1
src/views/production-safety/risk-identification-and-control/labor-products-requisition-manage/add.vue

@@ -1 +1,211 @@
-<template>add</template>
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">新增劳防用品领用</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <el-form
+        ref="formRef"
+        :model="form"
+        :rules="formRules"
+        label-width="120px"
+        class="req-form"
+      >
+      <el-form-item label="职业危害因素" prop="hazardFactor">
+        <el-input v-model="form.hazardFactor" placeholder="请输入职业危害因素" clearable />
+      </el-form-item>
+      <el-form-item label="发放人" prop="issuerUserId">
+        <el-select
+          v-model="issuerUserId"
+          placeholder="请选择发放人"
+          filterable
+          clearable
+          style="width: 100%"
+          @change="onIssuerChange"
+        >
+          <el-option
+            v-for="u in issuerUserList"
+            :key="u.id"
+            :label="u.realname"
+            :value="u.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="发放时间" prop="issueTime">
+          <el-date-picker
+            v-model="form.issueTime"
+            type="date"
+            placeholder="选择发放时间"
+            value-format="YYYY-MM-DD"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="防护用品名称" prop="ppeName">
+          <el-input v-model="form.ppeName" placeholder="请输入防护用品名称" clearable />
+        </el-form-item>
+        <el-form-item label="型号" prop="version">
+          <el-input v-model="form.version" placeholder="请输入型号" clearable />
+        </el-form-item>
+        <el-form-item label="领用数量" prop="receiveNum">
+          <el-input-number
+            v-model="form.receiveNum"
+            :min="1"
+            :precision="0"
+            placeholder="领用数量"
+            controls-position="right"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="领用人" prop="receiverUserId">
+          <el-select
+            v-model="receiverUserId"
+            placeholder="请选择领用人"
+            filterable
+            clearable
+            style="width: 100%"
+            @change="onReceiverChange"
+          >
+            <el-option
+              v-for="u in receiverUserList"
+              :key="u.id"
+              :label="u.realname"
+              :value="u.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="用品状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio :label="true">启用</el-radio>
+            <el-radio :label="false">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="领用备注" prop="remark">
+          <el-input
+            v-model="form.remark"
+            type="textarea"
+            placeholder="请输入领用备注"
+            :rows="3"
+            clearable
+          />
+        </el-form-item>
+      </el-form>
+    </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="router.back()">返回</el-button>
+      <el-button type="primary" :loading="submitting" @click="handleSubmit">提交</el-button>
+    </footer>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, onMounted } from 'vue';
+  import { useRouter } from 'vue-router';
+  import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import { savePersonalProtectiveEquipmentReceive } from '@/api/production-safety/personal-protective-equipment-receive';
+  import { queryAvailableUserList } from '@/api/production-safety/responsibility-implementation';
+  import type { UserLisItem } from '@/api/system/user-operate';
+
+  const router = useRouter();
+  const formRef = ref<FormInstance>();
+  const submitting = ref(false);
+
+  const issuerUserList = ref<UserLisItem[]>([]);
+  const receiverUserList = ref<UserLisItem[]>([]);
+  const issuerUserId = ref<number | null>(null);
+  const receiverUserId = ref<number | null>(null);
+
+  const loadUserList = async () => {
+    try {
+      const res = await queryAvailableUserList({
+        pageNumber: 1,
+        pageSize: 9999,
+        queryParam: {},
+      });
+      const list = (res as { records?: UserLisItem[] })?.records ?? [];
+      issuerUserList.value = list;
+      receiverUserList.value = list;
+    } catch (e) {
+      console.error('获取用户列表失败:', e);
+      issuerUserList.value = [];
+      receiverUserList.value = [];
+    }
+  };
+
+  const onIssuerChange = (userId: number | null) => {
+    const u = issuerUserList.value.find((x) => x.id === userId);
+    form.issuerCode = u?.username ?? '';
+    form.issuerName = u?.realname ?? '';
+  };
+
+  const onReceiverChange = (userId: number | null) => {
+    const u = receiverUserList.value.find((x) => x.id === userId);
+    form.receiverCode = u?.username ?? '';
+    form.receiverName = u?.realname ?? '';
+  };
+
+  const form = reactive({
+    equipmentId: '',
+    version: '',
+    ppeName: '',
+    hazardFactor: '',
+    issuerCode: '',
+    issuerName: '',
+    issueTime: '',
+    receiveNum: undefined as number | undefined,
+    receiverCode: '',
+    receiverName: '',
+    status: true as boolean,
+    remark: '',
+  });
+
+  const formRules: FormRules = {  };
+
+  onMounted(() => {
+    loadUserList();
+  });
+
+  const handleSubmit = async () => {
+    const valid = await formRef.value?.validate().then(() => true).catch(() => false);
+    if (!valid) return;
+    submitting.value = true;
+    try {
+      await savePersonalProtectiveEquipmentReceive({
+        equipmentId: form.equipmentId || undefined,
+        version: form.version || undefined,
+        ppeName: form.ppeName || undefined,
+        hazardFactor: form.hazardFactor || undefined,
+        issuerCode: form.issuerCode || undefined,
+        issuerName: form.issuerName || undefined,
+        issueTime: form.issueTime || undefined,
+        receiveNum: form.receiveNum,
+        receiverCode: form.receiverCode || undefined,
+        receiverName: form.receiverName || undefined,
+        status: form.status,
+        remark: form.remark || undefined,
+      });
+      ElMessage.success('新增成功');
+      router.back();
+    } catch (e) {
+      console.error('新增领用记录失败:', e);
+      ElMessage.error('新增失败,请重试');
+    } finally {
+      submitting.value = false;
+    }
+  };
+</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;
+  }
+  .req-form {
+    max-width: 600px;
+  }
+</style>

+ 275 - 1
src/views/production-safety/risk-identification-and-control/labor-products-requisition-manage/edit.vue

@@ -1 +1,275 @@
-<template>edit</template>
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">编辑劳防用品领用</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <el-form
+        v-loading="loading"
+        ref="formRef"
+        :model="form"
+        :rules="formRules"
+        label-width="120px"
+        class="req-form"
+      >
+        <el-form-item label="职业危害因素" prop="hazardFactor">
+          <el-input v-model="form.hazardFactor" placeholder="请输入职业危害因素" clearable />
+        </el-form-item>
+        <el-form-item label="发放人" prop="issuerUserId">
+          <el-select
+            v-model="issuerUserId"
+            placeholder="请选择发放人"
+            filterable
+            clearable
+            style="width: 100%"
+            @change="onIssuerChange"
+          >
+            <el-option
+              v-for="u in issuerUserList"
+              :key="u.id"
+              :label="u.realname"
+              :value="u.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="发放时间" prop="issueTime">
+          <el-date-picker
+            v-model="form.issueTime"
+            type="date"
+            placeholder="选择发放时间"
+            value-format="YYYY-MM-DD"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="防护用品名称" prop="ppeName">
+          <el-input v-model="form.ppeName" placeholder="请输入防护用品名称" clearable />
+        </el-form-item>
+        <el-form-item label="型号" prop="version">
+          <el-input v-model="form.version" placeholder="请输入型号" clearable />
+        </el-form-item>
+        <el-form-item label="领用数量" prop="receiveNum">
+          <el-input-number
+            v-model="form.receiveNum"
+            :min="1"
+            :precision="0"
+            placeholder="领用数量"
+            controls-position="right"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="领用人" prop="receiverUserId">
+          <el-select
+            v-model="receiverUserId"
+            placeholder="请选择领用人"
+            filterable
+            clearable
+            style="width: 100%"
+            @change="onReceiverChange"
+          >
+            <el-option
+              v-for="u in receiverUserList"
+              :key="u.id"
+              :label="u.realname"
+              :value="u.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="用品状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio :label="true">启用</el-radio>
+            <el-radio :label="false">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="领用备注" prop="remark">
+          <el-input
+            v-model="form.remark"
+            type="textarea"
+            placeholder="请输入领用备注"
+            :rows="3"
+            clearable
+          />
+        </el-form-item>
+      </el-form>
+    </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="router.back()">返回</el-button>
+      <el-button type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
+    </footer>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, onMounted, computed } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import {
+    queryPersonalProtectiveEquipmentReceiveDetail,
+    updatePersonalProtectiveEquipmentReceive,
+  } from '@/api/production-safety/personal-protective-equipment-receive';
+  import { queryAvailableUserList } from '@/api/production-safety/responsibility-implementation';
+  import type { UserLisItem } from '@/api/system/user-operate';
+
+  const router = useRouter();
+  const route = useRoute();
+  const formRef = ref<FormInstance>();
+  const loading = ref(false);
+  const submitting = ref(false);
+
+  const issuerUserList = ref<UserLisItem[]>([]);
+  const receiverUserList = ref<UserLisItem[]>([]);
+  const issuerUserId = ref<number | null>(null);
+  const receiverUserId = ref<number | null>(null);
+
+  const loadUserList = async () => {
+    try {
+      const res = await queryAvailableUserList({
+        pageNumber: 1,
+        pageSize: 9999,
+        queryParam: {},
+      });
+      const list = (res as { records?: UserLisItem[] })?.records ?? [];
+      issuerUserList.value = list;
+      receiverUserList.value = list;
+    } catch (e) {
+      console.error('获取用户列表失败:', e);
+      issuerUserList.value = [];
+      receiverUserList.value = [];
+    }
+  };
+
+  const onIssuerChange = (userId: number | null) => {
+    const u = issuerUserList.value.find((x) => x.id === userId);
+    form.issuerCode = u?.username ?? '';
+    form.issuerName = u?.realname ?? '';
+  };
+
+  const onReceiverChange = (userId: number | null) => {
+    const u = receiverUserList.value.find((x) => x.id === userId);
+    form.receiverCode = u?.username ?? '';
+    form.receiverName = u?.realname ?? '';
+  };
+
+  const syncIssuerReceiverFromForm = () => {
+    const ic = form.issuerCode;
+    const iname = form.issuerName;
+    const foundIssuer = issuerUserList.value.find(
+      (x) => x.username === ic || x.realname === iname,
+    );
+    issuerUserId.value = foundIssuer?.id ?? null;
+    const rc = form.receiverCode;
+    const rname = form.receiverName;
+    const foundReceiver = receiverUserList.value.find(
+      (x) => x.username === rc || x.realname === rname,
+    );
+    receiverUserId.value = foundReceiver?.id ?? null;
+  };
+
+  const id = computed(() => Number(route.query.id));
+
+  const form = reactive({
+    id: undefined as number | undefined,
+    equipmentId: '',
+    version: '',
+    ppeName: '',
+    hazardFactor: '',
+    issuerCode: '',
+    issuerName: '',
+    issueTime: '',
+    receiveNum: undefined as number | undefined,
+    receiverCode: '',
+    receiverName: '',
+    status: true as boolean,
+    remark: '',
+  });
+
+  const formRules: FormRules = {};
+
+  const getDetail = async () => {
+    if (!id.value) {
+      ElMessage.error('缺少 id');
+      return;
+    }
+    loading.value = true;
+    try {
+      const res = await queryPersonalProtectiveEquipmentReceiveDetail(id.value);
+      if (res) {
+        form.id = res.id;
+        form.equipmentId = res.equipmentId != null ? String(res.equipmentId) : '';
+        form.version = res.version ?? '';
+        form.ppeName = res.ppeName ?? '';
+        form.hazardFactor = res.hazardFactor ?? '';
+        form.issuerCode = res.issuerCode ?? '';
+        form.issuerName = res.issuerName ?? '';
+        form.issueTime = res.issueTime ?? '';
+        const rn = res.receiveNum;
+        form.receiveNum =
+          rn !== '' && rn != null && !Number.isNaN(Number(rn)) ? Number(rn) : undefined;
+        form.receiverCode = res.receiverCode ?? '';
+        form.receiverName = res.receiverName ?? '';
+        form.status = res.status === true || res.status === 'true';
+        form.remark = res.remark ?? '';
+      }
+    } catch (e) {
+      console.error('获取领用详情失败:', e);
+      ElMessage.error('获取详情失败');
+    } finally {
+      loading.value = false;
+    }
+  };
+
+  const handleSubmit = async () => {
+    const valid = await formRef.value?.validate().then(() => true).catch(() => false);
+    if (!valid) return;
+    if (!form.id) {
+      ElMessage.error('缺少 id');
+      return;
+    }
+    submitting.value = true;
+    try {
+      await updatePersonalProtectiveEquipmentReceive({
+        id: form.id,
+        equipmentId: form.equipmentId || undefined,
+        version: form.version || undefined,
+        ppeName: form.ppeName || undefined,
+        hazardFactor: form.hazardFactor || undefined,
+        issuerCode: form.issuerCode || undefined,
+        issuerName: form.issuerName || undefined,
+        issueTime: form.issueTime || undefined,
+        receiveNum: form.receiveNum,
+        receiverCode: form.receiverCode || undefined,
+        receiverName: form.receiverName || undefined,
+        status: form.status,
+        remark: form.remark || undefined,
+      });
+      ElMessage.success('保存成功');
+      router.back();
+    } catch (e) {
+      console.error('编辑领用记录失败:', e);
+      ElMessage.error('保存失败,请重试');
+    } finally {
+      submitting.value = false;
+    }
+  };
+
+  onMounted(async () => {
+    await loadUserList();
+    await getDetail();
+    syncIssuerReceiverFromForm();
+  });
+</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;
+  }
+  .req-form {
+    max-width: 600px;
+  }
+</style>

+ 93 - 175
src/views/production-safety/risk-identification-and-control/labor-products-requisition-manage/list.vue

@@ -1,102 +1,75 @@
 <template>
   <div class="safety-platform-container">
     <header class="safety-platform-container__header">
-      <div class="breadcrumb-title"> 劳防用品领用管理 </div>
+      <div class="breadcrumb-title">劳防用品领用管理</div>
     </header>
     <main class="safety-platform-container__main">
       <div class="search-form">
         <el-form :inline="true">
-          <el-form-item label="搜索">
+          <el-form-item label="标题">
             <el-input
-              v-model="queryParams.queryParam.mergeFiled"
-              placeholder="搜索楼号/楼宇/楼层/房间"
+              v-model="queryParams.queryParam.name"
+              placeholder="请输入标题"
+              clearable
               style="width: 170px"
             />
           </el-form-item>
           <el-form-item label="状态">
-            <el-select v-model="queryParams.queryParam.status" clearable placeholder="状态" style="width: 170px">
-              <el-option :value="1" label="正常" />
-              <el-option :value="2" label="待确认" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="部门名称">
-            <el-cascader
-              v-model="queryParams.queryParam.responsibleDepartmentId"
-              style="width: 170px"
-              ref="cascaderRef"
-              :options="firstLevelDepts"
-              :props="cascaderProp"
-              :show-all-levels="false"
-              placeholder="部门名称"
-              filterable
-              @change="handleChangeDept"
-            />
-          </el-form-item>
-          <el-form-item label="风险类型">
             <el-select
-              v-model="queryParams.queryParam.riskCategory"
+              v-model="queryParams.queryParam.status"
+              placeholder="请选择状态"
               clearable
-              placeholder="风险类型"
               style="width: 170px"
             >
-              <el-option :value="1" label="III级危险点" />
-              <el-option :value="2" label="II级危险点" />
-              <el-option :value="3" label="I级危险点" />
-              <el-option :value="4" label="UPS" />
-              <el-option :value="5" label="电力设施(强电)" />
-              <el-option :value="6" label="高低温气体液体、高压气体" />
-              <el-option :value="7" label="试验设施设备" />
-              <el-option :value="8" label="特种设备" />
-              <el-option :value="9" label="危化品、易燃易爆固液气体" />
-              <el-option :value="10" label="有限空间" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="风险级别">
-            <el-select v-model="queryParams.queryParam.riskLevel" clearable placeholder="风险类型" style="width: 170px">
-              <el-option :value="1" label="B" />
-              <el-option :value="2" label="C" />
+              <el-option label="启用" :value="true" />
+              <el-option label="禁用" :value="false" />
             </el-select>
           </el-form-item>
         </el-form>
-
         <div>
-          <el-button type="primary" @click="$router.push({ name: 'riskManageAdd' })">添加 </el-button>
+          <el-button
+            type="primary"
+            @click="$router.push({ name: 'laborProductsRequisitionManageAdd' })"
+          >
+            添加
+          </el-button>
           <el-button type="primary" @click="queryTableList">查询</el-button>
           <el-button @click="handleRestParams">重置</el-button>
         </div>
       </div>
 
       <div class="table-content">
-        <el-table :data="tableData.data">
-          <el-table-column type="index" label="序号" width="80" />
-          <el-table-column label="楼号/区域" prop="buildingArea" width="180" />
-          <el-table-column label="楼宇名称" prop="buildingName" width="180" />
-          <el-table-column label="楼层/位置" prop="floorLocation" width="180" />
-          <el-table-column label="房间号(名称)" prop="roomName" width="180" />
-          <el-table-column label="安全责任人" prop="roomSafetyResponsibleName" width="180" />
-          <el-table-column label="是否存在风险点" prop="hasRiskPointName" width="180" />
-          <el-table-column label="风险点类别" prop="riskCategoryName" width="180" />
-          <el-table-column label="变更原因" prop="changeReason" width="170" />
-          <el-table-column label="状态" prop="statusName" width="100" />
-          <el-table-column fixed="right" min-width="240" label="操作">
+        <el-table v-loading="loading" :data="tableData.data">
+          <el-table-column label="职业危害因素" prop="hazardFactor" min-width="120" show-overflow-tooltip />
+          <el-table-column label="防护用品名称" prop="ppeName" min-width="120" />
+          <el-table-column label="型号" prop="version" min-width="100" />
+          <el-table-column label="领用数量" prop="receiveNum" width="90" align="center" />
+          <el-table-column label="发放人" prop="issuerName" width="100" />
+          <el-table-column label="发放时间" prop="issueTime" width="170" />
+          <el-table-column label="领用人" prop="receiverName" width="100" />
+          <el-table-column label="用品状态" width="90">
+            <template #default="{ row }">
+              {{ row.status === true || row.status === 'true' ? '启用' : row.status === false || row.status === 'false' ? '禁用' : '-' }}
+            </template>
+          </el-table-column>
+          <el-table-column label="领用备注" prop="remark" min-width="120" show-overflow-tooltip />
+          <el-table-column fixed="right" label="操作" width="260">
             <template #default="scope">
               <el-button
                 type="primary"
                 link
-                @click="$router.push({ name: 'riskManageEdit', query: { id: scope.row.id } })"
-                >编辑</el-button
+                @click="$router.push({ name: 'laborProductsRequisitionManageView', query: { id: scope.row.id } })"
               >
-              <el-button type="primary" link @click="handleConfirmDeleteRow(scope)">删除</el-button>
-              <el-button type="primary" link>查看</el-button>
+                查看
+              </el-button>
               <el-button
                 type="primary"
                 link
-                @click="$router.push({ name: 'riskManageChange', query: { id: scope.row.id } })"
-                >变更</el-button
+                @click="$router.push({ name: 'laborProductsRequisitionManageEdit', query: { id: scope.row.id } })"
               >
-              <el-button type="primary" link @click="handleApprove(scope, 1)">确认</el-button>
-              <el-button type="primary" link @click="handleApprove(scope, 0)">拒绝</el-button>
-              <el-button type="primary" link @click="handleApprove(scope, 0)">撤回</el-button>
+                编辑
+              </el-button>
+              <el-button type="primary" link @click="handleDelete(scope.row)">删除</el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -107,6 +80,8 @@
           :current-page="queryParams.pageNumber"
           :page-size="queryParams.pageSize"
           :total="tableData.total"
+          :page-sizes="[10, 20, 50]"
+          layout="total, sizes, prev, pager, next"
           @size-change="handleSizeChange"
           @current-change="handleCurrentChange"
         />
@@ -114,124 +89,84 @@
     </main>
   </div>
 </template>
+
 <script lang="ts" setup>
   import { onMounted, reactive, ref } from 'vue';
-  import dayjs from 'dayjs';
-  import { ElMessage } from 'element-plus';
-  import { useRouter } from 'vue-router';
+  import { ElMessage, ElMessageBox } from 'element-plus';
   import {
-    safetyRiskListQueryPage,
-    safetyRiskListDelete,
-    safetyRiskListApprove,
-  } from '@/api/production-safety/responsibility-implementation';
-  import { omit } from 'lodash-es';
-  import { useUserInfoHook } from '@/hooks/useUserInfoHook';
-  import { unformatAttachment } from '@/components/UploadFiles/utils';
-  import { downloadFile } from '@/views/disaster/utils';
-  import { formatDeptTree } from '@/views/disaster/utils/formatDeptTree';
-  import { getAllDepartments } from '@/api/auth/dept';
+    queryPersonalProtectiveEquipmentReceiveList,
+    deletePersonalProtectiveEquipmentReceive,
+    type QueryPersonalProtectiveEquipmentReceivePageReq,
+    type PersonalProtectiveEquipmentReceive,
+  } from '@/api/production-safety/personal-protective-equipment-receive';
+  import type { QueryPageRequest } from '@/types/basic-query';
 
-  const router = useRouter();
-  const { id } = useUserInfoHook();
-  const firstLevelDepts = ref<any[]>([]);
-  const cascaderProp = {
-    expandTrigger: 'click',
-    checkStrictly: true,
-    // emitPath: false,
-    value: 'id',
-    label: 'deptName',
-  };
-  const queryParams = reactive<any>({
+  const loading = ref(false);
+  const queryParams = reactive<
+    QueryPageRequest<QueryPersonalProtectiveEquipmentReceivePageReq>
+  >({
     pageNumber: 1,
     pageSize: 10,
     queryParam: {
-      mergeFiled: '',
-      status: '',
-      responsibleDepartment: '',
-      riskCategory: '',
-      riskLevel: '',
-      userId: id,
-      responsibleDepartmentId: [],
+      name: '',
+      status: undefined,
     },
   });
-  const cascaderRef = ref();
 
-  const tableData = reactive({
+  const tableData = reactive<{
+    data: PersonalProtectiveEquipmentReceive[];
+    total: number;
+  }>({
     data: [],
     total: 0,
   });
 
-  const handleSizeChange = (value) => {};
-  const handleCurrentChange = (value) => {
-    queryParams.pageNumber = value;
+  const handleSizeChange = (value: number) => {
+    queryParams.pageSize = value;
+    queryParams.pageNumber = 1;
     queryTableList();
   };
 
-  const getDeptData = () => {
-    getAllDepartments().then((res) => {
-      firstLevelDepts.value = formatDeptTree(res);
-    });
-  };
-  const handleChangeDept = () => {
-    const deptInfo = cascaderRef.value?.getCheckedNodes();
-    if (deptInfo?.[0]) {
-      queryParams.queryParam.responsibleDepartment = deptInfo[0].label;
-    }
+  const handleCurrentChange = (value: number) => {
+    queryParams.pageNumber = value;
+    queryTableList();
   };
 
-  const handleApprove = (scope, approveType) => {
-    safetyRiskListApprove({
-      id: scope.row.id,
-      approveType,
-    }).then(() => {
-      ElMessage.success('操作成功!');
-      queryTableList();
-    });
+  const queryTableList = () => {
+    loading.value = true;
+    queryPersonalProtectiveEquipmentReceiveList(queryParams)
+      .then((res: any) => {
+        tableData.data = (res?.records ?? res?.list ?? []) as PersonalProtectiveEquipmentReceive[];
+        tableData.total = res?.totalRow ?? res?.total ?? 0;
+      })
+      .finally(() => {
+        loading.value = false;
+      });
   };
 
-  const handleDownloadLink = (scope) => {
-    const attachment = unformatAttachment(scope.row.attachment);
-    attachment?.forEach((item: any) => {
-      downloadFile(item.fileUrl, item.fileName);
-    });
-  };
-  const handleConfirmDeleteRow = (scope) => {
-    safetyRiskListDelete(scope.row.id).then(() => {
-      ElMessage.success('删除成功!');
-      queryTableList();
-    });
+  const handleDelete = (row: PersonalProtectiveEquipmentReceive) => {
+    if (row.id == null) return;
+    ElMessageBox.confirm('确定要删除该领用记录吗?', '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    })
+      .then(() => deletePersonalProtectiveEquipmentReceive(row.id!))
+      .then(() => {
+        ElMessage.success('删除成功');
+        queryTableList();
+      })
+      .catch(() => {});
   };
 
-  const queryTableList = () => {
-    safetyRiskListQueryPage({
-      ...queryParams,
-      queryParam: {
-        ...omit(queryParams.queryParam, 'responsibleDepartmentId'),
-      },
-    }).then((res) => {
-      tableData.data = res.records;
-      tableData.total = res.totalRow;
-    });
-  };
   const handleRestParams = () => {
-    Object.assign(queryParams, {
-      pageNumber: 1,
-      pageSize: 10,
-      queryParam: {
-        ...queryParams.queryParam,
-        mergeFiled: '',
-        status: '',
-        responsibleDepartment: '',
-        riskCategory: '',
-        riskLevel: '',
-        responsibleDepartmentId: [],
-      },
-    });
+    queryParams.pageNumber = 1;
+    queryParams.pageSize = 10;
+    queryParams.queryParam = { name: '', status: undefined };
     queryTableList();
   };
 
-  onMounted(async () => {
-    await getDeptData();
+  onMounted(() => {
     queryTableList();
   });
 </script>
@@ -241,19 +176,6 @@
   @use '@/styles/page-main-layout.scss' as *;
   @use '@/styles/basic-table-action.scss' as *;
 
-  :deep(.el-tabs__header) {
-    margin: 0;
-  }
-  :deep(.el-tabs__item) {
-    font-size: 14px !important;
-  }
-  :deep(.flexContent) {
-    display: flex;
-  }
-  :deep(.breadcrumb .title) {
-    margin-left: 0;
-  }
-
   :deep(.el-form) {
     flex: 1;
     display: flex;
@@ -268,23 +190,19 @@
     flex-direction: column;
   }
   .search-form {
-    min-width: 800px;
+    min-width: 600px;
     display: flex;
-
     justify-content: space-between;
     align-items: center;
     margin-bottom: 20px;
   }
-
-  .button-content {
-    margin-bottom: 20px;
-  }
   .table-content {
     flex: 1;
     overflow: hidden;
     overflow-y: auto;
   }
-  .page-content {
+  .pagination-container {
+    margin-top: 16px;
     display: flex;
     justify-content: flex-end;
   }

+ 215 - 1
src/views/production-safety/risk-identification-and-control/labor-products-requisition-manage/view.vue

@@ -1 +1,215 @@
-<template>view</template>
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">查看劳防用品领用</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <el-form v-loading="loading" :model="form" label-width="120px" class="req-form">
+        <el-form-item label="职业危害因素" prop="hazardFactor">
+          <el-input v-model="form.hazardFactor" disabled />
+        </el-form-item>
+        <el-form-item label="发放人" prop="issuer">
+          <el-select
+            v-model="issuerUserId"
+            placeholder="请选择发放人"
+            filterable
+            clearable
+            disabled
+            style="width: 100%"
+          >
+            <el-option
+              v-for="u in issuerUserList"
+              :key="u.id"
+              :label="u.realname"
+              :value="u.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="发放时间" prop="issueTime">
+          <el-input v-model="form.issueTime" disabled />
+        </el-form-item>
+        <el-form-item label="防护用品名称" prop="ppeName">
+          <el-input v-model="form.ppeName" disabled />
+        </el-form-item>
+        <el-form-item label="型号" prop="version">
+          <el-input v-model="form.version" disabled />
+        </el-form-item>
+        <el-form-item label="领用数量" prop="receiveNum">
+          <el-input-number
+            v-model="form.receiveNum"
+            :min="1"
+            :precision="0"
+            disabled
+            controls-position="right"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="领用人" prop="receiver">
+          <el-select
+            v-model="receiverUserId"
+            placeholder="请选择领用人"
+            filterable
+            clearable
+            disabled
+            style="width: 100%"
+          >
+            <el-option
+              v-for="u in receiverUserList"
+              :key="u.id"
+              :label="u.realname"
+              :value="u.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="用品状态" prop="status">
+          <el-radio-group v-model="form.status" disabled>
+            <el-radio :label="true">启用</el-radio>
+            <el-radio :label="false">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="领用备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" :rows="3" disabled />
+        </el-form-item>
+      </el-form>
+    </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="router.back()">返回</el-button>
+    </footer>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, onMounted, computed } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
+  import { queryPersonalProtectiveEquipmentReceiveDetail } from '@/api/production-safety/personal-protective-equipment-receive';
+  import { queryAvailableUserList } from '@/api/production-safety/responsibility-implementation';
+  import type { UserLisItem } from '@/api/system/user-operate';
+
+  const router = useRouter();
+  const route = useRoute();
+  const loading = ref(false);
+
+  const issuerUserList = ref<UserLisItem[]>([]);
+  const receiverUserList = ref<UserLisItem[]>([]);
+  const issuerUserId = ref<number | null>(null);
+  const receiverUserId = ref<number | null>(null);
+
+  const id = computed(() => Number(route.query.id));
+
+  const loadUserList = async () => {
+    try {
+      const res = await queryAvailableUserList({
+        pageNumber: 1,
+        pageSize: 9999,
+        queryParam: {},
+      });
+      const list = (res as { records?: UserLisItem[] })?.records ?? [];
+      issuerUserList.value = list;
+      receiverUserList.value = list;
+    } catch (e) {
+      console.error('获取用户列表失败:', e);
+      issuerUserList.value = [];
+      receiverUserList.value = [];
+    }
+  };
+
+  const syncIssuerReceiverFromForm = () => {
+    const foundIssuer = issuerUserList.value.find(
+      (x) => x.username === form.issuerCode || x.realname === form.issuerName,
+    );
+    issuerUserId.value = foundIssuer?.id ?? null;
+    const foundReceiver = receiverUserList.value.find(
+      (x) => x.username === form.receiverCode || x.realname === form.receiverName,
+    );
+    receiverUserId.value = foundReceiver?.id ?? null;
+  };
+
+  const form = reactive<{
+    equipmentId: string;
+    version: string;
+    ppeName: string;
+    hazardFactor: string;
+    issuerCode: string;
+    issuerName: string;
+    issueTime: string;
+    receiveNum?: number;
+    receiverCode: string;
+    receiverName: string;
+    status: boolean;
+    remark: string;
+    createdAt: string;
+    updatedAt: string;
+  }>({
+    equipmentId: '',
+    version: '',
+    ppeName: '',
+    hazardFactor: '',
+    issuerCode: '',
+    issuerName: '',
+    issueTime: '',
+    receiveNum: undefined,
+    receiverCode: '',
+    receiverName: '',
+    status: true,
+    remark: '',
+    createdAt: '',
+    updatedAt: '',
+  });
+
+  const getDetail = async () => {
+    if (!id.value) {
+      ElMessage.error('缺少 id');
+      return;
+    }
+    loading.value = true;
+    try {
+      const res = await queryPersonalProtectiveEquipmentReceiveDetail(id.value);
+      if (res) {
+        form.equipmentId = res.equipmentId != null ? String(res.equipmentId) : '';
+        form.version = res.version ?? '';
+        form.ppeName = res.ppeName ?? '';
+        form.hazardFactor = res.hazardFactor ?? '';
+        form.issuerCode = res.issuerCode ?? '';
+        form.issuerName = res.issuerName ?? '';
+        form.issueTime = res.issueTime ?? '';
+        const rn = res.receiveNum;
+        form.receiveNum =
+          rn !== '' && rn != null && !Number.isNaN(Number(rn)) ? Number(rn) : undefined;
+        form.receiverCode = res.receiverCode ?? '';
+        form.receiverName = res.receiverName ?? '';
+        form.status = res.status === true || res.status === 'true';
+        form.remark = res.remark ?? '';
+        form.createdAt = res.createdAt ?? '';
+        form.updatedAt = res.updatedAt ?? '';
+      }
+    } catch (e) {
+      console.error('获取领用详情失败:', e);
+      ElMessage.error('获取详情失败');
+    } finally {
+      loading.value = false;
+    }
+  };
+
+  onMounted(async () => {
+    await loadUserList();
+    await getDetail();
+    syncIssuerReceiverFromForm();
+  });
+</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;
+  }
+  .req-form {
+    max-width: 600px;
+  }
+</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.148: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/#/',