chauncey 11 месяцев назад
Родитель
Сommit
19460328d0

+ 25 - 2
src/api/disaster-precaution/index.ts

@@ -7,7 +7,8 @@ import type {
   TaskExecutionDetailResponse,
   SaveTaskDetailRequest,
 } from '@/types/disaster-precaution';
-import type { UserInfo } from '@/types/push-object';
+import type { SpanTableData } from '@/views/disaster/disaster-precaution/src/type';
+import type { PersonGroupItem } from '@/types/person-group/type';
 import type { QueryPageRequest, QueryPageResponse } from '@/types/disaster';
 import type { TaskManagementRuleForm } from '@/views/disaster/disaster-precaution/src/type';
 /**
@@ -100,7 +101,7 @@ export function getTaskExecutionList(query: QueryPageRequest<TaskExecutionListQu
  * 查询检查人员
  */
 export const getTaskInspectorList = (preventInspectTaskId: number) => {
-  return http.request<UserInfo[]>({
+  return http.request<PersonGroupItem[]>({
     url: '/preventInspectTask/queryTaskInspector',
     method: 'get',
     params: {
@@ -144,6 +145,17 @@ export const withdrawTaskInspect = (preventInspectTaskId: number) => {
   });
 };
 
+/**
+ * 撤回审批任务
+ */
+export const withdrawTaskApproval = (preventInspectTaskId: number) => {
+  return http.request({
+    url: '/preventInspectTask/updateTaskReviewState',
+    method: 'get',
+    params: { preventInspectTaskId },
+  });
+};
+
 /**
  * 执行任务审批
  */
@@ -154,3 +166,14 @@ export const saveTaskApproval = (data: SaveTaskDetailRequest) => {
     data,
   });
 };
+
+/**
+ * 查询模板详情
+ */
+export const getTaskTemplateDetail = (templateId: number) => {
+  return http.request<SpanTableData[]>({
+    url: '/preventInspectTask/queryInspectTemplateDetail',
+    method: 'get',
+    params: { templateId },
+  });
+};

+ 54 - 14
src/views/disaster/disaster-precaution/PageTaskExecution.vue

@@ -59,7 +59,7 @@
                 :popconfirm="{
                   title: '确定撤回吗?',
                 }"
-                @confirm="handleWithdrawTask(scope.row.id)"
+                @confirm="handleWithdrawTask(scope.row.id, scope.row.taskState)"
               />
             </div>
             <!-- 完成任务操作入口 -->
@@ -67,11 +67,14 @@
               <ActionButton text="查看" @click="handleCheckItem(scope.row.id, 'view')" />
               <!-- 仅审批人员可以看到 -->
               <ActionButton
-                v-if="scope.row.userTypeList.includes(USER_TYPE.APPROVER)"
+                v-if="
+                  scope.row.userTypeList.includes(USER_TYPE.APPROVER) && !isOverdue24Hours(scope.row.dueCompleteTime)
+                "
                 text="撤回"
                 :popconfirm="{
                   title: '确定撤回吗?',
                 }"
+                @confirm="handleWithdrawTask(scope.row.id, scope.row.taskState)"
               />
             </div>
           </template>
@@ -82,12 +85,17 @@
 </template>
 
 <script setup lang="ts">
-  import { ref, onMounted, reactive } from 'vue';
+  import { ref, onMounted, reactive, onUnmounted } from 'vue';
   import BasicTable from '@/components/BasicTable.vue';
   import ActionButton from '@/components/ActionButton.vue';
   import Search from '@/views/disaster/components/Search.vue';
   import useTableConfig from '@/hooks/useTableConfigHook';
-  import { getTaskExecutionList, getTaskInspectorList, withdrawTaskInspect } from '@/api/disaster-precaution';
+  import {
+    getTaskExecutionList,
+    getTaskInspectorList,
+    withdrawTaskInspect,
+    withdrawTaskApproval,
+  } from '@/api/disaster-precaution';
   import type { TaskExecutionListResponse } from '@/types/disaster-precaution';
   import OverdueIcon from '@/assets/svg/overdue.svg';
   import { TASK_STAGE } from './src/constants/task-execution';
