yxh
7 天以前 4afb944c92aed7287cc4691fc9019f3b9faea1c0
增加对pdf预览支持,统计分析对时间范围的限定要求
已修改25个文件
已添加4个文件
2799 ■■■■■ 文件已修改
src/App.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/pdf-resize-dialog.scss 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/AttachmentPreviewPane/index.vue 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/FilePreview/index.vue 465 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/fileNameCheck.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/filePreview.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donationdetails/index.vue 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donorcharge/donorchargedetail/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/applyDetail/index.vue 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/applyDetail0/index.vue 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/ethicalExpertFeeApply/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/expertFeeApply/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/medicalCostApply/index.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/officeExpenseApply/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/performancedetails/index.vue 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fundcheck/medicalCostCheck/index.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fundcheck/officeExpenseCheck/index.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/statistics/applyStatistics/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/statistics/casePanorama/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/statistics/caseStatistics/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/statistics/chargeStatistics/index.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/statistics/performanceStaristics/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/tallage/computingBill/index.vue 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/travelexpenseapply/edit.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/travelexpenseapply/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/travelexpenseapply/travelexpensedetail/index.vue 1165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/travelexpensecheck/index.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/unitCost/particulars/index.vue 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue
@@ -3,12 +3,16 @@
    <keep-alive>
    <router-view />
    </keep-alive>
    <file-preview ref="filePreview" />
  </div>
</template>
<script>
import FilePreview from "@/components/FilePreview/index.vue";
export default  {
  name:  'App',
  components: { FilePreview },
    metaInfo() {
        return {
            title: this.$store.state.settings.dynamicTitle && this.$store.state.settings.title,
src/assets/styles/pdf-resize-dialog.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,109 @@
/* ç¥¨æ®/附件预览弹窗:可拖拽右下角改变大小,内容区随弹窗高度自适应 */
.pdf-resize-dialog {
  width: 80vw !important;
  height: 82vh !important;
  min-width: 720px;
  min-height: 480px;
  max-width: 96vw;
  max-height: 94vh;
  overflow: auto;
  resize: both;
  display: flex;
  flex-direction: column;
}
.pdf-resize-dialog .el-dialog__header,
.pdf-resize-dialog .el-dialog__footer {
  flex: 0 0 auto;
}
.pdf-resize-dialog .el-dialog__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: 10px 20px;
}
.pdf-resize-dialog .pdfimg {
  height: 600px;
  min-height: 520px;
}
.pdf-resize-dialog .pdfimg .box-pdf {
  height: 100%;
  overflow: auto;
}
.pdf-resize-dialog .pdfimgmin {
  min-height: 520px;
}
.pdf-resize-dialog .pdfimgmin .el-image,
.pdf-resize-dialog .pdfimgmin .el-image__inner,
.pdf-resize-dialog .pdfimgmin img {
  max-height: 100%;
}
.pdf-resize-dialog.is-fullscreen,
.pdf-resize-dialog.pdf-fullscreen {
  width: 100vw !important;
  height: 100vh !important;
  max-width: 100vw !important;
  max-height: 100vh !important;
  margin: 0 !important;
  top: 0 !important;
  left: 0 !important;
  resize: none;
}
.pdf-fullscreen-btn {
  float: right;
  margin-right: 28px;
}
.file-preview-dialog.is-fullscreen {
  width: 100vw !important;
  height: 100vh !important;
  max-width: 100vw !important;
  max-height: 100vh !important;
  margin: 0 !important;
  top: 0 !important;
  left: 0 !important;
  resize: none;
}
/* å…¨å±€ PDF/图片预览弹窗也支持拖拽改变大小 */
.file-preview-dialog {
  width: 80vw !important;
  height: 82vh !important;
  min-width: 720px;
  min-height: 480px;
  max-width: 96vw;
  max-height: 94vh;
  overflow: auto;
  resize: both;
  display: flex;
  flex-direction: column;
}
.file-preview-dialog .el-dialog__header,
.file-preview-dialog .el-dialog__footer {
  flex: 0 0 auto;
}
.file-preview-dialog .el-dialog__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: 10px 20px;
}
.file-preview-dialog .file-preview {
  height: 70vh;
  min-height: 420px;
}
.file-preview-dialog .file-preview__pdf,
.file-preview-dialog .file-preview__list {
  height: 100%;
}
src/components/AttachmentPreviewPane/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,172 @@
<template>
  <div class="attachment-preview-pane">
    <div v-if="!file" class="attachment-preview-pane__tip">
      è¯·é€‰æ‹©å·¦ä¾§æ–‡ä»¶é¢„览
    </div>
    <template v-else>
      <div v-if="fileType === 'image'" class="attachment-preview-pane__toolbar">
        <el-button-group>
          <el-button
            size="mini"
            icon="el-icon-zoom-out"
            title="缩小"
            @click="zoom(-0.2)"
          />
          <el-button size="mini" class="attachment-preview-pane__scale">
            {{ Math.round(scale * 100) }}%
          </el-button>
          <el-button
            size="mini"
            icon="el-icon-zoom-in"
            title="放大"
            @click="zoom(0.2)"
          />
        </el-button-group>
        <el-button-group>
          <el-button
            size="mini"
            icon="el-icon-refresh-left"
            title="向左旋转"
            @click="rotate(-90)"
          />
          <el-button
            size="mini"
            icon="el-icon-refresh-right"
            title="向右旋转"
            @click="rotate(90)"
          />
        </el-button-group>
        <el-button size="mini" icon="el-icon-refresh" @click="reset"
          >重置</el-button
        >
      </div>
      <div v-if="fileType === 'image'" class="attachment-preview-pane__wrap">
        <img
          :src="file.url"
          class="attachment-preview-pane__image"
          :style="{
            transform: 'scale(' + scale + ') rotate(' + deg + 'deg)'
          }"
          alt="图片预览"
        />
      </div>
      <iframe
        v-else-if="fileType === 'pdf'"
        :src="file.url"
        class="attachment-preview-pane__pdf"
        title="PDF预览"
      />
      <div v-else class="attachment-preview-pane__tip">
        è¯¥æ–‡ä»¶ç±»åž‹æš‚不支持预览,请下载后查看
      </div>
    </template>
  </div>
</template>
<script>
export default {
  name: "AttachmentPreviewPane",
  props: {
    file: {
      type: Object,
      default: null
    }
  },
  data() {
    return {
      scale: 1,
      deg: 0
    };
  },
  computed: {
    fileType() {
      if (!this.file) {
        return "other";
      }
      if (this.file.type) {
        return this.file.type;
      }
      const ext = String(this.file.name || "")
        .split(".")
        .pop()
        .toLowerCase();
      if (ext === "pdf") {
        return "pdf";
      }
      if (["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext)) {
        return "image";
      }
      return "other";
    }
  },
  watch: {
    file() {
      this.reset();
    }
  },
  methods: {
    zoom(delta) {
      const next = Math.round((this.scale + delta) * 100) / 100;
      this.scale = Math.min(5, Math.max(0.2, next));
    },
    rotate(deg) {
      this.deg += deg;
    },
    reset() {
      this.scale = 1;
      this.deg = 0;
    }
  }
};
</script>
<style scoped>
.attachment-preview-pane {
  width: 100%;
  height: 100%;
  min-height: 420px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
}
.attachment-preview-pane__toolbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 4px 0 10px;
}
.attachment-preview-pane__scale {
  min-width: 64px;
  cursor: default;
}
.attachment-preview-pane__wrap {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.attachment-preview-pane__image {
  max-width: 100%;
  max-height: 100%;
  transition: transform 0.2s ease;
  transform-origin: center center;
}
.attachment-preview-pane__pdf {
  flex: 1 1 auto;
  width: 100%;
  min-height: 420px;
  border: 0;
}
.attachment-preview-pane__tip {
  color: #909399;
  font-size: 14px;
  text-align: center;
  padding: 20px;
}
</style>
src/components/FilePreview/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,465 @@
<template>
  <el-dialog
    :visible.sync="visible"
    width="80%"
    top="5vh"
    append-to-body
    :close-on-click-modal="false"
    :destroy-on-close="false"
    :fullscreen="fullscreen"
    custom-class="file-preview-dialog"
    @closed="handleClosed"
  >
    <div slot="title" class="file-preview-title">
      <span>{{ currentFile ? currentFile.name : "文件预览" }}</span>
      <el-button
        class="file-preview-fullscreen-btn"
        size="mini"
        :icon="fullscreen ? 'el-icon-close' : 'el-icon-full-screen'"
        @click="fullscreen = !fullscreen"
        >{{ fullscreen ? "还原" : "全屏" }}</el-button
      >
    </div>
    <div v-loading="loading" class="file-preview">
      <div class="file-preview__list">
        <el-table
          :data="previewFiles"
          highlight-current-row
          size="mini"
          height="70vh"
          @row-click="handleSelect"
        >
          <el-table-column prop="name" label="文件" show-overflow-tooltip />
        </el-table>
      </div>
      <div class="file-preview__body">
        <template v-if="currentFile">
          <div v-if="currentFile.error && !currentFile.previewUrl" class="file-preview__tip">
            æ–‡ä»¶åŠ è½½å¤±è´¥ï¼š{{ currentFile.error }}
          </div>
          <iframe
            v-else-if="currentFile.type === 'pdf'"
            :src="currentFile.previewUrl"
            class="file-preview__pdf"
            title="PDF预览"
          />
          <div
            v-else-if="currentFile.type === 'image'"
            class="file-preview__image-pane"
          >
            <div class="file-preview__toolbar">
              <el-button-group>
                <el-button
                  size="mini"
                  icon="el-icon-zoom-out"
                  title="缩小"
                  @click="zoomImage(-0.2)"
                />
                <el-button size="mini" class="file-preview__scale">
                  {{ Math.round(imageScale * 100) }}%
                </el-button>
                <el-button
                  size="mini"
                  icon="el-icon-zoom-in"
                  title="放大"
                  @click="zoomImage(0.2)"
                />
              </el-button-group>
              <el-button-group>
                <el-button
                  size="mini"
                  icon="el-icon-refresh-left"
                  title="向左旋转"
                  @click="rotateImage(-90)"
                />
                <el-button
                  size="mini"
                  icon="el-icon-refresh-right"
                  title="向右旋转"
                  @click="rotateImage(90)"
                />
              </el-button-group>
              <el-button
                size="mini"
                icon="el-icon-refresh"
                @click="resetImage"
                >重置</el-button
              >
            </div>
            <div class="file-preview__image-wrap">
              <img
                :src="currentFile.previewUrl"
                class="file-preview__image"
                :style="{
                  transform:
                    'scale(' + imageScale + ') rotate(' + imageRotate + 'deg)'
                }"
                alt="图片预览"
              />
            </div>
          </div>
          <div v-else class="file-preview__tip">
            è¯¥æ–‡ä»¶ç±»åž‹æš‚不支持预览,请下载后查看
          </div>
        </template>
        <div v-else class="file-preview__tip">暂无可预览文件</div>
      </div>
    </div>
    <span slot="footer" class="dialog-footer">
      <span class="file-preview-pager">
        <el-button
          v-if="currentIndex > 0"
          size="mini"
          icon="el-icon-arrow-left"
          @click="prevFile"
          >上一张</el-button
        >
        <span class="file-preview-pager__text">
          {{ previewFiles.length ? currentIndex + 1 : 0 }} /
          {{ previewFiles.length }}
        </span>
        <el-button
          v-if="currentIndex < previewFiles.length - 1"
          size="mini"
          @click="nextFile"
          >下一张<i class="el-icon-arrow-right el-icon--right"></i
        ></el-button>
      </span>
      <el-button
        v-if="currentFile && currentFile.type === 'pdf' && currentFile.previewUrl"
        icon="el-icon-top-right"
        @click="openInNewWindow"
        >新窗口打开</el-button
      >
      <el-button type="primary" icon="el-icon-download" @click="handleDownload"
        >下载</el-button
      >
      <el-button @click="visible = false">关闭</el-button>
    </span>
  </el-dialog>
