yxh
2026-09-30 d21d7dd23f15a0ca395a4c9d1b3bb31330ff56c0
src/views/project/documentfiles/index.vue
@@ -659,34 +659,100 @@
    download() {
      let list = this.fileList;
      if (list.length > 0) {
        let downUrl = list[0].url;
        const link = document.createElement("a");
        link.href = this.baseUrl + downUrl;
        link.setAttribute("download", this.getFileName(list[0].name));
        document.body.appendChild(link);
        link.click();
        this.saveFile(list[0].url, this.getFileName(list[0].name, list[0].url));
      } else {
        this.$modal.msgWarning("未获取到文件地址,请确认");
      }
    },
    handleDownload(row) {
      if (row.fileurl != "" && row.fileurl != null) {
        const link = document.createElement("a");
        link.href = this.baseUrl + row.fileurl;
        link.setAttribute("download", this.getFileName(row.fileurl));
        document.body.appendChild(link);
        link.click();
        this.saveFile(
          row.fileurl,
          this.getFileName(row.filename, row.fileurl, row.filename)
        );
      } else {
        this.$modal.msgWarning("未获取到文件地址,请确认");
      }
    },
    // 获取文件名称
    getFileName(name) {
      if (name.lastIndexOf("/") > -1) {
        return name.slice(name.lastIndexOf("/") + 1).toLowerCase();
      } else {
        return "";
    /**
     * 下载文件(必须带鉴权)。
     *
     * 背景:/profile/** 静态资源未在 SecurityConfig 中放行(anyRequest().authenticated()),
     * 而后端 TokenService 只从请求头 Authorization 取 token。
     * 因此用 <a href> 直接跳转下载不会携带 token,服务端返回 401,表现为“点下载没反应/下不到文件”。
     * 这里改为 fetch 带 Authorization 头取回文件流,再用 blob 本地另存。
     *
     * @param {string} fileUrl 文件地址(库中存的是 /profile/upload/... 相对路径)
     * @param {string} fileName 另存为的文件名
     */
    saveFile(fileUrl, fileName) {
      if (!fileUrl) {
        this.$modal.msgWarning("未获取到文件地址,请确认");
        return;
      }
      // 兼容历史数据中已是绝对地址的情况,避免重复拼接
      const isAbsolute = /^https?:\/\//i.test(fileUrl);
      const url = isAbsolute ? fileUrl : this.baseUrl + fileUrl;
      this.$modal.loading("正在下载文件,请耐心等待!");
      fetch(url, {
        method: "get",
        headers: { Authorization: "Bearer " + getToken() },
      })
        .then((response) => {
          if (!response.ok) {
            // 401/403/404 必须显式提示,否则会下到一个内容为错误页的文件
            throw new Error("HTTP " + response.status);
          }
          return response.blob();
        })
        .then((blob) => {
          const blobUrl = window.URL.createObjectURL(blob);
          const link = document.createElement("a");
          link.href = blobUrl;
          link.setAttribute("download", fileName || "下载文件");
          document.body.appendChild(link);
          link.click();
          document.body.removeChild(link);
          window.URL.revokeObjectURL(blobUrl);
        })
        .catch((err) => {
          console.error("文件下载失败:" + url, err);
          this.$modal.msgError("文件下载失败,请确认文件是否存在或登录是否过期");
        })
        .finally(() => {
          this.$modal.closeLoading();
        });
    },
    /**
     * 获取另存为的文件名。
     *
     * 优先级:带扩展名的原始文件名 > 文件地址末段 > 文档标题。
     * 之所以不直接用 form.filename:该字段在表单中可手工编辑(如“人体器官移植条例”),
     * 不带扩展名,直接用作另存名会导致系统无法识别打开方式。
     *
     * @param {string} storedName 原始文件名(如 form.filename 中存的 xxx.docx)
     * @param {string} fileUrl 文件地址
     * @param {string} title 备用名称(文档标题)
     */
    getFileName(storedName, fileUrl, title) {
      const hasExt = (name) => !!name && name.lastIndexOf(".") > -1;
      if (hasExt(storedName)) {
        return storedName;
      }
      const source = fileUrl || "";
      if (source.lastIndexOf("/") > -1) {
        const fromUrl = source.slice(source.lastIndexOf("/") + 1);
        if (fromUrl) {
          return fromUrl;
        }
      }
      return title || storedName || "下载文件";
    },
    //文件上传成功回调