Kaynağa Gözat

feat: 灾后评估及重建

bxy 5 ay önce
ebeveyn
işleme
06deaf9761

+ 12 - 0
src/api/disaster-control/index.ts

@@ -17,6 +17,7 @@ import type {
   DisposalRectificationFormData,
   DisasterLossDetailQuery,
   DisasterLossDetailResponse,
+  UpdateDisasterHandleTaskQuery,
 } from '@/types/disaster-control';
 import type { QueryPageResponse, QueryPageRequest } from '@/types/basic-query';
 /**
@@ -49,6 +50,17 @@ export const createDisasterHandleTask = (query: DisposalManagementCreateQuery) =
   });
 };
 
+/**
+ * 更新灾害处置任务
+ */
+export const updateDisasterHandleTask = (query: UpdateDisasterHandleTaskQuery) => {
+  return http.request({
+    url: '/disasterHandle/updateDisasterHandleTask',
+    method: 'put',
+    data: query,
+  });
+};
+
 /**
  * 创建灾害处置上报任务
  */

+ 6 - 3
src/components/UploadFiles/UploadFiles.vue

@@ -2,7 +2,7 @@
   <div class="upload-wrapper">
     <!-- 上传按钮 -->
     <div class="upload-button-container">
-      <label for="fileInput" class="upload-button" :class="{ disabled: isUploadDisabled }">
+      <label :for="inputId" class="upload-button" :class="{ disabled: isUploadDisabled }">
         <el-icon><UploadFilled /></el-icon>
         <span>{{ label }}</span>
       </label>
@@ -10,7 +10,8 @@
     </div>
     <input
       type="file"
-      id="fileInput"
+      :id="inputId"
+      class="upload-input"
       multiple
       accept=".pdf,.docx,.xlsx,.pptx"
       @change="handleFileSelect"
@@ -59,6 +60,8 @@
     maxCount?: number;
   }>();
 
+  const inputId = `upload-file-${Math.random().toString(36).slice(2, 10)}`;
+
   // 常量定义
   const MAX_SIZE = computed(() => (props.maxSize || 5) * 1024 * 1024); // 默认5MB
   const MAX_COUNT = computed(() => props.maxCount || 9); // 默认最多9个文件
@@ -299,7 +302,7 @@
     }
   }
 
-  #fileInput {
+  .upload-input {
     display: none;
   }
 

+ 40 - 0
src/router/routers/disaster.ts

@@ -447,6 +447,46 @@ const disasterPreventionRoute = {
           path: 'disposal-rectification-item-detail/:id',
           redirect: '',
         },
+        {
+          component: '/disaster/disaster-control/PagePostDisaster',
+          id: 1046,
+          meta: {
+            activeMenu: '',
+            alwaysShow: false,
+            frameSrc: '',
+            hidden: false,
+            icon: '',
+            isFrame: 0,
+            isRoot: false,
+            noCache: false,
+            query: '',
+            title: '评估及重建',
+          },
+          name: 'disaster-control-post-disaster',
+          parentId: 1027,
+          path: 'post-disaster',
+          redirect: '',
+        },
+        {
+          component: '/disaster/disaster-control/PagePostDisasterItem',
+          id: 1047,
+          meta: {
+            activeMenu: '/disaster-prevention/disaster-control/post-disaster',
+            alwaysShow: false,
+            frameSrc: '',
+            hidden: false,
+            icon: '',
+            isFrame: 0,
+            isRoot: false,
+            noCache: false,
+            query: '',
+            title: '更新灾后评估及重建材料',
+          },
+          name: 'disaster-control-post-disaster-item',
+          parentId: 1027,
+          path: 'post-disaster-item',
+          redirect: '',
+        },
       ],
       component: '',
       id: 1032,

+ 34 - 0
src/store/modules/usePostDisasterMaterial.ts

@@ -0,0 +1,34 @@
+/**
+ * @description: 灾后评估及重建材料编辑和上传
+ * @return {
+ *  id: 任务id
+ *  type: 编辑或上传
+ *  disasterAssessMaterials: 灾后评估材料
+ *  disasterReconstructMaterials: 灾后重建材料
+ * }
+ */
+
+import { ref } from 'vue';
+import { defineStore } from 'pinia';
+
+export const usePostDisasterMaterial = defineStore('postDisasterMaterial', () => {
+  const id = ref<number>();
+  const type = ref<'edit' | 'upload'>('upload');
+  const disasterAssessMaterials = ref<string>('');
+  const disasterReconstructMaterials = ref<string>('');
+
+  const initData = () => {
+    id.value = undefined;
+    type.value = 'upload';
+    disasterAssessMaterials.value = '';
+    disasterReconstructMaterials.value = '';
+  };
+
+  return {
+    id,
+    type,
+    disasterAssessMaterials,
+    disasterReconstructMaterials,
+    initData,
+  };
+});

