sinake
5 天以前 df97bab9fcb3e6044ad736cd7483498f61f0feb4
src/views/project/fund/performancedetails/index.vue
@@ -11,7 +11,7 @@
                clearable
                style="width: 100%"
                v-model="form.applyTime"
                type="date"
                type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="申请日期"
                :disabled="true"
@@ -59,9 +59,17 @@
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="5">
            <el-form-item label="联系电话" prop="phone">
              <el-input v-model="form.phone" placeholder="请输入联系电话" />
           <el-col :span="5">
            <el-form-item label="绩效日期" prop="jxrq">
              <el-date-picker
                clearable
                style="width: 100%"
                v-model="form.jxrq"
                type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="绩效日期"
              >
              </el-date-picker>
            </el-form-item>
          </el-col>
          <el-col :span="5">
@@ -90,9 +98,14 @@
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item label="联系电话" prop="phone">
              <el-input v-model="form.phone" placeholder="请输入联系电话" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row style="text-align: left">
          <el-col :span="8">
          <el-col :span="12">
            <el-form-item label="申请说明" prop="remark">
              <el-input v-model="form.remark" placeholder="申请说明" />
            </el-form-item>
@@ -280,6 +293,31 @@
                      : "点击选择"
                  }}</el-button
                >
              </template>
            </el-table-column>
            <el-table-column
              prop="deptName"
              align="center"
              label="绩效科室"
              width="160"
            >
              <template slot-scope="scope">
                <el-select
                  v-model="scope.row.deptId"
                  size="mini"
                  clearable
                  filterable
                  placeholder="请选择绩效科室"
                  style="width: 100%"
                  @change="value => handleRowDeptChange(scope.row, value)"
                >
                  <el-option
                    v-for="dept in deptList"
                    :key="dept.deptId"
                    :label="dept.deptName"
                    :value="dept.deptId"
                  />
                </el-select>
              </template>
            </el-table-column>
            <el-table-column
@@ -558,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
@@ -686,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">
@@ -791,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>
@@ -858,7 +882,8 @@
            :label="city"
            :key="city.id"
            >{{ city.username }}{{ city.isUnitPerson ? "(单位)" : ""
            }}{{ city.unitname ? " · " + city.unitname : "" }}</el-checkbox
            }}{{ city.unitname ? " · " + city.unitname : ""
            }}{{ city.deptName ? " · 科室:" + city.deptName : "" }}</el-checkbox
          >
        </el-checkbox-group>
      </div>
@@ -915,10 +940,12 @@
import { listItemexpense } from "@/api/project/itemexpense";
import { getUserProfile } from "@/api/system/user";
import { listDept } from "@/api/system/dept";
import Li_area_select from "@/components/Address";
import OrgSelecter from "@/views/project/components/orgselect";
import { getToken } from "@/utils/auth";
import { moneySum, moneyAdd, moneySub, moneyRound } from "@/utils/money";
import { checkFileName, checkDetails } from "@/utils/fileNameCheck";
import CustomImageViewer from "@/components/CustomImageViewer";
export default {
@@ -969,6 +996,8 @@
      expertlist: [],
      //医疗机构列表
      unitList: [],
      //绩效科室列表
      deptList: [],
      //费用报销机构列表
      feeUnitList: [],
      batchdialogVisible: false,
@@ -1218,7 +1247,7 @@
      invoicefileListto: [],
      invoicepdfimg: "",
      invoicepdfimgsrcList: [],
      activeName: 1, //文件类型
      activeName: 2, //文件类型:默认发票文件
      //上传附件路径
      uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload",
      //文件上传token
@@ -1228,6 +1257,7 @@
      //票据文件
      pdftitle: "",
      pdfimg: "",
      invoiceEmbeddedFile: null,
      pdfimgsrcList: [],
      pdfVisible: false,
      previewpdf: false,
@@ -1660,6 +1690,11 @@
    },
    /** 提交按钮 */
    submitForm: debounce(function() {
      // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行)
      if (!this.checkDetailFileNames()) {
        return;
      }
      if (!this.randomnumber) {
        this.randomnumber = this.generateRandomString(9);
        this.form.randomnumber = this.randomnumber;
@@ -1885,11 +1920,7 @@
          this.$router.go(-1);
        } else {
          this.$modal.closeLoading();
          if (res.msg) {
            this.$modal.msgError("操作失误");
          } else {
            this.$modal.msgError(res.msg);
          }
          this.$modal.msgError(res.msg || "操作失败");
        }
      });
    },
