xiaweibo 3 månader sedan
förälder
incheckning
35b09d79d9

+ 50 - 15
src/api/drawLessons/index.ts

@@ -12,12 +12,23 @@ export interface DrawLessonsItem {
   associationOtObligationDeptIds: string;
   associationOtObligationDeptName: string;
   associationOtTimeLimit: string;
+  planStartDate?: string;
   attachments?: string;
   statusId: number;
   statusName: string;
+  issueCount?: number;
+  feedbackCount?: number;
+  feedbackRate?: number;
   creatorName?: string;
   createdAt: string;
   updatedAt: string;
+  canEdit?: boolean;
+  canDelete?: boolean;
+  canNotify?: boolean;
+  canSend?: boolean;
+  canAudit?: boolean;
+  canVoid?: boolean;
+  canFeedback?: boolean;
 }
 
 /**
@@ -63,14 +74,13 @@ export function getDrawLessonsAdminDetail(id: number) {
  * POST /api/drawLessons/admin/save
  */
 export interface SaveDrawLessonsRequest {
-  dangerId?: number;
-  problem: string;
-  associationOtObligationDeptId?: number;
-  associationOtObligationDeptName?: string;
-  associationOtObligationDeptIds?: string;
-  associationOneThree: string;
-  associationOtTimeLimit: string;
-  attachments?: string;
+  dangerId: number; // 隐患ID(选择隐患名称)
+  associationOneThree: string; // 举一反三要求
+  associationOtObligationDeptIds: string; // 举一反三责任部门ID列表,逗号分隔
+  associationOtTimeLimit: string; // 举一反三时限
+  planStartDate?: string; // 计划开始日期 yyyy-MM-dd
+  /** 兼容:隐患问题名称,后端可能根据 dangerId 带出 */
+  problem?: string;
 }
 
 export function saveDrawLessons(data: SaveDrawLessonsRequest) {
@@ -85,9 +95,17 @@ export function saveDrawLessons(data: SaveDrawLessonsRequest) {
  * 2.4 修改举一反三
  * PUT /api/drawLessons/admin/update
  */
-export interface UpdateDrawLessonsRequest extends Partial<SaveDrawLessonsRequest> {
+export interface UpdateDrawLessonsRequest {
   id: number;
+  associationOneThree?: string;
+  associationOtObligationDeptIds?: string;
+  associationOtTimeLimit?: string;
+  planStartDate?: string;
   statusId?: number;
+  statusName?: string;
+  /** 兼容视图 spread 传参 */
+  dangerId?: number;
+  problem?: string;
 }
 
 export function updateDrawLessons(data: UpdateDrawLessonsRequest) {
@@ -125,6 +143,10 @@ export interface IssueDrawLessonsRequest {
   associationOneThree?: string;
   associationOtTimeLimit?: string;
   attachments?: string;
+  /** 计划开始日期(下发弹窗) */
+  planStartDate?: string;
+  /** 计划结束时间(下发弹窗) */
+  planEndTime?: string;
 }
 
 export function issueDrawLessons(data: IssueDrawLessonsRequest) {
@@ -136,14 +158,15 @@ export function issueDrawLessons(data: IssueDrawLessonsRequest) {
 }
 
 /**
- * 2.7 反馈(管理端)
- * POST /api/drawLessons/admin/feedback
+ * 2.7 反馈(管理端 / 部门端
+ * POST /api/drawLessons/admin/feedback 或 /api/drawLessons/dept/feedback
  */
 export interface DrawLessonsFeedbackRequest {
-  id?: number;
+  id: number; // 下发记录ID(举一反三下发表 id)
   /** 部门端反馈时用下发记录 id,提交时与 id 二选一 */
   issueId?: number;
-  feedbackResult: string;
+  feedbackHasIssue?: number; // 是否存在问题:1-是 0-否
+  feedbackResult?: string;
   feedbackTime?: string;
   attachments?: string;
 }
@@ -153,7 +176,13 @@ export function submitDrawLessonsAdminFeedback(data: DrawLessonsFeedbackRequest)
   return http.request({
     url: '/drawLessons/admin/feedback',
     method: 'post',
-    data: { id, feedbackResult: data.feedbackResult, feedbackTime: data.feedbackTime, attachments: data.attachments },
+    data: {
+      id,
+      feedbackHasIssue: data.feedbackHasIssue,
+      feedbackResult: data.feedbackResult,
+      feedbackTime: data.feedbackTime,
+      attachments: data.attachments,
+    },
   });
 }
 
@@ -214,7 +243,13 @@ export function submitDrawLessonsDeptFeedback(data: DrawLessonsFeedbackRequest)
   return http.request({
     url: '/drawLessons/dept/feedback',
     method: 'post',
-    data: { id, feedbackResult: data.feedbackResult, feedbackTime: data.feedbackTime, attachments: data.attachments },
+    data: {
+      id,
+      feedbackHasIssue: data.feedbackHasIssue,
+      feedbackResult: data.feedbackResult,
+      feedbackTime: data.feedbackTime,
+      attachments: data.attachments,
+    },
   });
 }
 

+ 33 - 0
src/api/hiddenDanger/index.ts

@@ -6,6 +6,8 @@ import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
  */
 export interface HiddenDangerItem {
   id: number;
+  sourceType?: number; // 数据来源:1-员工上报 2-台账新增 3-台账导入 4-区域检查
+  sourceRefId?: number;
   dangerCode: string;
   dangerProblem: string;
   typeId: number;
@@ -27,6 +29,8 @@ export interface HiddenDangerItem {
   drawLessonsDepartmentIds?: string;
   drawLessonsDepartmentNames?: string;
   drawLessonsDeadline?: string;
+  entryTime?: string;
+  currentNode?: string;
   associationOtId?: number;
   statusId: number;
   statusName: string;
@@ -39,6 +43,13 @@ export interface HiddenDangerItem {
   rectificationCompletionStatus?: string;
   rectificationCompletionTime?: string;
   attachments?: string;
+  canEdit?: boolean;
+  canDelete?: boolean;
+  canIssue?: boolean;
+  canRectify?: boolean;
+  canReview?: boolean;
+  canDeduct?: boolean;
+  canView?: boolean;
 }
 
 /**
@@ -118,6 +129,8 @@ export interface SaveHiddenDangerRequest {
   reviewDepartmentId?: number;
   reviewPersonId?: number;
   reviewPersonName?: string;
+  /** 附件(详情中附件参数) */
+  attachments?: string;
 }
 
 export function saveHiddenDanger(data: SaveHiddenDangerRequest) {
@@ -145,6 +158,26 @@ export function updateHiddenDanger(data: UpdateHiddenDangerRequest) {
   });
 }
 
+/**
+ * 隐患台账下发
+ * POST /api/productionHiddenDanger/issueHiddenDanger
+ */
+export interface IssueHiddenDangerRequest {
+  danger_id: number;
+  rectification_department_ids: string;
+  rectification_responsible_person: string;
+  dept_name: string;
+  rectification_responsible_ids: string;
+}
+
+export function issueHiddenDanger(data: IssueHiddenDangerRequest) {
+  return http.request({
+    url: '/productionHiddenDanger/issueHiddenDanger',
+    method: 'post',
+    data,
+  });
+}
+
 /**
  * 1.6 整改隐患
  * POST /api/productionHiddenDanger/rectifyHiddenDanger

+ 9 - 3
src/api/production-safety/index.ts

@@ -16,6 +16,7 @@ export interface EmployeeHazardReportDTO {
   reportTime: string; // 上报时间
   sourceType: number; // 提交类型:1-员工,2-供应商,3-第三方
   sourceTypeName: string; // 提交类型名称
+  taskSource: string; // 任务来源
   reporterName: string; // 上报人姓名
   reporterJobNo: string; // 上报人工号
   reporterMobile: string; // 联系电话
@@ -23,9 +24,12 @@ export interface EmployeeHazardReportDTO {
   status: number; // 状态:1-待审核,2-需求部门通过,3-需求部门驳回,4-安全部门通过,5-安全部门驳回,6-已入账,7-已关闭
   statusName: string; // 状态名称
   statusType?: number; // 状态类型:1-待审核,2-审核通过,3-审核不通过
-  isRewardApplied: number; // 是否申请奖品:1-是,0-否
-  createdById: number; // 创建人 ID
-  createdByName: string; // 创建人姓名
+  canApprove?: boolean; // 可审核
+  canAccount?: boolean; // 可入账
+  canView?: boolean; // 可查看
+  isRewardApplied?: number; // 是否申请奖品:1-是,0-否
+  createdById?: number; // 创建人 ID
+  createdByName?: string; // 创建人姓名
   createdAt: string; // 创建时间
   updatedAt: string; // 更新时间
 }
@@ -37,6 +41,7 @@ export interface SaveEmployeeHazardReportReq {
   location: string; // 隐患地点
   reportTime: string; // 上报时间
   sourceType: number; // 提交类型:1-员工,2-供应商,3-第三方
+  taskSource: string; // 任务来源
   reporterName: string; // 上报人姓名
   reporterJobNo: string; // 上报人工号
   reporterMobile: string; // 联系电话
@@ -51,6 +56,7 @@ export interface UpdateEmployeeHazardReportReq {
   location?: string; // 隐患地点
   reportTime?: string; // 上报时间
   sourceType?: number; // 提交类型:1-员工,2-供应商,3-第三方
+  taskSource?: string; // 任务来源
   reporterName?: string; // 上报人姓名
   reporterJobNo?: string; // 上报人工号
   reporterMobile?: string; // 联系电话

+ 21 - 6
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/hiddenTroubleAccountManagement/components/hiddenTroubleAccountManagementDetail.vue

@@ -80,6 +80,7 @@
           placeholder="请输入复查意见(选填),限300字"
           maxlength="300"
           show-word-limit
+          :disabled="isViewMode && !isReviewMode"
         />
       </template>
     </BasicForm>
@@ -186,6 +187,8 @@
   import type { FormInstance, FormRules } from 'element-plus';
   import BasicForm from '@/components/BasicForm.vue';
   import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import type { FileItem } from '@/components/UploadFiles/types';
+  import { formatAttachmentList } from '@/components/UploadFiles/utils';
   import { useFormConfigHook } from '@/hooks/useFormConfigHook';
   import {
     HIDDEN_DANGER_FORM_CONFIG,
@@ -231,8 +234,9 @@
   const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } =
     useFormConfigHook(HIDDEN_DANGER_FORM_CONFIG, HIDDEN_DANGER_FORM_DATA, HIDDEN_DANGER_FORM_RULES);
 
+  /** 查看详情:与复查时字段一致,全部为禁用状态 */
   const viewFormConfig = computed(() =>
-    HIDDEN_DANGER_FORM_CONFIG.map((item) => ({
+    HIDDEN_DANGER_REVIEW_FORM_CONFIG.map((item) => ({
       ...item,
       componentProps: { ...item.componentProps, disabled: true },
     })),
@@ -261,8 +265,18 @@
     });
   }
   const attachmentsFileList = computed(() => convertAttachmentsToFileItems(ruleFormData.attachments || ''));
-  function handleAttachmentsUploadSuccess(files: Array<{ fileUrl?: string }>) {
-    ruleFormData.attachments = (files || []).map((f) => f.fileUrl).filter(Boolean).join(',');
+  async function handleAttachmentsUploadSuccess(files: FileItem[]) {
+    if (!files?.length) {
+      ruleFormData.attachments = '';
+      return;
+    }
+    try {
+      const list = await formatAttachmentList(files);
+      ruleFormData.attachments = (list || []).map((r) => r.fileUrl).filter(Boolean).join(',');
+    } catch (e) {
+      console.error('附件上传失败:', e);
+      ElMessage.error('附件上传失败,请重试');
+    }
   }
 
   const basicFormRef = ref<InstanceType<typeof BasicForm>>();
@@ -319,10 +333,10 @@
     ruleFormData.drawLessonsDepartmentIds = drawLessonsDeptIdsArray.value.join(',');
   }
 
-  /** 状态:与文档一致 4待下发 5待整改 6待复查 7已完成(用 statusId,兼容 statusOrder) */
+  /** 状态:1待下发 2待整改 3待复查 4已完成 5待入账(用 statusId,兼容 statusOrder) */
   const detailStatusOrder = ref<number>(0);
-  const canRectify = computed(() => detailStatusOrder.value === 5);
-  const canReview = computed(() => detailStatusOrder.value === 6);
+  const canRectify = computed(() => detailStatusOrder.value === 2);
+  const canReview = computed(() => detailStatusOrder.value === 3);
 
   const getDetail = async () => {
     if (!currentId.value) return;
@@ -389,6 +403,7 @@
         drawLessonsContent: ruleFormData.drawLessonsContent || undefined,
         drawLessonsDepartmentIds: ruleFormData.drawLessonsDepartmentIds || undefined,
         drawLessonsDeadline: ruleFormData.drawLessonsDeadline || undefined,
+        attachments: ruleFormData.attachments || undefined,
       };
       if (isCreateMode.value) {
         await saveHiddenDanger(payload);

+ 1 - 1
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/hiddenTroubleAccountManagement/configs/tables.ts

@@ -53,7 +53,7 @@ export const HIDDEN_DANGER_TABLE_COLUMNS: TableColumnProps[] = [
   },
   {
     label: '当前流程节点',
-    prop: 'currentNode',
+    // prop: 'currentNode',
     slot: 'currentNode',
     align: 'center',
     minWidth: '150px',

+ 45 - 36
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/hiddenTroubleAccountManagement/hiddenTroubleAccountManagement.vue

@@ -36,13 +36,11 @@
                   clearable
                 >
                   <el-option label="全部" :value="0" />
-                  <el-option label="上级检查" :value="1" />
-                  <el-option label="院内自查" :value="2" />
-                  <el-option label="专家安全评估" :value="3" />
-                  <el-option label="待下发" :value="4" />
-                  <el-option label="待整改" :value="5" />
-                  <el-option label="待复查" :value="6" />
-                  <el-option label="已完成" :value="7" />
+                  <el-option label="待下发" :value="1" />
+                  <el-option label="待整改" :value="2" />
+                  <el-option label="待复查" :value="3" />
+                  <el-option label="已完成" :value="4" />
+                  <el-option label="待入账" :value="5" />
                 </el-select>
               </div>
             </section>
@@ -62,15 +60,15 @@
             @update:pageNumber="handleCurrentChange"
           >
             <template #currentNode="scope">
-              <span>{{ scope.row.currentNode ?? scope.row.statusName ?? '-' }}</span>
+              <span>{{ scope.row.currentNode  }}</span>
             </template>
             <template #status="scope">
               <span>{{ scope.row.statusName || '-' }}</span>
             </template>
             <template #action="scope">
               <div class="action-container--div" style="justify-content: left">
-                <!-- 待下发 statusId=4:编辑、删除、查看、下发、扣分 -->
-                <template v-if="scope.row.statusId === 4">
+                <!-- 待下发 statusId=1:编辑、删除、查看、下发、扣分 -->
+                <template v-if="scope.row.statusId === 1">
                   <ActionButton text="编辑" @click="handleEdit(scope.row.id)" />
                   <ActionButton
                     text="删除"
@@ -81,20 +79,25 @@
                   <ActionButton text="下发" @click="handleOpenIssue(scope.row.id)" />
                   <ActionButton text="扣分" @click="handleOpenDeduct(scope.row.id)" />
                 </template>
-                <!-- 待整改 statusId=5:查看、整改、扣分 -->
-                <template v-else-if="scope.row.statusId === 5">
+                <!-- 待整改 statusId=2:查看、整改、扣分 -->
+                <template v-else-if="scope.row.statusId === 2">
                   <ActionButton text="查看" @click="handleView(scope.row.id)" />
                   <ActionButton text="整改" @click="handleRectify(scope.row.id)" />
                   <ActionButton text="扣分" @click="handleOpenDeduct(scope.row.id)" />
                 </template>
-                <!-- 待复查 statusId=6:查看、复查、扣分 -->
-                <template v-else-if="scope.row.statusId === 6">
+                <!-- 待复查 statusId=3:查看、复查、扣分 -->
+                <template v-else-if="scope.row.statusId === 3">
                   <ActionButton text="查看" @click="handleView(scope.row.id)" />
                   <ActionButton text="复查" @click="handleReview(scope.row.id)" />
                   <ActionButton text="扣分" @click="handleOpenDeduct(scope.row.id)" />
                 </template>
-                <!-- 已完成 statusId=7:查看、扣分 -->
-                <template v-else-if="scope.row.statusId === 7">
+                <!-- 已完成 statusId=4:查看、扣分 -->
+                <template v-else-if="scope.row.statusId === 4">
+                  <ActionButton text="查看" @click="handleView(scope.row.id)" />
+                  <ActionButton text="扣分" @click="handleOpenDeduct(scope.row.id)" />
+                </template>
+                <!-- 待入账 statusId=5:查看、扣分 -->
+                <template v-else-if="scope.row.statusId === 5">
                   <ActionButton text="查看" @click="handleView(scope.row.id)" />
                   <ActionButton text="扣分" @click="handleOpenDeduct(scope.row.id)" />
                 </template>
@@ -196,7 +199,7 @@
     queryHiddenDangerPage,
     deleteHiddenDanger,
     exportHiddenDanger,
-    updateHiddenDanger,
+    issueHiddenDanger,
     deductHiddenDangerPoints,
     type HiddenDangerQueryParam,
     type HiddenDangerDeductPointsRequest,
@@ -240,15 +243,6 @@
   };
 
 
-  /** 主列表假数据(接口无数据或失败时展示,statusId 与文档一致:4待下发 5待整改 6待复查 7已完成) */
-  const MOCK_LIST_DATA = [
-    { id: 1001, dangerProblem: '消防通道堆放杂物,影响疏散', reasonName: '物的不安全状态', createdAt: '2025-01-15 09:30:00', taskSource: '院内自查', rectificationRequirement: '限期清理通道,设置标识', typeName: '建筑安全类', currentNode: '待下发', statusName: '待下发', statusId: 4 },
-    { id: 1002, dangerProblem: '安全培训记录不完整', reasonName: '管理缺陷', createdAt: '2025-01-18 14:20:00', taskSource: '上级检查', rectificationRequirement: '补全培训记录并归档', typeName: '规章制度类', currentNode: '待整改', statusName: '待整改', statusId: 5 },
-    { id: 1003, dangerProblem: '配电箱未上锁', reasonName: '物的不安全状态', createdAt: '2025-01-20 11:00:00', taskSource: '院内自查', rectificationRequirement: '加装锁具,落实责任人', typeName: '建筑安全类', currentNode: '已完成', statusName: '已完成', statusId: 7 },
-    { id: 1004, dangerProblem: '作业人员未佩戴安全帽', reasonName: '人的不安全行为', createdAt: '2025-01-22 08:45:00', taskSource: '上级检查', rectificationRequirement: '加强现场监督与教育', typeName: '规章制度类', currentNode: '待下发', statusName: '待下发', statusId: 4 },
-    { id: 1005, dangerProblem: '车间照明不足', reasonName: '环境的不利影响', createdAt: '2025-01-25 16:00:00', taskSource: '院内自查', rectificationRequirement: '增设照明设备', typeName: '建筑安全类', currentNode: '待复查', statusName: '待复查', statusId: 6 },
-  ];
-
   async function getTableData() {
     tableConfig.loading = true;
     try {
@@ -263,19 +257,19 @@
           taskSource: item.taskSource,
           rectificationRequirement: item.rectificationRequirement,
           typeName: item.typeName,
-          currentNode: item.statusName,
+          currentNode: item.currentNode,
           statusName: item.statusName,
           statusId: item.statusId,
         }));
         pagination.total = (res as any).totalRow ?? (res as any).total ?? 0;
       } else {
-        tableData.value = MOCK_LIST_DATA;
-        pagination.total = MOCK_LIST_DATA.length;
+        tableData.value = [];
+        pagination.total = 0;
       }
     } catch (e) {
       console.error('获取隐患台账列表失败:', e);
-      tableData.value = MOCK_LIST_DATA;
-      pagination.total = MOCK_LIST_DATA.length;
+      tableData.value = [];
+      pagination.total = 0;
     } finally {
       tableConfig.loading = false;
     }
@@ -405,6 +399,19 @@
     issueForm.value.rectificationResponsiblePersonName = '';
   };
 
+  /** 从部门树中根据 id 查找部门名称 */
+  function findDeptNameById(nodes: DeptTree[] | undefined, id: number): string {
+    if (!nodes?.length) return '';
+    for (const n of nodes) {
+      if (n.id === id) return n.deptName ?? '';
+      if (n.children?.length) {
+        const found = findDeptNameById(n.children, id);
+        if (found) return found;
+      }
+    }
+    return '';
+  }
+
   const handleOpenIssue = (id: number) => {
     issueDangerId.value = id;
     issueForm.value = {
@@ -424,12 +431,14 @@
     }
     const selectedUser = issueUserList.value.find((u) => u.id === rectificationResponsibleUserId);
     const personName = selectedUser?.realname ?? selectedUser?.username ?? '';
+    const deptName = findDeptNameById(issueDeptTree.value, rectificationDepartmentId);
     try {
-      await updateHiddenDanger({
-        id: issueDangerId.value,
-        statusId: 6,
-        rectificationDepartmentIds: String(rectificationDepartmentId),
-        rectificationResponsiblePerson: personName,
+      await issueHiddenDanger({
+        danger_id: issueDangerId.value,
+        rectification_department_ids: String(rectificationDepartmentId),
+        rectification_responsible_person: personName,
+        dept_name: deptName,
+        rectification_responsible_ids: String(rectificationResponsibleUserId),
       });
       ElMessage.success('下发成功');
       showIssueDialog.value = false;

+ 43 - 117
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/oneByOneManagement/components/OneByOneNotifyTarget.vue

@@ -84,6 +84,9 @@
             <template #status="scope">
               <span>{{ scope.row.statusName || '-' }}</span>
             </template>
+            <template #feedbackRatio="scope">
+              <span>{{ scope.row.feedbackRatio ?? (scope.row.issueCount ? `${scope.row.feedbackCount ?? 0}/${scope.row.issueCount}` : '-') }}</span>
+            </template>
             <template #action="scope">
               <div class="action-container--div" style="justify-content: left">
                 <ActionButton
@@ -113,10 +116,9 @@
   import BasicTable from '@/components/BasicTable.vue';
   import ActionButton from '@/components/ActionButton.vue';
   import useTableConfig from '@/hooks/useTableConfigHook';
-  import {
-    getDrawLessonsDeptDetail,
-    getDrawLessonsAdminDetail,
-  } from '@/api/drawLessons';
+  import { queryDrawLessonsAdminPage, type DrawLessonsQueryParam } from '@/api/drawLessons';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import { DRAW_LESSONS_TABLE_COLUMNS } from '../configs/tables';
 
   const NOTIFY_TARGET_STATUS_OPTIONS = [
     { label: '未下发', value: '未下发' },
@@ -126,81 +128,17 @@
     { label: '已完成', value: '已完成' },
   ];
 
-  const NOTIFY_TARGET_COLUMNS = [
-    { label: '隐患问题', prop: 'problem', align: 'left', minWidth: '160px', showOverflowTooltip: true },
-    { label: '状态', prop: 'statusName', slot: 'status', align: 'center', width: '100px' },
-    { label: '部门名称', prop: 'associationOtObligationDeptName', align: 'left', minWidth: '140px' },
-    { label: '部门负责人', prop: 'associationOtObligationDeptUserName', align: 'left', minWidth: '100px' },
-    { label: '举一反三要求', prop: 'associationOneThree', align: 'left', minWidth: '180px', showOverflowTooltip: true },
-    { label: '计划完成时间', prop: 'associationOtTimeLimit', align: 'left', width: '160px' },
-    { label: '操作', prop: 'action', slot: 'action', fixed: 'right', width: '140px', align: 'left' },
-  ];
-
   const TABLE_OPTIONS = {
     emptyText: '暂无通知对象',
     loading: false,
     maxHeight: 'calc(70vh - 200px)',
   };
 
-  /** 假数据:无 id 或接口无数据时使用 */
-  const MOCK_HEADER = {
-    problem: '7号楼水闸四眼和山红石煤塌场内外侧斜角有安全隐患',
-    creatorName: '柳小虎',
-    createdAt: '2024-01-01 13:30:00',
-  };
-
-  const MOCK_TABLE_DATA = [
-    {
-      id: 101,
-      problem: '7号楼水闸四眼和山红石煤塌场内外侧斜角有安全隐患',
-      statusName: '待反馈',
-      associationOtObligationDeptName: '生产部',
-      associationOtObligationDeptUserName: '张三',
-      associationOneThree: '全厂同类区域排查,加固防护栏并设置警示标识。',
-      associationOtTimeLimit: '2025-02-28 00:00:00',
-      feedbackResult: '',
-      feedbackTime: '',
-    },
-    {
-      id: 102,
-      problem: '7号楼水闸四眼和山红石煤塌场内外侧斜角有安全隐患',
-      statusName: '待审核',
-      associationOtObligationDeptName: '安全部',
-      associationOtObligationDeptUserName: '李四',
-      associationOneThree: '全厂同类区域排查,加固防护栏并设置警示标识。',
-      associationOtTimeLimit: '2025-02-28 00:00:00',
-      feedbackResult: '已组织现场排查并完成整改。',
-      feedbackTime: '2025-01-15 10:20:00',
-    },
-    {
-      id: 103,
-      problem: '7号楼水闸四眼和山红石煤塌场内外侧斜角有安全隐患',
-      statusName: '已完成',
-      associationOtObligationDeptName: '综合部',
-      associationOtObligationDeptUserName: '王五',
-      associationOneThree: '全厂同类区域排查,加固防护栏并设置警示标识。',
-      associationOtTimeLimit: '2025-02-28 00:00:00',
-      feedbackResult: '整改已完成,复查通过。',
-      feedbackTime: '2025-01-18 14:00:00',
-    },
-    {
-      id: 104,
-      problem: '7号楼水闸四眼和山红石煤塌场内外侧斜角有安全隐患',
-      statusName: '未下发',
-      associationOtObligationDeptName: '设备部',
-      associationOtObligationDeptUserName: '赵六',
-      associationOneThree: '全厂同类区域排查,加固防护栏并设置警示标识。',
-      associationOtTimeLimit: '2025-02-28 00:00:00',
-      feedbackResult: '',
-      feedbackTime: '',
-    },
-  ];
-
   const router = useRouter();
   const route = useRoute();
   const id = computed(() => Number(route.query.id));
 
-  const { tableConfig, pagination } = useTableConfig(NOTIFY_TARGET_COLUMNS, TABLE_OPTIONS, true);
+  const { tableConfig, pagination } = useTableConfig(DRAW_LESSONS_TABLE_COLUMNS, TABLE_OPTIONS, true);
   const basicTableRef = ref<InstanceType<typeof BasicTable>>();
   const rawTableData = ref<any[]>([]);
   const detailData = ref<{
@@ -231,7 +169,7 @@
     if (searchDateRange.value?.length === 2) {
       const [start, end] = searchDateRange.value;
       list = list.filter((row) => {
-        const t = (row.feedbackTime || '').slice(0, 10);
+        const t = (row.createdAt || row.associationOtTimeLimit || '').toString().slice(0, 10);
         return t >= start && t <= end;
       });
     }
@@ -262,60 +200,48 @@
     pagination.pageNumber = value;
   };
 
-  /** 顶部信息:使用 drawLessons/dept/queryDetail?id=xxx,无 id 或失败时用假数据 */
-  const loadHeaderDetail = async () => {
-    if (id.value) {
-      try {
-        const res = await getDrawLessonsDeptDetail(id.value);
-        const data = (res as any)?.data ?? res;
-        if (data && typeof data === 'object') {
+  /** 列表与顶部信息:直接使用外面的列表接口 queryDrawLessonsAdminPage,再按当前 id 筛出当前记录 */
+  const loadList = async () => {
+    try {
+      tableConfig.loading = true;
+      const query: QueryPageRequest<DrawLessonsQueryParam> = {
+        pageNumber: 1,
+        pageSize: 9999,
+        queryParam: {},
+      };
+      const res = await queryDrawLessonsAdminPage(query);
+      const records = (res && (res as { records?: unknown[] }).records) ?? [];
+      const list = Array.isArray(records) ? records : [];
+      if (id.value) {
+        const current = list.filter((r: { id?: number }) => r.id === id.value);
+        rawTableData.value = current;
+        if (current.length) {
+          const row = current[0] as { problem?: string; creatorName?: string; createdAt?: string };
           detailData.value = {
-            problem: data.problem,
-            creatorName: data.creatorName,
-            createdAt: data.createdAt,
+            problem: row.problem,
+            creatorName: row.creatorName,
+            createdAt: row.createdAt,
           };
-          return;
+        } else {
+          detailData.value = null;
         }
-      } catch (e) {
-        console.error('获取举一反三详情(部门端)失败:', e);
+      } else {
+        rawTableData.value = list;
+        const first = list[0] as { problem?: string; creatorName?: string; createdAt?: string } | undefined;
+        detailData.value = first
+          ? { problem: first.problem, creatorName: first.creatorName, createdAt: first.createdAt }
+          : null;
       }
+    } catch (e) {
+      console.error('获取举一反三列表失败:', e);
+      rawTableData.value = [];
+      detailData.value = null;
+    } finally {
+      tableConfig.loading = false;
     }
-    detailData.value = { ...MOCK_HEADER };
   };
 
-  /** 表格数据:使用管理端详情(含 issueRecords),无 id 或失败时用假数据 */
-  const loadTableData = async () => {
-    if (id.value) {
-      try {
-        tableConfig.loading = true;
-        const res = await getDrawLessonsAdminDetail(id.value);
-        const data = (res as any)?.data ?? res;
-        if (data && typeof data === 'object') {
-          const records = Array.isArray(data.issueRecords) ? data.issueRecords : [];
-          if (records.length) {
-            rawTableData.value = records.map((r: any) => ({
-              ...r,
-              problem: data.problem ?? r.problem,
-              associationOneThree: data.associationOneThree ?? r.associationOneThree,
-              associationOtTimeLimit: data.associationOtTimeLimit ?? r.associationOtTimeLimit,
-            }));
-            return;
-          }
-        }
-      } catch (e) {
-        console.error('获取举一反三详情(管理端)失败:', e);
-      } finally {
-        tableConfig.loading = false;
-        if (rawTableData.value.length) return;
-      }
-    }
-    rawTableData.value = [...MOCK_TABLE_DATA];
-    pagination.pageNumber = 1;
-  };
-
-  const getDetail = async () => {
-    await Promise.all([loadHeaderDetail(), loadTableData()]);
-  };
+  const getDetail = () => loadList();
 
   const handleTabChange = () => {
     // 切换 tab 后列表由 filteredTableData 自动过滤

+ 145 - 12
src/views/production-safety/hiddenTroubleInvestigationAndGovernance/oneByOneManagement/oneByOneManagement.vue

@@ -72,12 +72,12 @@
                     @confirm="handleDelete(scope.row.id)"
                   />
                   <ActionButton text="通知对象" @click="handleNotifyTarget(scope.row)" />
-                  <ActionButton text="发送" @click="handleSend(scope.row.id)" />
+                  <ActionButton text="发送" @click="handleSend(scope.row)" />
                 </template>
                 <!-- 待反馈 statusId=3 -->
                 <template v-else-if="scope.row.statusId === 3">
                   <ActionButton text="通知对象" @click="handleNotifyTarget(scope.row)" />
-                  <ActionButton text="发送" @click="handleSend(scope.row.id)" />
+                  <ActionButton text="发送" @click="handleSend(scope.row)" />
                   <ActionButton text="作废" @click="handleCancel(scope.row.id)" />
                 </template>
                 <!-- 待审核 statusId=4 -->
@@ -105,6 +105,56 @@
         </div>
       </div>
     </main>
+    <!-- 下发举一反三弹窗:点击「发送」弹出,保存时调用 api/drawLessons/admin/issue -->
+    <el-dialog
+      v-model="showIssueDialog"
+      title="下发举一反三"
+      width="520px"
+      destroy-on-close
+      @close="resetIssueForm"
+    >
+      <el-form ref="issueFormRef" :model="issueForm" :rules="issueRules" label-width="120px">
+        <el-form-item label="下发分组名称" prop="groupDeptId" required>
+          <el-select
+            v-model="issueForm.groupDeptId"
+            placeholder="请选择分组名称"
+            clearable
+            filterable
+            style="width: 100%"
+            @change="onIssueGroupChange"
+          >
+            <el-option
+              v-for="d in groupOptions"
+              :key="d.id"
+              :label="d.deptName"
+              :value="d.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="计划开始日期" prop="planStartDate" required>
+          <el-date-picker
+            v-model="issueForm.planStartDate"
+            type="date"
+            value-format="YYYY-MM-DD"
+            placeholder="选择计划开始日期"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="计划结束时间" prop="planEndTime" required>
+          <el-date-picker
+            v-model="issueForm.planEndTime"
+            type="date"
+            value-format="YYYY-MM-DD"
+            placeholder="选择计划结束日期"
+            style="width: 100%"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="showIssueDialog = false">取消</el-button>
+        <el-button type="primary" @click="handleIssueSubmit">保存</el-button>
+      </template>
+    </el-dialog>
     <BatchImport
       v-if="batchImportVisible"
       :visible="batchImportVisible"
@@ -127,12 +177,16 @@
   import { TABLE_OPTIONS, DRAW_LESSONS_TABLE_COLUMNS } from './configs/tables';
   import { useRouter } from 'vue-router';
   import type { QueryPageRequest } from '@/types/basic-query';
+  import type { FormInstance, FormRules } from 'element-plus';
   import {
     queryDrawLessonsAdminPage,
     deleteDrawLessons,
     approveDrawLessons,
+    issueDrawLessons,
     type DrawLessonsQueryParam,
   } from '@/api/drawLessons';
+  import { getAllDepartments } from '@/api/auth/dept';
+  import type { DeptTree } from '@/types/dept/type';
   import { downloadByData } from '@/utils/file/download';
   import { useGlobSetting } from '@/hooks/setting';
   import urlJoin from 'url-join';
@@ -175,6 +229,7 @@
       if (res?.records) {
         tableData.value = res.records.map((item: any) => ({
           id: item.id,
+          dangerId: item.dangerId,
           problem: item.problem,
           statusId: item.statusId,
           statusName: item.statusName,
@@ -284,17 +339,94 @@
     });
   };
 
-  /** 发送:跳转详情页进行下发 */
-  const handleSend = (id: number) => {
-    router.push({
-      name: 'oneByOneManagementItem',
-      query: {
-        id: String(id),
-        operate: 'one-by-one-view',
-        action: 'issue',
-      },
-    });
+  /** 发送:弹出「下发举一反三」弹窗,保存时调用 api/drawLessons/admin/issue */
+  const showIssueDialog = ref(false);
+  const currentIssueRow = ref<{
+    id: number;
+    dangerId?: number;
+    associationOneThree?: string;
+  } | null>(null);
+  const issueFormRef = ref<FormInstance>();
+  const issueForm = ref({
+    groupDeptId: undefined as number | undefined,
+    groupDeptName: '',
+    planStartDate: '',
+    planEndTime: '',
+  });
+  const issueRules: FormRules = {
+    groupDeptId: [{ required: true, message: '请选择分组名称', trigger: 'change' }],
+    planStartDate: [{ required: true, message: '请选择计划开始日期', trigger: 'change' }],
+    planEndTime: [{ required: true, message: '请选择计划结束时间', trigger: 'change' }],
   };
+  const groupOptions = ref<Array<{ id: number; deptName: string }>>([]);
+
+  function flattenDeptTree(nodes: DeptTree[] | undefined): Array<{ id: number; deptName: string }> {
+    if (!nodes?.length) return [];
+    const list: Array<{ id: number; deptName: string }> = [];
+    const walk = (items: DeptTree[]) => {
+      items.forEach((n) => {
+        if (n.id != null) list.push({ id: n.id, deptName: n.deptName ?? '' });
+        if (n.children?.length) walk(n.children);
+      });
+    };
+    walk(nodes);
+    return list;
+  }
+
+  async function loadGroupOptions() {
+    try {
+      const res = await getAllDepartments();
+      const tree = (res as DeptTree[]) ?? [];
+      groupOptions.value = flattenDeptTree(Array.isArray(tree) && tree[0]?.children ? tree[0].children : tree);
+    } catch (e) {
+      console.error('获取分组列表失败:', e);
+      groupOptions.value = [];
+    }
+  }
+
+  function onIssueGroupChange(deptId: number) {
+    const d = groupOptions.value.find((x) => x.id === deptId);
+    issueForm.value.groupDeptName = d?.deptName ?? '';
+  }
+
+  function resetIssueForm() {
+    issueForm.value = {
+      groupDeptId: undefined,
+      groupDeptName: '',
+      planStartDate: '',
+      planEndTime: '',
+    };
+    currentIssueRow.value = null;
+  }
+
+  function handleSend(row: { id: number; dangerId?: number; associationOneThree?: string }) {
+    currentIssueRow.value = { id: row.id, dangerId: row.dangerId, associationOneThree: row.associationOneThree };
+    issueForm.value = { groupDeptId: undefined, groupDeptName: '', planStartDate: '', planEndTime: '' };
+    showIssueDialog.value = true;
+  }
+
+  async function handleIssueSubmit() {
+    await issueFormRef.value?.validate?.().catch(() => {});
+    if (!currentIssueRow.value) return;
+    try {
+      await issueDrawLessons({
+        associationOtId: currentIssueRow.value.id,
+        dangerId: currentIssueRow.value.dangerId,
+        associationOtObligationDeptId: issueForm.value.groupDeptId,
+        associationOtObligationDeptName: issueForm.value.groupDeptName,
+        associationOneThree: currentIssueRow.value.associationOneThree,
+        associationOtTimeLimit: issueForm.value.planEndTime || undefined,
+        planStartDate: issueForm.value.planStartDate || undefined,
+        planEndTime: issueForm.value.planEndTime || undefined,
+      });
+      ElMessage.success('下发成功');
+      showIssueDialog.value = false;
+      getTableData();
+    } catch (e) {
+      console.error('下发失败:', e);
+      ElMessage.error('下发失败,请重试');
+    }
+  }
 
   /** 作废:变更为已作废状态 */
   const handleCancel = async (id: number) => {
@@ -313,6 +445,7 @@
   };
 
   onMounted(() => {
+    loadGroupOptions();
     getTableData();
   });
 </script>

+ 76 - 125
src/views/production-safety/safetyAssessment/pointDeduction/components/PointDeductionDetail.vue

@@ -13,47 +13,22 @@
         </el-radio-group>
       </template>
       <template #dedResonList>
-        <div class="deduction-reason-table">
-          <el-table :data="ruleFormData.dedResonList" border style="width: 100%">
-            <el-table-column prop="serialNum" label="编号" width="100" align="center">
-              <template #default="{ row }">
-                <span>{{ row.serialNum }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column prop="deductionReason" label="扣分项" min-width="200">
-              <template #default="{ row }">
-                <el-input
-                  v-if="!isViewMode"
-                  v-model="row.deductionReason"
-                  placeholder="请输入扣分项"
-                  size="small"
-                />
-                <span v-else>{{ row.deductionReason }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column prop="scoreVal" label="分值" width="120" align="center">
-              <template #default="{ row }">
-                <el-input-number
-                  v-if="!isViewMode"
-                  v-model="row.scoreVal"
-                  :min="1"
-                  :max="99999"
-                  :precision="0"
-                  size="small"
-                  style="width: 100%"
-                  @change="calculateTotalScore"
-                />
-                <span v-else>{{ row.scoreVal }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column label="操作" width="180" align="center" v-if="!isViewMode">
-              <template #default="{ $index }">
-                <el-button type="primary" link size="small" @click="handleAddReasonAfter($index)">新增</el-button>
-                <el-button type="danger" link size="small" @click="handleDeleteReason($index)">删除</el-button>
-              </template>
-            </el-table-column>
-          </el-table>
-        </div>
+        <el-select
+          v-if="!isViewMode"
+          v-model="deductionReasonValue"
+          placeholder="请选择扣分原因"
+          filterable
+          clearable
+          style="width: 100%"
+        >
+          <el-option
+            v-for="item in hiddenDangerOptions"
+            :key="item.id"
+            :label="item.dangerProblem"
+            :value="item.dangerProblem"
+          />
+        </el-select>
+        <span v-else>{{ deductionReasonValue }}</span>
       </template>
     </BasicForm>
   </main>
@@ -81,6 +56,8 @@
     updateMonthlyDeduction,
     queryDeductionDetail,
   } from '@/api/pointDeduction';
+  import { queryHiddenDangerPage } from '@/api/hiddenDanger';
+  import type { HiddenDangerItem } from '@/api/hiddenDanger';
 
   const router = useRouter();
   const route = useRoute();
@@ -115,69 +92,57 @@
 
   const basicFormRef = ref<InstanceType<typeof BasicForm>>();
 
-  // 自动排序编号
-  const updateSerialNumbers = () => {
-    ruleFormData.dedResonList.forEach((item, index) => {
-      item.serialNum = index + 1;
-    });
-  };
-
-  // 自动计算总扣分值
-  const calculateTotalScore = () => {
-    const totalScore = ruleFormData.dedResonList.reduce((sum, item) => sum + (item.scoreVal || 0), 0);
-    ruleFormData.deductionValue = totalScore;
-  };
+  // 扣分原因下拉选项(来自隐患台账 productionHiddenDanger/queryPage)
+  const hiddenDangerOptions = ref<HiddenDangerItem[]>([]);
 
-  // 扣分原因列表操作 - 在指定位置后新增
-  const handleAddReasonAfter = (index: number) => {
-    ruleFormData.dedResonList.splice(index + 1, 0, {
-      id: 0,
-      pmdId: currentId.value || 0,
-      serialNum: 0, // 会自动排序
-      deductionReason: '',
-      scoreVal: 1, // 默认值为1
-    });
-    updateSerialNumbers();
-    calculateTotalScore();
+  const loadHiddenDangerOptions = async () => {
+    try {
+      const res = await queryHiddenDangerPage({
+        pageNumber: 1,
+        pageSize: 500,
+        queryParam: {},
+      });
+      if (res?.records?.length) {
+        hiddenDangerOptions.value = res.records;
+      }
+    } catch (e) {
+      console.error('获取扣分原因选项失败:', e);
+    }
   };
 
-  // 删除扣分原因
-  const handleDeleteReason = (index: number) => {
-    ruleFormData.dedResonList.splice(index, 1);
-    updateSerialNumbers();
-    calculateTotalScore();
-  };
+  // 扣分原因下拉框与 dedResonList 双向绑定(单条)
+  const deductionReasonValue = computed({
+    get: () => {
+      const list = ruleFormData.dedResonList;
+      return (list && list[0] && list[0].deductionReason) || '';
+    },
+    set: (val) => {
+      if (val) {
+        const list = ruleFormData.dedResonList;
+        const prev = list && list[0];
+        ruleFormData.dedResonList = [
+          {
+            id: (prev && prev.id) || 0,
+            pmdId: (prev && prev.pmdId) || currentId.value || 0,
+            serialNum: 1,
+            deductionReason: val,
+            scoreVal: ruleFormData.deductionValue || 1,
+          },
+        ];
+      } else {
+        ruleFormData.dedResonList = [];
+      }
+    },
+  });
 
   const handleValidate = async () => {
     if (!basicFormRef.value) return;
     const res = await basicFormRef.value.validateForm();
     if (!res) return false;
-    
-    // 验证扣分原因列表
-    if (!ruleFormData.dedResonList || ruleFormData.dedResonList.length === 0) {
-      ElMessage.error('请至少添加一条扣分原因');
+    if (!deductionReasonValue.value || !deductionReasonValue.value.trim()) {
+      ElMessage.error('请选择扣分原因');
       return false;
     }
-    
-    for (let i = 0; i < ruleFormData.dedResonList.length; i++) {
-      const item = ruleFormData.dedResonList[i];
-      if (!item.deductionReason || item.deductionReason.trim() === '') {
-        ElMessage.error(`第${i + 1}条扣分项的扣分项不能为空`);
-        return false;
-      }
-      if (!item.scoreVal || item.scoreVal <= 0) {
-        ElMessage.error(`第${i + 1}条扣分项的分值必须大于0`);
-        return false;
-      }
-      if (item.scoreVal > 99999) {
-        ElMessage.error(`第${i + 1}条扣分项的分值不能大于99999`);
-        return false;
-      }
-    }
-    
-    // 计算总扣分值
-    calculateTotalScore();
-    
     return true;
   };
 
@@ -191,15 +156,7 @@
         ruleFormData.deductionValue = res.deductionScore;
         ruleFormData.departmentName = res.departmentName;
         ruleFormData.status = res.status ? 'ENABLE' : 'DISABLE';
-        ruleFormData.dedResonList = res.dedResonList || [];
-        // 确保编号排序和分值默认值
-        ruleFormData.dedResonList.forEach((item) => {
-          if (!item.scoreVal || item.scoreVal === 0) {
-            item.scoreVal = 1;
-          }
-        });
-        updateSerialNumbers();
-        calculateTotalScore();
+        ruleFormData.dedResonList = res.dedResonList?.length ? [res.dedResonList[0]] : [];
       }
       cloneRuleFormData();
     } catch (e) {
@@ -211,6 +168,19 @@
   const handleSubmit = async () => {
     const res = await handleValidate();
     if (!res) return;
+    // 提交前同步扣分原因到 dedResonList(单条,分值为表单扣分值)
+    const dedResonList = deductionReasonValue.value
+      ? [
+          {
+            deductionReason: deductionReasonValue.value,
+            scoreVal: ruleFormData.deductionValue || 1,
+            serialNum: 1,
+            ...(ruleFormData.dedResonList?.[0]?.id
+              ? { id: ruleFormData.dedResonList[0].id, pmdId: ruleFormData.dedResonList[0].pmdId }
+              : {}),
+          },
+        ]
+      : [];
     try {
       const basePayload = {
         deductionTitle: ruleFormData.deductionTitle,
@@ -218,9 +188,8 @@
         deductionScore: ruleFormData.deductionValue,
         departmentName: ruleFormData.departmentName,
         status: ruleFormData.status === 'ENABLE',
-        dedResonList: ruleFormData.dedResonList.map((item) => {
-          // 如果是新增的数据(id为0),不传递id和pmdId;如果是后端已有的数据,保留所有字段
-          if (item.id === 0) {
+        dedResonList: dedResonList.map((item) => {
+          if (!item.id) {
             const { id, pmdId, ...rest } = item;
             return rest;
           }
@@ -249,21 +218,7 @@
   onMounted(() => {
     cloneRuleFormData();
     beforeRouteLeave();
-    // 创建模式下,初始化一条空的扣分原因记录
-    if (isCreateMode.value && (!ruleFormData.dedResonList || ruleFormData.dedResonList.length === 0)) {
-      ruleFormData.dedResonList = [
-        {
-          id: 0,
-          pmdId: 0,
-          serialNum: 1,
-          deductionReason: '',
-          scoreVal: 1, // 默认值为1
-        },
-      ];
-    } else {
-      // 确保编号排序
-      updateSerialNumbers();
-    }
+    loadHiddenDangerOptions();
     if (isEditMode.value || isViewMode.value) {
       getDetail();
     }
@@ -272,9 +227,5 @@
 
 <style scoped lang="scss">
   @use '@/styles/page-details-layout.scss' as *;
-
-  .deduction-reason-table {
-    width: 100%;
-  }
 </style>