chauncey пре 11 месеци
родитељ
комит
f34370d4d6

Разлика између датотеке није приказан због своје велике величине
+ 52 - 0
mock/disaster-warning/info.ts


+ 21 - 1
mock/login/routers.ts

@@ -10,7 +10,7 @@ const list = [
           activeMenu: null,
           activeMenu: null,
           alwaysShow: false,
           alwaysShow: false,
           frameSrc: '',
           frameSrc: '',
-          hidden: true,
+          hidden: false,
           icon: 'OverviewIcon',
           icon: 'OverviewIcon',
           isFrame: 0,
           isFrame: 0,
           isRoot: false,
           isRoot: false,
@@ -85,6 +85,26 @@ const list = [
             path: 'defense-notice',
             path: 'defense-notice',
             redirect: '',
             redirect: '',
           },
           },
+          {
+            component: '/disaster/disaster-warning/PageDefenseNoticeItem',
+            id: 1038,
+            meta: {
+              activeMenu: null,
+              alwaysShow: false,
+              frameSrc: '',
+              hidden: true,
+              icon: '',
+              isFrame: 0,
+              isRoot: false,
+              noCache: false,
+              query: '',
+              title: '防御通知详情',
+            },
+            name: '/disaster-prevention/disaster-warning/defense-notice-item',
+            parentId: 1027,
+            path: 'defense-notice-item',
+            redirect: '',
+          },
         ],
         ],
         component: '',
         component: '',
         id: 1027,
         id: 1027,

+ 10 - 0
src/views/disaster/WorkRecord.md

@@ -0,0 +1,10 @@
+### 25/5/13
+##### 灾害防范模块
+- [x] 准备通用模块-树结构选择
+##### 灾害预警-预警信息
+- [x] 不确定后端能否拿到数据 添加页面先不做
+##### 灾害预警-防御通知
+- [x]  创建灾害防御通知页面
+- [x]  提取公共scss(新建页面)
+- [x]  文件上传组件(是否通用?提取公共组件)
+- [ ]  文件在线预览组件、文件在线下载组件

+ 320 - 0
src/views/disaster/components/Upload.vue

