瀏覽代碼

Merge branch 'feature/knowledge-base' of maoming/yjhtgl-web into dev

黄文锋 9 月之前
父節點
當前提交
785ece4afe

+ 1 - 1
.env.development

@@ -5,7 +5,7 @@ VITE_APP_TITLE = 智慧应急工作台
 VITE_APP_ENV = 'development'
 
 # 开发环境
-VITE_APP_BASE_API = 'http://10.181.7.236:9988/prod_api'
+VITE_APP_BASE_API = 'http://10.181.7.236:9988'
 
 # 应用访问路径 例如使用前缀 /admin/
 VITE_APP_CONTEXT_PATH = '/'

+ 1 - 3
.env.production

@@ -14,15 +14,13 @@ VITE_APP_MONITOR_ADMIN = '/admin/applications'
 VITE_APP_SNAILJOB_ADMIN = '/snail-job'
 
 # 生产环境
-VITE_APP_BASE_API = '/prod-api'
+VITE_APP_BASE_API = '/prod_api'
 
 # 是否在打包时开启压缩,支持 gzip 和 brotli
 VITE_BUILD_COMPRESS = gzip
 
 VITE_APP_PORT = 80
 
-# 接口加密功能开关(如需关闭 后端也必须对应关闭)
-VITE_APP_ENCRYPT = false
 # 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
 VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
 # 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换

+ 29 - 0
package-lock.json

@@ -9,6 +9,7 @@
       "version": "5.2.1",
       "license": "MIT",
       "dependencies": {
+        "@amap/amap-jsapi-loader": "^1.0.1",
         "@element-plus/icons-vue": "2.3.1",
         "@highlightjs/vue-plugin": "2.1.0",
         "@vueup/vue-quill": "1.2.0",
@@ -31,6 +32,7 @@
         "nprogress": "0.2.0",
         "pinia": "2.1.7",
         "screenfull": "6.0.2",
+        "uuid": "^10.0.0",
         "vue": "3.4.25",
         "vue-cropper": "1.1.1",
         "vue-i18n": "9.10.2",
@@ -46,6 +48,7 @@
         "@types/js-cookie": "3.0.6",
         "@types/node": "18.18.2",
         "@types/nprogress": "0.2.3",
+        "@types/uuid": "^10.0.0",
         "@typescript-eslint/eslint-plugin": "7.3.1",
         "@typescript-eslint/parser": "7.3.1",
         "@unocss/preset-attributify": "0.58.6",
@@ -80,6 +83,12 @@
         "vue-tsc": "2.0.13"
       }
     },
