Ver código fonte

完成表单组件 通过配置项来创造 完成灾害预警静态界面

chauncey 1 ano atrás
pai
commit
c50f40bbea
43 arquivos alterados com 1242 adições e 830 exclusões
  1. 21 0
      mock/disaster-warning/info.ts
  2. 6 17
      mock/disaster-warning/table.ts
  3. 20 0
      mock/login/routers.ts
  4. 23 1
      src/api/disaster-warning/index.ts
  5. 59 0
      src/components/BasicForm.vue
  6. 3 3
      src/components/BasicTable.vue
  7. 0 7
      src/components/SvgIcon/SvgIcon.vue
  8. 5 0
      src/components/SvgIcon/SvgIcon.vue.d.ts
  9. 64 0
      src/hooks/useFormConfigHook.ts
  10. 8 0
      src/types/basic-form/index.ts
  11. 15 12
      src/types/disaster-warning/index.ts
  12. 9 2
      src/views/disaster/WorkRecord.md
  13. 70 0
      src/views/disaster/components/Search.vue
  14. 113 0
      src/views/disaster/components/SelectGroup.vue
  15. 22 68
      src/views/disaster/disaster-warning/PageDefenseNotice.vue
  16. 6 20
      src/views/disaster/disaster-warning/PageDefenseNoticeItem.vue
  17. 62 76
      src/views/disaster/disaster-warning/PageWarningInfo.vue
  18. 62 0
      src/views/disaster/disaster-warning/PageWarningInfoItem.vue
  19. 14 0
      src/views/disaster/disaster-warning/src/common.scss
  20. 58 64
      src/views/disaster/disaster-warning/src/components/CreateDefenseNoticeItem.vue
  21. 70 0
      src/views/disaster/disaster-warning/src/components/CreateWarningInfoItem.vue
  22. 60 77
      src/views/disaster/disaster-warning/src/components/EditDefenseNoticeItem.vue
  23. 85 0
      src/views/disaster/disaster-warning/src/components/EditWarningInfoItem.vue
  24. 0 49
      src/views/disaster/disaster-warning/src/components/Search.vue
  25. 182 0
      src/views/disaster/disaster-warning/src/config/form.ts
  26. 24 0
      src/views/disaster/disaster-warning/src/config/index.ts
  27. 54 0
      src/views/disaster/disaster-warning/src/config/search.ts
  28. 95 0
      src/views/disaster/disaster-warning/src/config/table.ts
  29. 1 1
      src/views/disaster/disaster-warning/src/constant.ts
  30. 7 13
      src/views/disaster/disaster-warning/src/type.ts
  31. 0 165
      src/views/disaster/disaster-warning/src/useFormHook.ts
  32. 13 0
      src/views/disaster/style/disaster.scss
  33. 0 7
      src/views/disaster/style/info-container.scss
  34. 9 0
      src/views/disaster/types/index.ts
  35. 2 5
      utils/devProxy/index.ts
  36. 0 27
      utils/devProxy/shangfei/app.config.js
  37. 0 36
      utils/devProxy/shangfei/proxy.ts
  38. 0 25
      utils/devProxy/yubei/app.config.js
  39. 0 32
      utils/devProxy/yubei/proxy.ts
  40. 0 28
      utils/devProxy/zhongjiancai/app.config.js
  41. 0 32
      utils/devProxy/zhongjiancai/proxy.ts
  42. 0 28
      utils/devProxy/zongbu/app.config.js
  43. 0 35
      utils/devProxy/zongbu/proxy.ts

Diferenças do arquivo suprimidas por serem muito extensas
+ 21 - 0
mock/disaster-warning/info.ts


+ 6 - 17
mock/disaster-warning/table.ts