@@ -0,0 +1,320 @@
+<template>
+  <div class="upload-wrapper">
+    <!-- 上传按钮 -->
+    <label for="fileInput" class="upload-button">
+      <el-icon><UploadFilled /></el-icon>
+      <span>{{ label }}</span>
+    </label>
+    <input
+      type="file"
+      id="fileInput"
+      multiple
+      accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
+      @change="handleFileSelect"
+    />
+
+    <!-- 总体进度条 -->
+    <div class="progress-container" v-show="showProgress">
+      <div class="progress-info">
+        <span>正在上传文件...</span>
+        <span>{{ progressPercent }}%</span>
+      </div>
+      <div class="progress-bar">
+        <div
+          class="progress-bar-fill"
+          :class="{ completed: isCompleted }"
+          :style="{ width: `${progressPercent}%` }"
+        ></div>
+      </div>
+    </div>
+
+    <!-- 文件列表 -->
+    <div class="file-list" v-if="fileList.length > 0">
+      <div v-for="file in fileList" :key="file.id" class="file-item">
+        <div class="file-info">
+          <img :src="FILE_TYPE_ICON[file.fileType as keyof typeof FILE_TYPE_ICON]" />
+          <span class="file-name">{{ file.file.name }}</span>
+        </div>
+        <button class="delete-button" @click="removeFile(file.id)">
+          <el-icon><Delete /></el-icon>
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, computed, nextTick } from 'vue';
+  import { UploadFilled, Delete } from '@element-plus/icons-vue';
+  import { ElMessage, ElMessageBox } from 'element-plus';
+  import { FILE_TYPE_ICON } from '../constant';
+  import type { FileItem } from '../types';
+  defineProps<{
+    label: string;
+  }>();
+
+  // 常量定义
+  const MAX_SIZE = 5 * 1024 * 1024; // 5MB
+
+  // 响应式状态
+  const fileList = ref<FileItem[]>([]);
+  const tempFiles = ref<File[]>([]);
+  const showProgress = ref(false);
+  const isCompleted = ref(false);
+  const progress = ref(0);
+
+  // 计算属性
+  const progressPercent = computed(() => {
+    return Math.round(progress.value);
+  });
+
+  // 检查文件是否已存在
+  const isFileAlreadyUploaded = (newFile: File): boolean => {
+    return fileList.value.some((item) => {
+      return item.file.name === newFile.name && item.file.size === newFile.size && item.file.type === newFile.type;
+    });
+  };
+
+  // 获取文件类型
+  const getFileType = (file: File): string => {
+    const fileType = file.type;
+
+    // 转换为系统中已有的四种类型
+    if (fileType.includes('pdf')) return 'pdf';
+    if (fileType.includes('word') || fileType.includes('msword') || fileType.includes('wordprocessingml'))
+      return 'word';
+    if (fileType.includes('excel') || fileType.includes('spreadsheetml') || fileType.includes('ms-excel'))
+      return 'excel';
+    if (fileType.includes('powerpoint') || fileType.includes('presentationml') || fileType.includes('ms-powerpoint'))
+      return 'ppt';
+
+    // 默认返回
+    return 'pdf';
+  };
+
+  // 方法
+  const handleFileSelect = (event: Event) => {
+    const input = event.target as HTMLInputElement;
+    if (!input.files || input.files.length === 0) return;
+
+    const files = Array.from(input.files);
+    let validFiles = 0;
+    tempFiles.value = [];
+
+    files.forEach((file) => {
+      if (!isValidFileType(file)) {
+        ElMessage.error(`${file.name} 不是允许的文件类型`);
+        return;
+      }
+
+      if (file.size > MAX_SIZE) {
+        ElMessage.error(`${file.name} 文件过大`);
+        return;
+      }
+
+      // 检查是否已经上传过相同的文件
+      if (isFileAlreadyUploaded(file)) {
+        ElMessage.warning(`${file.name} 已经上传过了`);
+        return;
+      }
+
+      // 保存到临时文件数组中,而不是立即添加到UI
+      tempFiles.value.push(file);
+      validFiles++;
+    });
+
+    progress.value = 0;
+
+    // 显示进度条
+    if (validFiles > 0) {
+      showProgress.value = true;
+      isCompleted.value = false;
+
+      // 确保DOM更新后再开始动画
+      nextTick(() => {
+        simulateFileUpload();
+      });
+    }
+
+    // 清空input以便再次选择相同文件
+    input.value = '';
+  };
+
+  const isValidFileType = (file: File): boolean => {
+    const allowedTypes = [
+      'application/pdf',
+      'application/msword',
+      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+      'application/vnd.ms-excel',
+      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+      'application/vnd.ms-powerpoint',
+      'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+    ];
+    return allowedTypes.includes(file.type);
+  };
+
+  const addFileToUI = (file: File) => {
+    const fileId = Date.now() + Math.floor(Math.random() * 1000);
+    const fileType = getFileType(file);
+
+    fileList.value.unshift({
+      id: fileId,
+      file: file,
+      fileType: fileType,
+    });
+  };
+
+  const removeFile = (id: number) => {
+    ElMessageBox.confirm('确定删除该文件吗?', '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+    }).then(() => {
+      const index = fileList.value.findIndex((item) => item.id === id);
+      if (index !== -1) {
+        fileList.value.splice(index, 1);
+      }
+      ElMessage.success('文件删除成功');
+    });
+  };
+
+  const simulateFileUpload = () => {
+    // 设置初始进度为5%,让用户立即看到进度
+    progress.value = 5;
+
+    // 使用更快的速度模拟上传进度
+    let targetProgress = 5;
+    const totalDuration = 1500; // 总上传时间,毫秒
+    const interval = 200; // 更新间隔
+    const steps = totalDuration / interval;
+    const increment = 95 / steps; // 每步增加的百分比,从5%到100%
+
+    const uploadInterval = setInterval(() => {
+      targetProgress += increment;
+
+      if (targetProgress >= 100) {
+        progress.value = 100;
+        clearInterval(uploadInterval);
+
+        // 上传完成后再添加文件到UI
+        setTimeout(() => {
+          tempFiles.value.forEach((file) => {
+            addFileToUI(file);
+          });
+
+          isCompleted.value = true;
+          ElMessage.success('文件上传成功');
+          emit('uploadSuccess', fileList.value);
+          setTimeout(() => {
+            showProgress.value = false;
+          }, 500);
+        }, 200);
+      } else {
+        progress.value = targetProgress;
+      }
+    }, interval);
+  };
+  const emit = defineEmits(['uploadSuccess']);
+</script>
+
+<style lang="scss" scoped>
+  .upload-wrapper {
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+  }
+
+  .upload-button {
+    @include flex-center;
+    gap: 5cpx;
+    width: 100cpx;
+    border: 1px solid rgba($text-color, 0.15);
+    color: $primary-color;
+    font-size: 14cpx;
+    border-radius: 2px;
+    text-align: center;
+    cursor: pointer;
+    transition: all 0.3s ease-in-out;
+
+    &:hover {
+      background-color: $primary-color;
+      color: $white-color;
+    }
+  }
+
+  #fileInput {
+    display: none;
+  }
+
+  .progress-container {
+    margin-top: 16cpx;
+  }
+
+  .progress-info {
+    display: flex;
+    justify-content: space-between;
+    font-size: 14cpx;
+    color: #666;
+  }
+
+  .progress-bar {
+    width: 100%;
+    height: 8px;
+    background-color: #e5e7eb;
+    border-radius: 9999px;
+    overflow: hidden;
+  }
+
+  .progress-bar-fill {
+    height: 100%;
+    background-color: $primary-color;
+    border-radius: 9999px;
+    transition: width 0.3s ease;
+
+    &.completed {
+      background-color: #10b981;
+    }
+  }
+
+  .file-list {
+    margin-top: 16cpx;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8cpx;
+  }
+
+  .file-item {
+    @include flex-center;
+    justify-content: space-between;
+    border: 1px solid #e5e7eb;
+    border-radius: 6cpx;
+    padding: 12cpx;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: #f8fafc;
+    }
+  }
+
+  .file-info {
+    display: flex;
+    align-items: center;
+    gap: 8cpx;
+    img {
+      width: 20cpx;
+    }
+  }
+
+  .file-name {
+    width: 200cpx;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .delete-button {
+    color: #ef4444;
+    background: none;
+    border: none;
+    cursor: pointer;
+  }
+</style>

+ 32 - 11
src/views/disaster/components/User.vue

@@ -25,25 +25,24 @@
           :props="defaultProps"
           :props="defaultProps"
           :default-expand-all="true"
           :default-expand-all="true"
           style="padding: 10px 0"
           style="padding: 10px 0"
+          @check-change="handleCheckChange"
         />
         />
       </div>
       </div>
     </div>
     </div>
     <div class="right">
     <div class="right">
-      <!-- <div class="head">
-        <span style="font-weight: 400; font-size: 16px; color: rgba(0, 0, 0, 0.88); line-height: 22px"
-          >已选择:{{ selectedPersonList.length }}人</span
-        >
-      </div> -->
-      <!-- <div class="selected">
+      <div class="head">
+        <span>已选择:{{ selectedPersonList.length }}人</span>
+      </div>
+      <div class="selected">
         <el-tag
         <el-tag
           v-for="person in selectedPersonList"
           v-for="person in selectedPersonList"
           :key="person.id"
           :key="person.id"
           closable
           closable
           @close="handleRemoveSelectedPerson(person.id)"
           @close="handleRemoveSelectedPerson(person.id)"
         >
         >
-          {{ person.staffNo + '-' + person.realname }}
+          {{ person.name }}
         </el-tag>
         </el-tag>
-      </div> -->
+      </div>
       <div class="footer">
       <div class="footer">
         <el-button @click="emit('cancel')">取消</el-button>
         <el-button @click="emit('cancel')">取消</el-button>
         <el-button type="primary">确定</el-button>
         <el-button type="primary">确定</el-button>
@@ -57,8 +56,10 @@
   import empty from 'assets/images/empty@1X.png';
   import empty from 'assets/images/empty@1X.png';
   import { queryAvailableUserList } from '@/api/push-object';
   import { queryAvailableUserList } from '@/api/push-object';
   import type { TreeNodeData } from '@/views/disaster/types';
   import type { TreeNodeData } from '@/views/disaster/types';
+  import type { ElTree } from 'element-plus';
   const loading = ref(false);
   const loading = ref(false);
   const queryContent = ref('');
   const queryContent = ref('');
+  const treeRef = ref<InstanceType<typeof ElTree>>();
   const nodeData = ref<TreeNodeData[]>([
   const nodeData = ref<TreeNodeData[]>([
     {
     {
       id: -1,
       id: -1,
@@ -67,6 +68,7 @@
     },
     },
   ]);
   ]);
   const searchResult = ref<TreeNodeData[]>([]);
   const searchResult = ref<TreeNodeData[]>([]);
+  const selectedPersonList = ref<TreeNodeData[]>([]);
   const defaultProps = {
   const defaultProps = {
     children: 'children',
     children: 'children',
     label: 'name',
     label: 'name',
@@ -88,14 +90,32 @@
       };
       };
     });
     });
     nodeData.value[0].children = searchResult.value;
     nodeData.value[0].children = searchResult.value;
