el-upload实现文件上传预览

本笔记仅提供el-upload附件预览的思路,不保证完全cv就可以使用。

需修改fileUploadYt.vue中后端接口返回的数据格式;

如果你是vue3项目,还需要在temp.vue中修改@vue-office的导入方式,直接打开我的注释并且删除旧的导入即可。

Vue2+element文件上传预览的功能实现https://blog.csdn.net/m0_74149462/article/details/129025238?ops_request_misc=%257B%2522request%255Fid%2522%253A%25225da3c6f21ac258714348fabc2f30021a%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=5da3c6f21ac258714348fabc2f30021a&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-3-129025238-null-null.nonecase&utm_term=%E9%A2%84%E8%A7%88&spm=1018.2226.3001.4450el-upload文件上传组件的封装https://blog.csdn.net/m0_74149462/article/details/130361411?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522c439a6f34bedbeba7f67cdadb8d2ab4d%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=c439a6f34bedbeba7f67cdadb8d2ab4d&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-130361411-null-null.nonecase&utm_term=el-upload&spm=1018.2226.3001.4450

需要用到的插件

复制代码
"@vue-office/docx": "^1.6.3",
"@vue-office/excel": "^1.7.14",
"@vue-office/pdf": "^2.0.10",
"@vue-office/pptx": "^1.0.1",
复制代码
// 如果是vue2项目的话还需要安装这个
"@vue/composition-api": "^1.7.2",
"vue-demi": "^0.14.10",

1. 新建/filePreviewer页面

每次点击预览都会跳到这个页面,该页面接收两个参数fileType:文件类型和fileSrc:文件地址【可以是流或者cdn链接如:192.....0/a.docx】

javascript 复制代码
 {
    path: "/filePreviewer",
    component: () => import("@/components/filePreviewer/filePreviewer.vue"),
    hidden: true
  },

2. 配置filePreviewer

filePreviewer.vue

html 复制代码
<template>
  <div class="file-previewer">
    <!--    <div class="file-previewer-header">-->
    <!--      <el-page-header @back="goBack">-->
    <!--        <template #content>-->
    <!--          <span class="text-large font-600 mr-3"> {{ title }} </span>-->
    <!--        </template>-->
    <!--        <div class="mt-4 text-sm font-bold"></div>-->
    <!--      </el-page-header>-->
    <!--    </div>-->

    <div class="file-previewer-content">
      <Temp
        ref="tempRef"
        :fileType="fileType"
        :fileSrc="fileSrc"
        v-bind="$attrs"
      />
    </div>
  </div>
</template>

<script>
import Temp from "@/components/filePreviewer/temp.vue";
import { getToken } from "@/utils/auth";
import axios from "axios";
import sysCode from "@/utils/sysCode";
import { blobValidate } from "@/utils/publicFun";
import NewMessage from "@/utils/rewriteElMessage";

