Selaa lähdekoodia

上传样式调整

Hwf 6 kuukautta sitten
vanhempi
commit
7c08ddc27c

+ 1 - 0
.env.development

@@ -7,6 +7,7 @@ VITE_APP_ENV = 'development'
 # 开发环境
 VITE_APP_BASE_API = 'http://10.181.7.236:9988'
 VITE_APP_BASE_API2 = 'http://10.181.7.235/'
+VITE_APP_BASE_DOWNLOAD_API = '/file/download/'
 
 # 应用访问路径 例如使用前缀 /admin/
 VITE_APP_CONTEXT_PATH = '/yjdp/'

+ 1 - 0
.env.production

@@ -16,6 +16,7 @@ VITE_APP_SNAILJOB_ADMIN = '/snail-job'
 # 生产环境
 VITE_APP_BASE_API = ''
 VITE_APP_BASE_API2 = '/'
+VITE_APP_BASE_DOWNLOAD_API = '/file/download/'
 
 # 是否在打包时开启压缩,支持 gzip 和 brotli
 VITE_BUILD_COMPRESS = gzip

+ 13 - 0
package-lock.json

@@ -45,6 +45,7 @@
         "proj4": "^2.11.0",
         "qrcodejs2": "^0.0.2",
         "screenfull": "6.0.2",
+        "uuid": "^11.0.3",
         "vue": "3.4.25",
         "vue-chartjs": "^5.3.1",
         "vue-cropper": "1.1.1",
@@ -12938,6 +12939,18 @@
         "base64-arraybuffer": "^1.0.2"
       }
     },