-    console.log(nodeData.value);
     await nextTick();
     await nextTick();
     loading.value = false;
     loading.value = false;
   };
   };
   const handleSearch = () => {
   const handleSearch = () => {
-    console.log(queryContent.value, selectType.value);
     getUserList();
     getUserList();
   };
   };
+  const removeSelectedPerson = (id: number) => {
+    const index = selectedPersonList.value.findIndex((item) => item.id === id);
+    if (index !== -1) {
+      selectedPersonList.value.splice(index, 1);
+    }
+  };
+  const handleCheckChange = (node: TreeNodeData, checked: boolean) => {
+    if (node.children.length) return;
+    if (checked) {
+      console.log(node, checked);
+      selectedPersonList.value.push(node);
+    } else {
+      removeSelectedPerson(node.id);
+    }
+  };
+  const handleRemoveSelectedPerson = (id: number) => {
+    removeSelectedPerson(id);
+    if (!treeRef.value) return;
+    treeRef.value.setChecked(id, false, true);
+  };
   const emit = defineEmits(['cancel', 'submit']);
   const emit = defineEmits(['cancel', 'submit']);
 </script>
 </script>
 
 
@@ -141,12 +161,13 @@
       height: 100%;
       height: 100%;
       position: relative;
       position: relative;
       margin-left: 16px;
       margin-left: 16px;