export default {
  name: "FilePreviewer",

  components: {
    Temp
  },

  props: {
    title: {
      type: String,
      required: false
    },
    pageType: {
      type: Number,
      default: 2
    }
  },

  data() {
    return {
      fileType: "",
      fileSrc: "",
      fileKey: null,
      interfaceParams: {} // 接口参数
    };
  },

  methods: {
    goBack() {
      this.pageType = 1;
      this.$emit("close");
    },

    async getFileBlob(obj) {
      const { interfaceUrl, key } = obj;

      try {
        const response = await axios.post(
          interfaceUrl,
          {
            ...this.interfaceParams
          },
          {
            headers: {
              Authorization: getToken(),
              "Token-Code-Header": sysCode.trading
            },
            responseType: "blob"
          }
        );

        const isLogin = await blobValidate(response.data);

        if (!isLogin) {
          const resText = await new Response(response.data).text();
          const rspObj = JSON.parse(resText);
          const errMsg = rspObj.message || "文件获取失败";
          NewMessage.error({
            message: errMsg,
            type: "error",
            duration: 5 * 1000
          });
          return null;
        }

        this.fileSrc = response.data;
        this.fileType = "docx";

        console.log("文件预览设置成功", {
          fileSrc: this.fileSrc,
          blobSize: response.data.size,
          blobType: response.data.type
        });

        return response.data;
      } catch (error) {
        console.error("获取文件流失败:", error);
        NewMessage.error({
          message: "获取文件出现错误,请联系管理员!",
          type: "error",
          duration: 5 * 1000
        });
        return null;
      }
    },

    async getFile(obj) {
      const { interfaceUrl, fetchType, key } = obj;

      const url = interfaceUrl;
      const header = {
        authorization: getToken(),
        "token-code-header": "system"
      };
      try {
        if (fetchType === "get") {
          const response = await fetch(url, {
            method: "GET",
            headers: header
          });

          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
          }
          const blob = await response.blob();
          // 注意:这里需要根据实际情况调整,Vue2中可能需要使用其他方式存储
          // this.docx = await blob.arrayBuffer();
          this.fileSrc = await blob.arrayBuffer();
        } else if (fetchType === "post") {
          await this.getFileBlob(obj);
        }
      } catch (error) {
        console.error("获取文档失败:", error);
        this.loading = false;
        this.errorHandler();
      }
    }
  },

  mounted() {
    const obj = this.$route.query;
    this.$nextTick(() => {
      /* 通过接口获取文件流 */
      if (obj.key) {
        this.fileKey = obj.key;
        this.interfaceParams = JSON.parse(
          localStorage.getItem(obj.key) || "{}"
        );
        localStorage.setItem(obj.key, JSON.stringify(this.interfaceParams));
        this.getFile(obj);
        return;
      }

      /* 直接获取文件地址 */
      this.fileType = obj.fileType;
      this.fileSrc = obj.fileSrc;
    });
  },

  destroyed() {
    if (this.fileKey) {
      localStorage.removeItem(this.fileKey);
    }
  }
};
</script>

<style lang="scss" scoped>
.file-previewer {
  width: 100%;
  //height: calc(100vh - 100px);
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: space-between;

  &-header {
    padding: 20px 0;
    height: 40px;
  }

  &-content {
    height: 100%;
    //height: calc(100% - 70px);
  }

  ::v-deep .el-page-header__back {
    &:hover {
      color: var(--el-color-primary);
    }
  }
}
</style>

temp.vue

html 复制代码
<template>
  <div class="temp" v-loading="loading">
    <vue-office-docx
      class="temp"
      v-if="fileType === 'docx'"
      :src="fileSrc"
      @rendered="renderedHandler"
      @error="errorHandler"
    />

    <vue-office-excel
      class="temp"
      v-if="fileType === 'excel'"
      :src="fileSrc"
      :options="excelOptions"
      @rendered="renderedHandler"
      @error="errorHandler"
    />

    <vue-office-pdf
      class="temp"
      v-if="fileType === 'pdf'"
      :src="fileSrc"
      @rendered="renderedHandler"
      @error="errorHandler"
    />

    <vue-office-pptx
      class="temp"
      v-if="fileType === 'pptx'"
      :src="fileSrc"
      @rendered="renderedHandler"
      @error="errorHandler"
    />
  </div>
</template>

<script>
import VueOfficeDocx from "@vue-office/docx";
// import VueOfficeDocx from "@vue-office/docx/lib/v3/vue-office-docx.mjs";
import "@vue-office/docx/lib/index.css";
// import "@vue-office/docx/lib/v3/index.css";

import VueOfficeExcel from "@vue-office/excel";
// import VueOfficeExcel from "@vue-office/excel/lib/v3/vue-office-excel.mjs";
import "@vue-office/excel/lib/index.css";
// import "@vue-office/excel/lib/v3/index.css";

import VueOfficePdf from "@vue-office/pdf";
// import VueOfficePdf from "@vue-office/pdf/lib/v3/vue-office-pdf.mjs";

import VueOfficePptx from "@vue-office/pptx";
// import VueOfficePptx from "@vue-office/pptx/lib/v3/vue-office-pptx.mjs";