@@ -1998,6 +2029,25 @@
        this.feeUnitList = res.data;
        console.log(this.feeUnitList);
      });
    },
    /** 绩效科室列表 */
    getDeptList() {
      listDept({}).then(res => {
        this.deptList = res.data || [];
      });
    },
    /** 表格内直接修改绩效科室:同步名称 */
    handleRowDeptChange(row, deptId) {
      if (deptId === null || deptId === undefined || deptId === "") {
        this.$set(row, "deptName", null);
        return;
      }
      const dept = this.deptList.find(
        item => String(item.deptId) === String(deptId)
      );
      this.$set(row, "deptName", dept ? dept.deptName : null);
    },
    //获取医疗机构列表
@@ -2182,6 +2232,11 @@
    },
    // 接受提醒立刻保存
    Savenow() {
      // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行)
      if (!this.checkDetailFileNames()) {
        return;
      }
      if (!this.randomnumber) {
        this.randomnumber = this.generateRandomString(9);
        this.form.randomnumber = this.randomnumber;
@@ -2502,6 +2557,8 @@
          isunit: isunit ? 1 : 0,
          unitname: item.unitname,
          unitno: item.unitno,
          deptId: item.deptId,
          deptName: item.deptName,
          uintuserno: null,
          title: item.title,
          idcardtype: null,
@@ -2727,6 +2784,8 @@
        singleDetail.title = person.title;
        singleDetail.unitname = person.unitname;
        singleDetail.unitno = person.unitno;
        singleDetail.deptId = person.deptId;
        singleDetail.deptName = person.deptName;
      } else {
        // 手工输入的人员:只写姓名,身份证号/银行账号由经办人填写
        singleDetail.beneficiaryname = String(key);
@@ -2754,41 +2813,67 @@
    },
    // 获取请求头
    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 字 /
     * 不含空格及 / \ : * ? " < > | # % 等特殊字符。
     */
    checkUploadFileName(file) {
      const raw = file && file.raw ? file.raw.name : file && file.name;
      const r = checkFileName(raw);
      if (!r.ok) {
        this.$modal.msgError(r.msg);
        return false;
      }
      return true;
    },
    /**
     * 2026-10-15:保存前校验整单明细附件文件名(含同一行内重名)。
     * 口径(业务确认):只校验"本次变更"的附件——老单据里已存在的不合规附件名不能把保存卡死
     * (否则老单据改一个金额都存不进去)。后端按 id 读旧记录放行历史附件;前端拿不到"哪些是历史"
     * 的权威信息,因此**仅新增单据**做保存前拦截,编辑老单据时新增的不合规附件由后端二次校验拦下。
     */
    checkDetailFileNames() {
      if (this.form && this.form.id) {
        return true;
      }
      const c = checkDetails(this.rbDetails);
      if (!c.ok) {
        this.$modal.msgError(c.msg);
        return false;
      }
      return true;
    },
    uploadSccess(response, file, fileList) {
      if (!this.checkUploadFileName(file)) {
        return;
      }
      this.rbDetails;
      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);
@@ -2807,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);
@@ -2858,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 => {
@@ -2891,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 => {
@@ -2925,7 +3007,7 @@
        this.pdftitle = "";
      }
      if (this.activeName == "common") {
      if (this.activeName == 1) {
        this.pdftitle = "共" + this.pdfimgsrcList.length + "项";
      } else {
        this.pdftitle = "共" + this.invoicepdfimgsrcList.length + "项";
@@ -2934,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);
@@ -3065,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) {
@@ -3119,6 +3189,7 @@
    this.getExternalList();
    this.getFeeUnitList();
    this.listperformance();
    this.getDeptList();
  },
  //生命周期 - 挂载完成(可以访问DOM元素)