+      gap: 20px;
       .head {
       .head {
         display: flex;
         display: flex;
         align-items: center;
         align-items: center;
         font-weight: 400;
         font-weight: 400;
         font-size: 16px;
         font-size: 16px;
-        color: rgba(0, 0, 0, 0.88);
+        color: rgba($text-color, 0.88);
         line-height: 22px;
         line-height: 22px;
         margin: 6px 0 6px;
         margin: 6px 0 6px;
       }
       }

+ 29 - 26
src/views/disaster/constant/index.ts

@@ -32,6 +32,17 @@ export const ACTIVE_STATUS_OPTIONS_DEFAULT = [
   },
   },
 ];
 ];
 
 
+export const ACTIVE_STATUS_OPTIONS_WARNING = [
+  {
+    label: '生效',
+    value: ACTIVE_STATUS.ACTIVE,
+  },
+  {
+    label: '未生效',
+    value: ACTIVE_STATUS.NOT_EFFECTIVE,
+  },
+];
+
 export const ACTIVE_STATUS_OPTIONS_MANAGEMENT = [
 export const ACTIVE_STATUS_OPTIONS_MANAGEMENT = [
   {
   {
     label: '未生效',
     label: '未生效',
@@ -66,62 +77,54 @@ export const DISASTER_TYPE = [
     label: '暴雨',
     label: '暴雨',
     value: '暴雨',
     value: '暴雨',
   },
   },
+  {
+    label: '大风',
+    value: '大风',
+  },
   {
   {
     label: '暴雪',
     label: '暴雪',
     value: '暴雪',
     value: '暴雪',
   },
   },
   {
   {
-    label: '寒潮',
-    value: '寒潮',
+    label: '高温',
+    value: '高温',
   },
   },
   {
   {
-    label: '大风',
-    value: '大风',
+    label: '强对流',
+    value: '强对流',
+  },
+  {
+    label: '寒潮',
+    value: '寒潮',
   },
   },
   {
   {
     label: '沙尘暴',
     label: '沙尘暴',
     value: '沙尘暴',
     value: '沙尘暴',
   },
   },
   {
   {
-    label: '温',
-    value: '温',
+    label: '温',
+    value: '温',
   },
   },
   {
   {
     label: '干旱',
     label: '干旱',
     value: '干旱',
     value: '干旱',
   },
   },
-  {
-    label: '雷电',
-    value: '雷电',
-  },
-  {
-    label: '冰雹',
-    value: '冰雹',
-  },
   {
   {
     label: '霜冻',
     label: '霜冻',
     value: '霜冻',
     value: '霜冻',
   },
   },
   {
   {
-    label: '大雾',
-    value: '大雾',
+    label: '冰冻',
+    value: '冰冻',
   },
   },
   {
   {
-    label: '道路结冰',
-    value: '道路结冰',
+    label: '大雾',
+    value: '大雾',
   },
   },
   {
   {
     label: '霾',
     label: '霾',
     value: '霾',
     value: '霾',
   },
   },
-  {
-    label: '雷雨大风',
-    value: '雷雨大风',
-  },
-  {
-    label: '重污染天气',
-    value: '重污染天气',
-  },
 ];
 ];
 
 
 /**
 /**

+ 3 - 14
src/views/disaster/disaster-precaution/src/components/CreateTaskItem.vue

@@ -1,11 +1,11 @@
 <template>
 <template>
-  <div class="task-item-container">
+  <div class="info-container">
     <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="auto">
     <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="auto">
       <el-form-item label="任务名称:" prop="name">
       <el-form-item label="任务名称:" prop="name">
         <el-input v-model="ruleForm.name" placeholder="请输入任务名称" maxlength="200" show-word-limit />
         <el-input v-model="ruleForm.name" placeholder="请输入任务名称" maxlength="200" show-word-limit />
       </el-form-item>
       </el-form-item>
       <el-form-item label="被检查(自查)单位:" prop="checkUnit">
       <el-form-item label="被检查(自查)单位:" prop="checkUnit">
-        <el-select v-model="ruleForm.checkUnit" placeholder="请选择被检查(自查)单位" />
+        <el-select v-model="ruleForm.checkUnit" placeholder="请选择被检查(自查)单位" multiple />
       </el-form-item>
       </el-form-item>
       <el-form-item label="检查类型:" prop="checkType">
       <el-form-item label="检查类型:" prop="checkType">
         <el-select-v2 v-model="ruleForm.checkType" placeholder="请选择检查类型" :options="TASK_TYPE_OPTIONS" />
         <el-select-v2 v-model="ruleForm.checkType" placeholder="请选择检查类型" :options="TASK_TYPE_OPTIONS" />
@@ -101,18 +101,7 @@
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
-  .task-item-container {
-    width: 100%;
-    max-height: calc(100vh - 350cpx);
-    overflow: auto;
-  }
-  .el-form {
-    display: flex;
-    flex-direction: column;
-    width: 600cpx;
-    height: 100%;
-    gap: 12cpx;
-  }
+  @use '@/views/disaster/style/info-container.scss' as *;
   :deep(.el-date-editor) {
   :deep(.el-date-editor) {
     --el-date-editor-width: 100%;
     --el-date-editor-width: 100%;
   }
   }

+ 27 - 4
src/views/disaster/disaster-warning/PageDefenseNotice.vue

@@ -6,7 +6,13 @@
     <main class="disaster-precaution-container__main">
     <main class="disaster-precaution-container__main">
       <div class="defense-notice-container">
       <div class="defense-notice-container">
         <header class="defense-notice-container__header">
         <header class="defense-notice-container__header">
-          <el-button type="primary" class="defense-notice__header-button" :icon="Plus">创建灾害防御通知</el-button>
+          <el-button
+            type="primary"
+            class="defense-notice__header-button"
+            :icon="Plus"
+            @click="handleCreateDefenseNotice"
+            >创建灾害防御通知</el-button
+          >
           <Search />
           <Search />
         </header>
         </header>
         <BasicTable
         <BasicTable
@@ -31,7 +37,7 @@
           </template>
           </template>
           <template #action="scope">
           <template #action="scope">
             <ActionButton text="编辑" v-if="scope.row.activeStatus === ACTIVE_STATUS.NOT_EFFECTIVE" />
             <ActionButton text="编辑" v-if="scope.row.activeStatus === ACTIVE_STATUS.NOT_EFFECTIVE" />
-            <ActionButton text="查看" />
+            <ActionButton text="查看" @click="handleViewDefenseNotice(scope.row.id)" />
             <ActionButton
             <ActionButton
               text="发布"
               text="发布"
               :popconfirm="{
               :popconfirm="{
@@ -40,9 +46,9 @@
               v-if="scope.row.activeStatus === ACTIVE_STATUS.NOT_EFFECTIVE"
               v-if="scope.row.activeStatus === ACTIVE_STATUS.NOT_EFFECTIVE"
             />
             />
             <ActionButton
             <ActionButton
-              text="取消发布"
+              text="撤回"
               :popconfirm="{
               :popconfirm="{
-                title: '确定要取消发布?',
+                title: '确定要撤回?',
               }"
               }"
               v-else-if="scope.row.activeStatus === ACTIVE_STATUS.ACTIVE"
               v-else-if="scope.row.activeStatus === ACTIVE_STATUS.ACTIVE"
             />
             />
@@ -71,7 +77,24 @@
   import type { DefenseNoticeListResponse } from '@/types/disaster-warning';
   import type { DefenseNoticeListResponse } from '@/types/disaster-warning';
   import { ACTIVE_STATUS, ACTIVE_STATUS_COLOR, ACTIVE_STATUS_MAP } from '@/views/disaster/constant';
   import { ACTIVE_STATUS, ACTIVE_STATUS_COLOR, ACTIVE_STATUS_MAP } from '@/views/disaster/constant';
   import { PUSH_STATUS_MAP, PUSH_STATUS } from './src/constant';
   import { PUSH_STATUS_MAP, PUSH_STATUS } from './src/constant';
+  import { useRouter } from 'vue-router';
   const tableData = ref<DefenseNoticeListResponse[]>([]);
   const tableData = ref<DefenseNoticeListResponse[]>([]);
+  const router = useRouter();
+  const defaultPath = '/disaster-prevention/disaster-warning/defense-notice-item';
+  const handleCreateDefenseNotice = () => {
+    router.push({
+      path: defaultPath,
+      query: {
+        operate: 'create',
+      },
+    });
+  };
+  const handleViewDefenseNotice = (id: number) => {
+    router.push({
+      path: defaultPath,
+      query: { id },
+    });
+  };
   const columns: TableColumnProps[] = [
   const columns: TableColumnProps[] = [
     {
     {
       prop: 'noticeTitle',
       prop: 'noticeTitle',

+ 64 - 0
src/views/disaster/disaster-warning/PageDefenseNoticeItem.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="disaster-precaution-container">
+    <header class="disaster-precaution-container__header">
+      <img :src="BackIcon" alt="back" class="back-icon" @click="router.back()" />
+      <span class="disaster-precaution-container__title">{{ headerTitle }}</span>
+    </header>
+    <main class="disaster-precaution-container__main">
+      <component :is="dynamicComponent" :id="id"/>
+    </main>
+    <footer class="disaster-precaution-container__footer" v-if="operate">
+      <el-button>取消</el-button>
+      <el-button type="primary">提交</el-button>
+    </footer>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { computed, defineAsyncComponent } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import BackIcon from 'assets/svg/back.svg';
+  const router = useRouter();
+  const route = useRoute();
+  const operate = route.query.operate;
+  const id = route.query.id;
+  const headerTitle = computed(() => {
+    const fixedTitle = '灾害防御通知';
+    if (operate === 'create') {
+      return `创建${fixedTitle}`;
+    } else if (operate === 'edit') {
+      return `编辑${fixedTitle}`;
+    }
+    return `查看${fixedTitle}`;
+  });
+  const dynamicComponent = computed(() => {
+    if (operate === 'create') {
+      return defineAsyncComponent(() => import('./src/components/CreateDefenseNoticeItem.vue'));
+    } else {
+      return defineAsyncComponent(() => import('./src/components/ViewDefenseNoticeItem.vue'));
+    }
+  });
+</script>
+
+<style lang="scss" scoped>
+  @use '../style/disaster.scss' as *;
+  .back-icon {
+    width: 16cpx;
+    cursor: pointer;
+  }
+  .disaster-precaution-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8cpx !important;
+  }
+  .disaster-precaution-container__footer {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    width: calc(100vw - 310cpx);
+    height: 88cpx;
+    padding: 28cpx;
+    background-color: $white-color;
+    border-radius: 4cpx;
+  }
+</style>

+ 2 - 2
src/views/disaster/disaster-warning/PageWarningInfo.vue

@@ -40,9 +40,9 @@
               v-if="scope.row.activeStatus === ACTIVE_STATUS.NOT_EFFECTIVE"
               v-if="scope.row.activeStatus === ACTIVE_STATUS.NOT_EFFECTIVE"
             />
             />
             <ActionButton
             <ActionButton
-              text="取消发布"
+              text="撤回"
               :popconfirm="{
               :popconfirm="{
-                title: '确定要取消发布?',
+                title: '确定要撤回?',
               }"
               }"
               v-else-if="scope.row.activeStatus === ACTIVE_STATUS.ACTIVE"
               v-else-if="scope.row.activeStatus === ACTIVE_STATUS.ACTIVE"
             />
             />

+ 106 - 0
src/views/disaster/disaster-warning/src/components/CreateDefenseNoticeItem.vue

@@ -0,0 +1,106 @@
+<template>
+  <div class="info-container">
+    <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="auto">
+      <el-form-item label="灾害类型:" prop="disasterType">
+        <el-select v-model="ruleForm.disasterType" placeholder="请选择灾害类型" filterable>
+          <el-option v-for="item in DISASTER_TYPE" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="灾害等级:" prop="disasterLevel">
+        <el-select v-model="ruleForm.disasterLevel" placeholder="请选择灾害等级" filterable>
+          <el-option v-for="item in DISASTER_LEVEL" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="通知标题:" prop="noticeTitle">
+        <el-input v-model="ruleForm.noticeTitle" placeholder="请输入通知标题" maxlength="200" show-word-limit />
+      </el-form-item>
+      <el-form-item label="通知内容:" prop="noticeContent">
+        <el-input
+          v-model="ruleForm.noticeContent"
+          type="textarea"
+          placeholder="请输入通知内容"
+          :rows="3"
+          maxlength="1000"
+          show-word-limit
+        />
+      </el-form-item>
+      <el-form-item label="通知附件:" prop="noticeAttachment">
+        <Upload label="上传附件" @uploadSuccess="handleUploadSuccess" />
+      </el-form-item>
+      <el-form-item label="是否推送:" prop="isPush">
+        <el-radio-group v-model="ruleForm.isPush">
+          <el-radio :value="true">是</el-radio>
+          <el-radio :value="false">否</el-radio>
+        </el-radio-group>
+        <PushObject v-if="ruleForm.isPush" />
+      </el-form-item>
+      <el-form-item label="创建人:">
+        <el-input v-model="ruleForm.createUser" disabled />
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { reactive, ref } from 'vue';
+  import PushObject from '@/views/disaster/components/PushObject.vue';
+  import Upload from '@/views/disaster/components/Upload.vue';
+  import type { FormInstance, FormRules } from 'element-plus';
+  import { DISASTER_TYPE, DISASTER_LEVEL } from '@/views/disaster/constant';
+  import type { FileItem } from '@/views/disaster/types';
+
+  interface RuleForm {
+    disasterType: string;
+    disasterLevel: string;
+    noticeTitle: string;
+    noticeContent: string;
+    noticeAttachment: FileItem[];
+    isPush: string;
+    createUser: string;
+  }
+
+  const ruleFormRef = ref<FormInstance>();
+  const ruleForm = reactive<RuleForm>({
+    disasterType: '',
+    disasterLevel: '',
+    noticeTitle: '',
+    noticeContent: '',
+    noticeAttachment: [],
+    isPush: '',
+    createUser: 'XXX',
+  });
+  const handleUploadSuccess = (fileList: FileItem[]) => {
+    ruleForm.noticeAttachment = fileList;
+  };
+
+  const rules = reactive<FormRules<RuleForm>>({
+    disasterType: [{ required: true, message: '请选择灾害类型', trigger: 'change' }],
+    disasterLevel: [
+      {
+        required: true,
+        message: '请选择灾害等级',
+        trigger: 'change',
+      },
+    ],
+    noticeTitle: [
+      {
+        required: true,
+        message: '请输入通知标题',
+        trigger: 'blur',
+      },
+    ],
+    noticeContent: [
+      {
+        required: true,
+        message: '请输入通知内容',
+        trigger: 'blur',
+      },
+    ],
+    noticeAttachment: [{ required: true, message: '请上传通知附件', trigger: 'change' }],
+    isPush: [{ required: true, message: '请选择是否推送', trigger: 'change' }],
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/views/disaster/style/info-container.scss' as *;
+</style>

+ 3 - 3
src/views/disaster/disaster-warning/src/components/Search.vue

@@ -17,7 +17,7 @@
         <span>生效状态:</span>
         <span>生效状态:</span>
         <el-select v-model="selectStatus" placeholder="请选择状态">
         <el-select v-model="selectStatus" placeholder="请选择状态">
           <el-option
           <el-option
-            v-for="item in ACTIVE_STATUS_OPTIONS_MANAGEMENT"
+            v-for="item in ACTIVE_STATUS_OPTIONS_WARNING"
             :key="item.value"
             :key="item.value"
             :label="item.label"
             :label="item.label"
             :value="item.value"
             :value="item.value"
@@ -34,8 +34,8 @@
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
   import { ref } from 'vue';
   import { ref } from 'vue';
-  import { ACTIVE_STATUS_OPTIONS_MANAGEMENT, DISASTER_TYPE } from '@/views/disaster/constant';
-  import { DISASTER_LEVEL } from '../constant';
+  import { ACTIVE_STATUS_OPTIONS_WARNING, DISASTER_TYPE } from '@/views/disaster/constant';
+  import { DISASTER_LEVEL } from '@/views/disaster/constant';
   const selectDisasterType = ref('');
   const selectDisasterType = ref('');
   const selectDisasterLevel = ref('');
   const selectDisasterLevel = ref('');
   const selectStatus = ref('');
   const selectStatus = ref('');

+ 13 - 0
src/views/disaster/disaster-warning/src/components/ViewDefenseNoticeItem.vue

@@ -0,0 +1,13 @@
+<template>
+  <div class="info-container">
+    {{ id }}
+  </div>
+</template>
+
+<script setup lang="ts">
+  const porps = defineProps<{
+    id: number;
+  }>();
+</script>
+
+<style scoped lang="scss"></style>

+ 0 - 22
src/views/disaster/disaster-warning/src/constant.ts

@@ -79,28 +79,6 @@ const colorMap: Record<AlertLevel, string> = {
   red: '红色',
   red: '红色',
 };
 };
 
 
-/**
- * 灾害等级
- */
-export const DISASTER_LEVEL = [
-  {
-    label: '蓝色',
-    value: '蓝色',
-  },
-  {
-    label: '黄色',
-    value: '黄色',
-  },
-  {
-    label: '橙色',
-    value: '橙色',
-  },
-  {
-    label: '红色',
-    value: '红色',
-  },
-];
-
 // 灾害类型定义
 // 灾害类型定义
 interface DisasterType {
 interface DisasterType {
   name: string;
   name: string;

Разлика између датотеке није приказан због своје велике величине
+ 42 - 0
src/views/disaster/disaster-warning/src/mock/info.json


+ 12 - 0
src/views/disaster/style/info-container.scss

@@ -0,0 +1,12 @@
+.info-container {
+  width: 100%;
+  max-height: calc(100vh - 350cpx);
+  overflow: auto;
+  .el-form {
+    display: flex;
+    flex-direction: column;
+    width: 600cpx;
+    height: 100%;
+    gap: 12cpx;
+  }
+}

+ 6 - 0
src/views/disaster/types/index.ts

@@ -8,3 +8,9 @@ export interface TreeNodeData {
   name: string;
   name: string;
   children: TreeNodeData[];
   children: TreeNodeData[];
 }
 }
+
+export interface FileItem {
+  id: number;
+  file: File;
+  fileType: string;
+}