+    "node_modules/uuid": {
+      "version": "11.0.3",
+      "resolved": "https://registry.npmmirror.com/uuid/-/uuid-11.0.3.tgz",
+      "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "bin": {
+        "uuid": "dist/esm/bin/uuid"
+      }
+    },
     "node_modules/vary": {
       "version": "1.1.2",
       "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz",

+ 1 - 0
package.json

@@ -53,6 +53,7 @@
     "proj4": "^2.11.0",
     "qrcodejs2": "^0.0.2",
     "screenfull": "6.0.2",
+    "uuid": "^11.0.3",
     "vue": "3.4.25",
     "vue-chartjs": "^5.3.1",
     "vue-cropper": "1.1.1",

+ 173 - 55
src/components/FileUpload/index.vue

@@ -1,24 +1,32 @@
 <template>
   <div class="upload-file">
-    <el-upload
-      ref="fileUploadRef"
-      multiple
-      :action="uploadFileUrl"
-      :before-upload="handleBeforeUpload"
-      :file-list="fileList"
-      :limit="limit"
-      :on-error="handleUploadError"
-      :on-exceed="handleExceed"
-      :on-success="handleUploadSuccess"
-      :show-file-list="false"
-      :headers="headers"
-      class="upload-file-uploader"
-    >
-      <!-- 上传按钮 -->
-      <el-button type="primary">选取文件</el-button>
-    </el-upload>
+    <div class="upload-flex">
+      <el-upload
+        ref="fileUploadRef"
+        multiple
+        :action="uploadFileUrl"
+        :before-upload="handleBeforeUpload"
+        :file-list="fileList"
+        :limit="limit"
+        :on-error="handleUploadError"
+        :on-exceed="handleExceed"
+        :on-success="handleUploadSuccess"
+        :show-file-list="false"
+        :headers="headers"
+        :http-request="uploadFile"
+        class="upload-file-uploader"
+      >
+        <!-- 上传按钮 -->
+        <div class="common-btn-primary3">{{ buttonText }}</div>
+      </el-upload>
+      <!-- 上传提示 -->
+      <div v-if="showTip" class="el-upload__tip">
+        <div>1.支持{{ fileType.join('、') }}文件</div>
+        <div>2.最多支持上传{{ limit }}个文件</div>
+      </div>
+    </div>
     <!-- 上传提示 -->
-    <div v-if="showTip" class="el-upload__tip">
+    <div v-if="showTip" class="el-upload__tip2">
       请上传
       <template v-if="fileSize">
         大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
@@ -31,33 +39,36 @@
     <!-- 文件列表 -->
     <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-text class="common-btn-text-primary" @click="handleDownload(file)">
           <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
-        </el-link>
-        <div class="ele-upload-list__item-content-action">
-          <el-button type="danger" link @click="handleDelete(index)">删除</el-button>
-        </div>
+        </el-text>
+        <i class="delete-icon" @click="handleDelete(index)" />
       </li>
     </transition-group>
   </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: {
     type: [String, Object, Array],
     default: () => []
   },
+  buttonText: {
+    type: String,
+    default: '选取文件'
+  },
   // 数量限制
   limit: propTypes.number.def(5),
   // 大小限制(MB)
   fileSize: propTypes.number.def(5),
   // 文件类型, 例如['png', 'jpg', 'jpeg']
-  fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']),
+  fileType: propTypes.array.def(['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'pdf']),
   // 是否显示提示
   isShowTip: propTypes.bool.def(true)
 });
@@ -68,6 +79,7 @@ const number = ref(0);
 const uploadList = ref<any[]>([]);
 
 const baseUrl = import.meta.env.VITE_APP_BASE_API;
+const downLoadApi = import.meta.env.VITE_APP_BASE_DOWNLOAD_API;
 const uploadFileUrl = ref(baseUrl + '/resource/oss/upload'); // 上传文件服务器地址
 const headers = ref(globalHeaders());
 
@@ -82,22 +94,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 +146,100 @@ const handleUploadError = () => {
   proxy?.$modal.msgError('上传文件失败');
 };
 
+const uploadFile = async ({ data, file }) => {
+  // data是上传时附带的额外参数,file是文件
+  let url = baseUrl + '/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 + '/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 +253,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 +263,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 +282,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(baseUrl + downLoadApi + file.url, file.name);
 };
 </script>
 
@@ -208,12 +300,38 @@ const listToString = (list: any[], separator?: string) => {
 .upload-file-uploader {
   margin-bottom: 5px;
 }
-
+.upload-flex {
+  display: flex;
+  align-items: flex-start;
+  .common-btn-primary3 {
+    margin-top: -20px;
+  }
+  .el-upload__tip {
+    font-size: 38px;
+    line-height: 56px;
+    color: #ffffff;
+    margin-left: 28.5px;
+    margin-top: 0;
+  }
+}
+.el-upload__tip2 {
+  font-size: 38px;
+  line-height: 38px;
+  color: #ffffff;
+}
 .upload-file-list .el-upload-list__item {
-  border: 1px solid #e4e7ed;
+  //border: 1px solid #e4e7ed;
   line-height: 2;
   margin-bottom: 10px;
   position: relative;
+  cursor: pointer;
+  &:hover {
+    background-color: transparent;
+  }
+  .el-text {
+    font-size: 38px;
+    color: #ffffff;
+  }
 }
 
 .upload-file-list .ele-upload-list__item-content {

+ 2 - 16
src/types/components.d.ts

@@ -21,7 +21,6 @@ declare module 'vue' {
     ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
     ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
     ElButton: typeof import('element-plus/es')['ElButton']
-    ElCard: typeof import('element-plus/es')['ElCard']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
@@ -43,28 +42,18 @@ declare module 'vue' {
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElOption: typeof import('element-plus/es')['ElOption']
     ElPagination: typeof import('element-plus/es')['ElPagination']
-    ElPopover: typeof import('element-plus/es')['ElPopover']
-    ElRadio: typeof import('element-plus/es')['ElRadio']
-    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
     ElSkeletonItem: typeof import('element-plus/es')['ElSkeletonItem']
     ElSlider: typeof import('element-plus/es')['ElSlider']
-    ElStep: typeof import('element-plus/es')['ElStep']
-    ElSteps: typeof import('element-plus/es')['ElSteps']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
-    ElTable: typeof import('element-plus/es')['ElTable']
-    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
-    ElTabPane: typeof import('element-plus/es')['ElTabPane']
-    ElTabs: typeof import('element-plus/es')['ElTabs']
+    ElText: typeof import('element-plus/es')['ElText']
     ElTimeline: typeof import('element-plus/es')['ElTimeline']
     ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
-    ElTooltip: typeof import('element-plus/es')['ElTooltip']
     ElTree: typeof import('element-plus/es')['ElTree']
-    ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
     ElUpload: typeof import('element-plus/es')['ElUpload']
     FileUpload: typeof import('./../components/FileUpload/index.vue')['default']
     FooterSection: typeof import('./../components/FooterSection/index.vue')['default']
@@ -75,7 +64,6 @@ declare module 'vue' {
     HikvisionPlayer: typeof import('./../components/HKVideo/hikvision-player.vue')['default']
     HKVideo: typeof import('./../components/HKVideo/index.vue')['default']
     IconSelect: typeof import('./../components/IconSelect/index.vue')['default']
-    IEpUploadFilled: typeof import('~icons/ep/upload-filled')['default']
     IFrame: typeof import('./../components/iFrame/index.vue')['default']
     ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default']
     ImageUpload: typeof import('./../components/ImageUpload/index.vue')['default']
@@ -109,9 +97,7 @@ declare module 'vue' {
     VideoContainer2: typeof import('./../components/HKVideo/video-container2.vue')['default']
     VideoTagEdit: typeof import('./../components/VideoTagEdit/index.vue')['default']
     YMap: typeof import('./../components/Map/YMap.vue')['default']
+    YMapold: typeof import('./../components/Map/YMapold.vue')['default']
     YztMap: typeof import('./../components/Map/YztMap/index.vue')['default']
   }
-  export interface ComponentCustomProperties {
-    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
-  }
 }

+ 24 - 0
src/utils/request.ts

@@ -197,5 +197,29 @@ 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, { 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;

+ 12 - 5
src/views/emergencyCommandMap/RightSection/RenWuDengJi.vue

@@ -43,16 +43,23 @@
             <el-input v-model="form.contact_phone" class="custom-input2" placeholder="发布人的联系电话" />
           </el-form-item>
           <el-form-item label="完成时限" label-width="200px" prop="expire_time">
-            <el-date-picker v-model="form.expire_time" value-format="YYYY-MM-DD" type="date" class="common-date-picker" :teleported="false" popper-class="common-date-popper" placeholder="选择完成时间" style="width: 100%" />
+            <el-date-picker
+              v-model="form.expire_time"
+              value-format="YYYY-MM-DD"
+              type="date"
+              class="common-date-picker"
+              :teleported="false"
+              popper-class="common-date-popper"
+              placeholder="选择完成时间"
+              style="width: 100%"
+            />
           </el-form-item>
-          <!-- 不会调整大小,隐藏
-          <el-form-item label="上传图片:" label-width="200px" prop="imgList">
+          <el-form-item label="上传图片:" label-width="220px" prop="imgList">
             <FileUpload v-model="form.imgList" :file-type="['jpg', 'jpeg', 'png']" :limit="9" :file-size="5" class="upload-box" />
           </el-form-item>
-          <el-form-item label="上传文件:" label-width="200px" prop="fileList">
+          <el-form-item label="上传文件:" label-width="220px" prop="fileList">
             <FileUpload v-model="form.fileList" :file-type="['mp4', 'avi', 'wmv']" :limit="6" :file-size="1024" class="upload-box" />
           </el-form-item>
-          -->
         </el-form>
       </div>
     </div>

+ 3 - 5
src/views/emergencyCommandMap/RightSection/RenWuGengXin.vue

@@ -74,22 +74,20 @@
                 </el-select>
               </el-form-item>
             </el-col>
-            <!-- 不会调整大小,隐藏
             <el-col :span="24">
               <el-form-item label="上传图片:" label-width="300px" prop="attachList">
                 <FileUpload v-model="newTask.attachList" :file-type="['jpg', 'jpeg', 'png']" :limit="9" :file-size="5" class="upload-box" />
               </el-form-item>
             </el-col>
-            -->
           </el-row>
         </el-form>
       </div>
       <div class="footer">
         <div class="flex">
           <div class="common-btn" @click="closeDialog">取消</div>
-          
+
           <div class="common-btn-primary" :disabled="!isFormValid" @click="confirmRegister">确定</div>
-          
+
         </div>
       </div>
     </div>
@@ -104,7 +102,7 @@ const proxy = getCurrentInstance()?.proxy;
 const props = defineProps({
   modelValue: { type: Boolean, required: true },
   task: { type: Object, default: () => (
-    { task_id: '', task_description: '', unit_name: '', registrar: '', processing_status: '', feedback_content: '', attachList: [] }) 
+    { task_id: '', task_description: '', unit_name: '', registrar: '', processing_status: '', feedback_content: '', attachList: [] })
   },
   eventId: { type: String, required: true }
 });