</template>
<script>
import { getToken } from "@/utils/auth";
const IMAGE_EXT = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
export default {
  name: "FilePreview",
  data() {
    return {
      visible: false,
      loading: false,
      fileList: [],
      previewFiles: [],
      currentIndex: 0,
      imageScale: 1,
      imageRotate: 0,
      fullscreen: false
    };
  },
  computed: {
    currentFile() {
      return this.previewFiles[this.currentIndex] || null;
    }
  },
  methods: {
    /**
     * æ‰“开预览
     * files: [{ name, url }] æˆ–单个 { name, url }
     * index: é»˜è®¤é€‰ä¸­ä¸‹æ ‡
     */
    open(files, index = 0) {
      this.revokeUrls();
      this.resetImage();
      this.fullscreen = true;
      this.fileList = Array.isArray(files) ? files : files ? [files] : [];
      this.currentIndex = Math.max(0, Number(index) || 0);
      this.previewFiles = [];
      this.visible = true;
      this.loadFiles();
    },
    async loadFiles() {
      if (!this.fileList.length) {
        this.loading = false;
        return;
      }
      this.loading = true;
      const token = getToken();
      const result = await Promise.all(
        this.fileList.map((file, idx) => this.loadOne(file, idx, token))
      );
      if (!this.visible) {
        result.forEach(item => {
          if (item.owned && item.previewUrl) URL.revokeObjectURL(item.previewUrl);
        });
        return;
      }
      this.previewFiles = result;
      if (this.currentIndex >= this.previewFiles.length) {
        this.currentIndex = 0;
      }
      this.loading = false;
    },
    async loadOne(file, idx, token) {
      const name = file.name || "未命名文件";
      const type = this.getFileType(name);
      const item = {
        name,
        type,
        rawUrl: file.url,
        previewUrl: "",
        owned: false,
        error: ""
      };
      // å…¶ä»–类型不加载预览,保留下载地址即可
      if (type === "other") {
        return item;
      }
      try {
        const url = this.resolveUrl(file.url);
        let previewUrl = url;
        // blob/data URL å·²ç»æ˜¯æœ¬åœ°èµ„源,无需再次请求
        if (!/^(blob:|data:)/i.test(url)) {
          const headers = token ? { Authorization: "Bearer " + token } : {};
          const response = await fetch(url, { headers, cache: "no-store" });
          if (!response.ok) {
            throw new Error("HTTP " + response.status);
          }
          const blob = await response.blob();
          if (type === "pdf") {
            await this.assertPdf(blob);
          } else if (type === "image" && blob.type && !/^image\//i.test(blob.type)) {
            throw new Error("文件内容不是有效图片,可能返回了接口提示信息");
          }
          const typedBlob =
            type === "pdf"
              ? new Blob([blob], { type: "application/pdf" })
              : blob;
          previewUrl = URL.createObjectURL(typedBlob);
          item.owned = true;
        } else if (type === "pdf" && /^blob:/i.test(url)) {
          // è°ƒç”¨æ–¹ä¼ å…¥çš„ PDF blob å¯èƒ½æ²¡æœ‰æ­£ç¡®çš„ MIME,重新包装
          const response = await fetch(url);
          const blob = await response.blob();
          await this.assertPdf(blob);
          previewUrl = URL.createObjectURL(
            new Blob([blob], { type: "application/pdf" })
          );
          item.owned = true;
        } else if (type === "image" && /^blob:/i.test(url)) {
          // æ ¡éªŒè°ƒç”¨æ–¹ä¼ å…¥çš„图片 blob,避免把 JSON é”™è¯¯å“åº”当图片显示
          const response = await fetch(url);
          const blob = await response.blob();
          if (blob.type && !/^image\//i.test(blob.type)) {
            throw new Error("文件内容不是有效图片,可能返回了接口提示信息");
          }
          previewUrl = URL.createObjectURL(blob);
          item.owned = true;
        }
        item.previewUrl = previewUrl;
      } catch (e) {
        item.error = e && e.message ? e.message : "加载失败";
        // å†…容非法时不要退回原地址,否则浏览器只会再次显示空白/“未能加载”
        item.previewUrl =
          type === "pdf" || /不是有效/.test(item.error)
            ? ""
            : this.resolveUrl(file.url);
      }
      return item;
    },
    async assertPdf(blob) {
      const head = await blob.slice(0, 5).text();
      if (head !== "%PDF-") {
        throw new Error(
          "文件内容不是有效 PDF,可能是登录失效或文件地址错误(返回了接口提示信息)"
        );
      }
    },
    resolveUrl(url) {
      if (!url) {
        return "";
      }
      if (/^(blob:|data:|https?:)/i.test(url)) {
        return url;
      }
      return (process.env.VUE_APP_BASE_API || "") + url;
    },
    getFileType(name) {
      const ext = String(name || "")
        .split(".")
        .pop()
        .toLowerCase();
      if (ext === "pdf") {
        return "pdf";
      }
      if (IMAGE_EXT.indexOf(ext) > -1) {
        return "image";
      }
      return "other";
    },
    handleSelect(row) {
      const index = this.previewFiles.indexOf(row);
      if (index > -1) {
        this.currentIndex = index;
        this.resetImage();
      }
    },
    prevFile() {
      if (this.currentIndex > 0) {
        this.currentIndex -= 1;
        this.resetImage();
      }
    },
    nextFile() {
      if (this.currentIndex < this.previewFiles.length - 1) {
        this.currentIndex += 1;
        this.resetImage();
      }
    },
    zoomImage(delta) {
      const next = Math.round((this.imageScale + delta) * 100) / 100;
      this.imageScale = Math.min(5, Math.max(0.2, next));
    },
    rotateImage(deg) {
      this.imageRotate += deg;
    },
    resetImage() {
      this.imageScale = 1;
      this.imageRotate = 0;
    },
    handleDownload() {
      const file = this.currentFile;
      if (!file) {
        return;
      }
      const href = file.previewUrl || this.resolveUrl(file.rawUrl);
      if (!href) {
        return;
      }
      const a = document.createElement("a");
      a.href = href;
      a.download = file.name;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
    },
    openInNewWindow() {
      if (this.currentFile && this.currentFile.previewUrl) {
        window.open(this.currentFile.previewUrl, "_blank");
      }
    },
    handleClosed() {
      this.revokeUrls();
      this.previewFiles = [];
      this.fileList = [];
      this.currentIndex = 0;
      this.resetImage();
      this.fullscreen = false;
    },
    revokeUrls() {
      this.previewFiles.forEach(item => {
        if (item.owned && item.previewUrl && /^blob:/i.test(item.previewUrl)) {
          URL.revokeObjectURL(item.previewUrl);
        }
      });
    }
  },
  beforeDestroy() {
    this.revokeUrls();
  }
};
</script>
<style scoped>
.file-preview-title {
  font-weight: 600;
  font-size: 15px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-right: 30px;
}
.file-preview-fullscreen-btn {
  margin-left: 12px;
}
.file-preview-pager {
  float: left;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.file-preview-pager__text {
  color: #606266;
  font-size: 12px;
}
.file-preview {
  display: flex;
  height: 70vh;
  min-height: 420px;
}
.file-preview__list {
  width: 240px;
  flex: 0 0 240px;
  border-right: 1px solid #ebeef5;
  padding-right: 10px;
  overflow: hidden;
}
.file-preview__body {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: auto;
}
.file-preview__pdf {
  width: 100%;
  height: 70vh;
  border: 0;
}
.file-preview__image {
  max-width: 100%;
  max-height: 100%;
  transition: transform 0.2s ease;
  transform-origin: center center;
}
.file-preview__image-pane {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.file-preview__toolbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 4px 0 10px;
}
.file-preview__scale {
  min-width: 64px;
  cursor: default;
}
.file-preview__image-wrap {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.file-preview__tip {
  color: #909399;
  font-size: 14px;
}
</style>
src/main.js
@@ -18,7 +18,9 @@
// è¿è¡Œæ—¶ä¸»é¢˜å±‚ + ç»„件默认外观:必须晚于 element-ui ä¸Ž ruoyi.scss,否则覆盖不生效
import '@/assets/styles/theme-runtime.scss'
import '@/assets/styles/element-override.scss'
import '@/assets/styles/pdf-resize-dialog.scss'
import App from './App'
import FilePreview from '@/components/FilePreview/index.vue'
import store from './store'
import router from './router'
import directive from './directive' //directive
@@ -43,6 +45,7 @@
import FileUpload from "@/components/FileUpload"
// å›¾ç‰‡ä¸Šä¼ ç»„ä»¶
import ImageUpload from "@/components/ImageUpload"
import AttachmentPreviewPane from "@/components/AttachmentPreviewPane/index.vue"
// å­—典标签组件
import DictTag from '@/components/DictTag'
// å¤´éƒ¨æ ‡ç­¾ç»„ä»¶
@@ -90,6 +93,30 @@
Vue.component('Editor', Editor)
Vue.component('FileUpload', FileUpload)
Vue.component('ImageUpload', ImageUpload)
Vue.component('AttachmentPreviewPane', AttachmentPreviewPane)
// é™„件内嵌预览:所有报销页共用
Vue.mixin({
  data() {
    return {
      embeddedFile: null,
      pdfFullscreen: false
    }
  },
  methods: {
    openEmbeddedPreview(item) {
      if (!item) return
      const ext = String(item.name || '').toLowerCase().split('.').pop()
      const type = ext === 'pdf'
        ? 'pdf'
        : ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext)
          ? 'image'
          : 'other'
      this.embeddedFile = { name: item.name, url: item.url, type }
      this.pdfVisible = true
    }
  }
})
Vue.use(directive)
Vue.use(plugins)
@@ -116,9 +143,28 @@
Vue.config.productionTip = false
new Vue({
// å‰ç«¯æž„建标识:用于确认浏览器实际加载的是哪一版代码
window.__APP_BUILD__ = '2026-11-03-PDF-PREVIEW-15'
const app = new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})
// æ–‡ä»¶é¢„览:App å†…挂载的全局组件,避免在应用外手工 $mount
let fallbackPreview = null
Vue.prototype.$previewFile = (files, index = 0) => {
  if (app.$refs.filePreview) {
    app.$refs.filePreview.open(files, index)
    return
  }
  // å…œåº•:App å†…组件尚未挂载时,仍保证有预览入口
  if (!fallbackPreview) {
    const FilePreviewCtor = Vue.extend(FilePreview)
    fallbackPreview = new FilePreviewCtor().$mount()
    document.body.appendChild(fallbackPreview.$el)
  }
  fallbackPreview.open(files, index)
}
src/utils/fileNameCheck.js
@@ -1,11 +1,12 @@
/**
 * æŠ¥é”€å•附件文件名规范校验(2026-10-15)
 * æŠ¥é”€å•附件文件名规范校验(2026-11-03 è°ƒæ•´ï¼‰
 *
 * ä¸šåŠ¡å£å¾„ï¼ˆä¸šåŠ¡æ–¹ç¡®è®¤ï¼‰ï¼š
 *   1. å¿…须包含中文(至少 1 ä¸ªæ±‰å­—)——"为方便查验附件,请用中文规范命名附件!"
 *   2. åŽ»æŽ‰æ‰©å±•ååŽçš„ä¸»å¹²ï¼ŒæŒ‰å­—ç¬¦æ•°è®¡ç®—ä¸èƒ½è¶…è¿‡ 20 ä¸ªå­———"为方便查验附件,附件名字长度不能超过20个字!"
 * ä¸šåŠ¡å£å¾„ï¼š
 *   1. å…è®¸æ–‡ä»¶åä¸å«ä¸­æ–‡ï¼›å«ä¸­æ–‡/纯英文/数字均可
 *   2. åŽ»æŽ‰æ‰©å±•ååŽçš„ä¸»å¹²ï¼ŒæŒ‰å­—ç¬¦æ•°è®¡ç®—ä¸èƒ½è¶…è¿‡ 20 ä¸ªå­—
 *   3. ä¸èƒ½åŒ…含空格及 / \ : * ? " < > | # % ç­‰ç‰¹æ®Šå­—符
 *   4. åŒä¸€æ˜Žç»†è¡Œå†…不能重名(比较时忽略扩展名大小写与首尾空格)
 *   5. åªæ ¡éªŒæ–°ä¸Šä¼ /新保存的附件;历史附件由后端按 existingNames æ”¾è¡Œ
 *
 * ä½¿ç”¨åœºæ™¯ï¼š
 *   - ä¸Šä¼ æˆåŠŸå›žè°ƒï¼šä¸åˆæ ¼ â†’ å¼¹æç¤ºä¸”不加入列表;
@@ -16,13 +17,10 @@
/** ä¸»å¹²ï¼ˆåŽ»æ‰©å±•åï¼‰æœ€å¤§å­—ç¬¦æ•° */
export const MAX_MAIN_LENGTH = 20;
/** æ±‰å­— */
const RE_CHINESE = /[\u4e00-\u9fa5]/;
/** éžæ³•字符:空格(含各类空白)+ / \ : * ? " < > | # % */
const RE_ILLEGAL = /[\s/\\:*?"<>|#%]/;
export const MSG_NO_CHINESE = "为方便查验附件,请用中文规范命名附件!";
export const MSG_EMPTY_NAME = "为方便查验附件,附件名不能为空!";
export const MSG_TOO_LONG = "为方便查验附件,附件名字长度不能超过20个字!";
export const MSG_ILLEGAL = '为方便查验附件,附件名不能包含空格及 / \\ : * ? " < > | # % ç­‰ç‰¹æ®Šå­—符!';
export const MSG_DUPLICATE = "为方便查验附件,同一行内不能有重名的附件!";
@@ -46,15 +44,13 @@
export function checkFileName(fileName) {
  const name = (fileName || "").trim();
  if (!name) {
    return { ok: false, msg: MSG_NO_CHINESE, main: "" };
    return { ok: false, msg: MSG_EMPTY_NAME, main: "" };
  }
  if (RE_ILLEGAL.test(name)) {
    return { ok: false, msg: MSG_ILLEGAL, main: mainName(name) };
  }
  const main = mainName(name);
  if (!RE_CHINESE.test(main)) {
    return { ok: false, msg: MSG_NO_CHINESE, main };
  }
  // ä¸å†å¼ºåˆ¶è¦æ±‚中文;含中文、纯英文、数字均可
  // æŒ‰å­—符数计(用扩展运算符按码点计数,中文/字母/数字/全角各计 1)
  if ([...main].length > MAX_MAIN_LENGTH) {
    return { ok: false, msg: MSG_TOO_LONG, main };
src/utils/filePreview.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
import Vue from "vue";
import FilePreview from "@/components/FilePreview/index.vue";
let instance = null;
/**
 * å…¨å±€æ–‡ä»¶é¢„览:图片、PDF
 * @param {Array|Object} files [{ name, url }] æˆ–单个 { name, url }
 * @param {Number} index é»˜è®¤é€‰ä¸­ä¸‹æ ‡
 */
export default function previewFile(files, index = 0) {
  if (!instance) {
    const FilePreviewCtor = Vue.extend(FilePreview);
    instance = new FilePreviewCtor().$mount();
    document.body.appendChild(instance.$el);
  }
  instance.open(files, index);
}
src/views/project/donationdetails/index.vue
@@ -2511,6 +2511,7 @@
      :title="pdftitle"
      :visible.sync="pdfVisible"
      width="68%"
      custom-class="pdf-resize-dialog"
    >
      <!-- <el-tabs style="margin-top: 20px;" v-model="activeName" type="card">
        <el-tab-pane :label="activetele" name="1"> </el-tab-pane>
@@ -4282,14 +4283,8 @@
    },
    // èŽ·å–è¯·æ±‚å¤´
    Getnetworkheader() {
      let string = window.location.href;
      if (string.includes("9091")) {
        const index = string.indexOf("9091");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–9091及其前部字符
      } else {
        const index = string.indexOf("8032");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–8032及其前部字符
      }
      // ä»¥å½“前页面 origin ä¸ºå‡†ï¼Œå…¼å®¹ 80/8032/9091 åŠåŽç»­ç«¯å£è°ƒæ•´
      this.Networkheader = window.location.origin;
    },
    // èŽ·å–é™„ä»¶æ•°æ®å¹¶åˆ†ç±»äºŒçº§åˆ—
@@ -4337,7 +4332,7 @@
      const config = {
        headers: { Authorization: "Bearer " + this.ICDtoken }
      };
      const pdfimg = this.Networkheader + "/prod-api" + response.fileName;
      const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + response.fileName;
      console.log(this.Networkheader);
      //获取票据信息位置
      if (response.code == 200) {
@@ -4398,7 +4393,7 @@
      this.pdfVisible = true;
      if (this.annexfilesList.length) {
        this.annexfilesList.forEach((value, indexson) => {
          const pdfimg = this.Networkheader + "/prod-api" + value.annexurl;
          const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + value.annexurl;
          fetch(pdfimg, config)
            .then(response => response.blob())
            .then(blob => {
@@ -4433,18 +4428,13 @@
      console.log(item, "点击文件拿item");
      this.pdftitle =
        "共" + this.donatelist.length + "项,当前选中" + item.annexname;
      let name = item.annexname.split(".");
      if (name[1] == "pdf") {
        this.$modal.msgWarning("当前文件暂不支持预览");
        this.previewpdf = false;
        this.hintitle = "当前文件暂不支持预览";
      } else if (name[1] == "jpg" || "png") {
        this.previewpdf = true;
        if (item.annexurl) {
          this.pdfimg = item.annexurl;
        } else {
          this.pdfimg = "";
        }
      const ext = String(item.annexname || "pdf").toLowerCase().split(".").pop();
      if (ext === "pdf") {
        this.$previewFile([{ name: item.annexname, url: item.annexurl }]);
        return;
      } else if (["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext)) {
        this.$previewFile([{ name: item.annexname, url: item.annexurl }]);
        return;
      } else {
        this.hintitle = "当前文件暂不支持预览";
        this.$modal.msgWarning("当前文件暂不支持预览");
src/views/project/donorcharge/donorchargedetail/index.vue
@@ -472,14 +472,8 @@
    handleUploadError() {},
    // èŽ·å–è¯·æ±‚å¤´
    Getnetworkheader() {
      let string = window.location.href;
      if (string.includes("9091")) {
        const index = string.indexOf("9091");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–9091及其前部字符
      } else {
        const index = string.indexOf("8032");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–8032及其前部字符
      }
      // ä»¥å½“前页面 origin ä¸ºå‡†ï¼Œå…¼å®¹ 80/8032/9091 åŠåŽç»­ç«¯å£è°ƒæ•´
      this.Networkheader = window.location.origin;
    },
    // å‘票切换
    handleClick(tab, event) {
src/views/project/fund/applyDetail/index.vue
@@ -516,7 +516,24 @@
      </span>
    </el-dialog>
    <el-dialog v-dialogDrags :modal="false" :close-on-click-modal="false" :title="pdftitle" :visible.sync="pdfVisible"
      width="60%">
      :fullscreen="pdfFullscreen" width="60%" custom-class="pdf-resize-dialog" @closed="pdfFullscreen = false">
      <template slot="title">
        <span>{{ pdftitle }}</span>
        <el-button class="pdf-fullscreen-btn" size="mini"
          :icon="pdfFullscreen ? 'el-icon-close' : 'el-icon-full-screen'"
          @click="pdfFullscreen = !pdfFullscreen">{{ pdfFullscreen ? "还原" : "全屏" }}</el-button>
      </template>
      <el-alert
        :title="
          activeName == 'invoice'
            ? '当前正在操作:发票文件(请确认票据属于发票)'
            : '当前正在操作:普通附件(请勿在此上传发票)'
        "
        :type="activeName == 'invoice' ? 'success' : 'warning'"
        :closable="false"
        show-icon
        style="margin-bottom: 10px;"
      />
      <div style="margin-bottom: 10px;">
        <el-table :data="tableDatatop" style="width: 100%">
          <el-table-column v-if="businessType == 4" prop="price" align="center" label="单价">
@@ -601,18 +618,7 @@
              </div>
            </div>
            <div v-if="this.previewpdf && pdfimgsrcList.length" class="pdfimgmin">
              <!-- <img :src="pdfimg" /> -->
              <el-image ref="imagePreview" style="width: 95%; height: 90%" :src="pdfimg"
                @click="handleImageClick(initialIndex)">
                <!-- <div slot="error" class="image-slot">
                <i class="el-icon-picture-outline"></i>
              </div> -->
              </el-image>
              <custom-image-viewer :url-list="pdfimgsrcList" :initial-index="currentIndex" :visible="viewerVisible"
                @update:visible="viewerVisible = $event" @close="handleViewerClose" />
            </div>
            <div v-else class="pdfimgmins">{{ hintitle }}</div>
            <attachment-preview-pane :file="embeddedFile" style="flex: 1; min-width: 0;" />
          </div>
        </el-tab-pane>
        <el-tab-pane label="发票文件" name="invoice">
@@ -653,19 +659,7 @@
              </div>
            </div>
            <div v-if="this.previewpdf && invoicepdfimgsrcList.length" class="pdfimgmin">
              <!-- <img :src="pdfimg" /> -->
              <el-image style="width: 95%; height: 90%" :src="invoicepdfimg"
                @click="invoicehandleImageClick(initialIndex)">
                <!-- <div slot="error" class="image-slot">
                <i class="el-icon-picture-outline"></i>
              </div> -->
              </el-image>
              <custom-image-viewer :url-list="invoicepdfimgsrcList" :initial-index="invoicecurrentIndex"
                :visible="invoiceviewerVisible" @update:visible="invoiceviewerVisible = $event"
                @close="invoicehandleViewerClose" />
            </div>
            <div v-else class="pdfimgmins">{{ hintitle }}</div>
            <attachment-preview-pane :file="invoiceEmbeddedFile" style="flex: 1; min-width: 0;" />
          </div>
        </el-tab-pane>
      </el-tabs>
@@ -1121,7 +1115,7 @@
      invoicepdfimg: "",
      invoicepdfimgsrcList: [],
      invoDatatop: [],
      activeName: "common", //文件类型
      activeName: "invoice", //文件类型:默认发票文件
      //上传附件路径
      uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload",
      ICDtoken: getToken(),
@@ -1132,6 +1126,7 @@
      //票据文件
      pdftitle: "",
      pdfimg: "",
      invoiceEmbeddedFile: null,
      pdfimgsrcList: [],
      currentIndex: 0, // åˆå§‹ç´¢å¼•
      invoicecurrentIndex: 0, // åˆå§‹ç´¢å¼•
@@ -1234,14 +1229,8 @@
    },
    // èŽ·å–è¯·æ±‚å¤´
    Getnetworkheader() {
      let string = window.location.href;
      if (string.includes("9091")) {
        const index = string.indexOf("9091");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–9091及其前部字符
      } else {
        const index = string.indexOf("8032");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–8032及其前部字符
      }
      // ä»¥å½“前页面 origin ä¸ºå‡†ï¼Œå…¼å®¹ 80/8032/9091 åŠåŽç»­ç«¯å£è°ƒæ•´
      this.Networkheader = window.location.origin;
    },
    /** é€šè¿‡å‚数获取业务类型 */
    getroute() {
@@ -2975,7 +2964,7 @@
    /**
     * 2026-10-15:上传时校验附件文件名(业务要求:不合格立即提示且不加入列表)。
     * è§„则与后端 AttachmentNameValidator åŒå£å¾„:必须含中文 / åŽ»æŽ‰æ‰©å±•ååŽä¸»å¹² â‰¤20 å­— /
     * è§„则与后端 AttachmentNameValidator åŒå£å¾„:不强制含中文 / åŽ»æŽ‰æ‰©å±•ååŽä¸»å¹² â‰¤20 å­— /
     * ä¸å«ç©ºæ ¼åŠ / \ : * ? " < > | # % ç­‰ç‰¹æ®Šå­—符。
     */
    checkUploadFileName(file) {
@@ -3014,27 +3003,27 @@
      const config = {
        headers: { Authorization: "Bearer " + this.ICDtoken }
      };
      const pdfimg = this.Networkheader + "/prod-api" + response.fileName;
      const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + response.fileName;
      //获取票据信息位置
      if (this.activeName == "common") {
        if (response.code == 200) {
          this.previewpdf = true;
          fetch(pdfimg, config)
            .then(response => response.blob())
            .then(blob => {
              // å°†èŽ·å–çš„æ•°æ®æµè½¬æ¢ä¸ºURL
              this.pdfimg = URL.createObjectURL(blob);
              this.pdfimgsrcList.push(URL.createObjectURL(blob));
              this.fileListto.push({
                name: file.name,
                url: URL.createObjectURL(blob)
              });
            })
            .catch(error => {
              console.error("Error loading image", error);
              return;
            });
          // æ–°ä¸Šä¼ æ–‡ä»¶ç›´æŽ¥ä½¿ç”¨æµè§ˆå™¨æœ¬åœ° File,避免服务端文件地址/Token
          // å¼‚常时把 JSON é”™è¯¯å“åº”当成图片或 PDF é¢„览。
          const localBlob = file.raw;
          const localUrl = URL.createObjectURL(localBlob);
          this.pdfimg = localUrl;
          this.embeddedFile = {
            name: file.name,
            url: localUrl,
            type: /\.pdf$/i.test(file.name) ? "pdf" : "image"
          };
          this.pdfimgsrcList.push(localUrl);
          this.fileListto.push({
            name: file.name,
            url: localUrl
          });
          this.$modal.msgSuccess(response.msg);
@@ -3053,21 +3042,19 @@
      } else {
        if (response.code == 200) {
          this.previewpdf = true;
          fetch(pdfimg, config)
            .then(response => response.blob())
            .then(blob => {
              // å°†èŽ·å–çš„æ•°æ®æµè½¬æ¢ä¸ºURL
              this.invoicepdfimg = URL.createObjectURL(blob);
              this.invoicepdfimgsrcList.push(URL.createObjectURL(blob));
              this.invoicefileListto.push({
                name: file.name,
                url: URL.createObjectURL(blob)
              });
            })
            .catch(error => {
              console.error("Error loading image", error);
              return;
            });
          const localBlob = file.raw;
          const localUrl = URL.createObjectURL(localBlob);
          this.invoicepdfimg = localUrl;
          this.invoiceEmbeddedFile = {
            name: file.name,
            url: localUrl,
            type: /\.pdf$/i.test(file.name) ? "pdf" : "image"
          };
          this.invoicepdfimgsrcList.push(localUrl);
          this.invoicefileListto.push({
            name: file.name,
            url: localUrl
          });
          this.$modal.msgSuccess(response.msg);
@@ -3103,7 +3090,7 @@
      if (this.rbDetails[index].annexfilesList) {
        // this.rbDetails[index].annexfilesList.forEach((value, indexson) => {
        //   const pdfimg = this.Networkheader + "/prod-api" + value.url;
        //   const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + value.url;
        //   fetch(pdfimg, config)
        //     .then(response => response.blob())
        //     .then(blob => {
@@ -3125,7 +3112,7 @@
        const fetchPromises = this.rbDetails[index].annexfilesList.map(
          (value, indexson) => {
            const pdfimg = this.Networkheader + "/prod-api" + value.url;
            const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + value.url;
            return fetch(pdfimg, config)
              .then(response => response.blob())
              .then(blob => {
@@ -3158,7 +3145,7 @@
      if (this.rbDetails[index].invoicefilesList) {
        const fetchPromises = this.rbDetails[index].invoicefilesList.map(
          (value, indexson) => {
            const pdfimg = this.Networkheader + "/prod-api" + value.url;
            const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + value.url;
            return fetch(pdfimg, config)
              .then(response => response.blob())
              .then(blob => {
@@ -3201,50 +3188,17 @@
      console.log(this.rbDetails[index].annexfilesList, "annexfilesList");
    },
    // ç‚¹å‡»å·²ä¸Šä¼ æ–‡ä»¶
    // è´¹ç”¨æŠ¥é”€ï¼šé™„件统一使用独立 FilePreview(录入、审批、详情一致)
    downFile(item) {
      if (this.activeName == "common") {
        this.pdftitle =
          "共" + this.pdfimgsrcList.length + "项,当前选中" + item.name;
        let name = item.name.split(".");
        if (name[1] == "pdf") {
          this.$modal.msgWarning("当前文件暂不支持预览");
          this.previewpdf = false;
          this.hintitle = "当前文件暂不支持预览";
        } else if (name[1] == "jpg" || "png") {
          console.log(item, "展示");
          this.previewpdf = true;
          if (item.url) {
            this.pdfimg = item.url;
          } else {
            this.pdfimg = "";
          }
        } else {
          this.hintitle = "当前文件暂不支持预览";
          this.$modal.msgWarning("当前文件暂不支持预览");
          this.previewpdf = false;
        }
      } else {
        this.pdftitle =
          "共" + this.invoicepdfimgsrcList.length + "项,当前选中" + item.name;
        let name = item.name.split(".");
        if (name[1] == "pdf") {
          this.$modal.msgWarning("当前文件暂不支持预览");
          this.previewpdf = false;
          this.hintitle = "当前文件暂不支持预览";
        } else if (name[1] == "jpg" || "png") {
          this.previewpdf = true;
          if (item.url) {
            this.invoicepdfimg = item.url;
          } else {
            this.invoicepdfimg = "";
          }
        } else {
          this.hintitle = "当前文件暂不支持预览";
          this.$modal.msgWarning("当前文件暂不支持预览");
          this.previewpdf = false;
        }
      const source =
        this.activeName == "common" ? this.fileListto : this.invoicefileListto;
      let list = (source || []).map(f => ({ name: f.name, url: f.url }));
      let index = list.findIndex(f => f.url === item.url);
      if (index < 0 && item && item.url) {
        list = [{ name: item.name, url: item.url }];
        index = 0;
      }
      this.$previewFile(list, index > -1 ? index : 0);
    },
    getIndexInArray(arr, obj) {
      return arr.indexOf(obj);
src/views/project/fund/applyDetail0/index.vue
@@ -1034,8 +1034,21 @@
      :close-on-click-modal="false"
      :title="pdftitle"
      :visible.sync="pdfVisible"
      :fullscreen="pdfFullscreen"
      width="60%"
      custom-class="pdf-resize-dialog"
      @closed="pdfFullscreen = false"
    >
      <template slot="title">
        <span>{{ pdftitle }}</span>
        <el-button
          class="pdf-fullscreen-btn"
          size="mini"
          :icon="pdfFullscreen ? 'el-icon-close' : 'el-icon-full-screen'"
          @click="pdfFullscreen = !pdfFullscreen"
          >{{ pdfFullscreen ? "还原" : "全屏" }}</el-button
        >
      </template>
      <div class="pdfimg">
        <div class="box-pdf">
          <div>
@@ -1125,16 +1138,7 @@
          </div> -->
        </div>
        <div v-if="this.previewpdf" class="pdfimgmin">
          <!-- <img :src="pdfimg" /> -->
          <el-image
            style="width: 95%; height: 90%"
            :src="pdfimg"
            :preview-src-list="pdfimgsrcList"
          >
          </el-image>
        </div>
        <div v-else class="pdfimgmins">{{ hintitle }}</div>
        <attachment-preview-pane :file="embeddedFile" style="flex: 1; min-width: 0;" />
      </div>
    </el-dialog>
    <el-dialog title="提示" :visible.sync="Savereminder" width="30%">
@@ -1552,15 +1556,8 @@
    },
    // èŽ·å–è¯·æ±‚å¤´
    Getnetworkheader() {
      let string = window.location.href;
      if (string.includes("9091")) {
        const index = string.indexOf("9091");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–9091及其前部字符
        // console.log(this.Networkheader);
      } else {
        const index = string.indexOf("8032");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–8032及其前部字符
      }
      // ä»¥å½“前页面 origin ä¸ºå‡†ï¼Œå…¼å®¹ 80/8032/9091 åŠåŽç»­ç«¯å£è°ƒæ•´
      this.Networkheader = window.location.origin;
    },
    /** é€šè¿‡å‚数获取业务类型 */
    getroute() {
@@ -3011,7 +3008,7 @@
    // },
    /**
     * 2026-10-15:上传时校验附件文件名(业务要求:不合格立即提示且不加入列表)。
     * è§„则与后端 AttachmentNameValidator åŒå£å¾„:必须含中文 / åŽ»æŽ‰æ‰©å±•ååŽä¸»å¹² â‰¤20 å­— /
     * è§„则与后端 AttachmentNameValidator åŒå£å¾„:不强制含中文 / åŽ»æŽ‰æ‰©å±•ååŽä¸»å¹² â‰¤20 å­— /
     * ä¸å«ç©ºæ ¼åŠ / \ : * ? " < > | # % ç­‰ç‰¹æ®Šå­—符。
     */
    checkUploadFileName(file) {
@@ -3056,9 +3053,15 @@
        this.fileListto.push({ name: file.name, url: response.fileName });
        // this.fileListto.push({ name: file.name, url: response.url });
        this.pdfimgsrcList.push(
          this.Networkheader + "/prod-api" + response.fileName
          this.Networkheader + process.env.VUE_APP_BASE_API + response.fileName
        );
        this.pdfimg = this.Networkheader + "/prod-api" + response.fileName;
        this.pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + response.fileName;
        const localUrl = URL.createObjectURL(file.raw);
        this.embeddedFile = {
          name: file.name,
          url: localUrl,
          type: /\.pdf$/i.test(file.name) ? "pdf" : "image"
        };
        this.rbDetails[this.atpresent].annexfilesList = this.fileListto;
      } else {
@@ -3073,11 +3076,11 @@
      if (this.rbDetails[index].annexfilesList) {
        this.previewpdf = true;
        this.fileListto = this.rbDetails[index].annexfilesList;
        this.pdfimg = this.Networkheader + "/prod-api" + this.fileListto[0].url;
        this.pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + this.fileListto[0].url;
        // this.pdfimg = this.fileListto[0].url;
        this.pdfimgsrcList = [];
        this.fileListto.forEach(item => {
          this.pdfimgsrcList.push(this.Networkheader + "/prod-api" + item.url);
          this.pdfimgsrcList.push(this.Networkheader + process.env.VUE_APP_BASE_API + item.url);
        });
        console.log(this.pdfimgsrcList);
        console.log(this.pdfimg);
@@ -3088,34 +3091,31 @@
      }
    },
    // ç‚¹å‡»å·²ä¸Šä¼ æ–‡ä»¶
    // ç‚¹å‡»å·²ä¸Šä¼ æ–‡ä»¶ï¼šåœ¨é™„件弹窗右侧内嵌预览
    downFile(item) {
      console.log(item);
      this.pdftitle = item.name;
      let name = item.name.split(".");
      if (name[1] == "pdf") {
        this.$modal.msgWarning("当前文件暂不支持预览");
        this.previewpdf = false;
        this.hintitle = "当前文件暂不支持预览";
      } else if (name[1] == "jpg" || "png") {
        this.previewpdf = true;
        if (item.url) {
          this.pdfimg = this.Networkheader + "/prod-api" + item.url;
          // if (!this.pdfimgsrcList.includes(this.pdfimg)) {
          //   this.pdfimgsrcList.push(this.pdfimg);
          //   console.log("加入");
          // }
          console.log(this.pdfimg);
          console.log(this.pdfimgsrcList, "imglist");
        } else {
          this.pdfimg = this.Networkheader + "/prod-api" + item.url;
        }
      } else {
        this.hintitle = "当前文件暂不支持预览";
        this.$modal.msgWarning("当前文件暂不支持预览");
        this.previewpdf = false;
      if (this.operationType == "detail") {
        const list = (this.fileListto || []).map(f => ({
          name: f.name,
          url: f.url
        }));
        const index = list.findIndex(f => f.url === item.url);
        this.$previewFile(list, index > -1 ? index : 0);
        return;
      }
      const ext = String(item.name || "").toLowerCase().split(".").pop();
      const isImage = ["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext);
      if (ext !== "pdf" && !isImage) {
        this.hintitle = "当前文件暂不支持预览";
        this.$modal.msgWarning("当前文件暂不支持预览");
        return;
      }
      this.embeddedFile = {
        name: item.name,
        url: item.url,
        type: ext === "pdf" ? "pdf" : "image"
      };
      this.pdftitle = item.name;
      this.pdfVisible = true;
    },
    getIndexInArray(arr, obj) {
      return arr.indexOf(obj);
@@ -3126,7 +3126,7 @@
      console.log(row);
      const indexlist = this.getIndexInArray(
        this.pdfimgsrcList,
        this.Networkheader + "/prod-api" + row.url
        this.Networkheader + process.env.VUE_APP_BASE_API + row.url
      );
      console.log(indexlist);
      this.pdfimgsrcList.splice(indexlist, 1);
src/views/project/fund/ethicalExpertFeeApply/index.vue
@@ -2064,8 +2064,16 @@
      }
    },
    // ä¸‹è½½æ–‡ä»¶
    // é¢„览/下载文件
    downFile(item) {
      const ext = String(item.name || "")
        .split(".")
        .pop()
        .toLowerCase();
      if (["pdf", "jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext)) {
        this.$previewFile([{ name: item.name, url: item.url }]);
        return;
      }
      const url = process.env.VUE_APP_BASE_API + item.url;
      var a = document.createElement("a");
      var event = new MouseEvent("click");
src/views/project/fund/expertFeeApply/index.vue
@@ -2661,8 +2661,16 @@
    //文件上传
    handleUploadError() {},
    /** ä¸‹è½½æ–‡ä»¶æŒ‰é’®æ“ä½œ */
    /** é¢„览/下载文件 */
    downFile(item) {
      const ext = String(item.name || "")
        .split(".")
        .pop()
        .toLowerCase();
      if (["pdf", "jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext)) {
        this.$previewFile([{ name: item.name, url: item.url }]);
        return;
      }
      const url = process.env.VUE_APP_BASE_API + item.url;
      var a = document.createElement("a");
      var event = new MouseEvent("click");
src/views/project/fund/medicalCostApply/index.vue
@@ -1366,18 +1366,18 @@
    // ç‚¹å‡»å·²ä¸Šä¼ æ–‡ä»¶
    downFile(item) {
      this.pdftitle = item.name;
      let name = item.name.split(".");
      if (name[1] == "pdf") {
        this.$modal.msgWarning("当前文件暂不支持预览");
        this.previewpdf = false;
        this.hintitle = "当前文件暂不支持预览";
      } else if (name[1] == "jpg" || "png") {
        this.previewpdf = true;
        if (item.url) {
          this.pdfimg = item.url;
        } else {
          this.pdfimg = item.response.url;
        }
      const ext = String(item.name || "pdf").toLowerCase().split(".").pop();
      if (ext === "pdf") {
        this.$previewFile([{ name: item.name, url: item.url }]);
        return;
      } else if (["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext)) {
        this.$previewFile([
          {
            name: item.name,
            url: item.url || (item.response && item.response.url)
          }
        ]);
        return;
      } else {
        this.hintitle = "当前文件暂不支持预览";
        this.$modal.msgWarning("当前文件暂不支持预览");
src/views/project/fund/officeExpenseApply/index.vue
@@ -1936,8 +1936,16 @@
        console.log(response.msg);
      }
    },
    // ä¸‹è½½æ–‡ä»¶
    // é¢„览/下载文件
    downFile(item) {
      const ext = String(item.name || "")
        .split(".")
        .pop()
        .toLowerCase();
      if (["pdf", "jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext)) {
        this.$previewFile([{ name: item.name, url: item.url }]);
        return;
      }
      const url = process.env.VUE_APP_BASE_API + item.url;
      var a = document.createElement("a");
      var event = new MouseEvent("click");
src/views/project/fund/performancedetails/index.vue
@@ -596,8 +596,28 @@
      :close-on-click-modal="false"
      :title="pdftitle"
      :visible.sync="pdfVisible"
      :fullscreen="pdfFullscreen"
      width="60%"
      custom-class="pdf-resize-dialog"
      @closed="pdfFullscreen = false"
    >
      <template slot="title">
        <span>{{ pdftitle }}</span>
        <el-button class="pdf-fullscreen-btn" size="mini"
          :icon="pdfFullscreen ? 'el-icon-close' : 'el-icon-full-screen'"
          @click="pdfFullscreen = !pdfFullscreen">{{ pdfFullscreen ? "还原" : "全屏" }}</el-button>
      </template>
      <el-alert
        :title="
          activeName == 2
            ? '当前正在操作:发票文件(请确认票据属于发票)'
            : '当前正在操作:普通附件(请勿在此上传发票)'
        "
        :type="activeName == 2 ? 'success' : 'warning'"
        :closable="false"
        show-icon
        style="margin-bottom: 10px;"
      />
      <div style="margin-bottom: 10px;">
        <el-table :data="tableDatatop" style="width: 100%">
          <el-table-column
@@ -724,24 +744,7 @@
              </div>
            </div>
            <div
              v-if="this.previewpdf && pdfimgsrcList.length"
              class="pdfimgmin"
            >
              <el-image
                style="width: 95%; height: 90%"
                :src="pdfimg"
                @click="handleImageClick(0)"
              />
              <custom-image-viewer
                :url-list="pdfimgsrcList"
                :initial-index="currentIndex"
                :visible="viewerVisible"
                @update:visible="viewerVisible = $event"
                @close="handleViewerClose"
              />
            </div>
            <div v-else class="pdfimgmins">{{ hintitle }}</div>
            <attachment-preview-pane :file="embeddedFile" style="flex: 1; min-width: 0;" />
          </div>
        </el-tab-pane>
        <el-tab-pane label="发票文件" :name="2">
@@ -829,24 +832,7 @@
              </div>
            </div>
            <div
              v-if="this.previewpdf && invoicepdfimgsrcList.length"
              class="pdfimgmin"
            >
              <el-image
                style="width: 95%; height: 90%"
                :src="invoicepdfimg"
                @click="invoiceHandleImageClick(0)"
              />
              <custom-image-viewer
                :url-list="invoicepdfimgsrcList"
                :initial-index="invoiceCurrentIndex"
                :visible="invoiceViewerVisible"
                @update:visible="invoiceViewerVisible = $event"
                @close="invoiceHandleViewerClose"
              />
            </div>
            <div v-else class="pdfimgmins">{{ hintitle }}</div>
            <attachment-preview-pane :file="invoiceEmbeddedFile" style="flex: 1; min-width: 0;" />
          </div>
        </el-tab-pane>
      </el-tabs>
@@ -1261,7 +1247,7 @@
      invoicefileListto: [],
      invoicepdfimg: "",
      invoicepdfimgsrcList: [],
      activeName: 1, //文件类型
      activeName: 2, //文件类型:默认发票文件
      //上传附件路径
      uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload",
      //文件上传token
@@ -1271,6 +1257,7 @@
      //票据文件
      pdftitle: "",
      pdfimg: "",
      invoiceEmbeddedFile: null,
      pdfimgsrcList: [],
      pdfVisible: false,
      previewpdf: false,
@@ -2826,18 +2813,12 @@
    },
    // èŽ·å–è¯·æ±‚å¤´
    Getnetworkheader() {
      let string = window.location.href;
      if (string.includes("9091")) {
        const index = string.indexOf("9091");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–9091及其前部字符
      } else {
        const index = string.indexOf("8032");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–8032及其前部字符
      }
      // ä»¥å½“前页面 origin ä¸ºå‡†ï¼Œå…¼å®¹ 80/8032/9091 åŠåŽç»­ç«¯å£è°ƒæ•´
      this.Networkheader = window.location.origin;
    },
    /**
     * 2026-10-15:上传时校验附件文件名(业务要求:不合格立即提示且不加入列表)。
     * è§„则与后端 AttachmentNameValidator åŒå£å¾„:必须含中文 / åŽ»æŽ‰æ‰©å±•ååŽä¸»å¹² â‰¤20 å­— /
     * è§„则与后端 AttachmentNameValidator åŒå£å¾„:不强制含中文 / åŽ»æŽ‰æ‰©å±•ååŽä¸»å¹² â‰¤20 å­— /
     * ä¸å«ç©ºæ ¼åŠ / \ : * ? " < > | # % ç­‰ç‰¹æ®Šå­—符。
     */
    checkUploadFileName(file) {
@@ -2876,27 +2857,23 @@
      const config = {
        headers: { Authorization: "Bearer " + this.ICDtoken }
      };
      const pdfimg = this.Networkheader + "/prod-api" + response.fileName;
      const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + response.fileName;
      //获取票据信息位置
      if (this.activeName == 1) {
        if (response.code == 200) {
          this.previewpdf = true;
          fetch(pdfimg, config)
            .then(response => response.blob())
            .then(blob => {
              // å°†èŽ·å–çš„æ•°æ®æµè½¬æ¢ä¸ºURL
              this.pdfimg = URL.createObjectURL(blob);
              this.pdfimgsrcList.push(URL.createObjectURL(blob));
              this.fileListto.push({
                name: file.name,
                url: URL.createObjectURL(blob)
              });
            })
            .catch(error => {
              console.error("Error loading image", error);
              return;
            });
          const localUrl = URL.createObjectURL(file.raw);
          this.pdfimg = localUrl;
          this.embeddedFile = {
            name: file.name,
            url: localUrl,
            type: /\.pdf$/i.test(file.name) ? "pdf" : "image"
          };
          this.pdfimgsrcList.push(localUrl);
          this.fileListto.push({
            name: file.name,
            url: localUrl
          });
          this.$modal.msgSuccess(response.msg);
@@ -2915,21 +2892,18 @@
      } else {
        if (response.code == 200) {
          this.previewpdf = true;
          fetch(pdfimg, config)
            .then(response => response.blob())
            .then(blob => {
              // å°†èŽ·å–çš„æ•°æ®æµè½¬æ¢ä¸ºURL
              this.invoicepdfimg = URL.createObjectURL(blob);
              this.invoicepdfimgsrcList.push(URL.createObjectURL(blob));
              this.invoicefileListto.push({
                name: file.name,
                url: URL.createObjectURL(blob)
              });
            })
            .catch(error => {
              console.error("Error loading image", error);
              return;
            });
          const localUrl = URL.createObjectURL(file.raw);
          this.invoicepdfimg = localUrl;
          this.invoiceEmbeddedFile = {
            name: file.name,
            url: localUrl,
            type: /\.pdf$/i.test(file.name) ? "pdf" : "image"
          };
          this.invoicepdfimgsrcList.push(localUrl);
          this.invoicefileListto.push({
            name: file.name,
            url: localUrl
          });
          this.$modal.msgSuccess(response.msg);
@@ -2966,7 +2940,7 @@
      if (this.rbDetails[index].annexfilesList) {
        const fetchPromises = this.rbDetails[index].annexfilesList.map(
          (value, indexson) => {
            const pdfimg = this.Networkheader + "/prod-api" + value.url;
            const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + value.url;
            return fetch(pdfimg, config)
              .then(response => response.blob())
              .then(blob => {
@@ -2999,7 +2973,7 @@
      if (this.rbDetails[index].invoicefilesList) {
        const fetchPromises = this.rbDetails[index].invoicefilesList.map(
          (value, indexson) => {
            const pdfimg = this.Networkheader + "/prod-api" + value.url;
            const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + value.url;
            return fetch(pdfimg, config)
              .then(response => response.blob())
              .then(blob => {
@@ -3033,7 +3007,7 @@
        this.pdftitle = "";
      }
      if (this.activeName == "common") {
      if (this.activeName == 1) {
        this.pdftitle = "共" + this.pdfimgsrcList.length + "项";
      } else {
        this.pdftitle = "共" + this.invoicepdfimgsrcList.length + "项";
@@ -3042,50 +3016,38 @@
      console.log(this.rbDetails[index].annexfilesList, "annexfilesList");
    },
    // ç‚¹å‡»å·²ä¸Šä¼ æ–‡ä»¶
    // ç‚¹å‡»å·²ä¸Šä¼ æ–‡ä»¶ï¼šåœ¨é™„件弹窗右侧内嵌预览
    downFile(item) {
      if (this.operationType == "detail") {
        const source =
          this.activeName == 1 ? this.fileListto : this.invoicefileListto;
        const list = (source || []).map(f => ({ name: f.name, url: f.url }));
        const index = list.findIndex(f => f.url === item.url);
        this.$previewFile(list, index > -1 ? index : 0);
        return;
      }
      const ext = String(item.name || "").toLowerCase().split(".").pop();
      const isImage = ["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext);
      if (ext !== "pdf" && !isImage) {
        this.hintitle = "当前文件暂不支持预览";
        this.$modal.msgWarning("当前文件暂不支持预览");
        return;
      }
      const file = {
        name: item.name,
        url: item.url,
        type: ext === "pdf" ? "pdf" : "image"
      };
      if (this.activeName == 1) {
        this.embeddedFile = file;
        this.pdftitle =
          "共" + this.pdfimgsrcList.length + "项,当前选中" + item.name;
        let name = item.name.split(".");
        if (name[1] == "pdf") {
          this.$modal.msgWarning("当前文件暂不支持预览");
          this.previewpdf = false;
          this.hintitle = "当前文件暂不支持预览";
        } else if (name[1] == "jpg" || "png") {
          console.log(item, "展示");
          this.previewpdf = true;
          if (item.url) {
            this.pdfimg = item.url;
          } else {
            this.pdfimg = "";
          }
        } else {
          this.hintitle = "当前文件暂不支持预览";
          this.$modal.msgWarning("当前文件暂不支持预览");
          this.previewpdf = false;
        }
      } else {
        this.invoiceEmbeddedFile = file;
        this.pdftitle =
          "共" + this.invoicepdfimgsrcList.length + "项,当前选中" + item.name;
        let name = item.name.split(".");
        if (name[1] == "pdf") {
          this.$modal.msgWarning("当前文件暂不支持预览");
          this.previewpdf = false;
          this.hintitle = "当前文件暂不支持预览";
        } else if (name[1] == "jpg" || "png") {
          this.previewpdf = true;
          if (item.url) {
            this.invoicepdfimg = item.url;
          } else {
            this.invoicepdfimg = "";
          }
        } else {
          this.hintitle = "当前文件暂不支持预览";
          this.$modal.msgWarning("当前文件暂不支持预览");
          this.previewpdf = false;
        }
      }
      this.pdfVisible = true;
    },
    getIndexInArray(arr, obj) {
      return arr.indexOf(obj);
@@ -3173,7 +3135,7 @@
    },
    Downloadfile(row) {
      console.log(row);
      window.location.href = this.Networkheader + "/prod-api" + row.url;
      window.location.href = this.Networkheader + process.env.VUE_APP_BASE_API + row.url;
    },
    //表格合计
    getSummaries(param) {
src/views/project/fundcheck/medicalCostCheck/index.vue
@@ -1760,8 +1760,21 @@
        console.log(response.msg);
      }
    },
    // ä¸‹è½½æ–‡ä»¶
    // é¢„览/下载文件
    downFile(item) {
      const ext = String(item.name || "")
        .split(".")
        .pop()
        .toLowerCase();
      if (["pdf", "jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext)) {
        const list = (this.fileList || []).map(f => ({
          name: f.name,
          url: f.url
        }));
        const index = list.findIndex(f => f.url === item.url);
        this.$previewFile(list, index > -1 ? index : 0);
        return;
      }
      const url = process.env.VUE_APP_BASE_API + item.url;
      var a = document.createElement("a");
      var event = new MouseEvent("click");
src/views/project/fundcheck/officeExpenseCheck/index.vue
@@ -1765,8 +1765,21 @@
        console.log(response.msg);
      }
    },
    // ä¸‹è½½æ–‡ä»¶
    // é¢„览/下载文件
    downFile(item) {
      const ext = String(item.name || "")
        .split(".")
        .pop()
        .toLowerCase();
      if (["pdf", "jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext)) {
        const list = (this.fileList || []).map(f => ({
          name: f.name,
          url: f.url
        }));
        const index = list.findIndex(f => f.url === item.url);
        this.$previewFile(list, index > -1 ? index : 0);
        return;
      }
      const url = process.env.VUE_APP_BASE_API + item.url;
      var a = document.createElement("a");
      var event = new MouseEvent("click");
src/views/project/statistics/applyStatistics/index.vue
@@ -338,6 +338,10 @@
    /** æŸ¥è¯¢æçŒ®åŸºç¡€åˆ—表 */
    getList() {
      if (!this.selecttime || this.selecttime.length !== 2) {
        this.$modal.msgWarning("请选择时间范围后再查询");
        return;
      }
      listbxiaostatistics(this.queryParams).then(res => {
        this.total = res.total;
        this.jxidata = res.rows;
@@ -503,7 +507,6 @@
  //生命周期 - åˆ›å»ºå®Œæˆï¼ˆå¯ä»¥è®¿é—®å½“前this实例)
  created() {
    //获取列表
    this.getList();
    this.listDept();
    this.getUserList();
  }
src/views/project/statistics/casePanorama/index.vue
@@ -210,11 +210,14 @@
  },
  created() {
    this.getDeptOptions();
    this.getList();
  },
  methods: {
    /** æŸ¥è¯¢æ¡ˆä¾‹å…¨æ™¯ */
    getList() {
      if (!this.dateRange || this.dateRange.length !== 2) {
        this.$modal.msgWarning("请选择时间范围后再查询");
        return;
      }
      this.loading = true;
      listCasePanorama(this.queryParams)
        .then(res => {
src/views/project/statistics/caseStatistics/index.vue
@@ -340,6 +340,10 @@
    /** æŸ¥è¯¢æçŒ®åŸºç¡€åˆ—表 */
    getList() {
      if (!this.selecttime || this.selecttime.length !== 2) {
        this.$modal.msgWarning("请选择时间范围后再查询");
        return;
      }
      listanlstatistics(this.queryParams).then(res => {
        this.jxidata = res.rows;
        this.jxidata.forEach(($obj, $index) => {
@@ -486,7 +490,6 @@
  //生命周期 - åˆ›å»ºå®Œæˆï¼ˆå¯ä»¥è®¿é—®å½“前this实例)
  created() {
    //获取列表
    this.getList();
    this.listDept();
  }
  // //生命周期 - æŒ‚载完成(可以访问DOM元素)
src/views/project/statistics/chargeStatistics/index.vue
@@ -337,6 +337,10 @@
    /** æŸ¥è¯¢æ”¶æ¬¾ç»Ÿè®¡ */
    getList() {
      if (!this.selecttime || this.selecttime.length !== 2) {
        this.$modal.msgWarning("请选择时间范围后再查询");
        return;
      }
      this.loading = true;
      listsfeistatistics(this.queryParams)
        .then(res => {
src/views/project/statistics/performanceStaristics/index.vue
@@ -281,6 +281,10 @@
    /** æŸ¥è¯¢æçŒ®åŸºç¡€åˆ—表 */
    getList() {
      if (!this.selecttime || this.selecttime.length !== 2) {
        this.$modal.msgWarning("请选择时间范围后再查询");
        return;
      }
      listjixstatistics(this.buildQueryParams()).then(res => {
        this.jxidata = res.rows;
        this.jxidata.forEach(($obj, $index) => {
@@ -447,7 +451,6 @@
  //生命周期 - åˆ›å»ºå®Œæˆï¼ˆå¯ä»¥è®¿é—®å½“前this实例)
  created() {
    //获取列表
    this.getList();
    this.listDept();
  }
  // //生命周期 - æŒ‚载完成(可以访问DOM元素)
src/views/project/tallage/computingBill/index.vue
@@ -439,8 +439,17 @@
        :close-on-click-modal="false"
        :title="pdftitle"
        :visible.sync="pdfVisible"
        :fullscreen="pdfFullscreen"
        width="60%"
        custom-class="pdf-resize-dialog"
        @closed="pdfFullscreen = false"
      >
        <template slot="title">
          <span>{{ pdftitle }}</span>
          <el-button class="pdf-fullscreen-btn" size="mini"
            :icon="pdfFullscreen ? 'el-icon-close' : 'el-icon-full-screen'"
            @click="pdfFullscreen = !pdfFullscreen">{{ pdfFullscreen ? "还原" : "全屏" }}</el-button>
        </template>
        <div class="pdfimg">
          <div class="box-pdf">
            <div>
@@ -526,19 +535,7 @@
            </div>
          </div>
          <div v-if="this.previewpdf && pdfimgsrcList.length" class="pdfimgmin">
            <!-- <img :src="pdfimg" /> -->
            <el-image
              style="width: 95%; height: 90%"
              :src="pdfimg"
              :preview-src-list="pdfimgsrcList"
            >
              <!-- <div slot="error" class="image-slot">
                <i class="el-icon-picture-outline"></i>
              </div> -->
            </el-image>
          </div>
          <div v-else class="pdfimgmins">{{ hintitle }}</div>
          <attachment-preview-pane :file="embeddedFile" style="flex: 1; min-width: 0;" />
        </div>
      </el-dialog>
    </div>
@@ -751,6 +748,7 @@
  created() {
    console.log(234);
    this.Networkheader = window.location.origin;
    this.GetMaxTaxid();
    //获取参数
@@ -1201,25 +1199,23 @@
      const config = {
        headers: { Authorization: "Bearer " + this.ICDtoken }
      };
      const pdfimg = this.Networkheader + "/prod-api" + response.fileName;
      const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + response.fileName;
      //获取票据信息位置
      if (response.code == 200) {
        this.previewpdf = true;
        fetch(pdfimg, config)
          .then(response => response.blob())
          .then(blob => {
            // å°†èŽ·å–çš„æ•°æ®æµè½¬æ¢ä¸ºURL
            this.pdfimg = URL.createObjectURL(blob);
            this.pdfimgsrcList.push(URL.createObjectURL(blob));
            this.fileListto.push({
              name: file.name,
              url: URL.createObjectURL(blob)
            });
          })
          .catch(error => {
            console.error("Error loading image", error);
            return;
          });
        const localBlob = file.raw;
        const localUrl = URL.createObjectURL(localBlob);
        this.pdfimg = localUrl;
        this.embeddedFile = {
          name: file.name,
          url: localUrl,
          type: /\.pdf$/i.test(file.name) ? "pdf" : "image"
        };
        this.pdfimgsrcList.push(localUrl);
        this.fileListto.push({
          name: file.name,
          url: localUrl
        });
        this.$modal.msgSuccess(response.msg);
@@ -1257,7 +1253,7 @@
      if (this.donorchargeorgans[index].annexFilesList) {
        const fetchPromises = this.donorchargeorgans[index].annexFilesList.map(
          (value, indexson) => {
            const pdfimg = this.Networkheader + "/prod-api" + value.url;
            const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + value.url;
            return fetch(pdfimg, config)
              .then(response => response.blob())
              .then(blob => {
@@ -1296,28 +1292,32 @@
      );
    },
    // ç‚¹å‡»å·²ä¸Šä¼ æ–‡ä»¶
    // ç‚¹å‡»å·²ä¸Šä¼ æ–‡ä»¶ï¼šåœ¨é™„件弹窗右侧内嵌预览
    downFile(item) {
      if (this.operationType == "detail") {
        const list = (this.fileListto || []).map(f => ({
          name: f.name,
          url: f.url
        }));
        const index = list.findIndex(f => f.url === item.url);
        this.$previewFile(list, index > -1 ? index : 0);
        return;
      }
      const ext = String(item.name || "").toLowerCase().split(".").pop();
      const isImage = ["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext);
      if (ext !== "pdf" && !isImage) {
        this.hintitle = "当前文件暂不支持预览";
        this.$modal.msgWarning("当前文件暂不支持预览");
        return;
      }
      this.embeddedFile = {
        name: item.name,
        url: item.url,
        type: ext === "pdf" ? "pdf" : "image"
      };
      this.pdftitle =
        "共" + this.pdfimgsrcList.length + "项,当前选中" + item.name;
      let name = item.name.split(".");
      if (name[1] == "pdf") {
        this.$modal.msgWarning("当前文件暂不支持预览");
        this.previewpdf = false;
        this.hintitle = "当前文件暂不支持预览";
      } else if (name[1] == "jpg" || "png") {
        console.log(item, "展示");
        this.previewpdf = true;
        if (item.url) {
          this.pdfimg = item.url;
        } else {
          this.pdfimg = "";
        }
      } else {
        this.hintitle = "当前文件暂不支持预览";
        this.$modal.msgWarning("当前文件暂不支持预览");
        this.previewpdf = false;
      }
      this.pdfVisible = true;
    },
    getIndexInArray(arr, obj) {
      return arr.indexOf(obj);
src/views/project/travelexpenseapply/edit.vue
@@ -382,17 +382,19 @@
    },
    methods: {
        handleUploadError() { },
        // ä¸‹è½½æ–‡ä»¶
        // é¢„览/下载文件
        downFile(item) {
            const ext = String(item.name || '').split('.').pop().toLowerCase();
            if (['pdf', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext)) {
                this.$previewFile([{ name: item.name, url: item.url }]);
                return;
            }
            const url = process.env.VUE_APP_BASE_API + item.url
            var a = document.createElement('a');
            var event = new MouseEvent('click');
            a.download = item.name;
            a.href = url;
            a.dispatchEvent(event);
        },
        remove(file) {
src/views/project/travelexpenseapply/index.vue
@@ -1265,8 +1265,16 @@
      console.log("上报", e);
    },
    // ä¸‹è½½æ–‡ä»¶
    // é¢„览/下载文件
    downFile(item) {
      const ext = String(item.name || "")
        .split(".")
        .pop()
        .toLowerCase();
      if (["pdf", "jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext)) {
        this.$previewFile([{ name: item.name, url: item.url }]);
        return;
      }
      const url = process.env.VUE_APP_BASE_API + item.url;
      var a = document.createElement("a");
      var event = new MouseEvent("click");
src/views/project/travelexpenseapply/travelexpensedetail/index.vue
@@ -3,37 +3,19 @@
    <div class="boxdiv">
      <div class="top-text">{{ title }}</div>
      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
        <el-row
          ><el-col :span="5">
        <el-row><el-col :span="5">
            <el-form-item label="申请日期" prop="applyTime">
              <el-date-picker
                style="width: 100%"
                v-model="form.applyTime"
                type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="申请日期"
                :disabled="true"
              >
              <el-date-picker style="width: 100%" v-model="form.applyTime" type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss" placeholder="申请日期" :disabled="true">
              </el-date-picker>
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item label="经办人" prop="username">
              <el-select
                v-model="form.username"
                clearable
                filterable
                allow-create
                ref="getReportname"
                default-first-option
                placeholder="请选择"
              >
                <el-option
                  v-for="item in personsList"
                  :key="item.reportNo"
                  :label="item.reportName"
                  :value="item.reportName"
                >
              <el-select v-model="form.username" clearable filterable allow-create ref="getReportname"
                default-first-option placeholder="请选择">
                <el-option v-for="(item, index) in personsList" :key="item.reportNo + '-' + index"
                  :label="item.reportName" :value="item.reportName">
                </el-option>
              </el-select>
            </el-form-item>
@@ -58,87 +40,44 @@
          </el-col>
          <el-col :span="5">
            <el-form-item label="报销类别" prop="costtype">
              <el-select
                v-model="form.costtype"
                value-key="value"
                placeholder="请选择报销人员类别"
              >
                <el-option
                  v-for="dict in dict.type.sys_travelexpensebelong"
                  :key="dict.value"
                  :label="dict.label"
                  :value="dict.value"
                ></el-option>
              <el-select v-model="form.costtype" value-key="value" placeholder="请选择报销人员类别">
                <el-option v-for="dict in dict.type.sys_travelexpensebelong" :key="dict.value" :label="dict.label"
                  :value="dict.value"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item
              label="捐献者"
              prop="infoid"
              v-show="form.costtype == 3 || form.costtype == 4"
            >
              <el-select
                v-model="form.infoid"
                @change="selectbaseinfo"
                clearable
                filterable
                placeholder="请选择捐献者"
              >
                <el-option
                  v-for="item in baselist"
                  :key="item.index"
                  :label="item.name"
                  :value="item.infoid"
                >
            <el-form-item label="捐献者" prop="infoid" v-show="form.costtype == 3 || form.costtype == 4">
              <el-select v-model="form.infoid" @change="selectbaseinfo" clearable filterable placeholder="请选择捐献者">
                <el-option v-for="(item, index) in baselist" :key="item.index + '-' + index" :label="item.name"
                  :value="item.infoid">
                </el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <div style="margin-left: 10px;">
              <dict-tag
                :options="dict.type.sys_DonationStatus"
                :value="form.invoicecount"
              />
              <dict-tag :options="dict.type.sys_DonationStatus" :value="form.invoicecount" />
            </div>
          </el-col>
        </el-row>
        <el-row style="margin-top: 10px">
          <el-col :span="5">
            <el-form-item label="审核状态" prop="flowlevel">
              <el-select
                v-model="form.recordstatus"
                placeholder="请选择审核状态"
                clearable
                :disabled="true"
                size="small"
              >
                <el-option
                  v-for="dict in dict.type.sys_recordstatus"
                  :key="dict.value"
                  :label="dict.label"
                  :value="dict.value"
                />
              <el-select v-model="form.recordstatus" placeholder="请选择审核状态" clearable :disabled="true" size="small">
                <el-option v-for="dict in dict.type.sys_recordstatus" :key="dict.value" :label="dict.label"
                  :value="dict.value" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item label="报销金额" prop="amountrequested">
              <el-input
                :disabled="true"
                v-model="form.amountrequested"
                placeholder="报销合计金额"
              />
              <el-input :disabled="true" v-model="form.amountrequested" placeholder="报销合计金额" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="大写金额" prop="bigstrmoney">
              <el-input
                :disabled="true"
                v-model="form.bigstrmoney"
                placeholder="报销大写金额"
              />
              <el-input :disabled="true" v-model="form.bigstrmoney" placeholder="报销大写金额" />
            </el-form-item>
          </el-col>
        </el-row>
@@ -234,324 +173,145 @@
        <el-row v-if="jurisdiction">
          <!-- <el-alert title="当前为查看状态,不支持修改存储。" type="warning">
          </el-alert> -->
          <el-alert
            title="当前为查看状态,不支持修改存储。"
            type="warning"
            show-icon
          >
          <el-alert title="当前为查看状态,不支持修改存储。" type="warning" show-icon>
          </el-alert>
        </el-row>
        <el-row style="margin-top: 25px; margin-bottom: 5px">
          <el-col :span="20">报销明细</el-col>
        </el-row>
        <el-row>
          <el-table
            :data="rbDetails"
            ref="table"
            border
            max-height="800"
            highlight-current-row
            :summary-method="getSummaries"
            show-summary
          >
            <el-table-column
              prop="orderno"
              slot=""
              label="序号"
              align="center"
              fixed
              width="88"
            >
          <el-table :data="rbDetails" ref="table" border max-height="800" highlight-current-row
            :summary-method="getSummaries" show-summary>
            <el-table-column prop="orderno" slot="" label="序号" align="center" fixed width="88">
              <template slot-scope="scope">
                <el-input v-model="scope.row.orderno" placeholder="序号" />
              </template>
            </el-table-column>
            <!--主键、外不展示-->
            <el-table-column
              prop="id"
              slot=""
              label="主键"
              align="center"
              fixed
              width="95"
              v-if="false"
            >
            <el-table-column prop="id" slot="" label="主键" align="center" fixed width="95" v-if="false">
              <template slot-scope="scope">
                <el-input v-model="scope.row.id" placeholder="id" />
              </template>
            </el-table-column>
            <el-table-column
              prop="rbid"
              slot=""
              label="外键"
              align="center"
              fixed
              width="95"
              v-if="false"
            >
            <el-table-column prop="rbid" slot="" label="外键" align="center" fixed width="95" v-if="false">
              <template slot-scope="scope">
                <el-input v-model="scope.row.rbid" placeholder="rbid" />
              </template>
            </el-table-column>
            <el-table-column
              prop="persontype"
              slot=""
              label="人员类别"
              align="center"
              fixed
              width="130"
            >
            <el-table-column prop="persontype" slot="" label="人员类别" align="center" fixed width="130">
              <template slot-scope="scope">
                <el-select
                  v-model="scope.row.persontype"
                  value-key="value"
                  placeholder="人员类别"
                  @change="getPersons(scope.row)"
                >
                  <el-option
                    v-for="dict in dict.type.sys_travelexpensebelong"
                    :key="dict.value"
                    :label="dict.label"
                    :value="dict.label"
                  ></el-option>
                <el-select v-model="scope.row.persontype" value-key="value" placeholder="人员类别"
                  @change="getPersons(scope.row)">
                  <el-option v-for="dict in dict.type.sys_travelexpensebelong" :key="dict.value" :label="dict.label"
                    :value="dict.label"></el-option>
                </el-select>
              </template>
            </el-table-column>
            <el-table-column
              prop="personname"
              slot=""
              label="费用人员"
              align="center"
              fixed
              width="110"
            >
            <el-table-column prop="personname" slot="" label="费用人员" align="center" fixed width="110">
              <template slot-scope="scope">
                <el-select
                  v-model="scope.row.personname"
                  clearable
                  filterable
                  allow-create
                  default-first-option
                  placeholder="请选择"
                >
                  <el-option
                    v-for="item in personsSel"
                    :key="item.id"
                    :label="item.reportNameDesc"
                    :value="item.reportName"
                  >
                <el-select v-model="scope.row.personname" clearable filterable allow-create default-first-option
                  placeholder="请选择">
                  <el-option v-for="item in personsSel" :key="item.id" :label="item.reportNameDesc"
                    :value="item.reportName">
                  </el-option>
                </el-select>
              </template>
            </el-table-column>
            <el-table-column
              prop="starttime"
              slot=""
              label="开始日期"
              fixed
              align="center"
              width="150"
            >
            <el-table-column prop="starttime" slot="" label="开始日期" fixed align="center" width="150">
              <template slot-scope="scope">
                <el-date-picker
                  :picker-options="{
                    disabledDate: time => {
                      if (scope.row.endtime) {
                        return (
                          time.getTime() >
                          new Date(scope.row.endtime.replace(/-/g, '/'))
                        );
                      }
                <el-date-picker :picker-options="{
                  disabledDate: time => {
                    if (scope.row.endtime) {
                      return (
                        time.getTime() >
                        new Date(scope.row.endtime.replace(/-/g, '/'))
                      );
                    }
                  }"
                  clearable
                  size="small"
                  style="width: 100%"
                  v-model="scope.row.starttime"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="开始日期"
                  @blur="getdays(scope.row)"
                >
                  }
                }" clearable size="small" style="width: 100%" v-model="scope.row.starttime" type="date"
                  format="yyyy-MM-dd" value-format="yyyy-MM-dd HH:mm:ss" placeholder="开始日期" @blur="getdays(scope.row)">
                </el-date-picker>
              </template>
            </el-table-column>
            <el-table-column
              prop="endtime"
              slot=""
              label="结束日期"
              align="center"
              fixed
              width="150"
            >
            <el-table-column prop="endtime" slot="" label="结束日期" align="center" fixed width="150">
              <template slot-scope="scope">
                <el-date-picker
                  :picker-options="{
                    disabledDate: time => {
                      if (scope.row.starttime) {
                        return (
                          time.getTime() <
                          new Date(scope.row.starttime.replace(/-/g, '/'))
                        );
                      }
                <el-date-picker :picker-options="{
                  disabledDate: time => {
                    if (scope.row.starttime) {
                      return (
                        time.getTime() <
                        new Date(scope.row.starttime.replace(/-/g, '/'))
                      );
                    }
                  }"
                  clearable
                  size="small"
                  style="width: 100%"
                  v-model="scope.row.endtime"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="结束日期"
                  @blur="getdays(scope.row)"
                >
                  }
                }" clearable size="small" style="width: 100%" v-model="scope.row.endtime" type="date"
                  format="yyyy-MM-dd" value-format="yyyy-MM-dd HH:mm:ss" placeholder="结束日期" @blur="getdays(scope.row)">
                </el-date-picker>
              </template>
            </el-table-column>
            <el-table-column
              prop="departure"
              slot=""
              label="出发地"
              align="center"
              width="100"
            >
            <el-table-column prop="departure" slot="" label="出发地" align="center" width="100">
              <template slot-scope="scope">
                <el-input v-model="scope.row.departure" placeholder="出发地" />
              </template>
            </el-table-column>
            <el-table-column
              label="到达地"
              prop="destination"
              align="center"
              width="140"
            >
            <el-table-column label="到达地" prop="destination" align="center" width="140">
              <template slot-scope="scope">
                <!--  <el-input v-model="scope.row.destination" placeholder="到达地" @blur="getallowance(scope.row)" /> -->
                <el-select
                  filterable
                  allow-create
                  v-model="scope.row.destination"
                  value-key="value"
                  placeholder="到达地"
                  @change="getallowance(scope.row)"
                >
                  <el-option
                    v-for="dict in dict.type.sys_area_name"
                    :key="dict.value"
                    :label="dict.label"
                    :value="dict.label"
                  ></el-option>
                <el-select filterable allow-create v-model="scope.row.destination" value-key="value" placeholder="到达地"
                  @change="getallowance(scope.row)">
                  <el-option v-for="dict in dict.type.sys_area_name" :key="dict.value" :label="dict.label"
                    :value="dict.label"></el-option>
                </el-select>
              </template>
            </el-table-column>
            <el-table-column
              prop="days"
              label="天数"
              width="100"
              align="center"
            >
            <el-table-column prop="days" label="天数" width="100" align="center">
              <template slot-scope="scope">
                <el-input
                  v-model="scope.row.days"
                  @blur="getallowance(scope.row)"
                  placeholder="天数"
                />
                <el-input v-model="scope.row.days" @blur="getallowance(scope.row)" placeholder="天数" />
              </template>
            </el-table-column>
            <el-table-column
              prop="traffictype"
              label="出发交通工具"
              align="center"
              width="100"
            >
            <el-table-column prop="traffictype" label="出发交通工具" align="center" width="100">
              <template slot-scope="scope">
                <el-select
                  v-model="scope.row.traffictype"
                  placeholder="出发交通工具"
                >
                  <el-option
                    v-for="dict in dict.type.sys_traffictype"
                    :key="dict.value"
                    :label="dict.label"
                    :value="dict.label"
                  ></el-option>
                <el-select v-model="scope.row.traffictype" placeholder="出发交通工具">
                  <el-option v-for="dict in dict.type.sys_traffictype" :key="dict.value" :label="dict.label"
                    :value="dict.label"></el-option>
                </el-select>
              </template>
            </el-table-column>
            <el-table-column
              prop="traffictype2"
              label="返回交通工具"
              align="center"
              width="100"
            >
            <el-table-column prop="traffictype2" label="返回交通工具" align="center" width="100">
              <template slot-scope="scope">
                <el-select
                  v-model="scope.row.traffictype2"
                  placeholder="返回交通工具"
                >
                  <el-option
                    v-for="dict in dict.type.sys_traffictype"
                    :key="dict.value"
                    :label="dict.label"
                    :value="dict.label"
                  ></el-option>
                <el-select v-model="scope.row.traffictype2" placeholder="返回交通工具">
                  <el-option v-for="dict in dict.type.sys_traffictype" :key="dict.value" :label="dict.label"
                    :value="dict.label"></el-option>
                </el-select>
              </template>
            </el-table-column>
            <el-table-column
              prop="trafficexpense"
              label="交通费"
              align="center"
              width="120"
            >
            <el-table-column prop="trafficexpense" label="交通费" align="center" width="120">
              <template slot-scope="scope">
                <el-input
                  v-model="scope.row.trafficexpense"
                  placeholder="交通费"
                  @blur="
                    val => {
                      sumRowFee(scope.row);
                    }
                  "
                />
                <el-input v-model="scope.row.trafficexpense" placeholder="交通费" @blur="
                  val => {
                    sumRowFee(scope.row);
                  }
                " />
              </template>
            </el-table-column>
            <el-table-column
              prop="cityfee"
              label="市内交通费"
              align="center"
              width="120"
            >
            <el-table-column prop="cityfee" label="市内交通费" align="center" width="120">
              <template slot-scope="scope">
                <el-input
                  v-model="scope.row.cityfee"
                  placeholder="市内交通费"
                  @blur="
                    val => {
                      sumRowFee(scope.row);
                    }
                  "
                  >{{ Number(scope.row.cityfee).toFixed(2) }}
                <el-input v-model="scope.row.cityfee" placeholder="市内交通费" @blur="
                  val => {
                    sumRowFee(scope.row);
                  }
                ">{{ Number(scope.row.cityfee).toFixed(2) }}
                </el-input>
              </template>
            </el-table-column>
            <el-table-column
              prop="hotelexpense"
              label="住宿费"
              align="center"
              width="120"
            >
            <el-table-column prop="hotelexpense" label="住宿费" align="center" width="120">
              <template slot-scope="scope">
                <el-input
                  v-model="scope.row.hotelexpense"
                  placeholder="住宿费"
                  @blur="
                    val => {
                      sumRowFee(scope.row);
                    }
                  "
                />
                <el-input v-model="scope.row.hotelexpense" placeholder="住宿费" @blur="
                  val => {
                    sumRowFee(scope.row);
                  }
                " />
              </template>
            </el-table-column>
@@ -573,118 +333,55 @@
                />
              </template>
            </el-table-column> -->
            <el-table-column
              prop="foodallowance"
              label="伙食补助"
              align="center"
              width="120"
            >
            <el-table-column prop="foodallowance" label="伙食补助" align="center" width="120">
              <template slot-scope="scope">
                <el-input
                  v-model="scope.row.foodallowance"
                  placeholder="伙食费补助"
                  @blur="
                    val => {
                      sumRowFee(scope.row);
                    }
                  "
                />
                <el-input v-model="scope.row.foodallowance" placeholder="伙食费补助" @blur="
                  val => {
                    sumRowFee(scope.row);
                  }
                " />
              </template>
            </el-table-column>
            <el-table-column
              prop="otherexpense"
              label="公杂费补助"
              align="center"
              width="120"
            >
            <el-table-column prop="otherexpense" label="公杂费补助" align="center" width="120">
              <template slot-scope="scope">
                <el-input
                  v-model="scope.row.otherexpense"
                  placeholder="杂费"
                  @blur="
                    val => {
                      sumRowFee(scope.row);
                    }
                  "
                />
                <el-input v-model="scope.row.otherexpense" placeholder="杂费" @blur="
                  val => {
                    sumRowFee(scope.row);
                  }
                " />
              </template>
            </el-table-column>
            <el-table-column
              prop="otherfeeamount"
              label="其他费用"
              align="center"
              width="120"
            >
            <el-table-column prop="otherfeeamount" label="其他费用" align="center" width="120">
              <template slot-scope="scope">
                <el-input
                  v-model="scope.row.otherfeeamount"
                  placeholder="其他费用"
                  @blur="
                    val => {
                      sumRowFee(scope.row);
                    }
                  "
                />
                <el-input v-model="scope.row.otherfeeamount" placeholder="其他费用" @blur="
                  val => {
                    sumRowFee(scope.row);
                  }
                " />
              </template>
            </el-table-column>
            <el-table-column
              prop="departure"
              slot=""
              label="其他费用说明"
              align="center"
              width="120"
            >
            <el-table-column prop="departure" slot="" label="其他费用说明" align="center" width="120">
              <template slot-scope="scope">
                <el-input
                  v-model="scope.row.otherfeedesc"
                  placeholder="其他费用说明"
                />
                <el-input v-model="scope.row.otherfeedesc" placeholder="其他费用说明" />
              </template>
            </el-table-column>
            <el-table-column
              prop="total"
              label="合计"
              align="center"
              width="110"
              fixed="right"
            >
            <el-table-column prop="total" label="合计" align="center" width="110" fixed="right">
              <template slot-scope="scope">
                <el-input v-model="scope.row.total" placeholder="合计" />
              </template>
            </el-table-column>
            <el-table-column
              fixed="right"
              label="操作"
              align="center"
              width="120"
            >
            <el-table-column fixed="right" label="操作" align="center" width="120">
              <template slot-scope="scope">
                <el-button
                  type="text"
                  size="mini"
                  @click="addDetailRow(scope.$index)"
                  v-if="operationType == 'edit'"
                  >新增</el-button
                >
                <el-button
                  @click.native.prevent="
                    deleteDetailRow(scope.$index, rbDetails, scope.row)
                  "
                  type="text"
                  size="small"
                  v-if="operationType == 'edit'"
                  >删除</el-button
                >
                <el-button
                  @click.native.prevent="
                    Filepopup(scope.$index, rbDetails, scope.row)
                  "
                  type="text"
                  size="small"
                  >票据</el-button
                >
                <el-button type="text" size="mini" @click="addDetailRow(scope.$index)"
                  v-if="operationType == 'edit'">新增</el-button>
                <el-button @click.native.prevent="
                  deleteDetailRow(scope.$index, rbDetails, scope.row)
                  " type="text" size="small" v-if="operationType == 'edit'">删除</el-button>
                <el-button @click.native.prevent="
                  Filepopup(scope.$index, rbDetails, scope.row)
                  " type="text" size="small">票据</el-button>
              </template>
            </el-table-column>
          </el-table>
@@ -693,154 +390,60 @@
          <el-col :span="20">打款明细</el-col>
        </el-row>
        <el-row style="margin-top: 5px">
          <el-table
            :data="rbPayees"
            border
            max-height="400"
            highlight-current-row
            :summary-method="getSummaries"
            show-summary
          >
            <el-table-column
              prop="orderno"
              slot=""
              label="序号"
              align="center"
              fixed
              width="65"
            >
          <el-table :data="rbPayees" border max-height="400" highlight-current-row :summary-method="getSummaries"
            show-summary>
            <el-table-column prop="orderno" slot="" label="序号" align="center" fixed width="65">
              <template slot-scope="scope">
                <el-input v-model="scope.row.orderno" placeholder="序号" />
              </template>
            </el-table-column>
            <!--主键、外键不展示-->
            <el-table-column
              prop="id"
              slot=""
              label="主键"
              align="center"
              fixed
              width="95"
              v-if="false"
            >
            <el-table-column prop="id" slot="" label="主键" align="center" fixed width="95" v-if="false">
              <template slot-scope="scope">
                <el-input v-model="scope.row.id" placeholder="id" />
              </template>
            </el-table-column>
            <el-table-column
              prop="rbid"
              slot=""
              label="外键"
              align="center"
              fixed
              width="95"
              v-if="false"
            >
            <el-table-column prop="rbid" slot="" label="外键" align="center" fixed width="95" v-if="false">
              <template slot-scope="scope">
                <el-input v-model="scope.row.rbid" placeholder="rbid" />
              </template>
            </el-table-column>
            <el-table-column
              prop="persontype"
              slot=""
              label="收款人类别"
              align="center"
              fixed
              width="150"
            >
            <el-table-column prop="persontype" slot="" label="收款人类别" align="center" fixed width="150">
              <template slot-scope="scope">
                <el-select
                  v-model="scope.row.persontype"
                  value-key="value"
                  placeholder="收款人类别"
                  @change="getPersons(scope.row)"
                >
                  <el-option
                    v-for="dict in dict.type.sys_travelexpensebelong"
                    :key="dict.value"
                    :label="dict.label"
                    :value="dict.label"
                  ></el-option>
                <el-select v-model="scope.row.persontype" value-key="value" placeholder="收款人类别"
                  @change="getPersons(scope.row)">
                  <el-option v-for="dict in dict.type.sys_travelexpensebelong" :key="dict.value" :label="dict.label"
                    :value="dict.label"></el-option>
                </el-select>
              </template>
            </el-table-column>
            <el-table-column
              prop="personname"
              slot=""
              label="收款人"
              align="center"
              fixed
              width="200"
            >
            <el-table-column prop="personname" slot="" label="收款人" align="center" fixed width="200">
              <template slot-scope="scope">
                <el-select
                  v-model="scope.row.personname"
                  clearable
                  filterable
                  allow-create
                  ref="getReportname"
                  default-first-option
                  placeholder="请选择"
                  @change="getAccountinfo2(scope.row, scope.row.persontype)"
                >
                  <el-option
                    v-for="item in personsSel"
                    :key="item.id"
                    :label="item.reportNameDesc"
                    :value="item.id"
                  >
                <el-select v-model="scope.row.personname" clearable filterable allow-create ref="getReportname"
                  default-first-option placeholder="请选择" @change="getAccountinfo2(scope.row, scope.row.persontype)">
                  <el-option v-for="item in personsSel" :key="item.id" :label="item.reportNameDesc" :value="item.id">
                  </el-option>
                </el-select>
              </template>
            </el-table-column>
            <el-table-column
              prop="bankname"
              slot=""
              label="收款银行"
              align="center"
              fixed
              width="250"
            >
            <el-table-column prop="bankname" slot="" label="收款银行" align="center" fixed width="250">
              <template slot-scope="scope">
                <el-input v-model="scope.row.bankname" placeholder="收款银行" />
              </template>
            </el-table-column>
            <el-table-column
              prop="bankcardno"
              slot=""
              label="银行账号"
              align="center"
              fixed
              width="200"
            >
            <el-table-column prop="bankcardno" slot="" label="银行账号" align="center" fixed width="200">
              <template slot-scope="scope">
                <el-input
                  v-model="scope.row.bankcardno"
                  placeholder="银行账号"
                />
                <el-input v-model="scope.row.bankcardno" placeholder="银行账号" />
              </template>
            </el-table-column>
            <el-table-column
              prop="amount"
              slot=""
              label="打款金额"
              align="center"
              fixed
              width="130"
            >
            <el-table-column prop="amount" slot="" label="打款金额" align="center" fixed width="130">
              <template slot-scope="scope">
                <el-input v-model="scope.row.amount" placeholder="打款金额" />
              </template>
            </el-table-column>
            <el-table-column
              prop="remark"
              slot=""
              label="请款说明"
              align="center"
              fixed
            >
            <el-table-column prop="remark" slot="" label="请款说明" align="center" fixed>
              <template slot-scope="scope">
                <el-input v-model="scope.row.remark" placeholder="说明" />
              </template>
@@ -853,74 +456,36 @@
                </el-date-picker>
              </template>
            </el-table-column>-->
            <el-table-column
              label="操作"
              align="center"
              width="100"
              v-if="operationType == 'edit'"
            >
            <el-table-column label="操作" align="center" width="100" v-if="operationType == 'edit'">
              <template slot-scope="scope">
                <el-button
                  type="text"
                  size="mini"
                  @click="addPayeeRow(scope.$index)"
                  >新增</el-button
                >
                <el-button
                  @click.native.prevent="
                    deletePayeeRow(scope.$index, rbPayees, scope.row)
                  "
                  type="text"
                  size="small"
                >
                <el-button type="text" size="mini" @click="addPayeeRow(scope.$index)">新增</el-button>
                <el-button @click.native.prevent="
                  deletePayeeRow(scope.$index, rbPayees, scope.row)
                  " type="text" size="small">
                  åˆ é™¤
                </el-button>
              </template>
            </el-table-column>
          </el-table>
        </el-row>
        <el-row
          style="margin-top: 25px; margin-bottom: 5px"
          v-if="operationType != 'edit'"
        >
        <el-row style="margin-top: 25px; margin-bottom: 5px" v-if="operationType != 'edit'">
          <el-col :span="20">审核记录</el-col>
        </el-row>
        <el-row v-if="operationType != 'add'">
          <el-table :data="fundflowList" border>
            <el-table-column
              label="审核日期"
              align="center"
              width="200"
              prop="createTime"
            />
            <el-table-column
              label="审核人"
              align="center"
              width="150"
              prop="checkusername"
            />
            <el-table-column
              label="审核结果"
              align="center"
              width="200"
              prop="flowconclusion"
            >
            <el-table-column label="审核日期" align="center" width="200" prop="createTime" />
            <el-table-column label="审核人" align="center" width="150" prop="checkusername" />
            <el-table-column label="审核结果" align="center" width="200" prop="flowconclusion">
              <template slot-scope="scope">
                <span v-if="scope.row.flowconclusion == 1">通过</span>
                <span v-if="scope.row.flowconclusion == 2">不通过</span>
              </template>
            </el-table-column>
            <el-table-column
              label="审核备注"
              align="center"
              prop="flowcontent"
            />
            <el-table-column label="审核备注" align="center" prop="flowcontent" />
          </el-table>
        </el-row>
        <el-row
          style="margin-top: 25px; margin-bottom: 5px"
          v-if="operationType == 'check' && userprofile.nickName == '陈慕华'"
        >
        <el-row style="margin-top: 25px; margin-bottom: 5px"
          v-if="operationType == 'check' && userprofile.nickName == '陈慕华'">
          <el-col :span="12">
            <el-form-item label="预审材料" prop="infoid">
              <el-radio-group v-model="form.checkstatus" align="left">
@@ -941,10 +506,7 @@
          </el-col>
        </el-row>
        <el-row
          style="margin-top: 25px; margin-bottom: 5px"
          v-if="operationType == 'check'"
        >
        <el-row style="margin-top: 25px; margin-bottom: 5px" v-if="operationType == 'check'">
          <el-col :span="7">
            <el-form-item label="审核结果" align="left">
              <el-radio-group v-model="checkObj.flowlevelone" align="left">
@@ -963,42 +525,50 @@
        </el-row>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button
          type="primary"
          @click="submitForm"
          v-if="operationType == 'edit' || userprofile.nickName == '陈慕华'"
          >保 å­˜</el-button
        >
        <el-button
          type="primary"
          @click="checksubmit"
          v-if="operationType == 'check'"
          >提交审核</el-button
        >
        <el-button type="primary" @click="submitForm" v-if="operationType == 'edit' || userprofile.nickName == '陈慕华'">保
          å­˜</el-button>
        <el-button type="primary" @click="checksubmit" v-if="operationType == 'check'">提交审核</el-button>
        <!--<el-button @click="cancel">退 å‡º</el-button>-->
      </div>
    </div>
    <el-dialog
      v-dialogDrags
      :modal="false"
      :close-on-click-modal="false"
      :title="pdftitle"
      :visible.sync="pdfVisible"
      width="60%"
    >
    <el-dialog v-dialogDrags :modal="false" :close-on-click-modal="false" :title="pdftitle" :visible.sync="pdfVisible"
      :fullscreen="pdfFullscreen" width="60%" custom-class="pdf-resize-dialog" @closed="pdfFullscreen = false">
      <template slot="title">
        <span>{{ pdftitle }}</span>
        <el-button class="pdf-fullscreen-btn" size="mini"
          :icon="pdfFullscreen ? 'el-icon-close' : 'el-icon-full-screen'" @click="pdfFullscreen = !pdfFullscreen">{{
            pdfFullscreen ? "还原" : "全屏" }}</el-button>
      </template>
      <el-alert
        :title="
          activeName == 2
            ? '当前正在操作:发票文件(请确认票据属于发票)'
            : '当前正在操作:普通附件(请勿在此上传发票)'
        "
        :type="activeName == 2 ? 'success' : 'warning'"
        :closable="false"
        show-icon
        style="margin-bottom: 10px;"
      />
      <div style="margin-bottom: 10px;">
        <el-table :data="tableDatatop" style="width: 100%">
          <el-table-column prop="orderno" label="序号"> </el-table-column>
          <el-table-column prop="personname" label="费用人员">
          </el-table-column>
          <el-table-column prop="starttime" label="开始日期"> </el-table-column>
          <el-table-column prop="endtime" label="结束日期"> </el-table-column>
          <el-table-column prop="starttime" label="开始日期">
            <template slot-scope="scope">
              {{ scope.row.starttime ? parseTime(scope.row.starttime, "{y}-{m}-{d}") : "" }}
            </template>
          </el-table-column>
          <el-table-column prop="endtime" label="结束日期">
            <template slot-scope="scope">
              {{ scope.row.endtime ? parseTime(scope.row.endtime, "{y}-{m}-{d}") : "" }}
            </template>
          </el-table-column>
          <el-table-column prop="departure" label="出发地"> </el-table-column>
          <el-table-column prop="destination" label="到达地"> </el-table-column>
          <el-table-column prop="days" label="天数"> </el-table-column>
        </el-table>
        <el-table :data="tableDatatop" style="width: 100%">
          <el-table-column prop="traffictype" label="出发交通工具">
          </el-table-column>
          <el-table-column prop="traffictype2" label="返回交通工具">
@@ -1016,66 +586,27 @@
          </el-table-column>
        </el-table>
      </div>
      <el-alert
        title="新上传文件需在提交保存后才可进行案例关联,否则视为放弃"
        type="error"
        show-icon
        close-text="知道了"
      >
      <el-alert title="新上传文件需在提交保存后才可进行案例关联,否则视为放弃" type="error" show-icon close-text="知道了">
      </el-alert>
      <el-tabs
        style="margin-top: 20px;"
        v-model="activeName"
        type="border-card"
        @tab-click="handleClick"
      >
      <el-tabs style="margin-top: 20px;" v-model="activeName" type="border-card" @tab-click="handleClick">
        <el-tab-pane label="普通附件" :name="1">
          <div class="pdfimg">
            <div class="box-pdf">
              <div>
                <el-upload
                  size="mini"
                  class="upload-demo"
                  :action="uploadFileUrl"
                  :file-list="fileListto"
                  :show-file-list="false"
                  multiple
                  drag
                  :headers="headers"
                  :on-success="
                    (response, file, fileList) =>
                <el-upload size="mini" class="upload-demo" :action="uploadFileUrl" :file-list="fileListto"
                  :show-file-list="false" multiple drag :headers="headers" :on-success="(response, file, fileList) =>
                      uploadSccess(response, file, fileList)
                  "
                  :on-preview="downFile"
                  :disabled="operationType == 'detail'"
                  :on-error="handleUploadError"
                  :on-remove="remove"
                  accept="image/*,.pdf"
                >
                    " :on-preview="downFile" :disabled="operationType == 'detail'" :on-error="handleUploadError"
                  :on-remove="remove" accept="image/*,.pdf">
                  <i class="el-icon-upload"></i>
                  <div class="el-upload__text">
                    å°†ç¥¨æ®æ‹–到此处,或
                    <em
                      ><el-button
                        :disabled="operationType == 'detail'"
                        size="small"
                        type="primary"
                        >点击上传</el-button
                      ></em
                    >
                    <em><el-button :disabled="operationType == 'detail'" size="small"
                        type="primary">点击上传</el-button></em>
                  </div>
                </el-upload>
                <el-table
                  :data="fileListto"
                  @row-click="downFile"
                  style="width: 100%"
                  height="400"
                >
                  <el-table-column
                    prop="name"
                    :show-overflow-tooltip="true"
                    label="名称"
                  >
                <el-table :data="fileListto" @row-click="downFile" style="width: 100%" height="400">
                  <el-table-column prop="name" :show-overflow-tooltip="true" label="名称">
                    <template slot-scope="scope">
                      <i style="color:#409EFF" class=" el-icon-s-order" />{{
                        scope.row.name
@@ -1083,110 +614,39 @@
                    </template>
                  </el-table-column>
                  <el-table-column
                    prop="name"
                    width="180"
                    :show-overflow-tooltip="true"
                    label="功能"
                  >
                  <el-table-column prop="name" width="180" :show-overflow-tooltip="true" label="功能">
                    <template slot-scope="scope">
                      <el-button
                        type="danger"
                        size="mini"
                        @click.native.prevent.stop="deletedowfile(scope.row)"
                        >删除</el-button
                      >
                      <el-button
                        type="primary"
                        size="mini"
                        @click.native.prevent.stop="moveupdowfile(scope.row)"
                        >上移</el-button
                      >
                      <el-button type="danger" size="mini"
                        @click.native.prevent.stop="deletedowfile(scope.row)">删除</el-button>
                      <el-button type="primary" size="mini"
                        @click.native.prevent.stop="moveupdowfile(scope.row)">上移</el-button>
                    </template>
                  </el-table-column>
                </el-table>
              </div>
            </div>
            <div
              v-if="this.previewpdf && pdfimgsrcList.length"
              class="pdfimgmin"
            >
              <!-- <img :src="pdfimg" /> -->
              <!-- <el-image
                style="width: 95%; height: 90%"
                :src="pdfimg"
                :preview-src-list="pdfimgsrcList"
              > -->
              <el-image
                ref="imagePreview"
                style="width: 95%; height: 90%"
                :src="pdfimg"
                @click="handleImageClick(initialIndex)"
              >
              </el-image>
              <custom-image-viewer
                :url-list="pdfimgsrcList"
                :initial-index="currentIndex"
                :visible="viewerVisible"
                @update:visible="viewerVisible = $event"
                @close="handleViewerClose"
              />
              <!-- <div slot="error" class="image-slot">
              <i class="el-icon-picture-outline"></i>
            </div> -->
              <!-- </el-image> -->
            </div>
            <div v-else class="pdfimgmins">{{ hintitle }}</div>
            <attachment-preview-pane :file="embeddedFile" style="flex: 1; min-width: 0;" />
          </div>
        </el-tab-pane>
        <el-tab-pane label="发票文件" :name="2">
          <div class="pdfimg">
            <div class="box-pdf">
              <div>
                <el-upload
                  size="mini"
                  class="upload-demo"
                  :action="uploadFileUrl"
                  :file-list="invoicefileList"
                  :show-file-list="false"
                  multiple
                  drag
                  :headers="headers"
                  :on-success="
                    (response, file, fileList) =>
                <el-upload size="mini" class="upload-demo" :action="uploadFileUrl" :file-list="invoicefileList"
                  :show-file-list="false" multiple drag :headers="headers" :on-success="(response, file, fileList) =>
                      uploadSccess(response, file, fileList)
                  "
                  :on-preview="downFile"
                  :disabled="operationType == 'detail'"
                  :on-error="handleUploadError"
                  :on-remove="remove"
                  accept="image/*,.pdf"
                >
                    " :on-preview="downFile" :disabled="operationType == 'detail'" :on-error="handleUploadError"
                  :on-remove="remove" accept="image/*,.pdf">
                  <i class="el-icon-upload"></i>
                  <div class="el-upload__text">
                    å°†å‘票文件拖到此处,或
                    <em
                      ><el-button
                        :disabled="operationType == 'detail'"
                        size="small"
                        type="primary"
                        >点击上传</el-button
                      ></em
                    >
                    <em><el-button :disabled="operationType == 'detail'" size="small"
                        type="primary">点击上传</el-button></em>
                  </div>
                </el-upload>
                <el-table
                  :data="invoicefileListto"
                  @row-click="downFile"
                  style="width: 100%"
                  height="400"
                >
                  <el-table-column
                    prop="name"
                    :show-overflow-tooltip="true"
                    label="名称"
                  >
                <el-table :data="invoicefileListto" @row-click="downFile" style="width: 100%" height="400">
                  <el-table-column prop="name" :show-overflow-tooltip="true" label="名称">
                    <template slot-scope="scope">
                      <i style="color:#409EFF" class=" el-icon-s-order" />{{
                        scope.row.name
@@ -1194,64 +654,28 @@
                    </template>
                  </el-table-column>
                  <el-table-column
                    prop="name"
                    width="180"
                    :show-overflow-tooltip="true"
                    label="功能"
                  >
                  <el-table-column prop="name" width="180" :show-overflow-tooltip="true" label="功能">
                    <template slot-scope="scope">
                      <el-button
                        type="primary"
                        size="mini"
                        @click.native.prevent.stop="deletedowfile(scope.row)"
                        >删除</el-button
                      >
                      <el-button
                        type="primary"
                        size="mini"
                        @click.native.prevent.stop="moveupdowfile(scope.row)"
                        >上移</el-button
                      >
                      <el-button type="primary" size="mini"
                        @click.native.prevent.stop="deletedowfile(scope.row)">删除</el-button>
                      <el-button type="primary" size="mini"
                        @click.native.prevent.stop="moveupdowfile(scope.row)">上移</el-button>
                    </template>
                  </el-table-column>
                </el-table>
              </div>
            </div>
            <div
              v-if="this.previewpdf && invoicepdfimgsrcList.length"
              class="pdfimgmin"
            >
              <!-- <img :src="pdfimg" /> -->
              <el-image
                style="width: 95%; height: 90%"
                :src="invoicepdfimg"
                @click="invoicehandleImageClick(initialIndex)"
              >
              </el-image>
              <custom-image-viewer
                :url-list="invoicepdfimgsrcList"
                :initial-index="invoicecurrentIndex"
                :visible="invoiceviewerVisible"
                @update:visible="invoiceviewerVisible = $event"
                @close="invoicehandleViewerClose"
              />
            </div>
            <div v-else class="pdfimgmins">{{ hintitle }}</div>
            <attachment-preview-pane :file="invoiceEmbeddedFile" style="flex: 1; min-width: 0;" />
          </div>
        </el-tab-pane>
      </el-tabs>
    </el-dialog>
    <el-dialog title="提示" :visible.sync="Savereminder" width="30%">
      <el-alert
        :title="
          '您已添加' +
            totalquantity +
            '条数据,为防止数据丢失是否提交保存再继续添加。'
        "
        type="warning"
      >
      <el-alert :title="'您已添加' +
        totalquantity +
        '条数据,为防止数据丢失是否提交保存再继续添加。'
        " type="warning">
      </el-alert>
      <span slot="footer" class="dialog-footer">
        <el-button @click="unsave">取 æ¶ˆ</el-button>
@@ -1340,13 +764,14 @@
      personsList: [],
      //下拉可以选择人员列表
      personsSel: [],
      activeName: 1, //文件类型
      activeName: 2, //文件类型:默认发票文件
      // éƒ¨é—¨æ ‘选项
      deptOptions: undefined,
      //票据文件
      pdftitle: "",
      pdfimg: "",
      invoiceEmbeddedFile: null,
      pdfVisible: false,
      costtypeobj: {
        value: 0,
@@ -1655,17 +1080,8 @@
    handleUploadError() { },
    // èŽ·å–è¯·æ±‚å¤´
    Getnetworkheader() {
      console.log(window.location.href);
      let string = window.location.href;
      if (string.includes("9091")) {
        const index = string.indexOf("9091");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–9091及其前部字符
        console.log(this.Networkheader);
      } else {
        const index = string.indexOf("8032");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–8032及其前部字符
        console.log(this.Networkheader);
      }
      // ä»¥å½“前页面 origin ä¸ºå‡†ï¼Œå…¼å®¹ 80/8032/9091 åŠåŽç»­ç«¯å£è°ƒæ•´
      this.Networkheader = window.location.origin;
    },
    // å‘票切换
    handleClick(tab, event) {
@@ -1693,7 +1109,7 @@
    //文件上传成功回调
    /**
     * 2026-10-15:上传时校验附件文件名(业务要求:不合格立即提示且不加入列表)。
     * è§„则与后端 AttachmentNameValidator åŒå£å¾„:必须含中文 / åŽ»æŽ‰æ‰©å±•ååŽä¸»å¹² â‰¤20 å­— /
     * è§„则与后端 AttachmentNameValidator åŒå£å¾„:不强制含中文 / åŽ»æŽ‰æ‰©å±•ååŽä¸»å¹² â‰¤20 å­— /
     * ä¸å«ç©ºæ ¼åŠ / \ : * ? " < > | # % ç­‰ç‰¹æ®Šå­—符。
     */
    checkUploadFileName(file) {
@@ -1732,33 +1148,27 @@
      const config = {
        headers: { Authorization: "Bearer " + this.ICDtoken }
      };
      const pdfimg = this.Networkheader + "/prod-api" + response.fileName;
      const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + response.fileName;
      //获取票据信息位置
      if (this.activeName == 1) {
        if (response.code == 200) {
          this.previewpdf = true;
          fetch(pdfimg, config)
            .then(response => response.blob())
            .then(blob => {
              // å°†èŽ·å–çš„æ•°æ®æµè½¬æ¢ä¸ºURL
              this.pdfimg = URL.createObjectURL(blob);
              this.pdfimgsrcList.push(URL.createObjectURL(blob));
              this.fileListto.push({
                name: file.name,
                url: URL.createObjectURL(blob)
              });
              // 2026-10-15:删除这里的第二次 push。原来一次上传会向 fileListto / pdfimgsrcList
              // å„写两条,且第二条的 name å–的是 file.response.fileName(存储路径);而 remove()
              // ä¼šæŠŠæ•´ä¸ª fileListto å›žå†™åˆ° rbDetails[atpresent].annexfilesList,导致明细附件里
              // æ··å…¥è·¯å¾„形态的 name,被附件文件名校验误拦(前端没有"路径形态跳过"豁免),
              // åŒæ—¶ç¥¨æ®åˆ—表会多出一行。
              console.log(URL.createObjectURL(blob), "ur转换");
            })
            .catch(error => {
              console.error("Error loading image", error);
              return;
            });
          // æ–°ä¸Šä¼ æ–‡ä»¶ç›´æŽ¥ä½¿ç”¨æµè§ˆå™¨æœ¬åœ° File,避免服务端文件地址/Token
          // å¼‚常时把 JSON é”™è¯¯å“åº”当成图片或 PDF é¢„览。
          const localBlob = file.raw;
          const localUrl = URL.createObjectURL(localBlob);
          this.pdfimg = localUrl;
          this.embeddedFile = {
            name: file.name,
            url: localUrl,
            type: /\.pdf$/i.test(file.name) ? "pdf" : "image"
          };
          this.pdfimgsrcList.push(localUrl);
          this.fileListto.push({
            name: file.name,
            url: localUrl
          });
          this.$modal.msgSuccess(response.msg);
@@ -1777,21 +1187,19 @@
      } else {
        if (response.code == 200) {
          this.previewpdf = true;
          fetch(pdfimg, config)
            .then(response => response.blob())
            .then(blob => {
              // å°†èŽ·å–çš„æ•°æ®æµè½¬æ¢ä¸ºURL
              this.invoicepdfimg = URL.createObjectURL(blob);
              this.invoicepdfimgsrcList.push(URL.createObjectURL(blob));
              this.invoicefileListto.push({
                name: file.name,
                url: URL.createObjectURL(blob)
              });
            })
            .catch(error => {
              console.error("Error loading image", error);
              return;
            });
          const localBlob = file.raw;
          const localUrl = URL.createObjectURL(localBlob);
          this.invoicepdfimg = localUrl;
          this.invoiceEmbeddedFile = {
            name: file.name,
            url: localUrl,
            type: /\.pdf$/i.test(file.name) ? "pdf" : "image"
          };
          this.invoicepdfimgsrcList.push(localUrl);
          this.invoicefileListto.push({
            name: file.name,
            url: localUrl
          });
          this.$modal.msgSuccess(response.msg);
@@ -1827,7 +1235,7 @@
      if (this.rbDetails[index].annexfilesList) {
        const fetchPromises = this.rbDetails[index].annexfilesList.map(
          (value, indexson) => {
            const pdfimg = this.Networkheader + "/prod-api" + value.url;
            const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + value.url;
            return fetch(pdfimg, config)
              .then(response => response.blob())
              .then(blob => {
@@ -1860,7 +1268,7 @@
      if (this.rbDetails[index].invoicefilesList) {
        const fetchPromises = this.rbDetails[index].invoicefilesList.map(
          (value, indexson) => {
            const pdfimg = this.Networkheader + "/prod-api" + value.url;
            const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + value.url;
            return fetch(pdfimg, config)
              .then(response => response.blob())
              .then(blob => {
@@ -1894,7 +1302,7 @@
        this.pdftitle = "";
      }
      if (this.activeName == "common") {
      if (this.activeName == 1) {
        this.pdftitle = "共" + this.pdfimgsrcList.length + "项";
      } else {
        this.pdftitle = "共" + this.invoicepdfimgsrcList.length + "项";
@@ -1903,50 +1311,17 @@
      console.log(this.rbDetails[index].annexfilesList, "annexfilesList");
    },
    // ç‚¹å‡»å·²ä¸Šä¼ æ–‡ä»¶
    // å·®æ—…报销:附件统一使用独立 FilePreview(录入、审批、详情一致)
    downFile(item) {
      if (this.activeName == 1) {
        this.pdftitle =
          "共" + this.pdfimgsrcList.length + "项,当前选中" + item.name;
        let name = item.name.split(".");
        if (name[1] == "pdf") {
          this.$modal.msgWarning("当前文件暂不支持预览");
          this.previewpdf = false;
          this.hintitle = "当前文件暂不支持预览";
        } else if (name[1] == "jpg" || "png") {
          console.log(item, "展示");
          this.previewpdf = true;
          if (item.url) {
            this.pdfimg = item.url;
          } else {
            this.pdfimg = "";
          }
        } else {
          this.hintitle = "当前文件暂不支持预览";
          this.$modal.msgWarning("当前文件暂不支持预览");
          this.previewpdf = false;
        }
      } else {
        this.pdftitle =
          "共" + this.invoicepdfimgsrcList.length + "项,当前选中" + item.name;
        let name = item.name.split(".");
        if (name[1] == "pdf") {
          this.$modal.msgWarning("当前文件暂不支持预览");
          this.previewpdf = false;
          this.hintitle = "当前文件暂不支持预览";
        } else if (name[1] == "jpg" || "png") {
          this.previewpdf = true;
          if (item.url) {
            this.invoicepdfimg = item.url;
          } else {
            this.invoicepdfimg = "";
          }
        } else {
          this.hintitle = "当前文件暂不支持预览";
          this.$modal.msgWarning("当前文件暂不支持预览");
          this.previewpdf = false;
        }
      const source =
        this.activeName == 1 ? this.fileListto : this.invoicefileListto;
      let list = (source || []).map(f => ({ name: f.name, url: f.url }));
      let index = list.findIndex(f => f.url === item.url);
      if (index < 0 && item && item.url) {
        list = [{ name: item.name, url: item.url }];
        index = 0;
      }
      this.$previewFile(list, index > -1 ? index : 0);
    },
    getIndexInArray(arr, obj) {
      return arr.indexOf(obj);
@@ -2182,8 +1557,8 @@
            row.otherexpense = "";
          } else if (
            tempStr.includes("桐庐") ||
          tempStr.includes("建德") ||
          tempStr.includes("淳安")
            tempStr.includes("建德") ||
            tempStr.includes("淳安")
          ) {
            row.otherexpense = (parseInt(row.days) * 60).toString();
          } else {
@@ -2775,7 +2150,7 @@
                path: this.$route.path,
                query: { id: next.id, pos: 4 }
              })
              .catch(() => {});
              .catch(() => { });
          })
          .catch(() => {
            this.exitToPage();
@@ -2882,7 +2257,7 @@
            //保存明细
            for (let i = 0; i < rbDetails.length; i++) {
              if (rbDetails[i].rbid != null) {
                if (rbDetails[i].persontype == "家属"||rbDetails[i].persontype == "专家") {
                if (rbDetails[i].persontype == "家属" || rbDetails[i].persontype == "专家") {
                  if (this.form.infoid) {
                    updateReimbursementdetail(rbDetails[i]);
                  } else {
src/views/project/travelexpensecheck/index.vue
@@ -1443,8 +1443,21 @@
      }
    },
    // ä¸‹è½½æ–‡ä»¶
    // é¢„览/下载文件
    downFile(item) {
      const ext = String(item.name || "")
        .split(".")
        .pop()
        .toLowerCase();
      if (["pdf", "jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext)) {
        const list = (this.fileList || []).map(f => ({
          name: f.name,
          url: f.url
        }));
        const index = list.findIndex(f => f.url === item.url);
        this.$previewFile(list, index > -1 ? index : 0);
        return;
      }
      const url = process.env.VUE_APP_BASE_API + item.url;
      var a = document.createElement("a");
      var event = new MouseEvent("click");
src/views/project/unitCost/particulars/index.vue
@@ -489,6 +489,7 @@
        :title="pdftitle"
        :visible.sync="pdfVisible"
        width="60%"
        custom-class="pdf-resize-dialog"
      >
        <div class="pdfimg">
          <div class="box-pdf">
@@ -981,14 +982,8 @@
    },
    // èŽ·å–è¯·æ±‚å¤´
    Getnetworkheader() {
      let string = window.location.href;
      if (string.includes("9091")) {
        const index = string.indexOf("9091");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–9091及其前部字符
      } else {
        const index = string.indexOf("8032");
        this.Networkheader = string.slice(0, index + 4); // æˆªå–8032及其前部字符
      }
      // ä»¥å½“前页面 origin ä¸ºå‡†ï¼Œå…¼å®¹ 80/8032/9091 åŠåŽç»­ç«¯å£è°ƒæ•´
      this.Networkheader = window.location.origin;
    },
    // è¡¨å•重置
    reset() {
@@ -1255,7 +1250,7 @@
      const config = {
        headers: { Authorization: "Bearer " + this.ICDtoken }
      };
      const pdfimg = this.Networkheader + "/prod-api" + response.fileName;
      const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + response.fileName;
      //获取票据信息位置
      if (response.code == 200) {
        this.previewpdf = true;
@@ -1311,7 +1306,7 @@
      if (this.donorchargeorgans[index].annexFilesList) {
        const fetchPromises = this.donorchargeorgans[index].annexFilesList.map(
          (value, indexson) => {
            const pdfimg = this.Networkheader + "/prod-api" + value.url;
            const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + value.url;
            return fetch(pdfimg, config)
              .then(response => response.blob())
              .then(blob => {
@@ -1354,19 +1349,13 @@
    downFile(item) {
      this.pdftitle =
        "共" + this.pdfimgsrcList.length + "项,当前选中" + item.name;
      let name = item.name.split(".");
      if (name[1] == "pdf") {
        this.$modal.msgWarning("当前文件暂不支持预览");
        this.previewpdf = false;
        this.hintitle = "当前文件暂不支持预览";
      } else if (name[1] == "jpg" || "png") {
        console.log(item, "展示");
        this.previewpdf = true;
        if (item.url) {
          this.pdfimg = item.url;
        } else {
          this.pdfimg = "";
        }
      const ext = String(item.name || "pdf").toLowerCase().split(".").pop();
      if (ext === "pdf") {
        this.$previewFile([{ name: item.name, url: item.url }]);
        return;
      } else if (["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext)) {
        this.$previewFile([{ name: item.name, url: item.url }]);
        return;
      } else {
        this.hintitle = "当前文件暂不支持预览";
        this.$modal.msgWarning("当前文件暂不支持预览");