@@ -109,6 +117,10 @@
 
   const tableData = ref<TaskExecutionListResponse[]>([]);
   const { tableConfig, pagination } = useTableConfig(TASK_EXECUTION_TABLE_COLUMNS, TABLE_OPTIONS_EXECUTION);
+  // 添加刷新时间变量,用于触发视图更新
+  const refreshTime = ref(Date.now());
+  let refreshTimer: number | null = null;
+
   let taskManagementListQuery: QueryPageRequest<TaskExecutionListQuery> = {
     pageNumber: pagination.pageNumber,
     pageSize: pagination.pageSize,
@@ -138,14 +150,15 @@
   const currentTaskInspectorList = ref<UserInfo[]>([]);
   const handleAddCheckUser = async (row: TaskExecutionListResponse) => {
     currentTaskId.value = row.id;
-    const res = await getTaskInspectorList(currentTaskId.value);
-    currentTaskInspectorList.value = res.map((item) => {
-      return {
-        ...item,
-        isSelected: true,
-      };
-    });
-    userInfo.value = true;
+    console.log(row);
+    // const res = await getTaskInspectorList(currentTaskId.value);
+    // currentTaskInspectorList.value = res.map((item) => {
+    //   return {
+    //     ...item,
+    //     isSelected: true,
+    //   };
+    // });
+    // userInfo.value = true;
   };
   const defaultRouterName = 'disaster-precaution-task-execution-detail';
   const handleCheckItem = (id: number, operationType: 'check' | 'approve' | 'view') => {
@@ -159,8 +172,12 @@
       },
     });
   };
-  const handleWithdrawTask = async (id: number) => {
-    await withdrawTaskInspect(id);
+  const handleWithdrawTask = async (id: number, state: number) => {
+    if (state === TASK_STAGE.PENDING_APPROVAL) {
+      await withdrawTaskInspect(id);
+    } else if (state === TASK_STAGE.COMPLETED) {
+      await withdrawTaskApproval(id);
+    }
     ElMessage.success('撤回成功');
     getTableData();
   };
@@ -171,8 +188,31 @@
     pagination.total = res.totalRow;
     tableConfig.loading = false;
   };
+  // 判断是否超过应完成时间24小时
+  const isOverdue24Hours = (dueCompleteTime: string) => {
+    if (!dueCompleteTime) return false;
+
+    const dueTime = new Date(dueCompleteTime).getTime();
+    const currentTime = refreshTime.value;
+
+    // 计算24小时的毫秒数: 24 * 60 * 60 * 1000 = 86400000
+    return currentTime > dueTime + 86400000;
+  };
   onMounted(() => {
     getTableData();
+
+    // 设置定时器,每分钟更新一次时间
+    refreshTimer = window.setInterval(() => {
+      refreshTime.value = Date.now();
+    }, 1000);
+  });
+
+  // 组件销毁时清除定时器
+  onUnmounted(() => {
+    if (refreshTimer !== null) {
+      clearInterval(refreshTimer);
+      refreshTimer = null;
+    }
   });
 </script>
 

+ 1 - 4
src/views/disaster/disaster-precaution/PageTaskTemplate.vue

@@ -31,15 +31,12 @@
       params: {
         id,
       },
-      query: {
-        operate: 'view',
-      },
     });
   };
 </script>
 
 <style lang="scss" scoped>