export default {
  name: "OfficeViewer",

  components: {
    VueOfficeDocx,
    VueOfficeExcel,
    VueOfficePdf,
    VueOfficePptx
  },

  props: {
    fileType: {
      type: String,
      required: true,
      validator: value => ["docx", "excel", "pdf", "pptx"].includes(value)
    },
    fileSrc: {
      type: [String, Blob, ArrayBuffer],
      required: true
    },
    excelOptions: {
      type: Object,
      default: () => {
        return {
          xls: false,
          minColLength: 0,
          minRowLength: 0,
          widthOffset: 10,
          heightOffset: 10,
          beforeTransformData: workbookData => {
            return workbookData;
          },
          transformData: workbookData => {
            return workbookData;
          }
        };
      }
    }
  },

  data() {
    return {
      loading: true
    };
  },

  methods: {
    renderedHandler() {
      this.loading = false;
      this.$emit("rendered");
      console.log("渲染完成");
    },

    errorHandler() {
      this.loading = false;
      this.$emit("error");
      console.log("渲染失败");
    },

    /**
     * @Event 方法
     * @description: 通过流地址预览 Demo
     * @author: mhf
     * @time: 2025-07-21 17:06:57
     **/
    async viewByBlob() {
      const url = "/reservoirRealData/reservoirReport/export?id=313";
      const header = {
        authorization:
          "eyJhbGciOiJIUzI1NiJ9.eyJ0b2tlbktleSI6IjY1Yjk4NWZhMWY2NzQyZGU5N2Q5NmQyZjI4MTU2YzQxIiwiZGVwdElkIjoxMDEsInVzZXJOYW1lIjoiYWRtaW4iLCJ1c2VySWQiOjEwfQ.p2AVdvAZtFVfhK_YTkG1dr9jfJKR-qD_8JJ6bv4Vw4k",
        "token-code-header": "system"
      };
      try {
        const response = await fetch(url, {
          method: "GET",
          headers: header
        });

        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const blob = await response.blob();
        // 注意:这里需要根据实际情况调整,Vue2中可能需要使用其他方式存储
        // this.docx = await blob.arrayBuffer();
      } catch (error) {
        console.error("获取文档失败:", error);
        this.loading = false;
        this.errorHandler();
      }
    }
  },

  mounted() {
    this.loading = true;
  }
};
</script>

<style scoped lang="scss">
.temp {
  width: 100%;
  height: 100%;
  overflow: auto;
}
</style>

3. 文件上传组件封装fileUploadYt.vue

html 复制代码
<!--
 * @Description: 文件上传通用 组件 页面
 * @Author: mhf
 * @Date: 2023-04-24 18:32:39
 * @Desc: 具体使用请参考 attachConfigDialog.vue 页面
 * @Warning: 弹窗中使用该组件时,需设置 v-if="dialogVisible" 否则将会存在文件缓存
-->
<template>
  <div class="">
    <el-upload
      class="upload-demo"
      ref="uploadFile"
      :disabled="utilsObj.isDisabled"
      :action="actionUrl"
      :headers="headerObj"
      :file-list="utilsObj.fileList"
      :limit="utilsObj.limitNum"
      :http-request="handleUpload"
      :multiple="utilsObj.isMultiple"
      :on-preview="handlePreview"
      :on-success="handleSuccess"
      :on-remove="handleRemove"
      :before-upload="handBeforeUpload"
      :on-exceed="handleExceed"
    >
      <!-- 上传按钮样式选择 -->
      <div v-if="utilsObj.typeStyle === 0">
        <!-- 按钮样式 -->
        <el-button
          :disabled="utilsObj.isDisabled"
          size="small"
          icon="iconfont if-biaodancaozuo-xinzeng"
          class=""
          >{{ utilsObj.content || "附件" }}
        </el-button>
      </div>

      <!-- el-icon样式 -->
      <div v-if="utilsObj.typeStyle === 1">
        <div v-if="!utilsObj.isDisabled" class="fileBox">
          <i class="iconfont if-daochu" />
          <div>{{ utilsObj.content || "点击上传" }}</div>
          <div class="tips">
            <div class="red">
              <i class="el-icon-remove"></i>涉密工作及涉密信息请勿上传
            </div>
            <div class="yellow">
              <i class="el-icon-warning yellow"></i
              >重要文件请修改好文件名称再上传
            </div>
          </div>
        </div>
      </div>

      <!--  若自定义样式,请使用插槽:<slot></slot>  -->
      <div v-if="utilsObj.typeStyle === 2">
        <slot name="customBtn" />
      </div>
      <el-progress
        v-if="singlePartFileProgress > 0 && singlePartFileProgress < 100"
        class="progress"
        :percentage="singlePartFileProgress"
      />
      <!-- 上传按钮样式选择 -->

      <div slot="file" slot-scope="{ file }" class="file-slot">
        <div class="file-slot-content">
          <i class="el-icon-upload-success el-icon-circle-check"></i>
          <div class="file-slot-content-name">{{ file.name }}</div>
          <el-button
            v-if="isPreviewable(file)"
            type="text"
            class="file-slot-content-preview"
            icon="el-icon-document"
            @click="filePreview(file)"
          >
            预览
          </el-button>
          <el-button
            type="text"
            class="file-slot-content-preview"
            icon="el-icon-download"
            @click="handlePreview(file)"
          >
            下载
          </el-button>
          <el-button
            v-if="!utilsObj.isDisabled"
            type="text"
            class="file-slot-content-remove"
            icon="el-icon-delete"
            @click="handleRemove(file)"
          >
            删除
          </el-button>
        </div>
      </div>
    </el-upload>
  </div>
