Browse Source

上传附件优化、关闭事件弹窗

Hwf 9 tháng trước cách đây
mục cha
commit
0a0c30a052

+ 10 - 1
src/api/duty/eventing.ts

@@ -1,5 +1,6 @@
 import request from '@/utils/request';
 
+// 获取事件列表
 export function getEvent(params) {
   return request({
     url: '/api/event/list',
@@ -8,7 +9,7 @@ export function getEvent(params) {
   });
 }
 
-// 获取路由
+// 新建事件
 export function addEvent(data) {
   return request({
     url: '/api/event/create',
@@ -24,3 +25,11 @@ export function getEventDetail(params) {
     params: params
   });
 }
+
+export function closeEvent(data) {
+  return request({
+    url: '/api/event/close',
+    method: 'post',
+    data: data
+  });
+}

+ 2 - 2
src/components/DictTag/index.vue

@@ -3,7 +3,7 @@
     <template v-for="(item, index) in options">
       <template v-if="values.includes(item.value)">
         <span
-          v-if="(item.elTagType === 'default' || item.elTagType === '') && (item.elTagClass === '' || item.elTagClass == null)"
+          v-if="(item.elTagType === 'default' || item.elTagType === '')"
           :key="item.value"
           :index="index"
           :class="item.elTagClass"
@@ -22,7 +22,7 @@
             item.elTagType === 'warning' ||
             item.elTagType === 'danger'
               ? item.elTagType
-              : 'primary'
+              : 'default'
           "
           :class="item.elTagClass"
         >

+ 113 - 30
src/components/FileUpload/index.vue

@@ -12,6 +12,7 @@
       :on-success="handleUploadSuccess"
       :show-file-list="false"
       :headers="headers"
+      :http-request="uploadFile"
       class="upload-file-uploader"
     >
       <!-- 上传按钮 -->
@@ -31,7 +32,7 @@
     <!-- 文件列表 -->
     <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
       <li v-for="(file, index) in fileList" :key="file.uid" class="el-upload-list__item ele-upload-list__item-content">
-        <el-link :href="`${file.url}`" :underline="false" target="_blank">
+        <el-link :underline="false" target="_blank" @click="handleDownload(file)">
           <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
         </el-link>
         <div class="ele-upload-list__item-content-action">
@@ -42,10 +43,11 @@
   </div>
 </template>
 
-<script setup lang="ts">
+<script setup lang="ts" name="FileUpload">
 import { propTypes } from '@/utils/propTypes';
-import { delOss, listByIds } from '@/api/system/oss';
-import { globalHeaders } from '@/utils/request';
+import { download2, globalHeaders } from '@/utils/request';
+import { v1 as uuidv1 } from 'uuid';
+import axios from 'axios';
 
 const props = defineProps({
   modelValue: {
@@ -82,22 +84,12 @@ watch(
     if (val) {
       let temp = 1;
       // 首先将值转为数组
-      let list: any[] = [];
-      if (Array.isArray(val)) {
-        list = val;
-      } else {
-        const res = await listByIds(val);
-        list = res.data.map((oss) => {
-          return {
-            name: oss.originalName,
-            url: oss.url,
-            ossId: oss.ossId
-          };
-        });
-      }
+      const list = Array.isArray(val) ? val : props.modelValue.split(',');
       // 然后将数组转为对象数组
       fileList.value = list.map((item) => {
-        item = { name: item.name, url: item.url, ossId: item.ossId };
+        if (typeof item === 'string') {
+          item = { name: item, url: item };
+        }
         item.uid = item.uid || new Date().getTime() + temp++;
         return item;
       });
@@ -144,13 +136,100 @@ const handleUploadError = () => {
   proxy?.$modal.msgError('上传文件失败');
 };
 
+const uploadFile = async ({ data, file }) => {
+  // data是上传时附带的额外参数,file是文件
+  let url = baseUrl + '/api/file/upload/uploadfile'; //上传文件接口
+  try {
+    // 如果文件大于等于5MB,分片上传
+    data.file = file;
+    const res = await uploadByPieces(url, data);
+    // 分片上传后操作
+    return res;
+  } catch (e) {
+    return e;
+  }
+};
+
+//分片上传
+const uploadByPieces = async (url, { fileName, file }) => {
+  // 上传过程中用到的变量
+  const chunkSize = 5 * 1024 * 1024; // 5MB一片
+  const chunkCount = Math.ceil(file.size / chunkSize); // 总片数
+  // 获取当前chunk数据
+  const identifier = uuidv1();
+  const getChunkInfo = (file, index) => {
+    let start = index * chunkSize;
+    let end = Math.min(file.size, start + chunkSize);
+    let chunknumber = file.slice(start, end);
+    const fileName = file.name;
+    return { chunknumber, fileName };
+  };
+  // 分片上传接口
+  const uploadChunk = (data, params) => {
+    return new Promise((resolve, reject) => {
+      axios({
+        url,
+        method: 'post',
+        data,
+        params,
+        headers: {
+          'Content-Type': 'multipart/form-data'
+        }
+      })
+        .then((res) => {
+          return resolve(res.data);
+        })
+        .catch((err) => {
+          return reject(err);
+        });
+    });
+  };
+  // 针对单个文件进行chunk上传
+  const readChunk = (index) => {
+    const { chunknumber } = getChunkInfo(file, index);
+    let fetchForm = new FormData();
+    fetchForm.append('file', chunknumber);
+    return uploadChunk(fetchForm, {
+      chunknumber: index,
+      identifier: identifier
+    });
+  };
+  // 针对每个文件进行chunk处理
+  const promiseList = [];
+  try {
+    for (let index = 0; index < chunkCount; ++index) {
+      promiseList.push(readChunk(index));
+    }
+    await Promise.all(promiseList);
+    // 文件分片上传完成后,调用合并接口
+    const res = await mergeChunks(identifier, file.name);
+    res.originalName = file.name;
+    return res;
+  } catch (e) {
+    return e;
+  }
+};
+const mergeChunks = async (identifier: string, filename: string) => {
+  try {
+    const response = await axios.post(baseUrl + '/api/file/upload/mergefile', null, {
+      params: {
+        identifier: identifier,
+        filename: filename,
+        chunkstar: 0 // 假设所有分片的开始序号为0
+      }
+    });
+
+    return response.data;
+  } catch (error) {
+    throw new Error('合并请求失败');
+  }
+};
 // 上传成功回调
 const handleUploadSuccess = (res: any, file: UploadFile) => {
   if (res.code === 200) {
     uploadList.value.push({
-      name: res.data.fileName,
-      url: res.data.url,
-      ossId: res.data.ossId
+      name: res.originalName,
+      url: res.filename
     });
     uploadedSuccessfully();
   } else {
@@ -164,10 +243,8 @@ const handleUploadSuccess = (res: any, file: UploadFile) => {
 
 // 删除文件
 const handleDelete = (index: number) => {
-  let ossId = fileList.value[index].ossId;
-  delOss(ossId);
   fileList.value.splice(index, 1);
-  emit('update:modelValue', listToString(fileList.value));
+  emit('update:modelValue', fileList.value);
 };
 
 // 上传结束处理
@@ -176,7 +253,7 @@ const uploadedSuccessfully = () => {
     fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value);
     uploadList.value = [];
     number.value = 0;
-    emit('update:modelValue', listToString(fileList.value));
+    emit('update:modelValue', fileList.value);
     proxy?.$modal.closeLoading();
   }
 };
@@ -195,12 +272,17 @@ const getFileName = (name: string) => {
 const listToString = (list: any[], separator?: string) => {
   let strs = '';
   separator = separator || ',';
-  list.forEach((item) => {
-    if (item.ossId) {
-      strs += item.ossId + separator;
+  for (let i in list) {
+    if (list[i].url) {
+      strs += list[i].url + separator;
     }
-  });
-  return strs != '' ? strs.substring(0, strs.length - 1) : '';
+  }
+  return strs != '' ? strs.substr(0, strs.length - 1) : '';
+};
+
+// 下载方法
+const handleDownload = (file: any) => {
+  download2('/api/file/download/' + file.url, file.name);
 };
 </script>
 
@@ -214,6 +296,7 @@ const listToString = (list: any[], separator?: string) => {
   line-height: 2;
   margin-bottom: 10px;
   position: relative;
+  padding: 3px 5px;
 }
 
 .upload-file-list .ele-upload-list__item-content {

+ 25 - 0
src/utils/request.ts

@@ -197,5 +197,30 @@ export function download(url: string, params: any, fileName: string) {
       downloadLoadingInstance.close();
     });
 }
+
+// 通用下载方法
+export function download2(url: string, fileName: string) {
+  downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' });
+  return service.get(url, {}, {
+    headers: { 'Content-Type': 'application/octet-stream' },
+    responseType: 'blob'
+  }).then(async (resp: any) => {
+    const isLogin = blobValidate(resp);
+    if (isLogin) {
+      const blob = new Blob([resp]);
+      FileSaver.saveAs(blob, fileName);
+    } else {
+      const resText = await resp.data.text();
+      const rspObj = JSON.parse(resText);
+      const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];
+      ElMessage.error(errMsg);
+    }
+    downloadLoadingInstance.close();
+  }).catch((r: any) => {
+    console.error(r);
+    ElMessage.error('下载文件出现错误,请联系管理员!');
+    downloadLoadingInstance.close();
+  });
+}
 // 导出 axios 实例
 export default service;

+ 111 - 0
src/views/duty/eventing/CloseEventDialog.vue

@@ -0,0 +1,111 @@
+<template>
+  <el-dialog ref="formDialogRef" :model-value="visible" title="关闭事件" width="750px" append-to-body @close="closeDialog">
+    <el-form ref="userFormRef" :model="form" :rules="rules" label-width="80px">
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="伤亡情况">
+            <el-row>
+              <el-col :span="8">
+                <el-form-item label="死亡" prop="deaths">
+                  <el-input v-model="form.deaths">
+                    <template #append>人</template>
+                  </el-input>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="受伤" prop="injuries">
+                  <el-input v-model="form.injuries">
+                    <template #append>人</template>
+                  </el-input>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="失联" prop="missing">
+                  <el-input v-model="form.missing">
+                    <template #append>人</template>
+                  </el-input>
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="总结报告" prop="fileNames">
+            <FileUpload v-model="form.fileNames" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="closeDialog">取 消</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { closeEvent } from '@/api/duty/eventing';
+
+interface Form {
+  eventId: string;
+  deaths: string;
+  injuries: string;
+  missing: string;
+  fileNames?: any;
+}
+interface Props {
+  modelValue: boolean;
+  eventId: string;
+  data: Form;
+}
+const props = withDefaults(defineProps<Props>(), {
+  modelValue: false
+});
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const emits = defineEmits(['update:modelValue']);
+watch(
+  () => props.modelValue,
+  () => {
+    if (props.modelValue) {
+      form.value = props.data;
+    }
+    visible.value = props.modelValue;
+  }
+);
+const router = useRouter();
+const visible = ref(false);
+const form = ref<Form>({
+  deaths: '',
+  injuries: '',
+  missing: '',
+  fileNames: []
+});
+const rules = reactive({
+  deaths: [{ required: true, message: '死亡人数不能为空', trigger: 'blur' }],
+  injuries: [{ required: true, message: '受伤人数不能为空', trigger: 'blur' }],
+  missing: [{ required: true, message: '失踪人数不能为空', trigger: 'blur' }]
+});
+const formRef = ref();
+const buttonLoading = ref(false);
+
+// 关闭事件
+const submitForm = () => {
+  formRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      closeEvent({ eventId: props.eventId, ...form.value }).then(() => {
+        proxy?.$modal.msgSuccess('关闭事件成功');
+        router.go(-1);
+      });
+    };
+  });
+};
+
+const closeDialog = () => {
+  emits('update:modelValue', false);
+};
+</script>
+
+<style lang="scss" scoped></style>

+ 75 - 17
src/views/duty/eventing/eventDetails.vue

@@ -9,7 +9,7 @@
       <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" @click="handleCloseEvent">关闭事件</el-button>
         <el-button type="danger">删除事件</el-button>
       </div>
     </div>
@@ -22,19 +22,21 @@
       <div class="line-item">
         <div class="item-label">事件类型:</div>
         <div class="item-value">
-<!--          <dict-tag-->
+          <dict-tag :options="mm_event_type" :value="detailData.eventType" />
         </div>
       </div>
       <div class="line-item">
         <div class="item-label">事件等级:</div>
-        <div class="item-value">
-          特别重大
+        <div class="item-value" style="display: flex; align-items: center">
+          <dict-tag :options="mm_event_level" :value="detailData.eventLevel" />
           <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 class="item-value">
+          <dict-tag :options="mm_event_state" :value="detailData.eventStatus" />
+        </div>
       </div>
     </div>
     <div class="line2">
@@ -112,22 +114,24 @@
           :timestamp="item.timestamp + '&nbsp;&nbsp;&nbsp;&nbsp;' + item.name"
           placement="top"
         >
-          <div>
+          <div class="dict-item">
             <dict-tag :options="mm_event_level" :value="item.levelStatus" />
           </div>
         </el-timeline-item>
       </el-timeline>
     </el-dialog>
+    <CloseEventDialog v-model="closeDialogState.show" :data="closeDialogState.form" :eventId="eventId" />
   </div>
 </template>
 
 <script lang="ts" setup>
 import { getEventDetail } from '@/api/duty/eventing';
+import CloseEventDialog from './CloseEventDialog.vue';
 
 const route = useRoute();
 const router = useRouter();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { mm_event_level } = toRefs<any>(proxy?.useDict('mm_event_level'));
+const { mm_event_type, mm_event_level, mm_event_state, region } = toRefs<any>(proxy?.useDict('mm_event_type', 'mm_event_level', 'mm_event_state', 'region'));
 // 事件等级数据
 const eventLevelState = reactive({
   show: false,
@@ -188,13 +192,38 @@ const eventTrackState = reactive({
 const handleBack = () => {
   router.go(-1);
 };
-let eventId = ref('')
-let detailData = ref({})
+let eventId = ref('');
+let detailData = ref({
+  eventId: '',
+  eventTitle: '',
+  eventType: '',
+  eventLevel: '',
+  eventStatus: '',
+  eventTime: '',
+  address: '',
+  reportTime: '',
+  eventSource: '',
+  reportedBy: ''
+});
+
+const closeDialogState = reactive({
+  show: false,
+  form: {
+    deaths: '',
+    injuries: '',
+    missing: ''
+  }
+});
+
+// 关闭事件
+const handleCloseEvent = () => {
+  closeDialogState.show = true;
+};
 onMounted(() => {
-  eventId.value = route.query.eventId
-  getEventDetail({eventId: eventId.value}).then(res => {
-    detailData.value = res.data
-  })
+  eventId.value = route.query.eventId;
+  getEventDetail({ eventId: eventId.value }).then((res) => {
+    detailData.value = res.data;
+  });
 });
 </script>
 
@@ -227,6 +256,10 @@ onMounted(() => {
       display: flex;
       .item-label {
         width: 80px;
+        flex-shrink: 0;
+      }
+      .item-label {
+        margin-right: 5px;
       }
     }
   }
@@ -281,10 +314,35 @@ onMounted(() => {
     cursor: pointer;
   }
 }
-:deep(.warning2) {
-  background-color: #fdfbe2;
-  border-color: #fad400 !important;
-  color: #fad400;
+.dict-item {
+  :deep(.warning2) {
+    background-color: #fdfbe2;
+    border: 1px solid #fad400;
+    color: #fad400;
+    padding: 5px 12px;
+    border-radius: 5px;
+  }
+  :deep(.primary2) {
+    background-color: #ecf5ff;
+    border: 1px solid #409eff;
+    color: #409eff;
+    padding: 5px 12px;
+    border-radius: 5px;
+  }
+  :deep(.danger2) {
+    background-color: #fef0f0;
+    border: 1px solid #f56c6c;
+    color: #f56c6c;
+    padding: 5px 12px;
+    border-radius: 5px;
+  }
+  :deep(.warning3) {
+    background-color: #fdf6ec;
+    border: 1px solid #e6a23c;
+    color: #e6a23c;
+    padding: 5px 12px;
+    border-radius: 5px;
+  }
 }
 .back-btn {
   cursor: pointer;

+ 22 - 37
src/views/duty/eventing/index.vue

@@ -10,10 +10,10 @@
                 <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"
+                    v-for="item in mm_event_type"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
                   ></el-option>
                 </el-select>
               </el-form-item>
@@ -23,10 +23,10 @@
                 <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"
+                    v-for="item in mm_event_level"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
                   ></el-option>
                 </el-select>
               </el-form-item>
@@ -36,10 +36,10 @@
                 <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"
+                    v-for="item in mm_event_state"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
                   ></el-option>
                 </el-select>
               </el-form-item>
@@ -48,7 +48,7 @@
               <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-option v-for="item in region" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue"></el-option>
                 </el-select>
               </el-form-item>
             </el-col>
@@ -83,8 +83,7 @@
         <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" />
@@ -131,17 +130,17 @@
         </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-option v-for="item in mm_event_type" :key="item.value" :label="item.label" :value="item.value"></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-option v-for="item in mm_event_level" :key="item.value" :label="item.label" :value="item.value"></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-option v-for="item in mm_event_state" :key="item.value" :label="item.label" :value="item.value"></el-option>
           </el-select>
         </el-form-item>
         <el-form-item label="事件地点" prop="address">
@@ -195,12 +194,10 @@
 
 <script setup lang="ts">
 import { ref, reactive, toRefs, onMounted } from 'vue';
-import { getDicts } from '@/api/system/dict/data';
 import { addEvent, getEvent } from '@/api/duty/eventing';
 
-let proxy = getCurrentInstance()?.proxy;
-const { mm_event_type,mm_event_level,mm_event_state } = toRefs<any>(proxy?.useDict('mm_event_type','mm_event_level','mm_event_state'));
-
+const proxy = getCurrentInstance()?.proxy;
+const { mm_event_type, mm_event_level, mm_event_state, region } = toRefs<any>(proxy?.useDict('mm_event_type', 'mm_event_level', 'mm_event_state', 'region'));
 const router = useRouter();
 const eventList = ref([]);
 const buttonLoading = ref(false);
@@ -261,7 +258,6 @@ const data = reactive({
     eventSource: [{ required: true, message: '事件来源不能为空', trigger: 'blur' }],
     eventDescription: [{ required: true, message: '事件描述不能为空', trigger: 'blur' }]
   },
-  eventTypeSelection: [],
   eventLevelSelection: [],
   eventStatusSelection: [],
   regionSelection: []
@@ -386,24 +382,13 @@ const openMapDialog = () => {
 
 const handleMapChange = (data) => {
   form.value.address = data.address;
-  form.value.longitude = data.lnglat[0].toString();
-  form.value.latitude = data.lnglat[1].toString();
-  mapDialogVisible.value = false;
-};
-
-const confirmMapLocation = () => {
+  form.value.longitude = data.lnglat[0];
+  form.value.latitude = data.lnglat[1];
   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>