+ 13 - 0
src/types/disaster-control/index.ts

@@ -41,9 +41,22 @@ export interface DisposalManagementListResponse {
   id: number;
   handleTaskId: number;
   taskName: string;
+  createdAt: string;
   updatedAt: string;
   dueCompleteTime: string;
   deptId: number;
+  disasterAssessMaterials: string; // 灾后评估材料
+  disasterReconstructMaterials: string; // 灾后重建材料
+}
+
+export interface UpdateDisasterHandleTaskQuery {
+  id?: number;
+  taskName?: string;
+  disasterAssessMaterials?: string; // 灾后评估材料
+  disasterReconstructMaterials?: string; // 灾后重建材料
+  createdAt?: string;
+  updatedAt?: string;
+  isDeleted?: number;
 }
 
 export interface DisposalManagementCollapseListResponse<T> extends DisposalManagementListResponse {

+ 6 - 1
src/views/disaster/components/PreviewOnline.vue

@@ -66,7 +66,12 @@
     height: 100% !important;
     overflow-y: auto !important;
   }
+  :deep(.vue-office-pptx) {
+    height: 100vh !important;
+    overflow-y: auto !important;
+  }
   :deep(.pptx-preview-wrapper) {
-    height: 1080px !important;
+    height: 100% !important;
+    overflow-y: auto !important;
   }
 </style>

+ 166 - 0
src/views/disaster/disaster-control/PagePostDisaster.vue