</template>

<script>
import { getToken } from "@/utils/auth";
import { upLoad, upLoadChunk } from "@/api/system/config";
export default {
  name: "Index",
  components: {},
  props: {
    /* 注意: 如果props里面的对象有默认参数时,必须用函数return一个对象 */
    utilsObj: {
      type: Object,
      default: () => ({
        isDisabled: false, // 是否禁用
        fileList: [], // 附件列表
        limitNum: 3, // 限制上传的文件数量 (个)
        fileSize: 50, // 单文件上传大小(MB)
        typeStyle: 0, // 文件上传的样式控制
        isMultiple: false, // 是否支持同时选择多个文件
        content: null // 上传按钮展示的文字内容
      })
    }, // 附件上传的配置项
    headerObj: {
      type: Object,
      default: function () {
        return {
          Authorization: getToken(),
          "Token-Code-Header": this.$tokenCodeHeader
        };
      }
    }, // 文件上传请求头参数 --- token
    url: {
      type: String,
      default: "/annexAction/upload"
    },
    chunkSize: {
      type: Number, //分块大小
      default: 10
    }
  },
  data() {
    return {
      resFileArr: [], // 最终需要的文件数据
      chunkFile: {}, //分片上传文件信息
      singlePartFileProgress: 0, //文件上传进度
      processTimer: null //文件上传定时器
    };
  },
  computed: {
    // 附件上传的配置项
    actionUrl() {
      return process.env.VUE_APP_BASE_API + this.url;
    }
  },
  created() {},
  mounted() {
    setTimeout(() => {
      if (this.utilsObj.fileList) {
        this.resFileArr = this.utilsObj.fileList;
      }
    }, 500);
  },
  methods: {
    /**
     * @Event 方法 文件切片
     * @description: 将文件切割成多块
     * */
    sliceFile(file, chunkSize) {
      const chunks = [];
      let offset = 0;
      while (offset < file.size) {
        const chunk = file.slice(offset, offset + chunkSize);
        chunks.push(chunk);
        offset += chunkSize;
      }

      return chunks;
    },
    // 分片上传文件
    async uploadFileChunks(file, chunkSize) {
      console.log("file", file, this.chunkFile);
      const chunks = this.sliceFile(file, chunkSize);
      console.log(chunks);
      let exeCount = 1;
      for (let i = 0; i < chunks.length; i++) {
        const formData = new FormData();

        formData.append("file", chunks[i]);
        formData.append("fileName", this.chunkFile.file.name);
        formData.append("fileUid", this.chunkFile.file.uid);
        formData.append("partIndex", i + 1);
        formData.append("partTotalNum", chunks.length);
        try {
          if (this.processTimer) {
            clearInterval(this.processTimer);
          }
          this.processTimer = setInterval(() => {
            if (
              this.singlePartFileProgress +
                Math.floor(100 / chunks.length / 10) <
              Math.floor((100 / chunks.length) * exeCount)
            ) {
              let process =
                Math.floor(100 / chunks.length / 10) <= 0
                  ? 1
                  : Math.floor(100 / chunks.length / 10);
              this.singlePartFileProgress =
                this.singlePartFileProgress + process;
            }
          }, 500);
          const res = await upLoadChunk(formData);
          console.log(res);
          if (res.code === 1) {
            this.singlePartFileProgress = Math.floor(
              (100 / chunks.length) * exeCount
            );
            exeCount++;
            if (this.processTimer) {
              clearInterval(this.processTimer);
            }
            if (i + 1 == chunks.length) {
              this.singlePartFileProgress = 100;
              this.handleSuccess(res);
            }
          } else {
            this.$message.error(res.msg);
          }
        } catch (error) {
          if (this.processTimer) {
            clearInterval(this.processTimer);
          }
          this.singlePartFileProgress = 0;
          // console.log(this.utilsObj.fileList);
          this.removeFailUpload();
          break;
        }
      }
    },
    /**
     * @Event 方法 当文件上传失败时,删除失败的文件
     * @description:  文件上传失败,依旧会在界面显示失败的一个文件,会造成上传成功的假象
     * */
    removeFailUpload() {
      let uid = this.chunkFile.file.uid; // 关键作用代码,去除文件列表失败文件
      let idx = this.$refs.uploadFile.uploadFiles.findIndex(
        item => item.uid === uid
      ); // 关键作用代码,去除文件列表失败文件(uploadFiles为el-upload中的ref值)

      this.$refs.uploadFile.uploadFiles.splice(idx, 1); // 关键作用代码,去除文件列表失败文件
    },
    /**
     * @Event 方法 实现文件上传逻辑,包括文件分片处理
     * @description:  覆盖默认的上传行为,可以自定义上传的实现
     * */
    handleUpload(file) {
      this.chunkFile = file;
      //判断当前文件是否需要分块上传 默认 10mb以上
      if (file.file.size > this.chunkSize * 1024 * 1024) {
        this.uploadFileChunks(file.file, this.chunkSize * 1024 * 1024);
      } else {
        const formData = new FormData();
        formData.append("file", file.file);
        if (this.processTimer) {
          clearInterval(this.processTimer);
        }
        this.processTimer = setInterval(() => {
          if (this.singlePartFileProgress + 10 < 100) {
            this.singlePartFileProgress = this.singlePartFileProgress + 10;
          } //定时器,显示一个上传进度
        }, 500);
        upLoad(formData)
          .then(res => {
            this.handleSuccess(res);
          })
          .catch(err => {
            this.removeFailUpload();
          })
          .finally(() => {
            if (this.processTimer) {
              this.singlePartFileProgress = 0;
              clearInterval(this.processTimer);
            }
          });
      }
    },
    /**
     * @Event 方法
     * @description: 点击文件列表中已上传的文件时的钩子
     * */
    handlePreview(file) {
      let fileType = file?.suffix || file?.response?.data?.suffix;

      if (file.response) {
        // 上传文件的时候 查看
        window.open(
          file.response.data.url,
          fileType === ".pdf" ? "_blank" : "_self"
        );
      } else {
        // 文件上传成功之后返回值 查看
        window.open(file.url, fileType === ".pdf" ? "_blank" : "_self");
      }
    },

    /**
     * @Event 方法
     * @description: 文件上传成功时的钩子
     * */
    handleSuccess(file) {
      this.singlePartFileProgress = 0;
      console.log("文件上传成功时的钩子", file);
      if (file.code === 1) {
        this.resFileArr.push(file.data);
        this.$emit("getFileUploadYt", this.resFileArr);
      } else {
        this.$message.warning(file.message);
      }
    },

    /**
     * @Event 方法
     * @description: 文件列表移除文件时的钩子
     * */
    handleRemove(file) {
      if (file.response) {
        this.resFileArr.map((item, index) => {
          if (
            item === file.response.data ||
            item.url === file.response.data.url
          ) {
            this.resFileArr.splice(index, 1);
            this.$emit("getFileUploadYt", this.resFileArr);
          }
        });
      } else {
        this.resFileArr.map((item, index) => {
          if (item === file || item.url === file.url) {
            this.resFileArr.splice(index, 1);
            this.$emit("getFileUploadYt", this.resFileArr);
          }
        });
      }
    },

    /**
     * @Event 方法
     * @description: 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。
     * */
    handBeforeUpload(file) {
      if (
        [
          "application/vnd.android.package-archive",
          "application/x-zip-compressed",
          "text/plain",
          "application/pdf",
          "application/msword",
          "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
          "application/vnd.ms-excel",
          "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        ].indexOf(file.type) === -1
      ) {
        this.$message.error(
          "请上传后缀名为 apk、zip、pdf、txt、doc、docx、xls、xlsx的文件"
        );
        return false;
      }
      if (file.size > this.utilsObj.fileSize * 1024 * 1024) {
        this.$message.error(`文件大小不能超过 ${this.utilsObj.fileSize}MB`);
        return false;
      }
    },

    /**
     * @Event 方法
     * @description: 文件超出个数限制时的钩子
     * */
    handleExceed(files, fileList) {
      this.$message.warning(
        `当前限制选择 ${this.utilsObj.limitNum} 个文件,本次选择了 ${
          files.length
        } 个文件,共选择了 ${files.length + fileList.length} 个文件`
      );
    },
    isPreviewable(file) {
      const fileType = file.suffix;

      const previewableTypes = [
        ".xls",
        ".xlsx",
        ".XLS",
        ".XLSX",
        ".doc",
        ".docx",
        ".DOC",
        ".DOCX",
        ".ppt",
        ".pptx",
        ".pdf",
        ".PPT",
        ".PPTX",
        ".PDF"
      ];
      let fileView = false;
      if (file && file.url) {
        fileView = previewableTypes.includes(fileType);
      }
      return fileView;
    },
    filePreview(file) {
      const suffix = file.suffix;
      const typeMapper = {
        docx: [".doc", ".docx", ".DOC", ".DOCX"],
        excel: [".xls", ".xlsx", ".XLS", ".XLSX"],
        pptx: [".ppt", ".pptx", ".PPT", ".PPTX"],
        pdf: [".pdf", ".PDF"]
      };
      const fileType = Object.keys(typeMapper).find(key =>
        typeMapper[key].includes(suffix)
      );

      window.open(
        "/filePreviewer?fileSrc=" + file.url + "&fileType=" + fileType
      );
    }
  }
};
</script>

<style lang="scss" scoped>
::v-deep .fileBox {
  height: 36px;
  background: rgba(255, 255, 255, 0);
  border-radius: 4px;
  display: flex;
  align-items: center;
  color: #1492ff !important;
  font-weight: bold;
  font-size: 16px;

  i {
    margin-right: 5px;
  }
}

.disabledClass {
  margin-top: -35px;
}

.file-slot {
  padding: 2px 10px;

  &-content {
    display: flex;
    flex-direction: row;
    align-items: center;

    &-name {
      margin-left: 10px;
    }

    &-preview {
      padding: 0 !important;
      border: none;
      margin-left: 10px;
    }

    &-remove {
      padding: 0 !important;
      border: none;
      color: #fc5c5c !important;
    }
  }
}

::v-deep .el-upload {
  display: block !important;
  text-align: left !important;
}
.tips {
  font-size: 14px;
  margin-left: 8px;
  display: flex;
  .red {
    color: #fc5c5c;
  }
  .yellow {
    margin-left: 8px;
    color: #f1924e;
  }
}
</style>

4. 使用示例

html 复制代码
<template>
  <el-dialog
    v-dialog-out
    width="700px"
    :title="title"
    :visible.sync="dialogVisible"
    :before-close="hideDialog"
    :close-on-click-modal="false"
  >
    <div class="dialog-body">
      <el-form
        ref="form"
        :model="formData"
        :rules="formRules"
        label-width="5.5rem"
      >
        <el-form-item label="考核标题 : " prop="title">
          <el-input
            :disabled="['详情'].includes(title)"
            v-model="formData.title"
            placeholder=""
          />
        </el-form-item>
        <!-- 1 区县 2 乡镇 -->
        <el-form-item label="区域类型 : " prop="areaType">
          <el-select
            v-model="formData.areaType"
            filterable
            placeholder=""
            :disabled="['详情'].includes(title)"
            @change="changeAreaType"
          >
            <el-option
              v-for="item in dict.type.situation_area_type"
              :key="item.value"
              :label="item.label"
              :value="parseInt(item.value)"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="考核区域 : " prop="areaNames">
          <el-input
            v-model="formData.areaNames"
            placeholder=""
            readonly
            :disabled="['详情'].includes(title)"
          >
            <template slot="append">
              <div class="choose-class" @click="chooseDialogs">
                <i class="iconfont if-sousuo" />
                <span style="margin-left: 10px">{{
                  title === "详情" ? "查看" : "选择"
                }}</span>
              </div>
            </template>
          </el-input>
        </el-form-item>
        <el-form-item label="考核周期 : " prop="period">
          <el-select
            v-model="formData.period"
            filterable
            placeholder=""
            :disabled="['详情'].includes(title)"
          >
            <el-option
              v-for="item in quarterList"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <!-- <el-form-item label="考核类型 : " prop="checkType">
            <el-input
              :disabled="['详情'].includes(title)"
              v-model="formData.checkType"
            />
          </el-form-item> -->
        <el-form-item label="备注 : " prop="remark">
          <el-input
            v-model="formData.remark"
            placeholder=""
            :disabled="['详情'].includes(title)"
            type="textarea"
            :rows="3"
            maxlength="200"
            show-word-limit
          ></el-input>
        </el-form-item>
        <el-form-item label="附件 : " prop="attachment">
          <fileUploadYt
            v-if="dialogVisible"
            ref="fileUploadYt"
            @getFileUploadYt="getAnnexUrl"
            :utilsObj="fileUploadUtils"
          />
        </el-form-item>
      </el-form>
    </div>

    <lineH v-if="!['详情'].includes(title)" />
    <div class="dialog-footer" v-if="!['详情'].includes(title)">
      <el-button @click="hideDialog">取 消</el-button>
      <el-button type="primary" @click="validateForm">确 定</el-button>
    </div>
    
  </el-dialog>
</template>

<script>
import {
  areaAssessInsert,
  areaAssessGetInfo,
  areaAssessUpdate
} from "@/api/assessEvaluate/assessArea";
import { listByPage } from "@/api/system/dept";
import { areaList } from "@/api/system/deptArea";
import { generateQuarterList } from "@/utils/index";
export default {
  name: "countyEvaluationDialog",
  components: {},
  props: {},
  dicts: ["situation_area_type"],
  provide() {
    return {
      requestName: areaList
    };
  },
  data() {
    return {
      defaultParams: {
        // areaType 1 区县 2 乡镇   nodeType 0 省市级 1 区县级 2公司/部门 3 乡镇
        // nodeType: "1"
      },
      formLabel: [
        {
          label: "区域名称",
          value: "areaName"
        }
      ],
      tableDataColumn: [
        {
          label: "区域名称",
          prop: "areaName"
        },
        {
          label: "创建时间",
          prop: "createTime"
        }
        // {
        //   label: "管理单位",
        //   prop: "deptName"
        // }
      ],
      dialogVisible: false,
      title: "",
      formData: {},
      formRules: {
        title: [{ required: true, message: "请输入模版标题", trigger: "blur" }],
        areaType: [
          { required: true, message: "请选择区域类型", trigger: "blur" }
        ],
        areaNames: [
          { required: true, message: "请选择考核区域", trigger: "blur" }
        ],
        period: [{ required: true, message: "请选择考核周期", trigger: "blur" }]
      },
      fileUploadUtils: {
        isDisabled: false,
        fileList: [],
        limitNum: 4,
        typeStyle: 1,
        content: "考核文件"
      },
      selectList: [],
      quarterList: generateQuarterList()
    };
  },
  methods: {
    changeAreaType(e) {
      // areaType 1 区县 2 乡镇   nodeType 0 省市级 1 区县级 2公司/部门 3 乡镇
      console.log("e", e);
      this.defaultParams.areaType = e == 1 ? "1" : "3";
      this.selectList = [];
      this.$set(this.formData, "areaNames", "");
      this.$set(this.formData, "areaIds", "");
    },
    getAnnexUrl(data) {
      console.log(data);
      this.$set(this.formData, "attachment", JSON.stringify(data));
    },
    chooseDialogs() {
      if (!this.formData.areaType) {
        this.$message.warning("请选择区域类型");
        return;
      }
      let passData = {
        data: this.selectList,
        title: "选择考核区域"
      };
      this.$refs.chooseDialogs.showDialog(passData);
    },

    getListByDialog(data) {
      this.$set(
        this.formData,
        "areaNames",
        data.map(i => i.areaName).join(",")
      );
      this.$set(
        this.formData,
        "areaIdList",
        data.map(i => i.id)
      );
      this.selectList = JSON.parse(JSON.stringify(data));
    },
    showDialog(data) {
      console.log("data", data);
      // areaType 1 区县 2 乡镇   nodeType 0 省市级 1 区县级 2公司/部门 3 乡镇
      this.$set(this.defaultParams, "areaType", 1);

      this.dialogVisible = true;
      this.title = data.type;
      if (data.data) {
        this.getDetail(data.data.id);
      }
      if (data.type === "详情") {
        this.fileUploadUtils.isDisabled = true;
      }
    },

    hideDialog() {
      this.dialogVisible = false;
      // this.isDisable = false;
      // this.$refs.form.resetFields();
      this.formData = {};
      this.fileUploadUtils.fileList = [];
      this.fileUploadUtils.isDisabled = false;
      this.selectList = [];
      this.$parent.getTableData();
    },

    getDetail(id) {
      areaAssessGetInfo({ id }).then(res => {
        if (res.code === 1) {
          this.formData = {
            ...res.data,
            period: `${res.data.year}-${res.data.quarter}`
          };
          this.defaultParams.areaType = res.data.areaType == 1 ? "1" : "3";
          this.selectList = res.data.areaIdList.map((id, index) => ({
            id: id,
            areaName: res.data.areaNames.split(",")[index]?.trim() || "未知区域"
          }));
          try {
            if (this.formData.attachment) {
              this.fileUploadUtils.fileList = JSON.parse(
                this.formData.attachment
              );
            } else {
              this.fileUploadUtils.fileList = [];
            }
          } catch (error) {
            this.fileUploadUtils.fileList = [];
          }
        }
      });
    },

    async validateForm() {
      try {
        if (await this.$refs.form.validate()) this.submitForm();
      } catch (e) {}
    },

    submitForm() {
      if (this.title === "新增") {
        areaAssessInsert({
          ...this.formData,
          year: this.formData.period.split("-")[0],
          quarter: this.formData.period.split("-")[1]
        }).then(res => {
          if (res.code === 1) {
            this.$message.success("新增成功");
            this.hideDialog();
          }
        });
      } else if (this.title === "编辑") {
        areaAssessUpdate({
          ...this.formData,
          year: this.formData.period.split("-")[0],
          quarter: this.formData.period.split("-")[1]
        }).then(res => {
          if (res.code === 1) {
            this.$message.success("编辑成功");
            this.hideDialog();
          }
        });
      }
    }
  },
  created() {},
  mounted() {}
};
</script>

<style lang="scss" scoped>
::v-deep .el-dialog__body {
  padding: 20px 0 0 !important;
}

.dialog-body {
  padding: 0 20px;
  max-height: 65vh;
  overflow-y: auto;
}

.dialog-footer {
  text-align: center;
  padding: 10px 0 18px;
}
</style>
相关推荐
excel2 小时前
🧩 深入剖析 Vue 编译器中的 TypeScript 类型系统(第四篇)
前端
excel2 小时前
🧩 深入剖析 Vue 编译器中的 TypeScript 类型系统(第二篇)
前端
老夫的码又出BUG了2 小时前
分布式Web应用场景下存在的Session问题
前端·分布式·后端
excel2 小时前
🧩 深入剖析 Vue 编译器中的 TypeScript 类型系统(第三篇)
前端
excel2 小时前
🧩 深入剖析 Vue 编译器中的 TypeScript 类型系统(第一篇)
前端
excel2 小时前
深度解析 Vue SFC 编译流程中的 processNormalScript 实现原理
前端
excel2 小时前
Vue SFC 编译器源码解析:processPropsDestructure 与 transformDestructuredProps
前端
excel2 小时前
深度解析 processDefineSlots:Vue SFC 编译阶段的 defineSlots 处理逻辑
前端
excel2 小时前
Vue SFC 模板依赖解析机制源码详解
前端