+    "node_modules/@amap/amap-jsapi-loader": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/@amap/amap-jsapi-loader/-/amap-jsapi-loader-1.0.1.tgz",
+      "integrity": "sha512-nPyLKt7Ow/ThHLkSvn2etQlUzqxmTVgK7bIgwdBRTg2HK5668oN7xVxkaiRe3YZEzGzfV2XgH5Jmu2T73ljejw==",
+      "license": "MIT"
+    },
     "node_modules/@ampproject/remapping": {
       "version": "2.3.0",
       "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -1817,6 +1826,13 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/uuid": {
+      "version": "10.0.0",
+      "resolved": "https://registry.npmmirror.com/@types/uuid/-/uuid-10.0.0.tgz",
+      "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/web-bluetooth": {
       "version": "0.0.20",
       "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
@@ -9963,6 +9979,19 @@
       "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
       "dev": true
     },
+    "node_modules/uuid": {
+      "version": "10.0.0",
+      "resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz",
+      "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "license": "MIT",
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
     "node_modules/vary": {
       "version": "1.1.2",
       "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz",

+ 5 - 2
package.json

@@ -18,6 +18,7 @@
     "url": "https://gitee.com/JavaLionLi/plus-ui.git"
   },
   "dependencies": {
+    "@amap/amap-jsapi-loader": "^1.0.1",
     "@element-plus/icons-vue": "2.3.1",
     "@highlightjs/vue-plugin": "2.1.0",
     "@vueup/vue-quill": "1.2.0",
@@ -40,6 +41,7 @@
     "nprogress": "0.2.0",
     "pinia": "2.1.7",
     "screenfull": "6.0.2",
+    "uuid": "^10.0.0",
     "vue": "3.4.25",
     "vue-cropper": "1.1.1",
     "vue-i18n": "9.10.2",
@@ -55,6 +57,7 @@
     "@types/js-cookie": "3.0.6",
     "@types/node": "18.18.2",
     "@types/nprogress": "0.2.3",
+    "@types/uuid": "^10.0.0",
     "@typescript-eslint/eslint-plugin": "7.3.1",
     "@typescript-eslint/parser": "7.3.1",
     "@unocss/preset-attributify": "0.58.6",
@@ -66,10 +69,10 @@
     "eslint": "8.57.0",
     "eslint-config-prettier": "9.1.0",
     "eslint-define-config": "2.1.0",
+    "eslint-plugin-import": "2.29.1",
+    "eslint-plugin-node": "11.1.0",
     "eslint-plugin-prettier": "5.1.3",
     "eslint-plugin-promise": "6.1.1",
-    "eslint-plugin-node": "11.1.0",
-    "eslint-plugin-import": "2.29.1",
     "eslint-plugin-vue": "9.23.0",
     "fast-glob": "3.3.2",
     "postcss": "8.4.36",

+ 1 - 1
src/api/system/menu/index.ts

@@ -46,7 +46,7 @@ export const tenantPackageMenuTreeselect = (packageId: string | number): AxiosPr
 // 新增菜单
 export const addMenu = (data: MenuForm) => {
   return request({
-    url: '/system/menu',
+    url: '/system/menu/create',
     method: 'post',
     data: data
   });

+ 159 - 0
src/components/ChunkUpload/index.vue

@@ -0,0 +1,159 @@
+<template>
+  <div class="chunk-upload">
+    <el-button type="primary" @click="handleClick" :disabled="uploading">
+      {{ uploading ? '上传中...' : '选择并上传文件' }}
+    </el-button>
+    <div v-for="(progress, index) in uploadProgressList" :key="index" class="progress-item">
+      <p>File {{ index + 1 }}: {{ progress.fileName }}</p>
+      <el-progress :percentage="progress.percentage"></el-progress>
+    </div>
+    <input type="file" ref="fileInput" @change="handleFileChange" style="display: none;" multiple />
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref } from "vue";
+import { ElMessage } from "element-plus";
+import axios from "axios";
+import { v1 as uuidv1 } from "uuid";
+
+axios.defaults.baseURL = "http://10.181.7.236:9988";
+
+export default defineComponent({
+  name: "ChunkUpload",
+  props: {
+    maxFileSize: {
+      type: Number,
+      default: 10 * 1024 * 1024, // 默认10MB
+    },
+    maxFiles: {
+      type: Number,
+      default: 3, // 默认最大上传3个文件
+    },
+  },
+  setup(props) {
+    const fileInput = ref<HTMLInputElement | null>(null);
+    const uploading = ref(false);
+    const uploadProgressList = ref<{ fileName: string; percentage: number }[]>([]);
+    const CHUNK_SIZE = 1 * 1024 * 1024; // 每个分片的大小为 1MB
+
+    const handleClick = () => {
+      fileInput.value?.click();
+    };
+
+    const handleFileChange = async (event: Event) => {
+      const files = (event.target as HTMLInputElement).files;
+      if (!files || files.length === 0) {
+        return;
+      }
+
+      // 检查文件数量
+      if (files.length > props.maxFiles) {
+        ElMessage.error(`最多只能上传 ${props.maxFiles} 个文件`);
+        return;
+      }
+
+      uploading.value = true;
+      uploadProgressList.value = Array.from(files).map(file => ({
+        fileName: file.name,
+        percentage: 0,
+      }));
+
+      try {
+        for (let i = 0; i < files.length; i++) {
+          const file = files[i];
+          if (file.size > props.maxFileSize) {
+            ElMessage.error(`文件 ${file.name} 大小不能超过 ${props.maxFileSize / (1024 * 1024)} MB`);
+            continue; // 跳过这个文件继续上传下一个
+          }
+
+          const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
+          const fileIdentifier = await generateFileIdentifier();
+
+          for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
+            const start = chunkIndex * CHUNK_SIZE;
+            const chunk = file.slice(start, start + CHUNK_SIZE);
+            await uploadChunk(chunk, chunkIndex, totalChunks, fileIdentifier);
+            uploadProgressList.value[i].percentage = Math.round(((chunkIndex + 1) / totalChunks) * 100);
+          }
+
+          // 文件分片上传完成后,调用合并接口
+          const uuidFilename = await mergeChunks(fileIdentifier, file.name);
+          console.log("上传成功的文件 UUID 名称:", uuidFilename);
+        }
+
+        ElMessage.success("文件上传完成!");
+      } catch (error) {
+        ElMessage.error("文件上传失败!");
+        console.error("Upload error:", error);
+      } finally {
+        uploading.value = false;
+      }
+    };
+
+    const uploadChunk = async (
+      chunk: Blob,
+      chunkNumber: number,
+      totalChunks: number,
+      fileIdentifier: string
+    ) => {
+      const formData = new FormData();
+      formData.append("file", chunk);
+
+      try {
+        await axios.post(`/api/file/upload/uploadfile`, formData, {
+          params: {
+            chunknumber: chunkNumber,
+            identifier: fileIdentifier,
+          },
+          headers: {
+            "Content-Type": "multipart/form-data",
+          },
+        });
+      } catch (error) {
+        throw new Error(`上传分片 ${chunkNumber} 失败`);
+      }
+    };
+
+    const mergeChunks = async (identifier: string, filename: string) => {
+      try {
+        const response = await axios.post("/api/file/upload/mergefile", null, {
+          params: {
+            identifier: identifier,
+            filename: filename,
+            chunkstar: 0,  // 假设所有分片的开始序号为0
+          },
+        });
+
+        if (response.status !== 200) {
+          throw new Error("文件合并失败");
+        }
+        return response.data.uuidFilename;
+      } catch (error) {
+        throw new Error("合并请求失败");
+      }
+    };
+
+    const generateFileIdentifier = async (): Promise<string> => {
+      return uuidv1();  // 生成一个固定的 UUID1
+    };
+
+    return {
+      fileInput,
+      uploading,
+      uploadProgressList,
+      handleClick,
+      handleFileChange,
+    };
+  },
+});
+</script>
+
+<style scoped>
+.chunk-upload {
+  padding: 20px;
+}
+.progress-item {
+  margin-top: 10px;
+}
+</style>

+ 35 - 0
src/components/Map/company-map.vue

@@ -0,0 +1,35 @@
+<template>
+  <div>
+    <div id="map"></div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import AMapLoader from '@amap/amap-jsapi-loader';
+
+const map = ref(null);
+
+onMounted(() => {
+  AMapLoader.load({
+    key: '30d3d8448efd68cb0b284549fd41adcf',
+    version: '2.0',
+    plugins: ['AMap.PlaceSearch']
+  }).then((AMap) => {
+    map.value = new AMap.Map('map', {
+      zoom: 10,
+      center: [110.919229,21.659751] // 茂名市
+    });
+
+
+  });
+});
+
+</script>
+
+<style scoped>
+#map {
+  width: 100%;
+  height: 400px; /* 根据需要调整地图高度 */
+}
+</style>

+ 5 - 0
src/types/components.d.ts

@@ -12,6 +12,8 @@ declare module 'vue' {
     BpmnView: typeof import('./../components/BpmnView/index.vue')['default']
     Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default']
     BuildCode: typeof import('./../components/BuildCode/index.vue')['default']
+    ChunkUpload: typeof import('./../components/ChunkUpload/index.vue')['default']
+    CompanyMap: typeof import('./../components/Map/company-map.vue')['default']
     DictTag: typeof import('./../components/DictTag/index.vue')['default']
     Editor: typeof import('./../components/Editor/index.vue')['default']
     ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
@@ -21,6 +23,7 @@ declare module 'vue' {
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElCollapse: typeof import('element-plus/es')['ElCollapse']
     ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
@@ -39,6 +42,7 @@ declare module 'vue' {
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElHeader: typeof import('element-plus/es')['ElHeader']
     ElIcon: typeof import('element-plus/es')['ElIcon']
+    ElImage: typeof import('element-plus/es')['ElImage']
     ElInput: typeof import('element-plus/es')['ElInput']
     ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
     ElLink: typeof import('element-plus/es')['ElLink']
@@ -48,6 +52,7 @@ declare module 'vue' {
     ElOption: typeof import('element-plus/es')['ElOption']
     ElPagination: typeof import('element-plus/es')['ElPagination']
     ElPopover: typeof import('element-plus/es')['ElPopover']
+    ElProgress: typeof import('element-plus/es')['ElProgress']
     ElRadio: typeof import('element-plus/es')['ElRadio']
     ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']

+ 276 - 0
src/views/duty/eventing/eventDetails.vue

@@ -0,0 +1,276 @@
+<template>
+  <div class="app-container">
+    <div class="back-btn">
+      <el-icon @click="handleBack"><Back /></el-icon>
+      返回上一级
+    </div>
+    <div class="line">
+      <div class="title">xxx公交站西侧出现积水倒灌情况</div>
+      <div class="flex">
+        <el-button type="primary">编辑</el-button>
+        <el-button type="primary">开始指挥</el-button>
+        <el-button type="danger">关闭事件</el-button>
+        <el-button type="danger">删除事件</el-button>
+      </div>
+    </div>
+    <div class="sub-title">基础信息</div>
+    <div class="line2">
+      <div class="line-item">
+        <div class="item-label">事件编号:</div>
+        <div class="item-value">YJSJ00000000001</div>
+      </div>
+      <div class="line-item">
+        <div class="item-label">事件类型:</div>
+        <div class="item-value">自然灾害</div>
+      </div>
+      <div class="line-item">
+        <div class="item-label">事件等级:</div>
+        <div class="item-value">
+          特别重大
+          <el-icon style="cursor: pointer" @click="handleEventLevelOpen"><Fold /></el-icon>
+        </div>
+      </div>
+      <div class="line-item">
+        <div class="item-label">事件状态:</div>
+        <div class="item-value">已登记</div>
+      </div>
+    </div>
+    <div class="line2">
+      <div class="line-item">
+        <div class="item-label">事发地点:</div>
+        <div class="item-value link">茂名市茂南区xxxx路道路左侧路面</div>
+      </div>
+      <div class="line-item">
+        <div class="item-label">事发时间:</div>
+        <div class="item-value">2024-08-18 17:18</div>
+      </div>
+      <div class="line-item">
+        <div class="item-label">上报时间:</div>
+        <div class="item-value">2024-08-18 17:25</div>
+      </div>
+      <div class="line-item">
+        <div class="item-label">伤亡情况:</div>
+        <div class="flex">
+          <span>未上报</span>
+          <span class="link" style="margin-left: 20px">去上报</span>
+        </div>
+      </div>
+    </div>
+    <div class="line2">
+      <div class="line-item">
+        <div class="item-label">登记人:</div>
+        <div class="item-value">张三</div>
+      </div>
+      <div class="line-item">
+        <div class="item-label">登记时间:</div>
+        <div class="item-value">2024-08-18 17:30</div>
+      </div>
+      <div class="line-item">
+        <div class="item-label">联系方式:</div>
+        <div class="item-value">18688888888</div>
+      </div>
+      <div class="line-item">
+        <div class="item-label">事件来源:</div>
+        <div class="item-value">110报送</div>
+      </div>
+    </div>
+    <div class="sub-title">事件概要</div>
+    <div class="textarea">2024年7月29日14时52分,接xxx报,7月29日12时02分,茂名市xxx公交站西侧出现积水倒灌情况。</div>
+    <div class="sub-title">事件跟踪</div>
+    <el-steps :active="eventTrackState.active" :align-center="true" style="width: 100%">
+      <el-step v-for="(item, index) in eventTrackState.listData" :key="index" :title="item.title" :description="item.description"></el-step>
+    </el-steps>
+    <div class="sub-title">指挥记录</div>
+    <div class="list">
+      <div class="list-item">
+        <i class="img"></i>
+        <div class="item-title">大屏指挥记录</div>
+      </div>
+      <div class="list-item">
+        <i class="img"></i>
+        <div class="item-title">中屏指挥记录</div>
+      </div>
+    </div>
+    <div class="sub-title">匹配预案</div>
+    <div class="list">
+      <div class="list-item2">预案名称11111111</div>
+    </div>
+    <div class="sub-title">事件总结报告</div>
+    <div class="list2">
+      <div class="list-item">
+        <div class="link">关于X市X镇XXX公交站西侧出现积水倒灌事故报告.pdf</div>
+        <el-icon class="icon"><Download /></el-icon>
+      </div>
+    </div>
+    <el-dialog v-model="eventLevelState.show" title="事件等级" width="500" :before-close="handleEventLevelClose">
+      <el-timeline>
+        <el-timeline-item
+          v-for="(item, index) in eventLevelState.data"
+          :key="index"
+          :timestamp="item.timestamp + '&nbsp;&nbsp;&nbsp;&nbsp;' + item.name"
+          placement="top"
+        >
+          <div>
+            <dict-tag :options="mm_event_level" :value="item.levelStatus" />
+          </div>
+        </el-timeline-item>
+      </el-timeline>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+const router = useRouter();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { mm_event_level } = toRefs<any>(proxy?.useDict('mm_event_level'));
+// 事件等级数据
+const eventLevelState = reactive({
+  show: false,
+  data: []
+});
+const handleEventLevelOpen = () => {
+  eventLevelState.show = true;
+  eventLevelState.data = [
+    {
+      timestamp: '2024/7/1 09:00:09',
+      name: '张佳佳',
+      levelStatus: '4'
+    },
+    {
+      timestamp: '2024/7/1 09:00:09',
+      name: '张佳佳',
+      levelStatus: '3'
+    },
+    {
+      timestamp: '2024/7/1 09:00:09',
+      name: '张佳佳',
+      levelStatus: '2'
+    },
+    {
+      timestamp: '2024/7/1 09:00:09',
+      name: '张佳佳',
+      levelStatus: '1'
+    }
+  ];
+};
+const handleEventLevelClose = () => {
+  eventLevelState.show = false;
+};
+
+// 事件跟踪数据
+const eventTrackState = reactive({
+  active: 2,
+  listData: [
+    {
+      title: '事件登记',
+      description: '3月01日17:30'
+    },
+    {
+      title: '进入指挥',
+      description: '3月01日17:30'
+    },
+    {
+      title: '结束指挥',
+      description: ''
+    },
+    {
+      title: '关闭事件',
+      description: ''
+    }
+  ]
+});
+// 返回上一级
+const handleBack = () => {
+  router.back();
+};
+</script>
+
+<style lang="scss" scoped>
+.app-container {
+  .line {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    width: 100%;
+    margin-top: 15px;
+    .title {
+      font-weight: bold;
+      font-size: 24px;
+    }
+  }
+  .sub-title {
+    font-size: 18px;
+    font-weight: bold;
+    line-height: 35px;
+    margin-top: 20px;
+  }
+  .line2 {
+    display: flex;
+    align-content: center;
+    .line-item {
+      flex: 1;
+      font-size: 16px;
+      line-height: 35px;
+      display: flex;
+      .item-label {
+        width: 80px;
+      }
+    }
+  }
+  .textarea {
+    background-color: #f2f2f2;
+    padding: 10px;
+    width: 100%;
+    min-height: 83px;
+  }
+  .list {
+    display: flex;
+    .list-item {
+      display: flex;
+      flex-direction: column;
+      align-content: center;
+      justify-content: center;
+      margin-right: 40px;
+      cursor: pointer;
+      .img {
+        width: 180px;
+        height: 90px;
+        background-color: #cccccc;
+      }
+      .item-title {
+        text-align: center;
+        margin-top: 6px;
+      }
+      &:last-child {
+        margin-right: 0;
+      }
+    }
+    .list-item2 {
+      margin-right: 40px;
+      cursor: pointer;
+      &:last-child {
+        margin-right: 0;
+      }
+    }
+  }
+  .list2 {
+    .list-item {
+      display: flex;
+      align-items: center;
+      .icon {
+        margin-left: 10px;
+        cursor: pointer;
+      }
+    }
+  }
+  .link {
+    color: #2598ff;
+    cursor: pointer;
+  }
+}
+:deep(.warning2) {
+  background-color: #fdfbe2;
+  border-color: #fad400 !important;
+  color: #fad400;
+}
+</style>

+ 438 - 0
src/views/duty/eventing/index.vue

@@ -0,0 +1,438 @@
+<template xmlns="">
+  <div class="app-container">
+    <transition name="fade">
+      <div v-show="showSearch" class="mb-[20px]">
+        <el-form ref="queryFormRef" :model="queryParams">
+          <el-row :gutter="10">
+            <!-- 第一行 -->
+            <el-col :span="6">
+              <el-form-item label="事件类型" prop="eventType">
+                <el-select v-model="queryParams.eventType" placeholder="全部" clearable>
+                  <el-option label="全部" value=""></el-option>
+                  <el-option
+                    v-for="item in data.eventTypeSelection"
+                    :key="item.dictValue"
+                    :label="item.dictLabel"
+                    :value="item.dictValue"
+                  ></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="事件等级" prop="eventLevel">
+                <el-select v-model="queryParams.eventLevel" placeholder="全部" clearable>
+                  <el-option label="全部" value=""></el-option>
+                  <el-option
+                    v-for="item in data.eventLevelSelection"
+                    :key="item.dictValue"
+                    :label="item.dictLabel"
+                    :value="item.dictValue"
+                  ></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="事件状态" prop="eventStatus">
+                <el-select v-model="queryParams.eventStatus" placeholder="全部" clearable>
+                  <el-option label="全部" value=""></el-option>
+                  <el-option
+                    v-for="item in data.eventStatusSelection"
+                    :key="item.dictValue"
+                    :label="item.dictLabel"
+                    :value="item.dictValue"
+                  ></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="行政区划" prop="regionCode">
+                <el-select v-model="queryParams.regionCode" placeholder="全部" clearable>
+                  <el-option label="全部" value=""></el-option>
+                  <el-option
+                    v-for="item in data.regionSelection"
+                    :key="item.dictValue"
+                    :label="item.dictLabel"
+                    :value="item.dictValue"
+                  ></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="10" class="mt-[10px]">
+            <el-col :span="6">
+              <el-form-item label="事发时间" prop="eventTime">
+                <el-date-picker
+                  v-model="queryParams.eventTime"
+                  type="datetime"
+                  placeholder="选择事发时间"
+                ></el-date-picker>
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-form-item>
+                <el-input v-model="queryParams.keyword" placeholder="请输入事件标题/事件地点" clearable
+                          @keyup.enter="handleQuery"/>
+              </el-form-item>
+            </el-col>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+          </el-row>
+        </el-form>
+      </div>
+    </transition>
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="Plus" @click="handleAdd">新增事件</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete(selectedRow)">删除事件
+        </el-button>
+      </el-col>
+    </el-row>
+
+    <!-- 表格组w件 -->
+    <el-table v-loading="loading" :data="eventList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center"/>
+      <el-table-column label="事件编号" align="center" prop="eventId"/>
+      <el-table-column label="事件标题" align="center" prop="eventTitle"/>
+      <el-table-column label="事件类型" align="center" prop="eventType"/>
+      <el-table-column label="事件等级" align="center" prop="eventLevel"/>
+      <el-table-column label="事件地点" align="center" prop="address"/>
+      <el-table-column label="事件状态" align="center" prop="eventStatus"/>
+      <el-table-column label="事件时间" align="center" prop="eventTime"/>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template #default="scope">
+          <el-tooltip content="查看" placement="top">
+            <el-button link type="primary" icon="View" @click="handleView(scope.row)"></el-button>
+          </el-tooltip>
+          <el-tooltip content="编辑" placement="top">
+            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
+          </el-tooltip>
+          <el-tooltip content="关闭事件" placement="top">
+            <el-button link type="primary" icon="Close" @click="handleClose(scope.row)"></el-button>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
+                :total="total" @pagination="getList"/>
+
+    <!-- 新增/修改弹窗 -->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
+      <el-form ref="eventFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="事件标题" prop="eventTitle">
+          <el-input v-model="form.eventTitle" placeholder="请输入事件标题"/>
+        </el-form-item>
+        <el-form-item label="事件类型" prop="eventType">
+          <el-select v-model="form.eventType" placeholder="请选择事件类型" clearable>
+            <el-option
+              v-for="item in data.eventTypeSelection"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="事件等级" prop="eventLevel">
+          <el-select v-model="form.eventLevel" placeholder="请选择事件等级" clearable>
+            <el-option
+              v-for="item in data.eventLevelSelection"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="事件状态" prop="eventStatus">
+          <el-select v-model="form.eventStatus" placeholder="请选择事件状态" clearable>
+            <el-option
+              v-for="item in data.eventStatusSelection"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="事件地点" prop="address">
+          <el-button type="primary" @click="openMapDialog">地图定位</el-button>
+          <el-input v-model="form.address" placeholder="请输入事件地点" readonly />
+        </el-form-item>
+        <el-form-item label="事发时间" prop="eventTime">
+          <el-date-picker
+            v-model="form.eventTime"
+            type="datetime"
+            placeholder="选择事发时间"
+          ></el-date-picker>
+        </el-form-item>
+        <el-form-item label="上报时间" prop="reportTime">
+          <el-date-picker
+            v-model="form.reportTime"
+            type="datetime"
+            placeholder="选择上报时间"
+          ></el-date-picker>
+        </el-form-item>
+        <el-form-item label="伤亡情况">
+          <el-row :gutter="10">
+            <el-col :span="6">
+              <el-input v-model="form.deceased" placeholder="死亡">
+                <template #suffix>人</template>
+              </el-input>
+            </el-col>
+            <el-col :span="6">
+              <el-input v-model="form.injured" placeholder="受伤">
+                <template #suffix>人</template>
+              </el-input>
+            </el-col>
+            <el-col :span="6">
+              <el-input v-model="form.missing" placeholder="失踪">
+                <template #suffix>人</template>
+              </el-input>
+            </el-col>
+          </el-row>
+        </el-form-item>
+        <el-form-item label="事件来源" prop="eventSource">
+          <el-input v-model="form.eventSource" placeholder="请输入事件来源"/>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确定</el-button>
+          <el-button @click="cancel">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 地图弹窗 -->
+    <el-dialog v-model="mapDialogVisible" title="地图定位" width="600px">
+      <company-map :visible.sync="mapDialogVisible" :address="form.address" @Change="handleMapChange"></company-map>
+      <template #footer>
+        <el-button @click="mapDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="confirmMapLocation">确定</el-button>
+      </template>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, toRefs, onMounted } from 'vue';
+import { ElMessage } from 'element-plus';
+import { getDicts } from "@/api/system/dict/data";
+
+const eventList = ref([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const selectedRow = ref(null);
+
+const queryFormRef = ref();
+const eventFormRef = ref();
+
+const dialog = reactive({
+  visible: false,
+  title: ''
+});
+
+const initFormData = {
+  eventId: '',
+  eventTitle: '',
+  eventType: '',
+  eventLevel: '',
+  eventStatus: '',
+  address: '',
+  eventTime: '',
+  reportTime: '',
+  deceased: '',
+  injured: '',
+  missing: '',
+  eventSource: ''
+};
+
+const data = reactive({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    eventType: '',
+    eventLevel: '',
+    eventStatus: '',
+    eventTime: '',
+    regionCode: '',
+    keyword: ''
+  },
+  rules: {
+    eventTitle: [{ required: true, message: '事件标题不能为空', trigger: 'blur' }],
+    eventType: [{ required: true, message: '事件类型不能为空', trigger: 'blur' }],
+    eventLevel: [{ required: true, message: '事件等级不能为空', trigger: 'blur' }],
+    eventStatus: [{ required: true, message: '事件状态不能为空', trigger: 'blur' }],
+    address: [{ required: true, message: '事件地点不能为空', trigger: 'blur' }],
+    eventTime: [{ required: true, message: '事发时间不能为空', trigger: 'blur' }],
+    reportTime: [{ required: true, message: '上报时间不能为空', trigger: 'blur' }],
+    eventSource: [{ required: true, message: '事件来源不能为空', trigger: 'blur' }]
+  },
+  eventTypeSelection: [],
+  eventLevelSelection: [],
+  eventStatusSelection: [],
+  regionSelection: []
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+const getList = async () => {
+  loading.value = true;
+  try {
+    const response = await fetchEvents(queryParams.value);
+    const { data, total } = response;
+    eventList.value = data;
+    total.value = total;
+  } catch (error) {
+    ElMessage.error('获取数据失败');
+  } finally {
+    loading.value = false;
+  }
+};
+
+const fetchEvents = async (params) => {
+  // 假设后端接口为 /api/events
+  const response = await fetch('/api/events', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    body: JSON.stringify(params)
+  });
+  const result = await response.json();
+  if (response.ok) {
+    return result;
+  } else {
+    throw new Error(result.message || '获取事件列表失败');
+  }
+};
+
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+const reset = () => {
+  form.value = { ...initFormData };
+  eventFormRef.value?.resetFields();
+};
+
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+const resetQuery = () => {
+  queryParams.value = { pageNum: 1, pageSize: 10, eventType: '', eventLevel: '', eventStatus: '', eventTime: '', regionCode: '', keyword: '' };
+  handleQuery();
+};
+
+const handleSelectionChange = (selection) => {
+  ids.value = selection.map((item) => item.eventId);
+  selectedRow.value = selection.length === 1 ? selection[0] : null;
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '新增事件';
+};
+
+// 修改事件
+const handleUpdate = (row) => {
+  if (row) {
+    reset();
+    Object.assign(form.value, row);
+    dialog.visible = true;
+    dialog.title = '修改事件';
+  }
+};
+
+// 提交表单
+const submitForm = () => {
+  eventFormRef.value?.validate((valid) => {
+    if (valid) {
+      buttonLoading.value = true;
+      setTimeout(() => {
+        if (form.value.eventId) {
+          // 更新逻辑
+        } else {
+          // 添加逻辑
+        }
+        buttonLoading.value = false;
+        dialog.visible = false;
+        getList();
+      }, 500);
+    }
+  });
+};
+
+const handleDelete = (row) => {
+  if (row) {
+    // 删除逻辑
+    setTimeout(() => {
+      eventList.value = eventList.value.filter((item) => item.eventId !== row.eventId);
+      getList();
+    }, 500);
+  }
+};
+
+// 关闭事件
+const handleClose = (row) => {
+  if (row) {
+    // 关闭逻辑
+    setTimeout(() => {
+      row.eventStatus = '3'; // 已关闭
+      getList();
+    }, 500);
+  }
+};
+
+// 查看事件详情
+const handleView = (row) => {
+  if (row) {
+    // 查看事件详情逻辑
+    console.log('查看事件详情', row);
+  }
+};
+
+// 地图定位
+const mapDialogVisible = ref(false);
+
+const openMapDialog = () => {
+  mapDialogVisible.value = true;
+};
+
+const handleMapChange = (lnglat) => {
+  form.value.address = `${lnglat[0]}, ${lnglat[1]}`;
+};
+
+const confirmMapLocation = () => {
+  mapDialogVisible.value = false;
+};
+
+
+onMounted(() => {
+  getList();
+  Promise.all([
+    getDicts("mm_event_type"),
+    getDicts("mm_event_level"),
+    getDicts("mm_event_state"),
+    getDicts("region")
+  ]).then(([eventTypeRes, eventLevelRes, eventStatusRes, regionRes]) => {
+    data.eventTypeSelection = eventTypeRes.data;
+    data.eventLevelSelection = eventLevelRes.data;
+    data.eventStatusSelection = eventStatusRes.data;
+    data.regionSelection = regionRes.data;
+  });
+});
+</script>

+ 137 - 0
src/views/knowledge/knowledge-management/detail.vue

@@ -0,0 +1,137 @@
+<template>
+  <div class="report-details">
+    <el-button type="text" @click="goBack">返回上一级</el-button>
+    <el-row>
+      <el-col :span="20">
+        <h2>{{ report.title }}</h2>
+      </el-col>
+      <el-col :span="4" class="text-right">
+        <el-button type="primary" @click="handleEdit">编辑</el-button>
+        <el-button type="danger" @click="handleDelete">删除</el-button>
+      </el-col>
+    </el-row>
+    <el-divider></el-divider>
+    <div class="basic-info">
+      <strong>基本信息</strong>
+      <el-row>
+        <el-col :span="8">报告编号:{{ report.reportId }}</el-col>
+        <el-col :span="8">主题词:{{ report.keyword }}</el-col>
+        <el-col :span="8">事件类型:{{ report.eventType }}</el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="8">发布日期:{{ report.publishDate }}</el-col>
+        <el-col :span="8">来源单位:{{ report.sourceUnit }}</el-col>
+        <el-col :span="8">通知类型:{{ report.notificationType }}</el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="24">摘要:{{ report.summary }}</el-col>
+      </el-row>
+    </div>
+    <div class="attachments">
+      <strong>报告附件:</strong>
+    </div>
+    <el-link :href="report.attachmentUrl" target="_blank">{{ report.attachmentName }}</el-link>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { ElMessageBox, ElMessage } from 'element-plus';
+
+const report = ref({
+  reportId: '',
+  title: '',
+  keyword: '',
+  eventType: '',
+  publishDate: '',
+  sourceUnit: '',
+  notificationType: '',
+  summary: '',
+  attachmentName: '',
+  attachmentUrl: ''
+});
+
+const route = useRoute();
+const router = useRouter();
+
+const goBack = () => {
+  router.go(-1);
+};
+
+const handleEdit = () => {
+  // 编辑逻辑
+  ElMessageBox.prompt('编辑报告', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    inputValue: report.value.title
+  }).then(({ value }) => {
+    report.value.title = value;
+    ElMessage({
+      type: 'success',
+      message: '报告已更新'
+    });
+  }).catch(() => {
+    ElMessage({
+      type: 'info',
+      message: '已取消编辑'
+    });
+  });
+};
+
+const handleDelete = () => {
+  // 删除逻辑
+  ElMessageBox.confirm('此操作将永久删除该报告, 是否继续?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(() => {
+    ElMessage({
+      type: 'success',
+      message: '报告已删除'
+    });
+    router.push('/');
+  }).catch(() => {
+    ElMessage({
+      type: 'info',
+      message: '已取消删除'
+    });
+  });
+};
+
+onMounted(() => {
+  const reportId = route.params.reportId;
+
+  // 模拟数据对象
+  const mockData = {
+    reportId: 'YJYA0000000001',
+    title: '广东省城市轨道交通运营突发事件总结报告',
+    keyword: '轨道交通',
+    eventType: '自然灾害',
+    publishDate: '2024-07-11 16:09:09',
+    sourceUnit: '茂名市应急管理局',
+    notificationType: '总结报告',
+    summary: '广东省城市轨道交通运营突发事件总结报告。',
+    attachmentName: '关于X市公交XXX公交车司机出现状况的调查报告.pdf',
+    attachmentUrl: '/path/to/attachment.pdf'
+  };
+
+  // 直接赋值给 report
+  report.value = mockData;
+});
+</script>
+
+<style scoped>
+.report-details {
+  padding: 20px;
+  background-color: #fff;
+}
+
+.basic-info {
+  margin-bottom: 20px;
+}
+
+.attachments {
+  margin-bottom: 20px;
+}
+</style>

+ 317 - 0
src/views/knowledge/knowledge-management/index.vue

@@ -0,0 +1,317 @@
+<template>
+  <div class="app-container">
+    <transition name="fade">
+      <div v-show="showSearch" class="mb-[10px]">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item style="width: 200px" label="事件类型" prop="eventType">
+              <el-select v-model="queryParams.eventType" placeholder="全部" clearable>
+                <el-option label="全部" value=""></el-option>
+                <el-option
+                  v-for="item in data.eventTypeSelection"
+                  :key="item.dictValue"
+                  :label="item.dictLabel"
+                  :value="item.dictValue"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="发布日期" prop="publishDate">
+              <el-date-picker
+                v-model="queryParams.publishDate"
+                type="daterange"
+                range-separator="-"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                value-format="yyyy-MM-dd"
+              ></el-date-picker>
+            </el-form-item>
+            <el-form-item>
+              <el-input v-model="queryParams.keyword" placeholder="请输入报告的关键词/主题词" clearable
+                        @keyup.enter="handleQuery"/>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+            </el-form-item>
+            <el-form-item>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+      </div>
+    </transition>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate(selectedRow)">修改
+            </el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete(selectedRow)">删除
+            </el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport">导出</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
+        </el-row>
+
+      <!--      表格组件-->
+      <el-table v-loading="loading" :data="demoList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center"/>
+        <el-table-column label="报告编号" align="center" prop="reportNumber"/>
+        <el-table-column label="报告名称" align="center" prop="reportName"/>
+        <el-table-column label="主题词" align="center" prop="keyword"/>
+        <el-table-column label="事件类型" align="center" prop="eventType"/>
+        <el-table-column label="摘要" align="center" prop="summary"/>
+        <el-table-column label="来源单位" align="center" prop="sourceUnit"/>
+        <el-table-column label="发布日期" align="center" prop="publishDate"/>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="查看" placement="top">
+              <el-button link type="primary" icon="View" @click="handleView(scope.row)"></el-button>
+            </el-tooltip>
+            <el-tooltip content="修改" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
+                  :total="total" @pagination="getList"/>
+
+    <!--    新增/修改弹窗-->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
+      <el-form ref="demoFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="报告编号" prop="reportNumber">
+          <el-input v-model="form.reportNumber" placeholder="请输入报告编号"/>
+        </el-form-item>
+        <el-form-item label="报告名称" prop="reportName">
+          <el-input v-model="form.reportName" placeholder="请输入报告名称"/>
+        </el-form-item>
+        <el-form-item label="主题词" prop="keyword">
+          <el-input v-model="form.keyword" placeholder="请输入主题词"/>
+        </el-form-item>
+        <el-form-item label="事件类型" prop="eventType">
+          <el-select v-model="form.eventType" placeholder="请选择事件类型" clearable>
+            <el-option
+              v-for="item in data.eventTypeSelection"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="摘要" prop="summary">
+          <el-input v-model="form.summary" placeholder="请输入摘要"/>
+        </el-form-item>
+        <el-form-item label="来源单位" prop="sourceUnit">
+          <el-input v-model="form.sourceUnit" placeholder="请输入来源单位"/>
+        </el-form-item>
+        <el-form-item label="发布日期" prop="publishDate">
+          <el-date-picker
+            v-model="form.publishDate"
+            type="date"
+            placeholder="选择发布日期"
+            value-format="yyyy-MM-dd"
+          ></el-date-picker>
+        </el-form-item>
+        <el-col :span="1.5">
+          <!-- 使用分片上传组件,每个分片-->
+          <chunk-upload :max-file-size="50 * 1024 * 1024" :max-files="5" />
+        </el-col>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确定</el-button>
+          <el-button @click="cancel">取消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import {ref, reactive, toRefs, onMounted} from 'vue';
+import {getDicts} from "@/api/system/dict/data";
+import {ElMessage} from 'element-plus';
+import ChunkUpload from '@/components/ChunkUpload/index.vue';
+
+const demoList = ref([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const selectedRow = ref(null);
+
+const queryFormRef = ref();
+const demoFormRef = ref();
+
+// const fileList = ref([]);
+
+const dialog = reactive({
+  visible: false,
+  title: ''
+});
+
+const initFormData = {
+  reportNumber: '',
+  reportName: '',
+  keyword: '',
+  eventType: '',
+  summary: '',
+  sourceUnit: '',
+  publishDate: ''
+};
+
+const data = reactive({
+  form: {...initFormData},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    eventType: '',
+    publishDate: ['', ''],
+    keyword: ''
+  },
+  rules: {
+    reportNumber: [{required: true, message: '报告编号不能为空', trigger: 'blur'}],
+    reportName: [{required: true, message: '报告名称不能为空', trigger: 'blur'}],
+    keyword: [{required: true, message: '主题词不能为空', trigger: 'blur'}],
+    eventType: [{required: true, message: '事件类型不能为空', trigger: 'blur'}],
+    summary: [{required: true, message: '摘要不能为空', trigger: 'blur'}],
+    sourceUnit: [{required: true, message: '来源单位不能为空', trigger: 'blur'}],
+    publishDate: [{required: true, message: '发布日期不能为空', trigger: 'blur'}]
+  },
+  eventTypeSelection: []
+});
+
+const {queryParams, form, rules} = toRefs(data);
+
+const getList = async () => {
+  loading.value = true;
+  try {
+    const response = await fetchReports(queryParams.value);
+    const {data, total} = response;
+    demoList.value = data;
+    total.value = total;
+  } catch (error) {
+    ElMessage.error('获取数据失败');
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 获取报告列表
+const fetchReports = async (params) => {
+  // 假设后端接口为 /api/reports
+  const response = await fetch('/api/reports', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    body: JSON.stringify(params)
+  });
+  const result = await response.json();
+  if (response.ok) {
+    return result;
+  } else {
+    throw new Error(result.message || '获取报告列表失败');
+  }
+};
+
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+const reset = () => {
+  form.value = {...initFormData};
+  demoFormRef.value?.resetFields();
+};
+
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+const resetQuery = () => {
+  queryParams.value = {pageNum: 1, pageSize: 10, eventType: '', publishDate: ['', ''], keyword: ''};
+  handleQuery();
+};
+
+const handleSelectionChange = (selection) => {
+  ids.value = selection.map((item) => item.reportNumber);
+  selectedRow.value = selection.length === 1 ? selection[0] : null;
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加报告';
+};
+
+// 查看报告
+const handleView = (row) => {
+  // 跳转到总结报告详情页
+  window.location.href = `/report/details/${row.reportId}`;
+};
+
+// 修改报告
+const handleUpdate = (row) => {
+  if (row) {
+    reset();
+    Object.assign(form.value, row);
+    dialog.visible = true;
+    dialog.title = '修改报告';
+  }
+};
+ // 提交表单
+const submitForm = () => {
+  demoFormRef.value?.validate((valid) => {
+    if (valid) {
+      buttonLoading.value = true;
+      setTimeout(() => {
+        if (form.value.reportNumber) {
+          // 更新逻辑
+        } else {
+          // 添加逻辑
+        }
+        buttonLoading.value = false;
+        dialog.visible = false;
+        getList();
+      }, 500);
+    }
+  });
+};
+
+const handleDelete = (row) => {
+  if (row) {
+    // 删除逻辑
+    setTimeout(() => {
+      demoList.value = demoList.value.filter((item) => item.reportNumber !== row.reportNumber);
+      getList();
+    }, 500);
+  }
+};
+
+const handleExport = () => {
+  // 导出逻辑
+  console.log('导出数据');
+};
+
+// 处理文件上传数量限制
+onMounted(() => {
+  getList();
+  getDicts("mm_event_type").then(res => {
+    data.eventTypeSelection = res.data;
+  });
+});
+</script>