@@ -0,0 +1,166 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <span class="breadcrumb-title">灾后评估及重建</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <BasicTable
+        :tableConfig="tableConfig"
+        :tableData="tableData"
+        @update:page-size="handleSizeChange"
+        @update:page-number="handleCurrentChange"
+      >
+        <template #disasterAssessMaterials="scope">
+          <div
+            class="file-container--div"
+            v-for="item in unformatAttachment(scope.row.disasterAssessMaterials)"
+            :key="item.fileId"
+          >
+            <img
+              class="file-container--div__icon"
+              @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
+              :src="FILE_TYPE_ICON[item.fileType]"
+            />
+            <span
+              class="file-container--div__name"
+              @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
+              >{{ item.fileName }}</span
+            >
+            <img
+              class="file-container--div__download"
+              :src="DownloadIcon"
+              @click="downloadFile(item.fileUrl, item.fileName)"
+            />
+          </div>
+        </template>
+        <template #disasterReconstructMaterials="scope">
+          <div
+            class="file-container--div"
+            v-for="item in unformatAttachment(scope.row.disasterReconstructMaterials)"
+            :key="item.fileId"
+          >
+            <img
+              class="file-container--div__icon"
+              @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
+              :src="FILE_TYPE_ICON[item.fileType]"
+            />
+            <span
+              class="file-container--div__name"
+              @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
+              >{{ item.fileName }}</span
+            >
+            <img
+              class="file-container--div__download"
+              :src="DownloadIcon"
+              @click="downloadFile(item.fileUrl, item.fileName)"
+            />
+          </div>
+        </template>
+        <template #action="scope">
+          <div class="action-container--div" style="justify-content: left">
+            <ActionButton
+              v-if="scope.row.disasterAssessMaterials || scope.row.disasterReconstructMaterials"
+              text="编辑"
+              @click="handleEditMaterials(scope.row)"
+            />
+            <ActionButton v-else text="上传" @click="handleUploadMaterials(scope.row)" />
+          </div>
+        </template>
+      </BasicTable>
+      <PreviewOnline ref="previewOnlineRef" />
+    </main>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import { useRouter } from 'vue-router';
+  import BasicTable from '@/components/BasicTable.vue';
+  import ActionButton from '@/components/ActionButton.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import { POST_DISASTER_TABLE_OPTIONS, POST_DISASTER_TABLE_COLUMNS } from './src/config/table';
+  import { getDisasterControlCollapseData } from '@/api/disaster-control';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import type { DisposalManagementListQuery, DisposalManagementListResponse } from '@/types/disaster-control';
+  import DownloadIcon from '@/views/disaster/disaster-control/src/svg/download.svg';
+  import { downloadFile } from '@/views/disaster/utils';
+  import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
+  import { FILE_TYPE_ICON } from '@/components/UploadFiles/constants';
+  import { unformatAttachment } from '@/components/UploadFiles/utils';
+  import { usePostDisasterMaterial } from '@/store/modules/usePostDisasterMaterial';
+  import { storeToRefs } from 'pinia';
+
+  const router = useRouter();
+
+  const postDisasterMaterialStore = usePostDisasterMaterial();
+  const { id, type, disasterAssessMaterials, disasterReconstructMaterials } = storeToRefs(postDisasterMaterialStore);
+
+  const { tableConfig, pagination } = useTableConfig(POST_DISASTER_TABLE_COLUMNS, POST_DISASTER_TABLE_OPTIONS);
+
+  const tableData = ref<DisposalManagementListResponse[]>([]);
+
+  const tableQuery = ref<QueryPageRequest<DisposalManagementListQuery>>({
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {},
+  });
+
+  const handleSizeChange = (value: number) => {
+    pagination.pageSize = value;
+    tableQuery.value.pageSize = value;
+    getTableData();
+  };
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    tableQuery.value.pageNumber = value;
+    getTableData();
+  };
+
+  async function getTableData() {
+    tableConfig.loading = true;
+    const res = await getDisasterControlCollapseData(tableQuery.value);
+    tableData.value = res.records;
+    pagination.total = res.totalRow;
+    tableConfig.loading = false;
+  }
+
+  // 预览
+  const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
+  const previewOnline = (url: string | undefined, type: keyof typeof FILE_TYPE_ICON) => {
+    if (url) {
+      previewOnlineRef.value?.open(url, type);
+    }
+  };
+
+  // 上传
+  const handleUploadMaterials = (row: DisposalManagementListResponse) => {
+    id.value = row.id;
+    type.value = 'upload';
+    disasterAssessMaterials.value = row.disasterAssessMaterials;
+    disasterReconstructMaterials.value = row.disasterReconstructMaterials;
+    router.push({
+      name: 'disaster-control-post-disaster-item',
+    });
+  };
+
+  // 编辑
+  const handleEditMaterials = (row: DisposalManagementListResponse) => {
+    id.value = row.id;
+    type.value = 'edit';
+    disasterAssessMaterials.value = row.disasterAssessMaterials;
+    disasterReconstructMaterials.value = row.disasterReconstructMaterials;
+    router.push({
+      name: 'disaster-control-post-disaster-item',
+    });
+  };
+
+  onMounted(() => {
+    getTableData();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+  @use '@/styles/basic-table-file.scss' as *;
+</style>

+ 128 - 0
src/views/disaster/disaster-control/PagePostDisasterItem.vue

@@ -0,0 +1,128 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ type === 'edit' ? '编辑' : '上传' }}灾后评估材料及重建方案</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="formRules" :formConfig="ruleFormConfig">
+        <template #disasterAssessMaterials>
+          <UploadFiles
+            label="上传文件"
+            ref="uploadFilesRef"
+            :fileList="ruleFormData.disasterAssessMaterials"
+            @upload-success="handleUploadSuccessAssessMaterials"
+          />
+        </template>
+        <template #disasterReconstructMaterials>
+          <UploadFiles
+            label="上传文件"
+            ref="uploadFilesRef"
+            :fileList="ruleFormData.disasterReconstructMaterials"
+            @upload-success="handleUploadSuccessReconstructMaterials"
+          />
+        </template>
+      </BasicForm>
+    </main>
+    <footer class="safety-platform-container__footer">
+      <el-button @click="router.back()">取消</el-button>
+      <el-button type="primary" @click="handleSubmit">提交</el-button>
+    </footer>
+    <UploadLoading :form-loading="formLoading" v-if="formLoading" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import { storeToRefs } from 'pinia';
+  import { useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import { usePostDisasterMaterial } from '@/store/modules/usePostDisasterMaterial';
+  import BasicForm from '@/components/BasicForm.vue';
+  import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
+  import UploadLoading from '@/components/UploadLoading.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { POST_DISASTER_MATERIAL_FORM_CONFIG, POST_DISASTER_MATERIAL_FORM_DATA } from './src/config/form';
+  import { FileItem } from '@/components/UploadFiles/types';
+  import { unformatAttachment, formatAttachmentList } from '@/components/UploadFiles/utils';
+  import type { UpdateDisasterHandleTaskQuery } from '@/types/disaster-control';
+  import { updateDisasterHandleTask } from '@/api/disaster-control';
+
+  interface PostDisasterMaterialFormData {
+    disasterAssessMaterials: FileItem[];
+    disasterReconstructMaterials: FileItem[];
+  }
+
+  const { ruleFormConfig, ruleFormData, formRules, cloneRuleFormData, beforeRouteLeave } =
+    useFormConfigHook<PostDisasterMaterialFormData>(
+      POST_DISASTER_MATERIAL_FORM_CONFIG,
+      POST_DISASTER_MATERIAL_FORM_DATA,
+    );
+
+  const postDisasterMaterialStore = usePostDisasterMaterial();
+  const { id, type, disasterAssessMaterials, disasterReconstructMaterials } = storeToRefs(postDisasterMaterialStore);
+
+  const router = useRouter();
+  const formLoading = ref(false);
+
+  const getDetail = async () => {
+    ruleFormData.disasterAssessMaterials = unformatAttachment(disasterAssessMaterials.value) || [];
+    ruleFormData.disasterReconstructMaterials = unformatAttachment(disasterReconstructMaterials.value) || [];
+    cloneRuleFormData();
+  };
+
+  const getFormData = async () => {
+    cloneRuleFormData();
+    const res: UpdateDisasterHandleTaskQuery = {
+      id: id.value,
+      disasterAssessMaterials: JSON.stringify(await formatAttachmentList(ruleFormData.disasterAssessMaterials)),
+      disasterReconstructMaterials: JSON.stringify(
+        await formatAttachmentList(ruleFormData.disasterReconstructMaterials),
+      ),
+    };
+    return res;
+  };
+
+  const handleUploadSuccessAssessMaterials = (fileList: FileItem[]) => {
+    ruleFormData.disasterAssessMaterials = fileList;
+  };
+
+  const handleUploadSuccessReconstructMaterials = (fileList: FileItem[]) => {
+    ruleFormData.disasterReconstructMaterials = fileList;
+  };
+
+  const handleSubmit = async () => {
+    const hasAssessMaterials = ruleFormData.disasterAssessMaterials?.length;
+    const hasReconstructMaterials = ruleFormData.disasterReconstructMaterials?.length;
+    if (!hasAssessMaterials && !hasReconstructMaterials) {
+      ElMessage.warning('请上传相关文件');
+      return;
+    }
+    try {
+      formLoading.value = true;
+      const params = await getFormData();
+      await updateDisasterHandleTask(params);
+      ElMessage.success(type.value === 'edit' ? '编辑成功' : '上传成功');
+      router.back();
+    } catch (e) {
+      console.log(e);
+    } finally {
+      formLoading.value = false;
+    }
+  };
+
+  onMounted(() => {
+    beforeRouteLeave();
+    getDetail();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+</style>

+ 19 - 0
src/views/disaster/disaster-control/src/config/form.ts

@@ -297,6 +297,20 @@ export const DISPOSAL_RECTIFICATION_FORM_CONFIG: FormConfig[] = [
   },
 ];
 
+// 灾后评估及重建材料表单信息
+export const POST_DISASTER_MATERIAL_FORM_CONFIG: FormConfig[] = [
+  {
+    label: '灾后评估材料:',
+    prop: 'disasterAssessMaterials',
+    slot: 'disasterAssessMaterials',
+  },
+  {
+    label: '灾后重建方案:',
+    prop: 'disasterReconstructMaterials',
+    slot: 'disasterReconstructMaterials',
+  },
+];
+
 // 通用表单数据
 const BASIC_FROM_DATA = {};
 const DISPOSAL_MANAGEMENT_BASIC_FROM_DATA = {
@@ -349,6 +363,11 @@ export const DISPOSAL_RECTIFICATION_FORM_DATA = {
   uploadFiles: [],
 };
 
+export const POST_DISASTER_MATERIAL_FORM_DATA = {
+  disasterAssessMaterials: [],
+  disasterReconstructMaterials: [],
+};
+
 // 通用表单规则
 const BASIC_FROM_RULES = {};
 

+ 41 - 0
src/views/disaster/disaster-control/src/config/table.ts

@@ -23,6 +23,12 @@ export const RECTIFICATION_INFO_TABLE_OPTIONS = {
   ...TABLE_OPTIONS,
 };
 
+// 灾后评估及重建表格配置
+export const POST_DISASTER_TABLE_OPTIONS = {
+  ...TABLE_OPTIONS,
+  maxHeight: 'calc(70vh - 30px)',
+};
+
 // 基础表格列配置项
 const BASIC_TABLE_COLUMNS = {
   INDEX: {
@@ -190,3 +196,38 @@ export const RECTIFICATION_INFO_TABLE_COLUMNS: TableColumnProps[] = [
     fixed: 'right',
   },
 ];
+
+// 灾后评估及重建表格列配置
+export const POST_DISASTER_TABLE_COLUMNS: TableColumnProps[] = [
+  BASIC_TABLE_COLUMNS.INDEX,
+  {
+    label: '灾害处置任务名称',
+    prop: 'taskName',
+    minWidth: '240px',
+  },
+  {
+    label: '创建时间',
+    prop: 'createdAt',
+    width: '200px',
+  },
+  {
+    label: '灾后评估材料',
+    prop: 'disasterAssessMaterials',
+    slot: 'disasterAssessMaterials',
+    minWidth: '300px',
+  },
+  {
+    label: '灾后重建方案',
+    prop: 'disasterReconstructMaterials',
+    slot: 'disasterReconstructMaterials',
+    minWidth: '300px',
+  },
+  {
+    label: '操作',
+    prop: 'action',
+    slot: 'action',
+    align: 'center',
+    width: '80px',
+    fixed: 'right',
+  },
+];