@@ -1,35 +1,24 @@
 import { resultSuccess } from '../_util';
 
 const warningInfoList = [
-  {
-    id: 1001,
-    warningIcon: '',
-    disasterType: '暴雨',
-    disasterLevel: '黄色',
-    warningTime: '2024-03-20 10:00:00',
-    warningContent: '上海市气象台7月12日1时发布暴雨黄色预警:未来两小时将有200毫米以上降水',
-    activeStatus: 0,
-    publishTime: '2024-03-20 10:00:00',
-    pushStatus: 0,
-  },
   {
     id: 1002,
     warningIcon: '',
-    disasterType: '台风',
-    disasterLevel: '黄色',
+    disasterType: '台风蓝色预警',
+    disasterLevel: 'IV级/一般',
     warningTime: '2024-03-20 10:00:00',
     warningContent: '上海市气象台7月12日1时发布台风黄色预警:预计未来24小时内将有特大暴雨',
     activeStatus: 1,
     publishTime: '2024-03-20 10:00:00',
-    pushStatus: 1,
+    pushStatus: 0,
   },
   {
     id: 1003,
     warningIcon: '',
-    disasterType: '高温',
-    disasterLevel: '红色',
+    disasterType: '暴雨橙色预警',
+    disasterLevel: 'III级/较重',
     warningTime: '2024-03-20 10:00:00',
-    warningContent: '上海市气象台7月12日1时发布高温红色预警:预计未来24小时内最高气温将升至40℃以上',
+    warningContent: '上海市气象台7月12日1时发布暴雨橙色预警:预计未来24小时内将有特大暴雨',
     activeStatus: 2,
     publishTime: '2024-03-20 10:00:00',
     pushStatus: 1,

+ 20 - 0
mock/login/routers.ts

@@ -65,6 +65,26 @@ const list = [
             path: 'warning-info',
             redirect: '',
           },
+          {
+            component: '/disaster/disaster-warning/PageWarningInfoItem',
+            id: 1036,
+            meta: {
+              activeMenu: null,
+              alwaysShow: false,
+              frameSrc: '',
+              hidden: true,
+              icon: '',
+              isFrame: 0,
+              isRoot: false,
+              noCache: false,
+              query: '',
+              title: '预警信息详情',
+            },
+            name: '/disaster-prevention/disaster-warning/warning-info-item',
+            parentId: 1027,
+            path: 'warning-info-item',
+            redirect: '',
+          },
           {
             component: '/disaster/disaster-warning/PageDefenseNotice',
             id: 1037,

+ 23 - 1
src/api/disaster-warning/index.ts

@@ -1,5 +1,10 @@
 import { http } from '@/utils/http/axios';
-import type { WarningInfoListResponse, DefenseNoticeListResponse, DefenseNoticeDetailResponse } from '@/types/disaster-warning';
+import type {
+  WarningInfoListResponse,
+  DefenseNoticeListResponse,
+  WarningInfoDetailResponse,
+  DefenseNoticeDetailResponse,
+} from '@/types/disaster-warning';
 /**
  * 获取预警信息列表
  */
@@ -29,6 +34,23 @@ export function getDefenseNoticeList() {
   );
 }
 
+/**
+ * 获取预警信息详情
+ */
+export function getWarningInfoDetail(id: number) {
+  return http.request<WarningInfoDetailResponse>(
+    {
+      url: 'admin/warning/getWarningInfoDetail',
+      method: 'get',
+      params: {
+        id,
+      },
+    },
+    {
+      ignoreTargetTenantId: true,
+    },
+  );
+}
 /**
  * 查看防御通知详情
  */

+ 59 - 0
src/components/BasicForm.vue

@@ -0,0 +1,59 @@
+<template>
+  <el-form ref="formRef" :model="formData" :rules="formRules" label-width="auto">
+    <el-form-item v-for="item in formConfig" :key="item.prop" :label="item.label" :prop="item.prop">
+      <!-- 自定义插槽处理 -->
+      <slot v-if="item.slot" :name="item.slot" />
+
+      <!-- 组件动态渲染 -->
+      <component v-else :is="item.component" v-model="formData[item.prop]" v-bind="item.componentProps">
+        <el-option
+          v-for="option in item.selectOptions"
+          :key="option.value"
+          :label="option.label"
+          :value="option.value"
+        />
+      </component>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import type { FormConfig } from '@/types/basic-form';
+  import type { FormInstance, FormRules } from 'element-plus';
+  const formRef = ref<FormInstance>();
+  defineProps<{
+    formData: Record<string, any>;
+    formRules: FormRules<any>;
+    formConfig: FormConfig[];
+  }>();
+
+  // 验证表单
+  const validateForm = () => {
+    return new Promise((resolve) => {
+      formRef.value?.validate((valid: boolean) => {
+        resolve(valid);
+      });
+    });
+  };
+
+  // 验证表单字段
+  const validateField = (field: string) => {
+    formRef.value?.validateField(field);
+  };
+
+  defineExpose({
+    validateForm,
+    validateField,
+  });
+</script>
+
+<style lang="scss" scoped>
+  .el-form {
+    display: flex;
+    flex-direction: column;
+    width: 600cpx;
+    height: 100%;
+    gap: 12cpx;
+  }
+</style>

+ 3 - 3
src/components/BasicTable.vue

@@ -87,9 +87,9 @@
     padding: 16cpx;
   }
   .el-pagination {
-    display: flex;
-    justify-content: flex-end;
-    margin-top: 20cpx;
+    position: absolute;
+    bottom: 30cpx;
+    right: 40cpx;
   }
   :deep(.el-loading-mask) {
     background-color: rgba(0, 0, 0, 0.05);

+ 0 - 7
src/components/SvgIcon/SvgIcon.vue

@@ -16,13 +16,6 @@
    * 4. 传入width和heightprops可动态修改svg的大小
    */
 
-  // const importAll = (requireContext) => requireContext.keys().forEach(requireContext);
-  // try {
-  //     importAll(import.meta.glob('@/assets/icons/*.svg'))
-  // } catch (error) {
-  //     console.log(error)
-  // }
-
   export default defineComponent({
     name: 'SvgIcon',
     props: {

+ 5 - 0
src/components/SvgIcon/SvgIcon.vue.d.ts

@@ -0,0 +1,5 @@
+declare module '*.vue' {
+  import { DefineComponent } from 'vue';
+  const component: DefineComponent<{}, {}, any>;
+  export default component;
+}

+ 64 - 0
src/hooks/useFormConfigHook.ts

@@ -0,0 +1,64 @@
+/**
+ * 通用表单配置hook
+ * @param config 表单配置
+ * @param data 表单数据
+ * @param rules 表单规则
+ * @returns initRuleFormData 初始化表单数据
+ * @returns ruleFormData 表单数据
+ * @returns formRules 表单规则
+ * @returns cloneRuleFormData 克隆表单数据
+ * @returns validateFormData 验证表单数据
+ * @returns beforeRouteLeave 离开页面前的确认
+ * @description 用于RuleForm的配置
+ * @author Chauncey
+ */
+import { ref, reactive } from 'vue';
+import type { FormRules } from 'element-plus';
+import { cloneDeep, isEqual } from 'lodash-es';
+import { onBeforeRouteLeave } from 'vue-router';
+import { ElMessageBox } from 'element-plus';
+import type { FormConfig } from '@/types/basic-form';
+export const useFormConfigHook = <T extends Record<string, any> = Record<string, any>>(
+  config: FormConfig[],
+  data: T,
+  rules: FormRules<T>,
+) => {
+  const ruleFormConfig = reactive<FormConfig[]>(config);
+  const ruleFormData = reactive<T>(cloneDeep(data));
+  const initRuleFormData = ref<T>();
+  const formRules = reactive<FormRules<T>>(rules);
+  const cloneRuleFormData = () => {
+    initRuleFormData.value = cloneDeep(ruleFormData as T);
+  };
+  const hasFormChanged = () => {
+    return isEqual(initRuleFormData.value, ruleFormData);
+  };
+  const beforeRouteLeave = () => {
+    onBeforeRouteLeave((to, from, next) => {
+      const hasChange = hasFormChanged();
+      if (hasChange) {
+        next();
+        return;
+      }
+      setTimeout(() => {
+        ElMessageBox.confirm('当前页面存在修改,是否确认离开当前页面?', '提示', {
+          confirmButtonText: '确认',
+          cancelButtonText: '取消',
+        })
+          .then(() => {
+            next();
+          })
+          .catch(() => {
+            next(false);
+          });
+      }, 200);
+    });
+  };
+  return {
+    ruleFormConfig,
+    ruleFormData,
+    formRules,
+    cloneRuleFormData,
+    beforeRouteLeave,
+  };
+};

+ 8 - 0
src/types/basic-form/index.ts

@@ -0,0 +1,8 @@
+export interface FormConfig {
+  prop: string; // 字段名
+  label: string; // 列名
+  component?: string; // 组件名
+  componentProps?: any; // 组件属性
+  selectOptions?: any; // 下拉选项
+  slot?: string; // 自定义插槽名
+}

+ 15 - 12
src/types/disaster-warning/index.ts

@@ -1,4 +1,3 @@
-import type { UserInfo } from '@/types/push-object';
 interface BasicResponse {
   id: number;
   disasterType: string;
@@ -17,6 +16,18 @@ export interface DefenseNoticeListResponse extends BasicResponse {
   noticeTitle: string;
 }
 
+interface BasicDetailResponse {
+  isPush: boolean;
+  userGroupList?: number[];
+  createUser: string;
+}
+
+export interface WarningInfoDetailResponse
+  extends BasicDetailResponse,
+    Omit<WarningInfoListResponse, 'activeStatus' | 'pushStatus' | 'publishTime' | 'warningIcon'> {
+  source: string;
+}
+
 export interface DefenseNoticeAttachment {
   id: number;
   fileName: string;
@@ -24,17 +35,9 @@ export interface DefenseNoticeAttachment {
   fileSize: string;
   downloadUrl?: string;
 }
-export interface DefenseNoticeDetailResponse {
-  id: number;
-  noticeTitle: string;
+export interface DefenseNoticeDetailResponse
+  extends BasicDetailResponse,
+    Omit<DefenseNoticeListResponse, 'activeStatus' | 'pushStatus' | 'publishTime'> {
   noticeContent: string;
-  disasterType: string;
-  disasterLevel: string;
-  publishTime: string;
-  createUser: string;
   noticeAttachment: DefenseNoticeAttachment[];
-  isPush: boolean;
-  recipientType?: number;
-  userGroupList?: number[];
-  customUserList?: UserInfo[];
 }

+ 9 - 2
src/views/disaster/WorkRecord.md

@@ -11,6 +11,13 @@
 
 ### 25/5/14
 #### 灾害防范模块
-- [ ] 准备通用hook-form表单
+- [x] 准备通用hook-form表单
 #### 灾害预警模块
-- [ ] 完成所有静态页面开发
+- [x] 完成所有静态页面开发
+
+### 25/5/15
+### 灾害预警模块
+- [x] search引用配置项来开发
+- [x] form引用配置来开发
+- [x] 预警信息做添加页面,将表单数据进行拆分(通用项和定制项)
+- [x] 完成灾害预警模块

+ 70 - 0
src/views/disaster/components/Search.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="search-box">
+    <section class="select-box">
+      <div class="select-box--item" v-for="item in searchConfig" :key="item.prop">
+        <span>{{ item.label }}</span>
+        <component :is="item.component" v-model="searchData[item.prop]" v-bind="item.componentProps">
+          <el-option
+            v-for="option in item.selectOptions"
+            :key="option.value"
+            :label="option.label"
+            :value="option.value"
+          />
+        </component>
+      </div>
+    </section>
+    <section class="search-btn">
+      <el-button type="primary" @click="handleSearch">查询</el-button>
+      <el-button @click="handleReset">重置</el-button>
+    </section>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import type { SearchConfig } from '@/views/disaster/types';
+  const props = defineProps<{
+    searchConfig: SearchConfig[];
+    searchData: any;
+  }>();
+  const emit = defineEmits<{
+    (e: 'update:searchData', data: any): void;
+  }>();
+  const handleSearch = () => {
+    emit('update:searchData', props.searchData);
+  };
+  const handleReset = () => {
+    for (const key in props.searchData) {
+      props.searchData[key] = '';
+    }
+    emit('update:searchData', props.searchData);
+  };
+</script>
+
+<style lang="scss" scoped>
+  .search-box {
+    @include flex-center;
+    justify-content: space-between;
+    width: 100%;
+  }
+  .select-box {
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    gap: 10cpx;
+    &--item {
+      @include flex-center;
+      white-space: nowrap;
+      gap: 10cpx;
+    }
+    span {
+      color: rgba(0, 0, 0, 0.85);
+      font-size: 14cpx;
+    }
+    .el-select {
+      width: 200cpx;
+    }
+  }
+  .search-btn {
+    display: flex;
+  }
+</style>

+ 113 - 0
src/views/disaster/components/SelectGroup.vue

@@ -0,0 +1,113 @@
+<template>
+  <div class="select-group-container">
+    <el-form :model="ruleForm" ref="ruleFormRef">
+      <el-form-item
+        label="选择分组"
+        prop="userGroupList"
+        :rules="[{ required: true, message: '请选择分组' }]"
+        class="user-group-list"
+      >
+        <el-select
+          v-model="ruleForm.userGroupList"
+          multiple
+          placeholder="请选择分组"
+          filterable
+          @change="emits('userGroupListChange', ruleForm.userGroupList)"
+        >
+          <el-option v-for="item in groupOptions" :key="item.id" :value="item.id" :label="item.name" />
+        </el-select>
+        <span @click="showGroupInfo" v-if="ruleForm.userGroupList.length > 0" class="group-info-span"> 人员详情 </span>
+      </el-form-item>
+    </el-form>
+    <el-dialog
+      v-model="groupInfo"
+      title="人员详情"
+      align-center
+      :close-on-click-modal="false"
+      :destroy-on-close="true"
+      class="customDialog--pushObject"
+    >
+      <Group :userGroupInfo="userGroupInfo" />
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, reactive, watch } from 'vue';
+  import type { FormInstance } from 'element-plus';
+  import type { GroupOptionType } from '@/views/disaster/types';
+  import type { UserGroupInfo } from '@/types/push-object';
+  import { queryUserGroupDetailByIds } from '@/api/push-object';
+  import Group from './Group.vue';
+  const props = defineProps<{
+    userGroupList: number[];
+  }>();
+  interface RuleForm {
+    userGroupList: number[];
+  }
+  const groupOptions = ref<GroupOptionType[]>([
+    {
+      id: 1,
+      name: '测试1',
+    },
+    {
+      id: 2,
+      name: '测试2',
+    },
+  ]);
+  const ruleFormRef = ref<FormInstance>();
+  const ruleForm = reactive<RuleForm>({
+    userGroupList: [],
+  });
+  const groupInfo = ref(false);
+  const showGroupInfo = () => {
+    groupInfo.value = true;
+    getUserGroupInfo();
+  };
+  const userGroupInfo = ref<UserGroupInfo[]>([]);
+  const getUserGroupInfo = async () => {
+    const res = await queryUserGroupDetailByIds(ruleForm.userGroupList);
+    userGroupInfo.value = res.map((item) => ({
+      ...item,
+      isExpand: false,
+      isHidden: false,
+    }));
+  };
+  const emits = defineEmits<{
+    (e: 'userGroupListChange', userGroupList: number[]): void;
+  }>();
+  const validateForm = () => {
+    return new Promise((resolve) => {
+      ruleFormRef.value?.validate((valid) => {
+        resolve(valid);
+      });
+    });
+  };
+  defineExpose({
+    validateForm,
+  });
+  watch(
+    () => props.userGroupList,
+    (newUserGroupList) => {
+      ruleForm.userGroupList = newUserGroupList ?? [];
+    },
+    { immediate: true },
+  );
+</script>
+
+<style lang="scss" scoped>
+  .select-group-container {
+    width: 100%;
+  }
+  .user-group-list {
+    position: relative;
+  }
+  .group-info-span {
+    position: absolute;
+    left: 0;
+    bottom: -30px;
+    cursor: pointer;
+    font-size: 10cpx;
+    color: $primary-color;
+  }
+</style>

+ 22 - 68
src/views/disaster/disaster-warning/PageDefenseNotice.vue

@@ -4,16 +4,20 @@
       <span class="disaster-precaution-container__title">防御通知管理列表</span>
     </header>
     <main class="disaster-precaution-container__main">
-      <div class="defense-notice-container">
+      <div class="notice-and-info-container">
         <header class="defense-notice-container__header">
           <el-button
             type="primary"
-            class="defense-notice__header-button"
+            class="notice-and-info__header-button"
             :icon="Plus"
             @click="handleCreateDefenseNotice"
-            >创建灾害防御通知</el-button
-          >
-          <Search />
+            >创建灾害防御通知
+          </el-button>
+          <Search
+            :searchConfig="DEFENSE_NOTICE_SEARCH_CONFIG"
+            :searchData="searchData"
+            @update:searchData="handleSearch"
+          />
         </header>
         <BasicTable
           :tableConfig="tableConfig"
@@ -26,7 +30,7 @@
               <div
                 class="dot"
                 :style="{ backgroundColor: ACTIVE_STATUS_COLOR[scope.row.activeStatus as ACTIVE_STATUS] }"
-              ></div>
+              />
               <span>{{ ACTIVE_STATUS_MAP[scope.row.activeStatus] }}</span>
             </div>
           </template>
@@ -70,17 +74,17 @@
 </template>
 
 <script lang="ts" setup>
-  import { ref, onMounted } from 'vue';
+  import { ref, onMounted, reactive } from 'vue';
   import { Plus } from '@element-plus/icons-vue';
-  import Search from './src/components/Search.vue';
+  import Search from '@/views/disaster/components/Search.vue';
   import BasicTable from '@/components/BasicTable.vue';
   import ActionButton from '@/components/ActionButton.vue';
-  import type { TableColumnProps } from '@/types/basic-table';
   import useTableConfig from '@/hooks/useTableConfigHook';
   import { getDefenseNoticeList } from '@/api/disaster-warning';
   import type { DefenseNoticeListResponse } from '@/types/disaster-warning';
   import { ACTIVE_STATUS, ACTIVE_STATUS_COLOR, ACTIVE_STATUS_MAP } from '@/views/disaster/constant';
   import { PUSH_STATUS_MAP, PUSH_STATUS } from './src/constant';
+  import { DEFENSE_NOTICE_SEARCH_CONFIG, TABLE_OPTIONS, DEFENSE_NOTICE_TABLE_COLUMNS } from './src/config';
   import { useRouter } from 'vue-router';
   const tableData = ref<DefenseNoticeListResponse[]>([]);
   const router = useRouter();
@@ -105,57 +109,15 @@
       query: { id },
     });
   };
-  const columns: TableColumnProps[] = [
-    {
-      prop: 'noticeTitle',
-      label: '通知标题',
-      align: 'center',
-    },
-    {
-      prop: 'disasterType',
-      label: '灾害类型',
-      align: 'center',
-    },
-    {
-      prop: 'disasterLevel',
-      label: '灾害等级',
-      align: 'center',
-    },
-    {
-      prop: 'publishTime',
-      label: '发布时间',
-      align: 'center',
-      width: '200cpx',
-    },
-    {
-      prop: 'activeStatus',
-      label: '生效状态',
-      align: 'center',
-      slot: 'activeStatus',
-      width: '120cpx',
-    },
-    {
-      prop: 'pushStatus',
-      label: '推送',
-      align: 'center',
-      slot: 'pushStatus',
-    },
-    {
-      prop: 'action',
-      label: '操作',
-      align: 'center',
-      slot: 'action',
-      fixed: 'right',
-      width: '250cpx',
-    },
-  ];
-  const options = {
-    emptyText: '暂无数据',
-    height: '60vh',
-    loading: true,
-    stripe: true,
+  const searchData = reactive({
+    disasterType: '',
+    disasterLevel: '',
+    activeStatus: '',
+  });
+  const handleSearch = (data: any) => {
+    console.log(data);
   };
-  const { tableConfig, pagination } = useTableConfig(columns, options);
+  const { tableConfig, pagination } = useTableConfig(DEFENSE_NOTICE_TABLE_COLUMNS, TABLE_OPTIONS);
   const handleSizeChange = (value: number) => {
     pagination.pageSize = value;
     tableConfig.loading = true;
@@ -179,13 +141,5 @@
 
 <style lang="scss" scoped>
   @use '../style/disaster.scss' as *;
-  .defense-notice-container {
-    display: flex;
-    flex-direction: column;
-    gap: 20cpx;
-  }
-  .defense-notice__header-button {
-    font-size: 14cpx;
-    margin-bottom: 20cpx;
-  }
+  @use './src/common.scss' as *;
 </style>

+ 6 - 20
src/views/disaster/disaster-warning/PageDefenseNoticeItem.vue

@@ -18,6 +18,7 @@
   import { ref, computed, defineAsyncComponent } from 'vue';
   import { useRoute, useRouter } from 'vue-router';
   import BackIcon from 'assets/svg/back.svg';
+  import { ElMessage } from 'element-plus';
   const router = useRouter();
   const route = useRoute();
   const operate = route.query.operate;
@@ -45,7 +46,10 @@
     if (!dynamicComponentRef.value) return;
     const res = await dynamicComponentRef.value.handleValidate();
     if (res) {
-      console.log('提交');
+      const formData = dynamicComponentRef.value.getFormData();
+      console.log(formData);
+      ElMessage.success('提交成功');
+      router.back();
     } else {
       console.log('不提交');
     }
@@ -54,23 +58,5 @@
 
 <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;
-  }
+  @use './src/common.scss' as *;
 </style>

+ 62 - 76
src/views/disaster/disaster-warning/PageWarningInfo.vue

@@ -4,9 +4,21 @@
       <span class="disaster-precaution-container__title">预警信息列表</span>
     </header>
     <main class="disaster-precaution-container__main">
-      <div class="warning-info-container">
+      <div class="notice-and-info-container">
         <header class="warning-info-container__header">
-          <Search :statusShow="true" />
+          <el-button
+            type="primary"
+            class="notice-and-info__header-button"
+            :icon="Plus"
+            @click="handleCreateWarningInfo"
+          >
+            创建灾害预警信息
+          </el-button>
+          <Search
+            :searchConfig="WARNING_INFO_SEARCH_CONFIG"
+            :searchData="searchData"
+            @update:searchData="handleSearch"
+          />
         </header>
         <BasicTable
           :tableConfig="tableConfig"
@@ -15,14 +27,14 @@
           @update:pageNumber="handleCurrentChange"
         >
           <template #warningIcon="scope">
-            <img :src="scope.row.warningIcon ? scope.row.warningIcon : PlaceHolderWeather" alt="预警图标" />
+            <img :src="getWarningIcon(scope.row.disasterType)" alt="预警图标" class="weather-warning-icon" />
           </template>
           <template #activeStatus="scope">
             <div class="active-status--div">
               <div
                 class="dot"
                 :style="{ backgroundColor: ACTIVE_STATUS_COLOR[scope.row.activeStatus as ACTIVE_STATUS] }"
-              ></div>
+              />
               <span>{{ ACTIVE_STATUS_MAP[scope.row.activeStatus] }}</span>
             </div>
           </template>
@@ -32,6 +44,11 @@
             </span>
           </template>
           <template #action="scope">
+            <ActionButton
+              text="编辑"
+              @click="handleEditWarningInfo(scope.row.id)"
+              v-if="scope.row.activeStatus === ACTIVE_STATUS.NOT_EFFECTIVE"
+            />
             <ActionButton
               text="发布"
               :popconfirm="{
@@ -60,82 +77,51 @@
 </template>
 
 <script lang="ts" setup>
-  import { ref, onMounted } from 'vue';
-  import Search from './src/components/Search.vue';
+  import { ref, onMounted, reactive } from 'vue';
+  import { Plus } from '@element-plus/icons-vue';
+  import Search from '@/views/disaster/components/Search.vue';
   import BasicTable from '@/components/BasicTable.vue';
   import ActionButton from '@/components/ActionButton.vue';
-  import type { TableColumnProps } from '@/types/basic-table';
   import useTableConfig from '@/hooks/useTableConfigHook';
   import { getWarningInfoList } from '@/api/disaster-warning';
   import type { WarningInfoListResponse } from '@/types/disaster-warning';
   import { ACTIVE_STATUS, ACTIVE_STATUS_COLOR, ACTIVE_STATUS_MAP } from '@/views/disaster/constant';
   import PlaceHolderWeather from './src/images/placeholder-weather@1X.png';
-  import { PUSH_STATUS_MAP, PUSH_STATUS } from './src/constant';
-  const tableData = ref<WarningInfoListResponse[]>([]);
-  const columns: TableColumnProps[] = [
-    {
-      prop: 'warningIcon',
-      label: '预警图标',
-      align: 'center',
-      slot: 'warningIcon',
-    },
-    {
-      prop: 'disasterType',
-      label: '灾害类型',
-      align: 'center',
-    },
-    {
-      prop: 'disasterLevel',
-      label: '灾害等级',
-      align: 'center',
-    },
-    {
-      prop: 'warningTime',
-      label: '预警时间',
-      align: 'center',
-      width: '200cpx',
-    },
-    {
-      prop: 'warningContent',
-      label: '发布内容',
-      align: 'center',
-      width: '200cpx',
-    },
-    {
-      prop: 'activeStatus',
-      label: '生效状态',
-      align: 'center',
-      slot: 'activeStatus',
-      width: '120cpx',
-    },
-    {
-      prop: 'publishTime',
-      label: '发布时间',
-      align: 'center',
-      width: '200cpx',
-    },
-    {
-      prop: 'pushStatus',
-      label: '推送',
-      align: 'center',
-      slot: 'pushStatus',
-    },
-    {
-      prop: 'action',
-      label: '操作',
-      align: 'center',
-      slot: 'action',
-      fixed: 'right',
-      width: '250cpx',
-    },
-  ];
-  const options = {
-    emptyText: '暂无数据',
-    height: '65vh',
-    loading: true,
-    stripe: true,
+  import { PUSH_STATUS_MAP, PUSH_STATUS, WEATHER_DISASTER_ALERT_TYPE } from './src/constant';
+  import { WARNING_INFO_SEARCH_CONFIG, TABLE_OPTIONS, WARNING_INFO_TABLE_COLUMNS } from './src/config';
+  import { useRouter } from 'vue-router';
+  const router = useRouter();
+  const searchData = reactive({
+    disasterType: '',
+    disasterLevel: '',
+  });
+  const handleSearch = (data: any) => {
+    console.log(data);
   };
-  const { tableConfig, pagination } = useTableConfig(columns, options);
+  const getWarningIcon = (disasterType: string) => {
+    const icon = WEATHER_DISASTER_ALERT_TYPE.find((item) => item.label === disasterType)?.icon;
+    return icon ?? PlaceHolderWeather;
+  };
+  const defaultPath = '/disaster-prevention/disaster-warning/warning-info-item';
+  const handleCreateWarningInfo = () => {
+    router.push({
+      path: defaultPath,
+      query: {
+        operate: 'create',
+      },
+    });
+  };
+  const handleEditWarningInfo = (id: number) => {
+    router.push({
+      path: defaultPath,
+      query: {
+        operate: 'edit',
+        id,
+      },
+    });
+  };
+  const tableData = ref<WarningInfoListResponse[]>([]);
+  const { tableConfig, pagination } = useTableConfig(WARNING_INFO_TABLE_COLUMNS, TABLE_OPTIONS);
   const handleSizeChange = (value: number) => {
     pagination.pageSize = value;
     tableConfig.loading = true;
@@ -159,9 +145,9 @@
 
 <style lang="scss" scoped>
   @use '../style/disaster.scss' as *;
-  .warning-info-container {
-    display: flex;
-    flex-direction: column;
-    gap: 20cpx;
+  @use './src/common.scss' as *;
+  .weather-warning-icon {
+    width: 50cpx;
+    height: 50cpx;
   }
 </style>

+ 62 - 0
src/views/disaster/disaster-warning/PageWarningInfoItem.vue

@@ -0,0 +1,62 @@
+<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" ref="dynamicComponentRef" />
+    </main>
+    <footer class="disaster-precaution-container__footer" v-if="operate">
+      <el-button @click="router.back()">取消</el-button>
+      <el-button type="primary" @click="submit">提交</el-button>
+    </footer>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, computed, defineAsyncComponent } from 'vue';
+  import { useRoute, useRouter } from 'vue-router';
+  import BackIcon from 'assets/svg/back.svg';
+  import { ElMessage } from 'element-plus';
+  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 '未知操作';
+  });
+  const dynamicComponent = computed(() => {
+    if (operate === 'create') {
+      return defineAsyncComponent(() => import('./src/components/CreateWarningInfoItem.vue'));
+    } else if (operate === 'edit') {
+      return defineAsyncComponent(() => import('./src/components/EditWarningInfoItem.vue'));
+    } else {
+      return '';
+    }
+  });
+  const dynamicComponentRef = ref();
+  const submit = async () => {
+    if (!dynamicComponentRef.value) return;
+    const res = await dynamicComponentRef.value.handleValidate();
+    if (res) {
+      const formData = dynamicComponentRef.value.getFormData();
+      console.log(formData);
+      ElMessage.success('提交成功');
+      router.back();
+    } else {
+      console.log('不提交');
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  @use '../style/disaster.scss' as *;
+  @use './src/common.scss' as *;
+</style>

+ 14 - 0
src/views/disaster/disaster-warning/src/common.scss

@@ -0,0 +1,14 @@
+.notice-and-info-container {
+  display: flex;
+  flex-direction: column;
+  gap: 20cpx;
+}
+.notice-and-info__header-button {
+  font-size: 14cpx;
+  margin-bottom: 20cpx;
+}
+.disaster-precaution-container__header {
+  flex-direction: row !important;
+  justify-content: flex-start !important;
+  gap: 8cpx !important;
+}

+ 58 - 64
src/views/disaster/disaster-warning/src/components/CreateDefenseNoticeItem.vue

@@ -1,87 +1,81 @@
 <template>
   <div class="info-container">
-    <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="auto">
-      <el-form-item v-for="item in formConfig" :key="item.prop" :label="item.label" :prop="item.prop">
-        <template v-if="item.slot">
-          <Upload v-if="item.slot === 'noticeAttachment'" label="上传附件" @uploadSuccess="handleUploadSuccess" />
-
-          <div v-else-if="item.slot === 'isPush'" class="push-container">
-            <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"
-              ref="pushObjectRef"
-              :recipientType="ruleForm.recipientType"
-              :userGroupList="ruleForm.userGroupList"
-              :customUserList="ruleForm.customUserList"
-              @recipientTypeChange="handleRecipientTypeChange"
-              @userGroupListChange="handleUserGroupListChange"
-              @customUserListChange="handleCustomUserListChange"
-            />
-          </div>
-        </template>
-
-        <component v-else :is="item.component" v-model="ruleForm[item.prop]" v-bind="item.componentProps">
-          <el-option
-            v-for="option in item.selectOptions"
-            :key="option.value"
-            :label="option.label"
-            :value="option.value"
-          />
-        </component>
-      </el-form-item>
-    </el-form>
+    <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="formRules" :formConfig="ruleFormConfig">
+      <template #noticeAttachment>
+        <Upload label="上传附件" @uploadSuccess="handleUploadSuccess" />
+      </template>
+      <template #isPush>
+        <el-radio-group v-model="ruleFormData.isPush">
+          <el-radio :value="true">是</el-radio>
+          <el-radio :value="false">否</el-radio>
+        </el-radio-group>
+        <SelectGroup
+          v-if="ruleFormData.isPush"
+          ref="selectGroupRef"
+          :userGroupList="ruleFormData.userGroupList || []"
+          @userGroupListChange="handleUserGroupListChange"
+        />
+      </template>
+    </BasicForm>
   </div>
 </template>
 
 <script setup lang="ts">
-  import { onMounted, ref } from 'vue';
-  import PushObject from '@/views/disaster/components/PushObject.vue';
+  import BasicForm from '@/components/BasicForm.vue';
   import Upload from '@/views/disaster/components/Upload.vue';
-  import { useDefenseNoticeFormHook } from '@/views/disaster/disaster-warning/src/useFormHook';
-  import type { UserInfo } from '@/types/push-object';
-  const {
-    formConfig,
-    ruleFormRef,
-    ruleForm,
-    rules,
-    validateForm,
-    handleUploadSuccess,
-    beforeRouteLeave,
-    cloneRuleForm,
-  } = useDefenseNoticeFormHook();
-  const handleRecipientTypeChange = (recipientType: number | null) => {
-    ruleForm.recipientType = recipientType;
-  };
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { DEFENSE_NOTICE_FROM_CONFIG, DEFENSE_NOTICE_FROM_DATA, DEFENSE_NOTICE_FROM_RULES } from '../config';
+  import { onMounted, ref } from 'vue';
+  import { DefenseNoticeRuleForm } from '../type';
+  import SelectGroup from '@/views/disaster/components/SelectGroup.vue';
+  import type { FileItem } from '@/views/disaster/types';
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+  const selectGroupRef = ref<InstanceType<typeof SelectGroup>>();
+
+  const { ruleFormConfig, ruleFormData, formRules, cloneRuleFormData, beforeRouteLeave } =
+    useFormConfigHook<DefenseNoticeRuleForm>(
+      DEFENSE_NOTICE_FROM_CONFIG,
+      DEFENSE_NOTICE_FROM_DATA,
+      DEFENSE_NOTICE_FROM_RULES,
+    );
+
   const handleUserGroupListChange = (userGroupList: number[]) => {
-    ruleForm.userGroupList = userGroupList;
+    ruleFormData.userGroupList = userGroupList;
   };
-  const handleCustomUserListChange = (customUserList: UserInfo[]) => {
-    ruleForm.customUserList = customUserList;
+
+  const handleUploadSuccess = (fileList: FileItem[]) => {
+    ruleFormData.noticeAttachment = fileList;
+    if (!basicFormRef.value) return;
+    basicFormRef.value.validateField('noticeAttachment');
   };
-  const pushObjectRef = ref<InstanceType<typeof PushObject>[]>([]);
+
   const handleValidate = async () => {
-    if (!pushObjectRef.value) return false;
-    const results = await Promise.all(pushObjectRef.value.map((item) => item.validateForm()));
-    const allValid = results.every((res) => res === true);
-    const res = await validateForm();
-    return allValid && res;
+    if (!basicFormRef.value) return;
+    const parentValidateResult = await basicFormRef.value.validateForm();
+    let childValidateResult = true;
+    if (selectGroupRef.value) {
+      childValidateResult = (await selectGroupRef.value.validateForm()) as boolean;
+    }
+    return parentValidateResult && childValidateResult;
+  };
+  const getFormData = () => {
+    if (!ruleFormData.isPush) {
+      ruleFormData.userGroupList = [];
+    }
+    cloneRuleFormData();
+    return ruleFormData;
   };
   defineExpose({
     handleValidate,
+    getFormData,
   });
   onMounted(() => {
-    ruleForm.createUser = 'XXX';
-    cloneRuleForm();
+    ruleFormData.createUser = 'XXX';
+    cloneRuleFormData();
     beforeRouteLeave();
   });
 </script>
 
 <style scoped lang="scss">
   @use '@/views/disaster/style/info-container.scss' as *;
-  .push-container {
-    width: 100%;
-  }
 </style>

+ 70 - 0
src/views/disaster/disaster-warning/src/components/CreateWarningInfoItem.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="info-container">
+    <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="formRules" :formConfig="ruleFormConfig">
+      <template #isPush>
+        <el-radio-group v-model="ruleFormData.isPush">
+          <el-radio :value="true">是</el-radio>
+          <el-radio :value="false">否</el-radio>
+        </el-radio-group>
+        <SelectGroup
+          v-if="ruleFormData.isPush"
+          ref="selectGroupRef"
+          :userGroupList="ruleFormData.userGroupList || []"
+          @userGroupListChange="handleUserGroupListChange"
+        />
+      </template>
+    </BasicForm>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import BasicForm from '@/components/BasicForm.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { WARNING_INFO_FROM_CONFIG, WARNING_INFO_FROM_DATA, WARNING_INFO_FROM_RULES } from '../config';
+  import { onMounted, ref } from 'vue';
+  import { WarningInfoRuleForm } from '../type';
+  import SelectGroup from '@/views/disaster/components/SelectGroup.vue';
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+  const selectGroupRef = ref<InstanceType<typeof SelectGroup>>();
+
+  const { ruleFormConfig, ruleFormData, formRules, cloneRuleFormData, beforeRouteLeave } =
+    useFormConfigHook<WarningInfoRuleForm>(WARNING_INFO_FROM_CONFIG, WARNING_INFO_FROM_DATA, WARNING_INFO_FROM_RULES);
+
+  const handleUserGroupListChange = (userGroupList: number[]) => {
+    ruleFormData.userGroupList = userGroupList;
+  };
+
+  const handleValidate = async () => {
+    if (!basicFormRef.value) return;
+    const parentValidateResult = await basicFormRef.value.validateForm();
+    let childValidateResult = true;
+    if (selectGroupRef.value) {
+      childValidateResult = (await selectGroupRef.value.validateForm()) as boolean;
+    }
+    return parentValidateResult && childValidateResult;
+  };
+  const getFormData = () => {
+    if (!ruleFormData.isPush) {
+      ruleFormData.userGroupList = [];
+    }
+    cloneRuleFormData();
+    return ruleFormData;
+  };
+  defineExpose({
+    handleValidate,
+    getFormData,
+  });
+
+  onMounted(() => {
+    ruleFormData.createUser = 'XXX';
+    cloneRuleFormData();
+    beforeRouteLeave();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/views/disaster/style/info-container.scss' as *;
+  :deep(.el-date-editor) {
+    --el-date-editor-width: 100%;
+  }
+</style>

+ 60 - 77
src/views/disaster/disaster-warning/src/components/EditDefenseNoticeItem.vue

@@ -1,100 +1,86 @@
 <template>
   <div class="info-container">
-    <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="auto">
-      <el-form-item v-for="item in formConfig" :key="item.prop" :label="item.label" :prop="item.prop">
-        <template v-if="item.slot">
-          <Upload
-            v-if="item.slot === 'noticeAttachment'"
-            label="上传附件"
-            :fileList="ruleForm.noticeAttachment"
-            @uploadSuccess="handleUploadSuccess"
-          />
-
-          <div v-else-if="item.slot === 'isPush'" class="push-container">
-            <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"
-              :recipientType="ruleForm.recipientType"
-              :userGroupList="ruleForm.userGroupList"
-              :customUserList="ruleForm.customUserList"
-              @recipientTypeChange="handleRecipientTypeChange"
-              @userGroupListChange="handleUserGroupListChange"
-              @customUserListChange="handleCustomUserListChange"
-            />
-          </div>
-        </template>
-
-        <component v-else :is="item.component" v-model="ruleForm[item.prop]" v-bind="item.componentProps">
-          <el-option
-            v-for="option in item.selectOptions"
-            :key="option.value"
-            :label="option.label"
-            :value="option.value"
-          />
-        </component>
-      </el-form-item>
-    </el-form>
+    <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="formRules" :formConfig="ruleFormConfig">
+      <template #noticeAttachment>
+        <Upload label="上传附件" :fileList="ruleFormData.noticeAttachment" @uploadSuccess="handleUploadSuccess" />
+      </template>
+      <template #isPush>
+        <el-radio-group v-model="ruleFormData.isPush">
+          <el-radio :value="true">是</el-radio>
+          <el-radio :value="false">否</el-radio>
+        </el-radio-group>
+        <SelectGroup
+          v-if="ruleFormData.isPush"
+          ref="selectGroupRef"
+          :userGroupList="ruleFormData.userGroupList || []"
+          @userGroupListChange="handleUserGroupListChange"
+        />
+      </template>
+    </BasicForm>
   </div>
 </template>
 
 <script setup lang="ts">
-  import { onMounted, ref } from 'vue';
-  import PushObject from '@/views/disaster/components/PushObject.vue';
+  import BasicForm from '@/components/BasicForm.vue';
   import Upload from '@/views/disaster/components/Upload.vue';
-  import { useDefenseNoticeFormHook } from '@/views/disaster/disaster-warning/src/useFormHook';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { DEFENSE_NOTICE_FROM_CONFIG, DEFENSE_NOTICE_FROM_DATA, DEFENSE_NOTICE_FROM_RULES } from '../config';
+  import { onMounted, ref } from 'vue';
+  import { DefenseNoticeRuleForm } from '../type';
+  import SelectGroup from '@/views/disaster/components/SelectGroup.vue';
+  import type { FileItem } from '@/views/disaster/types';
   import { getDefenseNoticeDetail } from '@/api/disaster-warning';
-  import type { UserInfo } from '@/types/push-object';
 
   const props = defineProps<{
     id: number;
   }>();
 
-  const {
-    formConfig,
-    ruleFormRef,
-    ruleForm,
-    rules,
-    validateForm,
-    handleUploadSuccess,
-    beforeRouteLeave,
-    cloneRuleForm,
-  } = useDefenseNoticeFormHook();
-  const handleRecipientTypeChange = (recipientType: number | null) => {
-    ruleForm.recipientType = recipientType;
-  };
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+  const selectGroupRef = ref<InstanceType<typeof SelectGroup>>();
+
+  const { ruleFormConfig, ruleFormData, formRules, cloneRuleFormData, beforeRouteLeave } =
+    useFormConfigHook<DefenseNoticeRuleForm>(
+      DEFENSE_NOTICE_FROM_CONFIG,
+      DEFENSE_NOTICE_FROM_DATA,
+      DEFENSE_NOTICE_FROM_RULES,
+    );
+
   const handleUserGroupListChange = (userGroupList: number[]) => {
-    ruleForm.userGroupList = userGroupList;
+    ruleFormData.userGroupList = userGroupList;
   };
-  const handleCustomUserListChange = (customUserList: UserInfo[]) => {
-    ruleForm.customUserList = customUserList;
+
+  const handleUploadSuccess = (fileList: FileItem[]) => {
+    ruleFormData.noticeAttachment = fileList;
+    if (!basicFormRef.value) return;
+    basicFormRef.value.validateField('noticeAttachment');
   };
+
   const getDefenseNoticeDetailData = async () => {
     const res = await getDefenseNoticeDetail(props.id);
-    ruleForm.createUser = res.createUser;
-    ruleForm.disasterType = res.disasterType;
-    ruleForm.disasterLevel = res.disasterLevel;
-    ruleForm.noticeTitle = res.noticeTitle;
-    ruleForm.noticeContent = res.noticeContent;
-    ruleForm.isPush = res.isPush;
-    ruleForm.noticeAttachment = res.noticeAttachment;
-    ruleForm.recipientType = res.recipientType ?? null;
-    ruleForm.userGroupList = res.userGroupList ?? [];
-    ruleForm.customUserList = res.customUserList ?? [];
-    cloneRuleForm();
+    for (const key in res) {
+      ruleFormData[key] = res[key as keyof typeof res];
+    }
+    cloneRuleFormData();
   };
-  const pushObjectRef = ref<InstanceType<typeof PushObject>[]>([]);
   const handleValidate = async () => {
-    if (!pushObjectRef.value) return false;
-    const results = await Promise.all(pushObjectRef.value.map((item) => item.validateForm()));
-    const allValid = results.every((res) => res === true);
-    const res = await validateForm();
-    return allValid && res;
+    if (!basicFormRef.value) return;
+    const parentValidateResult = await basicFormRef.value.validateForm();
+    let childValidateResult = true;
+    if (selectGroupRef.value) {
+      childValidateResult = (await selectGroupRef.value.validateForm()) as boolean;
+    }
+    return parentValidateResult && childValidateResult;
+  };
+  const getFormData = () => {
+    if (!ruleFormData.isPush) {
+      ruleFormData.userGroupList = [];
+    }
+    cloneRuleFormData();
+    return ruleFormData;
   };
   defineExpose({
     handleValidate,
+    getFormData,
   });
   onMounted(() => {
     getDefenseNoticeDetailData();
@@ -104,7 +90,4 @@
 
 <style scoped lang="scss">
   @use '@/views/disaster/style/info-container.scss' as *;
-  .push-container {
-    width: 100%;
-  }
 </style>

+ 85 - 0
src/views/disaster/disaster-warning/src/components/EditWarningInfoItem.vue

@@ -0,0 +1,85 @@
+<template>
+  <div class="info-container">
+    <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="formRules" :formConfig="ruleFormConfig">
+      <template #isPush>
+        <el-radio-group v-model="ruleFormData.isPush">
+          <el-radio :value="true">是</el-radio>
+          <el-radio :value="false">否</el-radio>
+        </el-radio-group>
+        <SelectGroup
+          v-if="ruleFormData.isPush"
+          ref="selectGroupRef"
+          :userGroupList="ruleFormData.userGroupList || []"
+          @userGroupListChange="handleUserGroupListChange"
+        />
+      </template>
+    </BasicForm>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import BasicForm from '@/components/BasicForm.vue';
+  import SelectGroup from '@/views/disaster/components/SelectGroup.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { WARNING_INFO_FROM_CONFIG, WARNING_INFO_FROM_DATA, WARNING_INFO_FROM_RULES } from '../config';
+  import { WarningInfoRuleForm } from '../type';
+  import { getWarningInfoDetail } from '@/api/disaster-warning';
+
+  const props = defineProps<{
+    id: number;
+  }>();
+
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+  const selectGroupRef = ref<InstanceType<typeof SelectGroup>>();
+
+  const { ruleFormConfig, ruleFormData, formRules, cloneRuleFormData, beforeRouteLeave } =
+    useFormConfigHook<WarningInfoRuleForm>(WARNING_INFO_FROM_CONFIG, WARNING_INFO_FROM_DATA, WARNING_INFO_FROM_RULES);
+
+  const handleUserGroupListChange = (userGroupList: number[]) => {
+    ruleFormData.userGroupList = userGroupList;
+  };
+
+  const getWarningInfoDetailData = async () => {
+    const res = await getWarningInfoDetail(props.id);
+    for (const key in res) {
+      if (key in ruleFormData) {
+        ruleFormData[key] = res[key as keyof typeof res];
+      }
+    }
+    cloneRuleFormData();
+  };
+
+  const handleValidate = async () => {
+    if (!basicFormRef.value) return;
+    const parentValidateResult = await basicFormRef.value.validateForm();
+    let childValidateResult = true;
+    if (selectGroupRef.value) {
+      childValidateResult = (await selectGroupRef.value.validateForm()) as boolean;
+    }
+    return parentValidateResult && childValidateResult;
+  };
+  const getFormData = () => {
+    if (!ruleFormData.isPush) {
+      ruleFormData.userGroupList = [];
+    }
+    cloneRuleFormData();
+    return ruleFormData;
+  };
+  defineExpose({
+    handleValidate,
+    getFormData,
+  });
+
+  onMounted(() => {
+    getWarningInfoDetailData();
+    beforeRouteLeave();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/views/disaster/style/info-container.scss' as *;
+  :deep(.el-date-editor) {
+    --el-date-editor-width: 100%;
+  }
+</style>

+ 0 - 49
src/views/disaster/disaster-warning/src/components/Search.vue

@@ -1,49 +0,0 @@
-<template>
-  <div class="search-box">
-    <section class="select-box">
-      <div class="select-box--item">
-        <span>灾害类型:</span>
-        <el-select v-model="selectDisasterType" filterable placeholder="请选择灾害类型">
-          <el-option v-for="item in DISASTER_TYPE" :key="item.value" :label="item.label" :value="item.value" />
-        </el-select>
-      </div>
-      <div class="select-box--item">
-        <span>灾害等级:</span>
-        <el-select v-model="selectDisasterLevel" placeholder="请选择灾害等级">
-          <el-option v-for="item in DISASTER_LEVEL" :key="item.value" :label="item.label" :value="item.value" />
-        </el-select>
-      </div>
-      <div class="select-box--item" v-if="!statusShow">
-        <span>生效状态:</span>
-        <el-select v-model="selectStatus" placeholder="请选择状态">
-          <el-option
-            v-for="item in ACTIVE_STATUS_OPTIONS_WARNING"
-            :key="item.value"
-            :label="item.label"
-            :value="item.value"
-          />
-        </el-select>
-      </div>
-    </section>
-    <section class="search-btn">
-      <el-button type="primary">查询</el-button>
-      <el-button>重置</el-button>
-    </section>
-  </div>
-</template>
-
-<script lang="ts" setup>
-  import { ref } from 'vue';
-  import { ACTIVE_STATUS_OPTIONS_WARNING, DISASTER_TYPE } from '@/views/disaster/constant';
-  import { DISASTER_LEVEL } from '@/views/disaster/constant';
-  defineProps<{
-    statusShow?: boolean;
-  }>();
-  const selectDisasterType = ref('');
-  const selectDisasterLevel = ref('');
-  const selectStatus = ref('');
-</script>
-
-<style lang="scss" scoped>
-  @use '@/views/disaster/style/search.scss' as *;
-</style>

+ 182 - 0
src/views/disaster/disaster-warning/src/config/form.ts

@@ -0,0 +1,182 @@
+/**
+ * 灾害预警信息表单配置
+ */
+import { DISASTER_TYPE, DISASTER_LEVEL } from '@/views/disaster/constant';
+import { WEATHER_DISASTER_ALERT_TYPE } from '../constant';
+import type { FormConfig } from '@/types/basic-form';
+// 通用表单信息
+const BASIC_FROM_CONFIG = {
+  DISASTER_LEVEL: {
+    label: '灾害等级',
+    prop: 'disasterLevel',
+    component: 'ElSelect',
+    componentProps: {
+      placeholder: '请选择灾害等级',
+      filterable: true,
+      clearable: true,
+    },
+    selectOptions: DISASTER_LEVEL,
+  },
+  IS_PUSH: {
+    label: '是否推送',
+    prop: 'isPush',
+    slot: 'isPush',
+  },
+  CREATE_USER: {
+    label: '创建人',
+    prop: 'createUser',
+    component: 'ElInput',
+    componentProps: {
+      disabled: true,
+    },
+  },
+};
+
+// 预警信息表单信息
+export const WARNING_INFO_FROM_CONFIG: FormConfig[] = [
+  {
+    label: '灾害类型',
+    prop: 'disasterType',
+    component: 'ElSelect',
+    componentProps: {
+      placeholder: '请选择灾害类型',
+      filterable: true,
+      clearable: true,
+    },
+    selectOptions: WEATHER_DISASTER_ALERT_TYPE,
+  },
+  BASIC_FROM_CONFIG.DISASTER_LEVEL,
+  {
+    label: '预警时间',
+    prop: 'warningTime',
+    component: 'ElDatePicker',
+    componentProps: {
+      placeholder: '请选择预警时间',
+      type: 'datetime',
+      format: 'YYYY-MM-DD HH:mm',
+      'date-format': 'MMM DD, YYYY',
+      'time-format': 'HH:mm',
+    },
+  },
+  {
+    label: '信息来源',
+    prop: 'source',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '国家预警信息发布中心',
+    },
+  },
+  {
+    label: '发布内容',
+    prop: 'warningContent',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入发布内容',
+      type: 'textarea',
+      rows: 5,
+      maxlength: 1000,
+      showWordLimit: true,
+    },
+  },
+  BASIC_FROM_CONFIG.IS_PUSH,
+  BASIC_FROM_CONFIG.CREATE_USER,
+];
+
+// 防御通知表单信息
+export const DEFENSE_NOTICE_FROM_CONFIG: FormConfig[] = [
+  {
+    label: '灾害类型',
+    prop: 'disasterType',
+    component: 'ElSelect',
+    componentProps: {
+      placeholder: '请选择灾害类型',
+      filterable: true,
+      clearable: true,
+    },
+    selectOptions: DISASTER_TYPE,
+  },
+  BASIC_FROM_CONFIG.DISASTER_LEVEL,
+  {
+    label: '通知标题',
+    prop: 'noticeTitle',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入通知标题',
+      maxlength: 200,
+      showWordLimit: true,
+    },
+  },
+  {
+    label: '通知内容',
+    prop: 'noticeContent',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入通知内容',
+      type: 'textarea',
+      rows: 5,
+      maxlength: 1000,
+      showWordLimit: true,
+    },
+  },
+  {
+    label: '通知附件',
+    prop: 'noticeAttachment',
+    slot: 'noticeAttachment',
+  },
+  BASIC_FROM_CONFIG.IS_PUSH,
+  BASIC_FROM_CONFIG.CREATE_USER,
+];
+
+// 通用表单数据
+const BASIC_FROM_DATA = {
+  disasterType: '',
+  disasterLevel: '',
+  userGroupList: [],
+  isPush: null,
+  createUser: '',
+};
+
+// 预警信息表单数据
+export const WARNING_INFO_FROM_DATA = {
+  ...BASIC_FROM_DATA,
+  warningTime: '',
+  source: '',
+  warningContent: '',
+};
+
+// 防御通知表单数据
+export const DEFENSE_NOTICE_FROM_DATA = {
+  ...BASIC_FROM_DATA,
+  noticeTitle: '',
+  noticeContent: '',
+  noticeAttachment: [],
+};
+
+// 通用表单规则
+const BASIC_FROM_RULES = {
+  disasterType: [{ required: true, message: '请选择灾害类型', trigger: 'change' }],
+  disasterLevel: [
+    {
+      required: true,
+      message: '请选择灾害等级',
+      trigger: 'change',
+    },
+  ],
+  userGroupList: [{ required: true, message: '请选择用户组', trigger: 'change' }],
+  isPush: [{ required: true, message: '请选择是否推送', trigger: 'change' }],
+};
+
+// 预警信息表单规则
+export const WARNING_INFO_FROM_RULES = {
+  ...BASIC_FROM_RULES,
+  warningTime: [{ required: true, message: '请选择预警时间', trigger: 'change' }],
+  source: [{ required: true, message: '请输入信息来源', trigger: 'blur' }],
+};
+
+// 防御通知表单规则
+export const DEFENSE_NOTICE_FROM_RULES = {
+  ...BASIC_FROM_RULES,
+  noticeTitle: [{ required: true, message: '请输入通知标题', trigger: 'blur' }],
+  noticeContent: [{ required: true, message: '请输入通知内容', trigger: 'blur' }],
+  noticeAttachment: [{ required: true, message: '请上传通知附件', trigger: 'change' }],
+};

+ 24 - 0
src/views/disaster/disaster-warning/src/config/index.ts

@@ -0,0 +1,24 @@
+import { WARNING_INFO_SEARCH_CONFIG, DEFENSE_NOTICE_SEARCH_CONFIG } from './search';
+import { TABLE_OPTIONS, WARNING_INFO_TABLE_COLUMNS, DEFENSE_NOTICE_TABLE_COLUMNS } from './table';
+import {
+  WARNING_INFO_FROM_CONFIG,
+  DEFENSE_NOTICE_FROM_CONFIG,
+  WARNING_INFO_FROM_DATA,
+  DEFENSE_NOTICE_FROM_DATA,
+  WARNING_INFO_FROM_RULES,
+  DEFENSE_NOTICE_FROM_RULES,
+} from './form';
+
+export {
+  WARNING_INFO_SEARCH_CONFIG,
+  DEFENSE_NOTICE_SEARCH_CONFIG,
+  TABLE_OPTIONS,
+  WARNING_INFO_TABLE_COLUMNS,
+  DEFENSE_NOTICE_TABLE_COLUMNS,
+  WARNING_INFO_FROM_CONFIG,
+  DEFENSE_NOTICE_FROM_CONFIG,
+  WARNING_INFO_FROM_DATA,
+  DEFENSE_NOTICE_FROM_DATA,
+  WARNING_INFO_FROM_RULES,
+  DEFENSE_NOTICE_FROM_RULES,
+};

+ 54 - 0
src/views/disaster/disaster-warning/src/config/search.ts

@@ -0,0 +1,54 @@
+/**
+ * 灾害预警信息搜索配置
+ */
+import { DISASTER_TYPE, DISASTER_LEVEL, ACTIVE_STATUS_OPTIONS_WARNING } from '@/views/disaster/constant';
+import { WEATHER_DISASTER_ALERT_TYPE } from '../constant';
+import type { SearchConfig } from '@/views/disaster/types';
+// 通用搜索配置
+const BASIC_SEARCH_CONFIG = {
+  DISASTER_LEVEL: {
+    label: '灾害等级',
+    prop: 'disasterLevel',
+    component: 'ElSelect',
+    selectOptions: DISASTER_LEVEL,
+    componentProps: {
+      placeholder: '请选择灾害等级',
+    },
+  },
+};
+
+// 预警信息搜索配置
+export const WARNING_INFO_SEARCH_CONFIG: SearchConfig[] = [
+  {
+    label: '灾害类型',
+    prop: 'disasterType',
+    component: 'ElSelect',
+    selectOptions: WEATHER_DISASTER_ALERT_TYPE,
+    componentProps: {
+      placeholder: '请选择灾害类型',
+      filterable: true,
+    },
+  },
+  BASIC_SEARCH_CONFIG.DISASTER_LEVEL,
+];
+
+// 防御通知搜索配置
+export const DEFENSE_NOTICE_SEARCH_CONFIG: SearchConfig[] = [
+  {
+    label: '灾害类型',
+    prop: 'disasterType',
+    component: 'ElSelect',
+    selectOptions: DISASTER_TYPE,
+    componentProps: {
+      placeholder: '请选择灾害类型',
+      filterable: true,
+    },
+  },
+  BASIC_SEARCH_CONFIG.DISASTER_LEVEL,
+  {
+    label: '生效状态',
+    prop: 'activeStatus',
+    component: 'ElSelect',
+    selectOptions: ACTIVE_STATUS_OPTIONS_WARNING,
+  },
+];

+ 95 - 0
src/views/disaster/disaster-warning/src/config/table.ts

@@ -0,0 +1,95 @@
+/**
+ * 灾害预警信息表格配置
+ */
+import type { TableColumnProps } from '@/types/basic-table';
+// 基础表格样式配置
+export const TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  height: '60vh',
+  loading: true,
+  stripe: true,
+};
+
+// 基础表格列配置
+const BASIC_TABLE_COLUMNS = {
+  DISASTER_TYPE: {
+    prop: 'disasterType',
+    label: '灾害类型',
+    align: 'center',
+  },
+  DISASTER_LEVEL: {
+    prop: 'disasterLevel',
+    label: '灾害等级',
+    align: 'center',
+  },
+  PUBLISH_TIME: {
+    prop: 'publishTime',
+    label: '发布时间',
+    align: 'center',
+    width: '200cpx',
+  },
+  ACTIVE_STATUS: {
+    prop: 'activeStatus',
+    label: '生效状态',
+    align: 'center',
+    slot: 'activeStatus',
+    width: '120cpx',
+  },
+  PUSH_STATUS: {
+    prop: 'pushStatus',
+    label: '推送',
+    align: 'center',
+    slot: 'pushStatus',
+  },
+  ACTION: {
+    prop: 'action',
+    label: '操作',
+    align: 'center',
+    slot: 'action',
+    fixed: 'right',
+    width: '250cpx',
+  },
+};
+
+// 预警信息表格列配置
+export const WARNING_INFO_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    prop: 'warningIcon',
+    label: '预警图标',
+    align: 'center',
+    slot: 'warningIcon',
+  },
+  BASIC_TABLE_COLUMNS.DISASTER_TYPE,
+  BASIC_TABLE_COLUMNS.DISASTER_LEVEL,
+  {
+    prop: 'warningTime',
+    label: '预警时间',
+    align: 'center',
+    width: '200cpx',
+  },
+  {
+    prop: 'warningContent',
+    label: '发布内容',
+    align: 'center',
+    width: '200cpx',
+  },
+  BASIC_TABLE_COLUMNS.ACTIVE_STATUS,
+  BASIC_TABLE_COLUMNS.PUBLISH_TIME,
+  BASIC_TABLE_COLUMNS.PUSH_STATUS,
+  BASIC_TABLE_COLUMNS.ACTION,
+];
+
+// 防御通知表格列配置
+export const DEFENSE_NOTICE_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    prop: 'noticeTitle',
+    label: '通知标题',
+    align: 'center',
+  },
+  BASIC_TABLE_COLUMNS.DISASTER_TYPE,
+  BASIC_TABLE_COLUMNS.DISASTER_LEVEL,
+  BASIC_TABLE_COLUMNS.PUBLISH_TIME,
+  BASIC_TABLE_COLUMNS.ACTIVE_STATUS,
+  BASIC_TABLE_COLUMNS.PUSH_STATUS,
+  BASIC_TABLE_COLUMNS.ACTION,
+];

+ 1 - 1
src/views/disaster/disaster-warning/src/constant.ts

@@ -173,7 +173,7 @@ const disasterTypes: Record<string, DisasterType> = {
 // 气象灾害预警类型
 export const WEATHER_DISASTER_ALERT_TYPE = Object.entries(disasterTypes).flatMap(([_, disaster]) =>
   disaster.levels.map((level) => {
-    const alertText = `${disaster.name}${colorMap[level]}预警信号`;
+    const alertText = `${disaster.name}${colorMap[level]}预警`;
     return {
       label: alertText,
       value: alertText,

+ 7 - 13
src/views/disaster/disaster-warning/src/type.ts

@@ -1,15 +1,9 @@
-import type { DefenseNoticeAttachment } from '@/types/disaster-warning';
-import type { UserInfo } from '@/types/push-object';
-export interface RuleForm {
-  disasterType: string;
-  disasterLevel: string;
-  noticeTitle: string;
-  noticeContent: string;
-  noticeAttachment: DefenseNoticeAttachment[];
+import type { WarningInfoDetailResponse, DefenseNoticeDetailResponse } from '@/types/disaster-warning';
+
+interface BasicRuleForm {
   isPush: boolean | null;
-  recipientType: number | null;
-  userGroupList: number[];
-  customUserList: UserInfo[];
-  createUser: string;
-  [key: string]: any; // 添加索引签名
+  [key: string]: any;
 }
+
+export interface WarningInfoRuleForm extends BasicRuleForm, Omit<WarningInfoDetailResponse, 'id' | 'isPush'> {}
+export interface DefenseNoticeRuleForm extends BasicRuleForm, Omit<DefenseNoticeDetailResponse, 'id' | 'isPush'> {}

+ 0 - 165
src/views/disaster/disaster-warning/src/useFormHook.ts

@@ -1,165 +0,0 @@
-/**
- * 灾害防御通知表单
- */
-import { ref, reactive } from 'vue';
-import type { FormInstance, FormRules } from 'element-plus';
-import { cloneDeep, isEqual } from 'lodash-es';
-import { DISASTER_TYPE, DISASTER_LEVEL } from '@/views/disaster/constant';
-import type { FileItem } from '@/views/disaster/types';
-import type { RuleForm } from './type';
-import { onBeforeRouteLeave } from 'vue-router';
-import { ElMessageBox } from 'element-plus';
-export const useDefenseNoticeFormHook = () => {
-  const formConfig = [
-    {
-      label: '灾害类型',
-      prop: 'disasterType',
-      component: 'ElSelect',
-      componentProps: {
-        placeholder: '请选择灾害类型',
-        filterable: true,
-        clearable: true,
-      },
-      selectOptions: DISASTER_TYPE,
-    },
-    {
-      label: '灾害等级',
-      prop: 'disasterLevel',
-      component: 'ElSelect',
-      componentProps: {
-        placeholder: '请选择灾害等级',
-        filterable: true,
-        clearable: true,
-      },
-      selectOptions: DISASTER_LEVEL,
-    },
-    {
-      label: '通知标题',
-      prop: 'noticeTitle',
-      component: 'ElInput',
-      componentProps: {
-        placeholder: '请输入通知标题',
-        maxlength: 200,
-        showWordLimit: true,
-      },
-    },
-    {
-      label: '通知内容',
-      prop: 'noticeContent',
-      component: 'ElInput',
-      componentProps: {
-        placeholder: '请输入通知内容',
-        type: 'textarea',
-        rows: 5,
-        maxlength: 1000,
-        showWordLimit: true,
-      },
-    },
-    {
-      label: '通知附件',
-      prop: 'noticeAttachment',
-      slot: 'noticeAttachment',
-    },
-    {
-      label: '是否推送',
-      prop: 'isPush',
-      slot: 'isPush',
-    },
-    {
-      label: '创建人',
-      prop: 'createUser',
-      component: 'ElInput',
-      componentProps: {
-        disabled: true,
-      },
-    },
-  ];
-  const ruleFormRef = ref<FormInstance>();
-  const ruleForm = reactive<RuleForm>({
-    disasterType: '',
-    disasterLevel: '',
-    noticeTitle: '',
-    noticeContent: '',
-    noticeAttachment: [],
-    isPush: null,
-    recipientType: null,
-    userGroupList: [],
-    customUserList: [],
-    createUser: '',
-  });
-  const initRuleForm = ref<RuleForm>();
-  const cloneRuleForm = () => {
-    initRuleForm.value = cloneDeep(ruleForm);
-  };
-  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' }],
-  });
-  const validateForm = () => {
-    return new Promise((resolve) => {
-      ruleFormRef.value?.validate((valid: boolean) => {
-        resolve(valid);
-      });
-    });
-  };
-  const hasFormChanged = () => {
-    return !isEqual(initRuleForm.value, ruleForm);
-  };
-  const beforeRouteLeave = () => {
-    onBeforeRouteLeave((to, from, next) => {
-      const res = hasFormChanged();
-      if (!res) {
-        next();
-        return;
-      }
-      setTimeout(() => {
-        ElMessageBox.confirm('当前页面存在修改,是否确认离开当前页面?', '提示', {
-          confirmButtonText: '确认',
-          cancelButtonText: '取消',
-        })
-          .then(() => {
-            next();
-          })
-          .catch(() => {
-            next(false);
-          });
-      }, 200);
-    });
-  };
-  const handleUploadSuccess = (fileList: FileItem[]) => {
-    ruleForm.noticeAttachment = fileList;
-    ruleFormRef.value?.validateField('noticeAttachment');
-  };
-  return {
-    formConfig,
-    ruleFormRef,
-    ruleForm,
-    rules,
-    cloneRuleForm,
-    validateForm,
-    beforeRouteLeave,
-    handleUploadSuccess,
-  };
-};

+ 13 - 0
src/views/disaster/style/disaster.scss

@@ -18,6 +18,10 @@
     gap: 16cpx;
     justify-content: space-between;
     padding: 16cpx 0 16cpx 22cpx;
+    .back-icon {
+      width: 16cpx;
+      cursor: pointer;
+    }
   }
   &__title {
     font-size: 20cpx;
@@ -28,6 +32,15 @@
     flex: 1;
     padding: 20cpx;
   }
+  &__footer {
+    @include flex-center;
+    justify-content: flex-end;
+    width: calc(100vw - 310cpx);
+    height: 88cpx;
+    padding: 28cpx;
+    background-color: $white-color;
+    border-radius: 4cpx;
+  }
 }
 
 .active-status--div {

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

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

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

@@ -13,3 +13,12 @@ export interface TreeNodeData {
 export interface FileItem extends DefenseNoticeAttachment {
   file?: File;
 }
+
+export interface SearchConfig {
+  label: string;
+  prop: string;
+  component: string;
+  selectOptions?: any[];
+  componentProps?: any;
+  [key: string]: any; // 添加索引签名
+}

+ 2 - 5
utils/devProxy/index.ts

@@ -1,8 +1,5 @@
 import { start } from './utils';
 
-import * as config from './shangfei/proxy';
-// import * as config from './zongbu/proxy';
-// import * as config from './zhongjiancai/proxy';
-// import * as config from './local/proxy';
+import * as config from './local/proxy';
 
-// export default start(config.proxy, config.appConfigPath);
+export default start(config.proxy, config.appConfigPath);

+ 0 - 27
utils/devProxy/shangfei/app.config.js

@@ -1,27 +0,0 @@
-window.__PRODUCTION__SKYEYEADMIN__CONF__ = {
-  // document的title,以及显示在左侧导航栏的title,一般是项目的名称
-  VITE_GLOB_APP_TITLE: 'xxx33',
-  // 租户tenantCode,部分项目必填
-  VITE_GLOB_TENANT_CODE: 'shangfei',
-  // 接口前缀
-  VITE_GLOB_API_URL_PREFIX: './eye_api_bak/api',
-  // app下载地址
-  // "VITE_GLOB_APP_DOWNLOAD_QRCODE": "/apk/skyeye.apk",
-  // 登录的前端地址
-  VITE_GLOB_LOGIN_APP: '/skyeye-login-shangfei/#/',
-  // 平台跳转地址
-  VITE_GLOB_APP_PC: '/skyeyev3pc-shangfei/',
-  // simple v2
-  // simple为简单闭环处理,v2为复杂闭环处理
-  VITE_GLOB_QUESTION_LIST_VERSION: '',
-  // 为上飞定制的是否允许修改组织结构编辑,上飞不允许编辑,其他项目允许编辑
-  VITE_GLOB_DISABLE_DEPARTMENT_EDIT: true,
-  // 消息管理可选择的推送渠道
-  VITE_GLOB_NOTICE_CHANNEL: ['lanxin', 'platform'],
-};
-
-Object.freeze(window.__PRODUCTION__SKYEYEADMIN__CONF__);
-Object.defineProperty(window, '__PRODUCTION__SKYEYEADMIN__CONF__', {
-  configurable: false,
-  writable: false,
-});

+ 0 - 36
utils/devProxy/shangfei/proxy.ts

@@ -1,36 +0,0 @@
-import { PROXY_TYPE } from '../types';
-import path from 'path';
-
-// 上飞staff环境
-const proxyStaff: PROXY_TYPE = {
-  serverHost: '192.168.13.102:62/eye_api_bak',
-  loginHost: 'http://192.168.13.102:62/skyeye-login-shangfei/',
-  skyeyeFileUploadHost: 'http://192.168.13.102:62/skyeye-file-upload',
-  nvrDownload: 'http://192.168.13.102:62/nvr_download',
-
-  push_stream_host: 'http://192.168.13.102:62/push_stream_host',
-  push_stream_host_shangfei: 'http://192.168.13.102:62/push_stream_host_shangfei',
-  push_stream_host_shangfeiyuan: 'http://192.168.13.102:62/push_stream_host_shangfeiyuan',
-  push_stream_host_beiyan: 'http://192.168.13.102:62/push_stream_host_beiyan',
-  push_stream_host_default: 'http://192.168.13.102:62/push_stream_host_default',
-};
-
-// 上飞生产
-const proxyPrd: PROXY_TYPE = {
-  serverHost: 'http://172.16.23.243/skyeye-admin-shangfei/eye_api_bak',
-  loginHost: 'http://172.16.23.243/skyeye-login-shangfei/',
-  skyeyeFileUploadHost: 'http://172.16.23.243/skyeye-admin-shangfei/skyeye-file-upload',
-  nvrDownload: 'http://172.16.23.243/skyeye-admim-shangfei/nvr_download',
-
-  push_stream_host: 'http://172.16.23.243/skyeye-admin-shangfei/push_stream_host',
-  push_stream_host_shangfei: 'http://172.16.23.243/skyeye-admin-shangfei/push_stream_host_shangfei',
-  push_stream_host_shangfeiyuan: 'http://172.16.23.243/skyeye-admin-shangfei/push_stream_host_shangfeiyuan',
-  push_stream_host_beiyan: 'http://172.16.23.243/skyeye-admin-shangfei/push_stream_host_beiyan',
-  push_stream_host_default: 'http://172.16.23.243/skyeye-admin-shangfei/push_stream_host_default',
-};
-
-// 如果是要连接本地,在对应的环境把serverHost改为后端本地地址就行
-
-export const proxy = proxyPrd;
-
-export const appConfigPath = path.resolve(__dirname, 'app.config.js');

+ 0 - 25
utils/devProxy/yubei/app.config.js

@@ -1,25 +0,0 @@
-
-window.__PRODUCTION__SKYEYEADMIN__CONF__ = {
-  // document的title,以及显示在左侧导航栏的title,一般是项目的名称
-  "VITE_GLOB_APP_TITLE": "天眼安全管控公共服务平台",
-  // 租户tenantCode,部分项目必填
-  // "VITE_GLOB_TENANT_CODE": "shangfeizongbu",
-  // 接口前缀
-  "VITE_GLOB_API_URL_PREFIX": "/eye_api_bak/api",
-  // app下载地址
-  // "VITE_GLOB_APP_DOWNLOAD_QRCODE": "/apk/skyeye.apk",
-  // 登录的前端地址
-  "VITE_GLOB_LOGIN_APP": "/skyeye-login/#/",
-  // 平台跳转地址
-  "VITE_GLOB_APP_PC": "/skyeyev3pc/",
-
-  'VITE_GLOB_QUESTION_LIST_VERSION': 'simple',
-
-  // 消息管理可选择的推送渠道
-  "VITE_GLOB_NOTICE_CHANNEL": ['lanxin', 'platform',]
-};
-Object.freeze(window.__PRODUCTION__SKYEYEADMIN__CONF__);
-Object.defineProperty(window, "__PRODUCTION__SKYEYEADMIN__CONF__", {
-  configurable: false,
-  writable: false,
-});

+ 0 - 32
utils/devProxy/yubei/proxy.ts

@@ -1,32 +0,0 @@
-import { PROXY_TYPE } from '../types';
-import path from 'path';
-
-// 渝北 staff环境
-const proxyStaff: PROXY_TYPE = {
-  serverHost: '192.168.13.102:61/eye_api_bak',
-  loginHost: 'http://192.168.13.102:61/skyeye-login/',
-  skyeyeFileUploadHost: 'http://192.168.13.102:61/skyeye-file-upload',
-  nvrDownload: 'http://192.168.13.102:61/nvr_download',
-  push_stream_host: 'http://192.168.13.102:61/push_stream_host',
-  push_stream_host_shangfei: 'http://192.168.13.102:61/push_stream_host_shangfei',
-  push_stream_host_shangfeiyuan: 'http://192.168.13.102:61/push_stream_host_shangfeiyuan',
-  push_stream_host_beiyan: 'http://192.168.13.102:61/push_stream_host_beiyan',
-  push_stream_host_default: 'http://192.168.13.102:61/push_stream_host_default',
-};
-
-// 渝北生产
-const proxyPrd: PROXY_TYPE = {
-  serverHost: '58.144.197.158:19980/eye_api_bak',
-  loginHost: 'http://58.144.197.158:19980/skyeye-login/',
-  skyeyeFileUploadHost: 'http://172.16.23.243/skyeye-admin-zongbu2/skyeye-file-upload',
-  nvrDownload: 'http://172.16.23.243/skyeye-admin-zongbu2/nvr_download',
-
-  push_stream_host: 'http://58.144.197.158:19980/push_stream_host',
-
-  push_stream_host_default: 'http://58.144.197.158:19980/push_stream_host_default',
-};
-
-// 对外导出的代理
-export const proxy = proxyPrd;
-
-export const appConfigPath = path.resolve(__dirname, 'app.config.js');

+ 0 - 28
utils/devProxy/zhongjiancai/app.config.js

@@ -1,28 +0,0 @@
-
-window.__PRODUCTION__SKYEYEADMIN__CONF__ = {
-  // document的title,以及显示在左侧导航栏的title,一般是项目的名称
-  "VITE_GLOB_APP_TITLE": "智能工厂视觉管控系统",
-  // 租户tenantCode,可以不用填
-  "VITE_GLOB_TENANT_CODE": "",
-  // 接口前缀
-  "VITE_GLOB_API_URL_PREFIX": "/eye_api_bak/api",
-  // app下载地址
-  "VITE_GLOB_APP_DOWNLOAD_QRCODE": "",
-  // 登录的前端地址
-  "VITE_GLOB_LOGIN_APP": "/skyeye-login/#/",
-  // 平台跳转地址
-  "VITE_GLOB_APP_PC": "/skyeyev3pc/",
-  /** 问题闭环处理,简单处理 */
-  "VITE_GLOB_QUESTION_LIST_VERSION": 'simple',
-  // 消息管理可选择的推送渠道
-  "VITE_GLOB_NOTICE_CHANNEL": ['platform', 'wecom',]
-
-};
-
-
-
-Object.freeze(window.__PRODUCTION__SKYEYEADMIN__CONF__);
-Object.defineProperty(window, "__PRODUCTION__SKYEYEADMIN__CONF__", {
-  configurable: false,
-  writable: false,
-});

+ 0 - 32
utils/devProxy/zhongjiancai/proxy.ts

@@ -1,32 +0,0 @@
-import { PROXY_TYPE } from '../types';
-import path from 'path';
-
-export const proxyOld: PROXY_TYPE = {
-  serverHost: '192.168.13.68:8811',
-  loginHost: 'http://192.168.13.68:70/skyeye-login/',
-  skyeyeFileUploadHost: 'http://192.168.13.68:9001',
-  nvrDownload: 'http://192.168.13.68/nvr_download',
-
-  push_stream_host: 'http://192.168.13.68:8080',
-  push_stream_host_shangfei: 'http://192.168.13.73:8080',
-  push_stream_host_shangfeiyuan: 'http://192.168.13.72:8080',
-  push_stream_host_beiyan: 'http://192.168.13.72:8080',
-  push_stream_host_default: 'http://192.168.13.73:8080',
-};
-
-export const proxyStaff: PROXY_TYPE = {
-  serverHost: 'http://192.168.13.102:61/eye_api_bak',
-  loginHost: 'http://192.168.13.102:61/skyeye-login/',
-  skyeyeFileUploadHost: 'http://192.168.13.102:61/skyeye-file-upload',
-  nvrDownload: 'http://192.168.13.102:61/nvr_download',
-
-  push_stream_host: 'http://192.168.13.102:61/push_stream_host',
-  push_stream_host_shangfei: 'http://192.168.13.102:61/push_stream_host',
-  push_stream_host_shangfeiyuan: 'http://192.168.13.102:61/push_stream_hostn',
-  push_stream_host_beiyan: 'http://192.168.13.102:61/push_stream_host',
-  push_stream_host_default: 'http://192.168.13.102:61/push_stream_host_zjc',
-};
-
-export const proxy = proxyStaff;
-
-export const appConfigPath = path.resolve(__dirname, 'app.config.js');

+ 0 - 28
utils/devProxy/zongbu/app.config.js

@@ -1,28 +0,0 @@
-
-window.__PRODUCTION__SKYEYEADMIN__CONF__ = {
-  // document的title,以及显示在左侧导航栏的title,一般是项目的名称
-  "VITE_GLOB_APP_TITLE": "xxx33",
-  // 租户tenantCode,部分项目必填
-  "VITE_GLOB_TENANT_CODE": "shangfeizongbu",
-  // 接口前缀
-  "VITE_GLOB_API_URL_PREFIX": "./eye_api_bak/api",
-  // app下载地址
-  // "VITE_GLOB_APP_DOWNLOAD_QRCODE": "/apk/skyeye.apk",
-  // 登录的前端地址
-  "VITE_GLOB_LOGIN_APP": "/skyeye-login-zongbu2/#/",
-  // 平台跳转地址
-  "VITE_GLOB_APP_PC": "/skyeyev3pc-zongbu2/",
-
-  'VITE_GLOB_QUESTION_LIST_VERSION': '',
-
-  VITE_GLOB_HIDE_REPORT_MESSAGE_TABS: true,
-
-  // 消息管理可选择的推送渠道
-  "VITE_GLOB_NOTICE_CHANNEL": ['lanxin', 'platform',]
-
-};
-Object.freeze(window.__PRODUCTION__SKYEYEADMIN__CONF__);
-Object.defineProperty(window, "__PRODUCTION__SKYEYEADMIN__CONF__", {
-  configurable: false,
-  writable: false,
-});

+ 0 - 35
utils/devProxy/zongbu/proxy.ts

@@ -1,35 +0,0 @@
-import { PROXY_TYPE } from '../types';
-import path from 'path';
-
-// 总部103环境
-const proxyStaff: PROXY_TYPE = {
-  serverHost: 'http://172.16.26.103/eye_api_bak',
-  loginHost: 'http://172.16.26.103/skyeye-login/',
-  skyeyeFileUploadHost: 'http://172.16.26.103/skyeye-file-upload',
-  nvrDownload: 'http://172.16.26.103/nvr_download',
-  push_stream_host: 'http://172.16.26.103/push_stream_host',
-  push_stream_host_shangfei: 'http://172.16.26.103/push_stream_host_shangfei',
-  push_stream_host_shangfeiyuan: 'http://172.16.26.103/push_stream_host_shangfeiyuan',
-  push_stream_host_beiyan: 'http://172.16.26.103/push_stream_host_beiyan',
-  push_stream_host_default: 'http://172.16.26.103/push_stream_host_default',
-};
-
-// 总部生产
-const proxyPrd: PROXY_TYPE = {
-  serverHost: 'http://172.16.23.243/skyeye-admin-zongbu2/eye_api_bak',
-  loginHost: 'http://172.16.23.243/skyeye-login-zongbu2/',
-  skyeyeFileUploadHost: 'http://172.16.23.243/skyeye-admin-zongbu2/skyeye-file-upload',
-  nvrDownload: 'http://172.16.23.243/skyeye-admin-zongbu2/nvr_download',
-
-  push_stream_host: 'http://172.16.23.243/skyeye-admin-zongbu2/push_stream_host',
-  push_stream_host_shangfei: 'http://172.16.23.243/skyeye-admin-zongbu2/push_stream_host_shangfei',
-  push_stream_host_shangfeiyuan:
-    'http://172.16.23.243/skyeye-admin-zongbu2/push_stream_host_shangfeiyuan',
-  push_stream_host_beiyan: 'http://172.16.23.243/skyeye-admin-zongbu2/push_stream_host_beiyan',
-  push_stream_host_default: 'http://172.16.23.243/skyeye-admin-zongbu2/push_stream_host_default',
-};
-
-// 对外导出的代理
-export const proxy = proxyStaff;
-
-export const appConfigPath = path.resolve(__dirname, 'app.config.js');