-    @use '../style/disaster.scss' as *;
+  @use '../style/disaster.scss' as *;
   .template-card-list {
     display: flex;
     gap: 32cpx;

+ 16 - 9
src/views/disaster/disaster-precaution/PageTaskTemplateDetail.vue

@@ -5,7 +5,13 @@
       <span class="disaster-precaution-container__title">{{ name }}</span>
     </header>
     <main class="disaster-precaution-container__main">
-      <TemplateTableMerge :operation-type="operationType" :main-table="data!" height="calc(70vh - 25px)" />
+      <TemplateTableMerge
+        :operation-type="'template'"
+        :main-table="templateDetail"
+        :opinion-data="{} as ContentItem"
+        :result-data="{} as ContentItem"
+        height="calc(70vh - 25px)"
+      />
     </main>
   </div>
 </template>
@@ -15,20 +21,21 @@
   import { useRoute, useRouter } from 'vue-router';
   import BackIcon from 'assets/svg/back.svg';
   import { TASK_TEMPLATE_LIST } from './src/constants/template-detail';
-  import TemplateTableMerge from './src/components/TemplateTableMerge Copy.vue';
+  import TemplateTableMerge from './src/components/TemplateTableMerge.vue';
+  import { getTaskTemplateDetail } from '@/api/disaster-precaution';
+  import type { SpanTableData } from '@/views/disaster/disaster-precaution/src/type';
+  import type { ContentItem } from '@/types/disaster-precaution';
 
   const route = useRoute();
   const router = useRouter();
-  const id = route.params.id;
-  const operationType = ref<'view' | 'edit'>('view');
+  const id = Number(route.params.id);
   const name = computed(() => {
     return TASK_TEMPLATE_LIST.find((item) => item.id === Number(id))?.name;
   });
-  const data = computed(() => {
-    return TASK_TEMPLATE_LIST.find((item) => item.id === Number(id))?.data;
-  });
-  onMounted(() => {
-    operationType.value = route.query.operate as unknown as 'view' | 'edit';
+  const templateDetail = ref<SpanTableData[]>([]);
+  onMounted(async () => {
+    const res = await getTaskTemplateDetail(id);
+    templateDetail.value = res;
   });
 </script>
 

+ 0 - 251
src/views/disaster/disaster-precaution/src/components/TemplateTableMerge Copy.vue

@@ -1,251 +0,0 @@
-<template>
-  <div class="el-table-page">
-    <el-table :data="TASK_TEMPLATE_HEADER" :show-header="false" border>
-      <el-table-column prop="title" align="center" width="60" />
-      <el-table-column prop="content" align="center" />
-    </el-table>
-    <el-table
-      v-bind="$attrs"
-      :data="mergedTableData"
-      :span-method="handleSpanMethod"
-      border
-      class="custom-table"
-      :row-class-name="tableRowClassName"
-    >
-      <el-table-column v-for="column in TASK_TEMPLATE_MAIN_COLUMNS" :key="column.prop" v-bind="column" align="center">
-        <template #default="scope">
-          <!-- 底部表格内容 - 第一列 -->
-          <template v-if="scope.row.rowType === 'footer' && column.prop === 'inspectItem'">
-            <div v-if="scope.row.footerType === 'inspectionAndSelftest'" class="inspection-result--div">
-              <span>检查结论及存在问题:</span>
-              <el-input
-                v-model="scope.row.inspectionResult"
-                maxlength="200"
-                style="width: 100%"
-                placeholder="必填,输入检查结论及存在问题,1-200字"
-                type="textarea"
-                :rows="2"
-                v-show="operationType === 'edit'"
-              />
-            </div>
-          </template>
-
-          <!-- 底部表格内容 - 第二列 -->
-          <template v-else-if="scope.row.rowType === 'footer' && column.prop === 'inspectStandard'">
-            <div v-if="scope.row.footerType === 'inspectionAndSelftest'" class="inspection-result--div">
-              <span>被检查(自查)单位意见:</span>
-              <el-input
-                v-model="scope.row.reviewResult"
-                maxlength="200"
-                style="width: 100%"
-                placeholder="必填,输入被检查(自查)单位意见,1-200字"
-                type="textarea"
-                :rows="2"
-                v-show="operationType === 'edit'"
-              />
-            </div>
-          </template>
-        </template>
-      </el-table-column>
-
-      <!-- 自检检查结果列 -->
-      <el-table-column label="自检检查结果" align="center" fixed="right">
-        <el-table-column prop="compliance" label="符合" align="center" width="150">
-          <template #default="scope">
-            <Compliance :radio-value="scope.row.compliance" :disabled="operationType === 'view'" />
-          </template>
-        </el-table-column>
-        <el-table-column prop="actualSituation" label="实际情况" align="center" width="410">
-          <template #default="scope">
-            <ActualSituation v-show="operationType === 'edit'" :actual-situation="scope.row.actualSituation" />
-          </template>
-        </el-table-column>
-      </el-table-column>
-    </el-table>
-  </div>
-</template>
-
-<script lang="ts" setup>
-  import { ref, computed, onMounted } from 'vue';
-  import type { SpanTableData, MainRow, MergedRow } from '../type';
-  import { formatSpanTableData } from '../utils/format-data';
-  import {
-    TASK_TEMPLATE_HEADER,
-    TASK_TEMPLATE_FOOTER,
-    TASK_TEMPLATE_MAIN_COLUMNS,
-    MERGE_FIELDS,
-  } from '../constants/template-detail';
-  import Compliance from './Compliance.vue';
-  import ActualSituation from './ActualSituation.vue';
-
-  const props = defineProps<{
-    operationType: 'view' | 'edit';
-    mainTable: SpanTableData[];
-  }>();
-
-  // 创建一个合并的数据源
-  const mergedTableData = computed<MergedRow[]>(() => {
-    const formattedMainData = formatSpanTableData(props.mainTable).map((item) => ({
-      ...item,
-      rowType: 'main' as const,
-    }));
-    // 为底部添加类型标记
-    const footerData = [
-      {
-        ...TASK_TEMPLATE_FOOTER[0],
-        rowType: 'footer' as const,
-        footerType: 'inspectionAndSelftest' as const, // 合并为一行
-      },
-    ];
-
-    return [...formattedMainData, ...footerData];
-  });
-
-  // 存储每列的合并状态
-  const spanMaps = ref<{ [key: string]: number[][] }>({});
-
-  // 根据列索引获取对应的属性名
-  const getPropNameByColumnIndex = (columnIndex: number) => {
-    return TASK_TEMPLATE_MAIN_COLUMNS[columnIndex]?.prop || '';
-  };
-
-  // 初始化合并信息
-  const initSpanMaps = () => {
-    const mainData = mergedTableData.value.filter((item): item is MainRow => item.rowType === 'main');
-    if (!mainData.length) return;
-
-    MERGE_FIELDS.forEach((field) => {
-      // 每个字段用于存储 [行的起始位置, 合并的行数] 的二维数组
-      const cellSpans: number[][] = [];
-      let currentValue = mainData[0][field];
-      let currentIndex = mainData[0].index;
-      let startIndex = 0;
-      let spanCount = 1;
-
-      for (let i = 1; i < mainData.length; i++) {
-        // 只有当当前值与前一个值相同,且index也相同时才合并
-        if (mainData[i][field] === currentValue && mainData[i].index === currentIndex) {
-          spanCount++;
-        } else {
-          cellSpans.push([startIndex, spanCount]);
-          currentValue = mainData[i][field];
-          currentIndex = mainData[i].index;
-          startIndex = i;
-          spanCount = 1;
-        }
-      }
-      // 处理最后一组数据
-      cellSpans.push([startIndex, spanCount]);
-      spanMaps.value[field] = cellSpans;
-    });
-  };
-
-  // 自定义合并方法
-  const handleSpanMethod = ({
-    row,
-    column,
-    rowIndex,
-    columnIndex,
-  }: {
-    row: MergedRow;
-    column: { property: string };
-    rowIndex: number;
-    columnIndex: number;
-  }) => {
-    // 处理底部行
-    if (row.rowType === 'footer') {
-      if (column.property === 'inspectItem') {
-        if (row.footerType === 'inspectionAndSelftest') {
-          // 第一个单元格占据左半部分
-          return { rowspan: 1, colspan: 3 };
-        }
-      } else if (column.property === 'inspectStandard' && row.footerType === 'inspectionAndSelftest') {
-        // 第二个单元格占据右半部分
-        return { rowspan: 1, colspan: 3 };
-      }
-      // 隐藏其余单元格
-      return { rowspan: 0, colspan: 0 };
-    }
-
-    // 处理主表数据的合并
-    if (row.rowType === 'main') {
-      const mainDataStartIndex = mergedTableData.value.findIndex((item) => item.rowType === 'main');
-      const adjustedRowIndex = rowIndex - mainDataStartIndex;
-
-      const propName = getPropNameByColumnIndex(columnIndex);
-
-      // 如果不在需要合并的字段列表中,直接返回不合并
-      if (!MERGE_FIELDS.includes(propName)) {
-        return { rowspan: 1, colspan: 1 };
-      }
-
-      // 获取当前字段的合并信息
-      const cellSpans = spanMaps.value[propName];
-      if (!cellSpans) return { rowspan: 1, colspan: 1 };
-
-      // 查找当前行是否是需要合并的起始行
-      for (const [startIndex, spanCount] of cellSpans) {
-        if (adjustedRowIndex === startIndex) {
-          // 是起始行,显示并合并
-          return { rowspan: spanCount, colspan: 1 };
-        } else if (adjustedRowIndex > startIndex && adjustedRowIndex < startIndex + spanCount) {
-          // 不是起始行,隐藏
-          return { rowspan: 0, colspan: 0 };
-        }
-      }
-    }
-
-    return { rowspan: 1, colspan: 1 };
-  };
-
-  // 新增:底部行粘性定位 class
-  const tableRowClassName = ({ row }: { row: MergedRow }) => {
-    if (row.rowType !== 'footer') return;
-    if (row.footerType === 'inspectionAndSelftest') {
-      return 'sticky-footer-inspection';
-    }
-    return;
-  };
-
-  // 监听数据变化,重新计算合并状态
-  onMounted(() => {
-    initSpanMaps();
-  });
-</script>
-
-<style lang="scss" scoped>
-  .el-table-page {
-    width: 100%;
-    height: 100%;
-  }
-
-  .inspection-result--div {
-    display: flex;
-    flex-direction: column;
-    align-items: flex-start;
-    gap: 5cpx;
-    padding: 5cpx;
-    height: 90cpx;
-  }
-
-  :deep(.custom-table .el-table__inner-wrapper:after) {
-    height: 0;
-  }
-
-  :deep(.custom-table th) {
-    background-color: $white-color !important;
-    color: #606266 !important;
-    font-weight: 500 !important;
-  }
-
-  .sticky-footer {
-    position: sticky;
-    z-index: 2;
-  }
-
-  :deep(.sticky-footer-inspection) {
-    @extend .sticky-footer;
-    bottom: 0;
-    box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
-  }
-</style>

+ 2 - 2
src/views/disaster/disaster-precaution/src/components/TemplateTableMerge.vue

@@ -27,7 +27,7 @@
                 :rows="3"
                 v-if="props.operationType === 'check'"
               />
-              <div class="inspection-result-approve" v-else="props.operationType === 'approve'">
+              <div class="inspection-result-approve" v-else-if="props.operationType === 'approve'">
                 <div class="execute-result">{{ resultData.executeResult }}</div>
                 <div class="execute-person">
                   <span>检查人员:{{ resultData.executeName }}</span>
@@ -100,7 +100,7 @@
   const { realname } = useUserInfoHook();
 
   const props = defineProps<{
-    operationType: 'view' | 'approve' | 'check';
+    operationType: 'view' | 'approve' | 'check' | 'template';
     mainTable: SpanTableData[];
     opinionData: ContentItem;
     resultData: ContentItem;