yxh
112 分钟以前 c75027743ea7c3ecc2d0885f01d11d2ad78679a3
Merge branch 'yxh' of http://116.62.18.175:6699/r/~yxh/opo-web
已修改111个文件
已添加10个文件
8415 ■■■■■ 文件已修改
README.md 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
build/checkMoney.js 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/index.html 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/project/fund.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/project/register.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/element-override.scss 365 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/element-variables.scss 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/index.scss 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/ruoyi.scss 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/sidebar.scss 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/theme-runtime.scss 270 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/tokens.css 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/tokens.scss 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/variables.scss 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ThemePicker/index.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Settings/index.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/index.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/settings.js 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/settings.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/fileNameCheck.js 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/money.js 240 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.js 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/theme.js 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/components/BarChart/index.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/components/BarChart2/index.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/components/BarChart3/index.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/components/BarChartH/index.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/components/MapChart/index.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/components/line1Chart/index.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/components/line2Chart/index.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/components/lineChart/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/job/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/job/log.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/logininfor/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/online/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/operlog/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/DonationEvaluation/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/DonationProcess/index.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/components/annexupload/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/distributedetail/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/documentfiles/index.vue 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donateannex/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donateannexrequirement/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donatebaseinfo/index.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donateconsolationfund/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donatefinish/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donatefinishcheck/index.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donatefollowup/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donateorgan/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donateorganstatics/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donatereview/index.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donationdetails/index.vue 214 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donationwitness/index.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donorcharge/donorcharge/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donorcharge/donorchargedetail/index.vue 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donorcharge/organcharge/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/ethicalReview/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/externalperson/index.vue 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/financesubject/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/applyDetail/index.vue 761 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/applyDetail0/index.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/ethicalExpertFeeApply/index.vue 476 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/expertFeeApply/index.vue 495 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/medicalCostApply/index.vue 538 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/officeExpenseApply/index.vue 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/performanceApply/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fund/performancedetails/index.vue 475 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fundcheck/ethicalExpertFeeCheck/index.vue 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fundcheck/expertFeeCheck/index.vue 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fundcheck/medicalCostCheck/index.vue 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fundcheck/officeExpenseCheck/index.vue 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fundcheck/performanceCheck/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/funddetail/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fundflow/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fundflowrule/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/fundstatistics/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/itemexpense/index.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/medicalevaluation/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/organallocation/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/organallocation/index0.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/organization/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/register/index.vue 351 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/reimbursement/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/reimbursementdetail/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/reimbursementpayee/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/relativesconfirmation/index.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/statistics/applyStatistics/index.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/statistics/caseStatistics/index.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/statistics/chargeStatistics/index.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/statistics/performanceStaristics/index.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/tallage/computingBill/index.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/tallage/index.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/travelexpenseapply/edit.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/travelexpenseapply/index.vue 1360 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/travelexpenseapply/travelexpensedetail/index.vue 272 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/travelexpensecheck/index.vue 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/travelexpensedeal/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/unitCost/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/unitCost/particulars/index.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/class/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/config/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/costmaintenance/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/data.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/grade/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/medicalevalustion/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/menu/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/notice/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/post/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/authUser.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/selectUser.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/student/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/importTable.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md
@@ -1,12 +1,9 @@
## å¼€å‘
> æœ¬ç›®å½• `opo-web/` å³ PC å‰ç«¯å·¥ç¨‹æ ¹ç›®å½•(原若依模板名为 `ruoyi-ui`,此处已改名)。
> ä»¥ä¸‹å‘½ä»¤**均在 `opo-web/` ä¸‹æ‰§è¡Œ**。
```bash
# å…‹éš†é¡¹ç›®
git clone https://gitee.com/y_project/RuoYi-Vue
# è¿›å…¥é¡¹ç›®ç›®å½•
cd ruoyi-ui
# å®‰è£…依赖
npm install
build/checkMoney.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,148 @@
#!/usr/bin/env node
/**
 * é‡‘额精度检查(防复发门禁)
 *
 * èƒŒæ™¯ï¼šé‡‘额浮点累加在本项目反复引发线上问题,典型症状是
 * ã€Œåˆè®¡å¯¹ä¸ä¸Šã€ã€Œå‡ºçް -0.00」「金额带一长串小数」。根因是 JS çš„ Number
 * æ˜¯ IEEE-754 åŒç²¾åº¦æµ®ç‚¹ï¼Œå¯¹ 2 ä½å°æ•°é‡‘额做 + - * ä¼šå¼•入二进制舍入误差,
 * ä¸”误差会随明细条数累积(实测约 39% çš„多明细组合会出现尾差)。
 *
 * å› æ­¤çº¦å®šï¼šé‡‘额运算一律使用 @/utils/money(内部基于 decimal.js,十进制精确运算)。
 *
 * æœ¬è„šæœ¬æ‰«æ src ä¸‹çš„ .js / .vue,找出「疑似对金额做浮点累加」的写法并报错退出,
 * ä»¥ä¾¿åœ¨æäº¤å‰æ‹¦ä½å›žå½’。建议挂到 pre-commit æˆ– CI。
 *
 * ç”¨æ³•:
 *   node build/checkMoney.js
 * æˆ– npm run check:money
 *
 * è‹¥ç¡®è®¤æŸå¤„是误报(例如累加的不是金额,而是数量、天数等),
 * åœ¨è¯¥è¡Œä¸Šæ–¹åŠ æ³¨é‡Šï¼š// money-check-ignore
 */
const fs = require("fs");
const path = require("path");
const SRC_DIR = path.resolve(__dirname, "../src");
const IGNORE_COMMENT = "money-check-ignore";
// å±é™©å†™æ³•:对 parseFloat ç»“果做累加,或以 parseFloat ç›¸ä¹˜åŽå‚与累加
const RULES = [
  {
    id: "float-accumulate",
    // å½¢å¦‚ totalFee += parseFloat(...)
    pattern: /\+=\s*parseFloat\s*\(/,
    message: "对 parseFloat ç»“果做累加,金额会累积浮点误差"
  },
  {
    id: "float-subtract-assign",
    // å½¢å¦‚ totalFee -= parseFloat(...)
    pattern: /-=\s*parseFloat\s*\(/,
    message: "对 parseFloat ç»“果做减法累加,金额会累积浮点误差"
  },
  {
    id: "float-text-math",
    // å½¢å¦‚ a = (parseFloat(x) - parseFloat(y)).toFixed(2)
    pattern: /=\s*\(?\s*parseFloat\s*\([^)]*\)\s*[-+*]\s*parseFloat\s*\(/,
    message: "parseFloat ä¹‹é—´ç›´æŽ¥åšç®—术运算再格式化,结果可能带浮点尾差"
  }
];
// æŽ’除第三方库与构建产物,仅检查业务源码
const EXCLUDE_DIRS = [
  path.join(SRC_DIR, "echarts"), // echarts ç¬¬ä¸‰æ–¹åº“源码
  path.join(SRC_DIR, "assets")
];
// å…è®¸çš„写法:这些是对单个值的合法性/零值校验,不构成累加,不会放大误差
const ALLOW = [/parseFloat\([^)]*\)\s*==\s*0/, /parseFloat\([^)]*\)\s*!=\s*0/];
function walk(dir, out) {
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      if (entry.name === "node_modules") continue;
      if (EXCLUDE_DIRS.indexOf(full) >= 0) continue;
      walk(full, out);
    } else if (/\.(js|vue)$/.test(entry.name)) {
      out.push(full);
    }
  }
  return out;
}
function checkFile(file) {
  const lines = fs.readFileSync(file, "utf8").split(/\r?\n/);
  const hits = [];
  let inBlockComment = false;
  lines.forEach((line, idx) => {
    const code = line.trim();
    // è·Ÿè¸ªè·¨è¡Œå—注释:块注释中的代码不参与检查(避免对已注释掉的代码误报)
    if (inBlockComment) {
      if (code.indexOf("*/") >= 0) inBlockComment = false;
      return;
    }
    if (code.indexOf("/*") === 0) {
      if (code.indexOf("*/") < 0) inBlockComment = true;
      return;
    }
    // è¡Œæ³¨é‡Š
    if (code.indexOf("//") === 0 || code.indexOf("*") === 0) return;
    // å…è®¸çš„写法:单个值的合法性/零值校验
    if (ALLOW.some((re) => re.test(line))) return;
    // æ˜¾å¼è¯¯æŠ¥æ ‡è®°
    const prev = idx > 0 ? lines[idx - 1] : "";
    if (prev.indexOf(IGNORE_COMMENT) >= 0) return;
    RULES.forEach((rule) => {
      if (rule.pattern.test(line)) {
        hits.push({ line: idx + 1, id: rule.id, message: rule.message, code: code.slice(0, 120) });
      }
    });
  });
  return hits;
}
function main() {
  if (!fs.existsSync(SRC_DIR)) {
    console.error(`找不到源码目录:${SRC_DIR}`);
    process.exit(1);
  }
  const files = walk(SRC_DIR, []);
  let total = 0;
  const report = [];
  files.forEach((file) => {
    const hits = checkFile(file);
    if (hits.length) {
      total += hits.length;
      report.push({ file: path.relative(path.resolve(__dirname, ".."), file), hits });
    }
  });
  if (total === 0) {
    console.log(`金额精度检查通过(已扫描 ${files.length} ä¸ªæ–‡ä»¶ï¼‰`);
    process.exit(0);
  }
  console.error("金额精度检查未通过:发现疑似金额浮点累加\n");
  report.forEach(({ file, hits }) => {
    console.error(`  ${file}`);
    hits.forEach((h) => {
      console.error(`    L${h.line}  [${h.id}] ${h.message}`);
      console.error(`           ${h.code}`);
    });
    console.error("");
  });
  console.error("请改用 @/utils/money æä¾›çš„æ–¹æ³•,例如:");
  console.error('  import { moneySum, moneyAdd, moneyMul, moneyRound } from "@/utils/money";');
  console.error("  const total = moneyRound(moneySum(list, \"amount\"));");
  console.error("  é‡‘额相等比较用 moneyEq(),不要用 parseFloat(...) == æˆ– ===。");
  console.error("  ç¡®å±žè¯¯æŠ¥æ—¶ï¼Œåœ¨è¯¥è¡Œä¸Šæ–¹åŠ æ³¨é‡Š // money-check-ignore");
  process.exit(1);
}
main();
package.json
@@ -5,11 +5,12 @@
  "author": "浙江亥尔时信息科技有限公司",
  "license": "MIT",
  "scripts": {
    "dev": "vue-cli-service serve",
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "build:stage": "vue-cli-service build --mode staging",
    "preview": "node build/index.js --preview",
    "lint": "eslint --ext .js,.vue src"
    "lint": "eslint --ext .js,.vue src",
    "check:money": "node build/checkMoney.js"
  },
  "husky": {
    "hooks": {
public/index.html
@@ -174,7 +174,7 @@
    }
    #loader-wrapper .load_title {
      font-family: 'Open Sans';
      font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;
      color: #FFF;
      font-size: 19px;
      width: 100%;
src/api/project/fund.js
@@ -160,6 +160,16 @@
  })
}
// ä¸“家劳务费:保存前的费用控制校验(干跑,不落库)
// è¿”回 { errors: [阻断性信息], warnings: [提示信息], blocked: æ˜¯å¦é˜»æ–­ }
export function validateExpertFee(data) {
  return request({
    url: '/project/fund/validateExpertFee',
    method: 'post',
    data: data
  })
}
// ä¼¦ç†ä¸“家费打印
export function getdownloadLL(id) {
  return request({
src/api/project/register.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
import request from '@/utils/request'
// æŸ¥è¯¢å™¨å®˜åŽ»å¤„åˆ—è¡¨ï¼ˆæ•°æ®æºï¼šè§†å›¾ v_service_donateorgan_register)
export function listRegister(query) {
  return request({
    url: '/project/register/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å™¨å®˜åŽ»å¤„è¯¦ç»†
export function getRegister(id) {
  return request({
    url: '/project/register/' + id,
    method: 'get'
  })
}
// å¯¼å‡ºå™¨å®˜åŽ»å¤„åˆ—è¡¨
export function exportRegister(query) {
  return request({
    url: '/project/register/export',
    method: 'get',
    params: query
  })
}
src/assets/styles/element-override.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,365 @@
// ============================================================
// ç»„件默认外观(静态令牌层)
// ------------------------------------------------------------
// ç›®çš„:不改任何业务页面模板,用全局样式把 Element çš„默认观感
//       ç»Ÿä¸€æˆ"紧凑、清爽、专业"的一套。
// é‡ç‚¹ï¼šè¡¨æ ¼ï¼ˆæ–‘马纹/去竖线/表头底色)、表单、按钮、弹窗、标签。
// é¡ºåºï¼šå¿…须晚于 element-ui ä¸Ž ruoyi.scss(见 main.js)。
// ============================================================
// ---------- å…¨å±€æŽ’版 ----------
body {
  font-family: var(--opo-font-family-base);
  font-size: var(--opo-font-size-base);
  color: var(--opo-text-primary);
  background-color: var(--opo-bg-page);
}
// è¦†ç›– index.scss é‡Œçš„ label { font-weight: 700 }
label {
  font-weight: 400;
}
.app-container {
  // 2026-10-22:统一收紧为 8/12/12(原 12/16),全站列表页一致
  padding: 8px 12px 12px;
}
// æ•°å­—类字段(金额/编号/身份证)可用此类获得等宽数字
.opo-number {
  font-family: var(--opo-font-family-number);
  font-variant-numeric: tabular-nums;
}
// ---------- è¡¨æ ¼ ----------
// 2026-10-22:数据行字号与表头统一(14px / å¸¸è§„字重)
// æ³¨æ„ï¼šå…¨å±€ size=small ä¼šè®©æ¯ä¸ªè¡¨æ ¼å¸¦ä¸Š .el-table--small,
// Element ç”¨å®ƒæŠŠå­—号写回 12px、单元格 padding å†™å›ž 8px,
// æ‰€ä»¥è¿™é‡Œå¿…须同时提高选择器特异性,否则会被压回去。
.el-table,
.el-table.el-table--small,
.el-table.el-table--medium,
.el-table.el-table--mini,
.el-table.el-table--large {
  color: var(--opo-text-primary);
  font-size: var(--opo-font-size-sm);
}
.el-table {
  th,
  th.el-table__cell {
    background-color: var(--opo-bg-thead) !important;
    color: var(--opo-text-primary);
    // è¡¨å¤´ä¸åŠ ç²—ï¼ˆ2026-10-22 åé¦ˆï¼‰
    font-weight: 400;
    font-size: var(--opo-font-size-sm);
    height: 40px;
    padding: 0;
  }
  td,
  td.el-table__cell,
  &.el-table--small td.el-table__cell,
  &.el-table--medium td.el-table__cell,
  &.el-table--mini td.el-table__cell {
    padding: 9px 0;
    // è¡Œåˆ†éš”线(2026-10-22 å†åŠ æ·±ä¸€æ¡£ï¼‰
    border-bottom: 1px solid var(--opo-table-border, #c8cfd8);
  }
  // è¡¨æ ¼åº•部那条线
  &::before {
    background-color: var(--opo-table-border, #c8cfd8);
  }
  // å¸¦è¾¹æ¡†çš„表格,外框线
  &.el-table--border {
    border-color: var(--opo-table-border, #c8cfd8);
  }
  .cell {
    line-height: 23px;
  }
  // è¡¨å¤´ä¸Žå†…容区的分隔线(比行线再深一点,作为分界)
  .el-table__header-wrapper th {
    border-bottom: 1px solid var(--opo-table-border-strong, #b7c0cc);
  }
  // æ–‘马纹:用 CSS å®žçŽ°ï¼Œæ— éœ€ç»™ 162 ä¸ªè¡¨æ ¼é€ä¸ªåŠ  stripe å±žæ€§
  .el-table__body tr:nth-child(2n) > td,
  .el-table__body tr:nth-child(2n) > td.el-table__cell {
    background-color: var(--opo-bg-stripe);
  }
  // é¼ æ ‡è¡Œé«˜äº®ï¼ˆè¦†ç›–斑马纹;Element é»˜è®¤ hover è‰²åç°ï¼‰
  .el-table__body tr:hover > td,
  .el-table__body tr:hover > td.el-table__cell {
    background-color: var(--opo-primary-light-95, #f5f9fd);
  }
  // åŽ»æŽ‰ç«–çº¿ï¼šborder å±žæ€§ä»ä¿ç•™åœ¨æ¨¡æ¿é‡Œï¼Œè¿™é‡Œåªè®©å®ƒåœ¨è§†è§‰ä¸Šæ¶ˆå¤±
  &.el-table--border td,
  &.el-table--border th,
  &.el-table--border th.el-table__cell,
  &.el-table--border td.el-table__cell {
    border-right: none;
  }
  &.el-table--border::after,
  &.el-table--group::after {
    display: none;
  }
  .el-table__empty-text {
    color: var(--opo-text-placeholder);
    font-size: var(--opo-font-size-sm);
  }
}
// éœ€è¦"强网格线"的明细录入表:模板上加 class="table-bordered" å³å¯æ¢å¤
.el-table.table-bordered {
  border: 1px solid var(--opo-table-border, #c8cfd8);
  td,
  th,
  td.el-table__cell,
  th.el-table__cell {
    border-right: 1px solid var(--opo-table-border, #c8cfd8);
  }
}
// è¡¨æ ¼å†…按钮更紧凑,避免把行撑高
.el-table .el-button--mini,
.el-table .el-button--small {
  padding: 5px 8px;
}
// ---------- è¡¨å• ----------
// æŸ¥è¯¢æ¡ä»¶/表单整体收紧:Element é»˜è®¤ margin-bottom 22px,行距明显偏大
.el-form-item {
  margin-bottom: 12px;
  &__label {
    font-weight: 400;
    color: var(--opo-text-regular);
    // æ ‡ç­¾ä¿æŒ 14px:查询区大量使用 label-width="68px"/"70px",
    // æ ‡ç­¾è‹¥éšæ­£æ–‡å‡åˆ° 16px,4 ä¸ªå­—(如"捐献案例")就会换行、行高翻倍
    font-size: var(--opo-font-size-sm);
    padding-right: 8px;
    line-height: 32px;
  }
  &__content {
    line-height: 32px;
    min-height: 32px;
  }
  &.is-required:not(.is-no-asterisk) > .el-form-item__label:before {
    color: var(--opo-danger);
  }
  &__error {
    font-size: var(--opo-font-size-xs);
    line-height: 1.4;
    padding-top: 2px;
  }
}
// æ¨ªå‘排列的查询条件再紧凑一档
.el-form--inline .el-form-item {
  margin-right: 12px;
  margin-bottom: 8px;
}
// ---------- ç´§å‡‘查询区(opt-in) ----------
// ç”¨æ³•:给查询用的 el-form åŠ  class="opo-query-form"(列表页统一约定)
// è¡Œè· 8→4px、标签与控件对齐、最后一行(搜索/重置)去掉下边距
.opo-query-form {
  // æŸ¥è¯¢æ¡ä»¶ä¸Žä¸‹æ–¹"新增/导出"工具栏(对表格的操作)之间留出间距
  margin-bottom: 12px;
  .el-form-item {
    margin-right: 8px;
    margin-bottom: 4px;
  }
  .el-form-item__label {
    line-height: 32px;
    padding-right: 6px;
  }
  .el-form-item__content {
    line-height: 32px;
    min-height: 32px;
  }
  // æœ€åŽä¸€è¡Œé€šå¸¸æ˜¯"搜索/重置",不再额外留白
  > .el-row:last-child .el-form-item {
    margin-bottom: 0;
  }
}
// ç´§è·ŸæŸ¥è¯¢åŒºä¹‹åŽçš„工具栏行(新增/导出):贴近表格,与查询区由 margin-bottom éš”å¼€
.opo-query-form + .el-row.mb8 {
  margin-bottom: 4px;
}
// é¡µé¢é‡Œçš„æŸ¥è¯¢åŒºå¸¸åŒ…在卡片或 el-row ä¸­ï¼ŒåŽ»æŽ‰å¤šä½™çš„å¤–å±‚ç•™ç™½
.el-card__body > .el-form:first-child,
.el-card__body > .el-row:first-child .el-form {
  margin-bottom: 0;
}
.el-input__inner,
.el-textarea__inner {
  border-radius: var(--opo-radius-base);
  color: var(--opo-text-primary);
  &::placeholder {
    color: var(--opo-text-placeholder);
  }
}
// ---------- æŒ‰é’® ----------
.el-button {
  border-radius: var(--opo-radius-base);
  font-weight: 400;
  [class*='el-icon-'] + span {
    margin-left: 4px;
  }
}
// å°/迷你按钮的字号在 Element é‡Œæ˜¯ç¡¬ç¼–码 12px,随正文一起上调(2026-10-22 å†å¤§ä¸€å· â†’ 16px)
.el-button--small,
.el-button--mini {
  font-size: var(--opo-font-size-base);
}
// ---------- å¼¹çª— ----------
.el-dialog {
  border-radius: var(--opo-radius-lg);
  box-shadow: var(--opo-shadow-lg);
  overflow: hidden;
  &__header {
    padding: 16px 20px 12px;
    border-bottom: 1px solid var(--opo-border-light);
  }
  &__title {
    font-size: var(--opo-font-size-lg);
    font-weight: 600;
    color: var(--opo-text-primary);
  }
  &__body {
    padding: 16px 20px;
    color: var(--opo-text-regular);
  }
  &__footer {
    padding: 12px 20px 16px;
    border-top: 1px solid var(--opo-border-light);
  }
}
.el-drawer__header {
  padding: 16px 20px;
  margin-bottom: 0;
  border-bottom: 1px solid var(--opo-border-light);
  color: var(--opo-text-primary);
  font-weight: 600;
}
// ---------- æ ‡ç­¾ ----------
.el-tag {
  border-radius: var(--opo-radius-sm);
  font-size: var(--opo-font-size-xs);
}
// ---------- å¡ç‰‡ ----------
.el-card {
  border-radius: var(--opo-radius-lg);
  border-color: var(--opo-border-light);
  box-shadow: var(--opo-shadow-sm) !important;
  &__header {
    padding: 14px 16px 10px;
    border-bottom: 1px solid var(--opo-border-light);
    font-weight: 600;
    color: var(--opo-text-primary);
  }
  &__body {
    padding: 12px 16px;
  }
}
// ---------- åˆ†é¡µ ----------
// Element æŠŠåˆ†é¡µæ–‡å­—写死 13px、字重 700,和 16px çš„表格正文不搭 â†’ ç»Ÿä¸€ 14px/常规
.el-pagination {
  font-weight: 400;
  color: var(--opo-text-regular);
  font-size: var(--opo-font-size-sm);
  button,
  span:not([class*='suffix']) {
    font-size: var(--opo-font-size-sm);
  }
  .el-pager li {
    font-size: var(--opo-font-size-sm);
  }
}
.pagination-container {
  margin-top: 12px;
  height: auto;
  padding: 8px 16px !important;
}
// ---------- é¢åŒ…屑 / é¡µç­¾ ----------
.el-breadcrumb {
  font-size: var(--opo-font-size-sm);
  &__inner,
  &__inner a {
    font-weight: 400 !important;
    color: var(--opo-text-secondary);
  }
  &__item:last-child .el-breadcrumb__inner {
    color: var(--opo-text-primary);
  }
}
// ---------- æç¤º / æ¶ˆæ¯ ----------
.el-message {
  border-radius: var(--opo-radius-base);
  box-shadow: var(--opo-shadow-md);
}
.el-tooltip__popper {
  font-size: var(--opo-font-size-xs);
}
// ---------- æ»šåŠ¨æ¡ï¼ˆè§‚æ„Ÿç»†èŠ‚ï¼‰ ----------
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
::-webkit-scrollbar-thumb {
  background-color: rgba(0, 0, 0, 0.18);
  border-radius: var(--opo-radius-pill);
  &:hover {
    background-color: rgba(0, 0, 0, 0.28);
  }
}
::-webkit-scrollbar-track {
  background-color: transparent;
}
src/assets/styles/element-variables.scss
@@ -1,23 +1,61 @@
/**
* I think element-ui's default theme color is too light for long-term use.
* So I modified the default color and you can modify it to your liking.
**/
 * Element UI ä¸»é¢˜å˜é‡ï¼ˆç¼–译期)
 * ------------------------------------------------------------
 * å“ç‰Œä¸»è‰²ï¼šå–自 src/assets/logo/OPO_Logo.png åœ†çŽ¯é’ #0E768F
 * å±é™©è‰²ï¼š  å–自同图心形玫红 #CD1D4E(白字对比度 5.41,优于原 #F56C6C çš„ 3.4)
 *
 * è¯´æ˜Žï¼šè¿™é‡Œå†³å®š"编译进 CSS çš„默认皮肤";
 *      è¿è¡Œæ—¶æ¢è‚¤ç”± assets/styles/theme-runtime.scss + utils/theme.js è´Ÿè´£ï¼Œ
 *      äºŒè€…通过 CSS å˜é‡ --opo-primary* è¡”接。
 */
/* theme color */
$--color-primary: #1890ff;
$--color-success: #13ce66;
$--color-warning: #ffba00;
$--color-danger: #ff4949;
// $--color-info: #1E1E1E;
/* ---------- ä¸»é¢˜è‰² ---------- */
$--color-primary: #0E768F;
$--color-success: #67C23A;
$--color-warning: #E6A23C;
$--color-danger: #CD1D4E;
$--color-info: #909399;
/* ---------- æ–‡å­— ---------- */
$--color-text-primary: #1F2937;
$--color-text-regular: #4B5563;
$--color-text-secondary: #6B7280;
$--color-text-placeholder: #9CA3AF;
/* ---------- è¾¹æ¡† ---------- */
$--border-color-base: #E5E7EB;
$--border-color-light: #F0F2F5;
$--border-color-lighter: #F5F7FA;
$--border-color-extra-light: #FAFBFC;
/* ---------- èƒŒæ™¯ ---------- */
$--background-color-base: #F5F7FA;
/* ---------- åœ†è§’ ---------- */
$--border-radius-base: 4px;
$--border-radius-small: 2px;
/* ---------- å­—号(2026-10-22 æ•´ä½“上调一号) ---------- */
$--font-size-extra-large: 22px;
$--font-size-large: 20px;
$--font-size-medium: 18px;
$--font-size-base: 16px;
$--font-size-small: 14px;
$--font-size-extra-small: 13px;
/* æŒ‰é’®å­—号默认在小尺寸里被硬编码成 12px,一并上调 */
$--button-font-size: 16px;
$--button-medium-font-size: 16px;
$--button-small-font-size: 14px;
$--button-mini-font-size: 14px;
/* ä¾§è¾¹æ èœå•字号(2026-10-22 ç”± 18px å›žè°ƒä¸€å·ï¼‰ */
$--menu-item-font-size: 16px;
$--button-font-weight: 400;
// $--color-text-regular: #1f2d3d;
$--border-color-light: #dfe4ed;
$--border-color-lighter: #e6ebf5;
$--table-border:1px solid#dfe6ec;
/* ---------- è¡¨æ ¼ ---------- */
$--table-border: 1px solid #E5E7EB;
/* icon font path, required */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
src/assets/styles/index.scss
@@ -10,11 +10,12 @@
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
  font-family: $opo-font-family-base;
}
// è¡¨å•标签不再全局加粗(原来 700 è®©æ‰€æœ‰é¡µé¢æ˜¾å¾—很"重")
label {
  font-weight: 700;
  font-weight: 400;
}
html {
@@ -103,8 +104,8 @@
  border-radius: 2px;
  display: block;
  line-height: 32px;
  font-size: 16px;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
  font-size: $opo-font-size-lg;
  font-family: $opo-font-family-base;
  color: #2c3e50;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
src/assets/styles/ruoyi.scss
@@ -3,6 +3,9 @@
 * Copyright (c) 2019 ruoyi
 */
/* è®¾è®¡ä»¤ç‰Œï¼ˆæœ¬æ–‡ä»¶ä¸ºç‹¬ç«‹å…¥å£ï¼Œéœ€è‡ªè¡Œå¼•入变量) */
@import './tokens.scss';
 /** åŸºç¡€é€šç”¨ **/
.pt5 {
    padding-top: 5px;
@@ -75,10 +78,10 @@
    .el-table__header-wrapper, .el-table__fixed-header-wrapper {
        th {
            word-break: break-word;
            background-color: #f8f8f9;
            color: #515a6e;
            background-color: $opo-bg-thead;
            color: $opo-text-primary;
            height: 40px;
            font-size: 13px;
            font-size: $opo-font-size-sm;
        }
    }
    .el-table__body-wrapper {
@@ -137,7 +140,7 @@
/** è¡¨æ ¼æ›´å¤šæ“ä½œä¸‹æ‹‰æ ·å¼ */
.el-table .el-dropdown-link {
    cursor: pointer;
    color: #409EFF;
    color: var(--opo-primary, #0E768F);
    margin-left: 5px;
}
src/assets/styles/sidebar.scss
@@ -67,17 +67,23 @@
      width: 100% !important;
    }
    // èœå•字号:比正文大两号(正文 16px â†’ èœå• 18px)
    .el-menu-item,
    .el-submenu__title {
      font-size: $opo-font-size-menu;
    }
    .el-menu-item, .el-submenu__title {
      overflow: hidden !important;
      text-overflow: ellipsis !important;
      white-space: nowrap !important;
    }
    // menu hover
    // menu hover(深色底上用透白,原来 rgba(0,0,0,.06) åœ¨æ·±è‰²ä¸Šçœ‹ä¸å‡ºæ¥ï¼‰
    .submenu-title-noDropdown,
    .el-submenu__title {
      &:hover {
        background-color: rgba(0, 0, 0, 0.06) !important;
        background-color: rgba(255, 255, 255, 0.08) !important;
      }
    }
@@ -90,7 +96,7 @@
      min-width: $base-sidebar-width !important;
      &:hover {
        background-color: rgba(0, 0, 0, 0.06) !important;
        background-color: rgba(255, 255, 255, 0.08) !important;
      }
    }
@@ -101,6 +107,36 @@
      &:hover {
        background-color: $base-sub-menu-hover !important;
      }
    }
    // ---- å½“前选中菜单高亮 ----
    // åŽŸçŠ¶ï¼šElement ç¼–译出的 .el-menu-item.is-active æ˜¯ä¸»è‰²æ–‡å­—,
    // åœ¨åŒæ ·æ˜¯é’色的深色侧边栏上几乎看不见 â†’ æ”¹ä¸º"品牌青实底 + ç™½å­— + å·¦ä¾§äº®æ¡"
    & .el-menu-item.is-active {
      position: relative;
      color: #ffffff !important;
      background-color: $opo-primary !important;
      font-weight: 500;
      &::before {
        content: '';
        position: absolute;
        left: 0;
        top: 0;
        bottom: 0;
        width: 3px;
        background-color: #ffffff;
      }
      &:hover {
        background-color: $opo-primary !important;
      }
    }
    // çˆ¶çº§åˆ†ç»„只是"包含选中项",用弱一点的底色,避免和叶子项一样重
    & .el-submenu.is-active > .el-submenu__title {
      background-color: rgba(255, 255, 255, 0.1) !important;
      color: #ffffff !important;
    }
  }
@@ -198,8 +234,8 @@
  .nest-menu .el-submenu>.el-submenu__title,
  .el-menu-item {
    &:hover {
      // you can use $subMenuHover
      background-color: rgba(0, 0, 0, 0.06) !important;
      // æ·±è‰²èœå•用透白覆盖
      background-color: rgba(255, 255, 255, 0.08) !important;
    }
  }
src/assets/styles/theme-runtime.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,270 @@
// ============================================================
// è¿è¡Œæ—¶ä¸»é¢˜å±‚(CSS å˜é‡æ¶ˆè´¹æ–¹ï¼‰
// ------------------------------------------------------------
// Element UI 2.x çš„主题色是 SCSS ç¼–译期常量,无法在运行时修改。
// æœ¬æ–‡ä»¶ä¸æ”¹ Element æºç ï¼Œåª"重写用到主色的选择器",值改为
// var(--opo-primary*);这些变量由 utils/theme.js åœ¨åˆ‡æ¢çš®è‚¤æ—¶å†™å…¥
// :root,因此换肤无需重新编译、无需刷新页面。
//
// è¦†ç›–顺序很关键:本文件必须在 element-ui ä¸Ž ruoyi.scss ä¹‹åŽå¼•å…¥
// ï¼ˆè§ main.js çš„ import é¡ºåºï¼‰ã€‚
// è¦†ç›–范围:按钮/链接/输入/单选复选开关/Tabs/分页/表格/步骤条/
//          æ—¥æœŸ/下拉/加载/进度,其余组件按需补充。
// ============================================================
$opo-fallback: #0E768F;
// ---------- æŒ‰é’® ----------
.el-button--primary {
  background-color: var(--opo-primary, #{$opo-fallback});
  border-color: var(--opo-primary, #{$opo-fallback});
  color: #fff;
  &:hover,
  &:focus {
    background-color: var(--opo-primary-hover, #{$opo-fallback});
    border-color: var(--opo-primary-hover, #{$opo-fallback});
    color: #fff;
  }
  &:active {
    background-color: var(--opo-primary-active, #{$opo-fallback});
    border-color: var(--opo-primary-active, #{$opo-fallback});
    color: #fff;
  }
  &.is-disabled,
  &.is-disabled:hover,
  &.is-disabled:focus,
  &.is-disabled:active {
    background-color: var(--opo-primary-light-5, #{$opo-fallback});
    border-color: var(--opo-primary-light-5, #{$opo-fallback});
    color: #fff;
  }
  &.is-plain {
    background-color: var(--opo-primary-light-9, #fff);
    border-color: var(--opo-primary-light-5, #{$opo-fallback});
    color: var(--opo-primary, #{$opo-fallback});
    &:hover,
    &:focus,
    &:active {
      background-color: var(--opo-primary, #{$opo-fallback});
      border-color: var(--opo-primary, #{$opo-fallback});
      color: #fff;
    }
  }
}
.el-button--text {
  color: var(--opo-primary, #{$opo-fallback});
  &:hover,
  &:focus {
    color: var(--opo-primary-hover, #{$opo-fallback});
  }
  &.is-disabled,
  &.is-disabled:hover {
    color: var(--opo-primary-light-5, #{$opo-fallback});
  }
}
// é»˜è®¤æŒ‰é’® hover æ—¶é€å‡ºå“ç‰Œè‰²ï¼ˆåŽŸæ¥æ˜¯ä¸€ç‰‡ç°ï¼‰
.el-button--default:hover,
.el-button--default:focus {
  color: var(--opo-primary, #{$opo-fallback});
  border-color: var(--opo-primary-light-5, #{$opo-fallback});
  background-color: var(--opo-primary-light-9, #fff);
}
// ---------- é“¾æŽ¥ ----------
.el-link.el-link--primary {
  color: var(--opo-primary, #{$opo-fallback});
  &:hover {
    color: var(--opo-primary-light-3, #{$opo-fallback});
  }
}
// ---------- è¾“入类 ----------
.el-input__inner:focus,
.el-textarea__inner:focus,
.el-input__inner:hover {
  border-color: var(--opo-primary-light-5, #{$opo-fallback});
}
.el-input__inner:focus,
.el-textarea__inner:focus {
  border-color: var(--opo-primary, #{$opo-fallback});
}
.el-select .el-input.is-focus .el-input__inner,
.el-select .el-input__inner:focus {
  border-color: var(--opo-primary, #{$opo-fallback});
}
.el-select-dropdown__item.selected {
  color: var(--opo-primary, #{$opo-fallback});
  font-weight: 600;
}
.el-input-number__decrease:hover,
.el-input-number__increase:hover {
  color: var(--opo-primary, #{$opo-fallback});
}
// ---------- å•选 / å¤é€‰ ----------
.el-radio__input.is-checked .el-radio__inner,
.el-checkbox__input.is-checked .el-checkbox__inner,
.el-checkbox__input.is-indeterminate .el-checkbox__inner {
  border-color: var(--opo-primary, #{$opo-fallback});
  background-color: var(--opo-primary, #{$opo-fallback});
}
.el-radio__input.is-checked + .el-radio__label,
.el-checkbox__input.is-checked + .el-checkbox__label {
  color: var(--opo-primary, #{$opo-fallback});
}
.el-radio-button__orig-radio:checked + .el-radio-button__inner,
.el-checkbox-button__orig-radio:checked + .el-checkbox-button__inner {
  background-color: var(--opo-primary, #{$opo-fallback});
  border-color: var(--opo-primary, #{$opo-fallback});
  box-shadow: -1px 0 0 0 var(--opo-primary, #{$opo-fallback});
  color: #fff;
}
// ---------- å¼€å…³ ----------
.el-switch.is-checked .el-switch__core {
  border-color: var(--opo-primary, #{$opo-fallback});
  background-color: var(--opo-primary, #{$opo-fallback});
}
// ---------- Tabs ----------
.el-tabs__item.is-active,
.el-tabs__item:hover {
  color: var(--opo-primary, #{$opo-fallback});
}
.el-tabs__active-bar {
  background-color: var(--opo-primary, #{$opo-fallback});
}
.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active {
  color: var(--opo-primary, #{$opo-fallback});
  border-bottom-color: transparent;
}
// ---------- åˆ†é¡µ ----------
.el-pager li.active,
.el-pager li:hover {
  color: var(--opo-primary, #{$opo-fallback});
}
.el-pagination.is-background .el-pager li:not(.disabled).active {
  background-color: var(--opo-primary, #{$opo-fallback});
  color: #fff;
}
.el-pagination button:hover {
  color: var(--opo-primary, #{$opo-fallback});
}
// ---------- è¡¨æ ¼ ----------
.el-table .ascending .sort-caret.ascending {
  border-bottom-color: var(--opo-primary, #{$opo-fallback});
}
.el-table .descending .sort-caret.descending {
  border-top-color: var(--opo-primary, #{$opo-fallback});
}
.el-table__body tr.current-row > td {
  background-color: var(--opo-primary-light-9, #fff) !important;
}
.el-table--enable-row-hover .el-table__body tr:hover > td {
  background-color: var(--opo-primary-light-95, #f5f9fd) !important;
}
// ---------- æ­¥éª¤æ¡ ----------
.el-step__head.is-process {
  color: var(--opo-primary, #{$opo-fallback});
  border-color: var(--opo-primary, #{$opo-fallback});
}
.el-step__title.is-process {
  color: var(--opo-primary, #{$opo-fallback});
  font-weight: 600;
}
.el-step__head.is-finish {
  color: var(--opo-primary, #{$opo-fallback});
  border-color: var(--opo-primary, #{$opo-fallback});
}
.el-step__title.is-finish {
  color: var(--opo-primary, #{$opo-fallback});
}
// ---------- æ—¥æœŸ / æ—¶é—´ ----------
.el-date-table td.today span {
  color: var(--opo-primary, #{$opo-fallback});
  font-weight: 700;
}
.el-date-table td.current:not(.disabled) span {
  background-color: var(--opo-primary, #{$opo-fallback});
  color: #fff;
}
.el-date-table td.available:hover {
  color: var(--opo-primary, #{$opo-fallback});
}
.el-date-picker__header-label.active,
.el-date-picker__header-label:hover {
  color: var(--opo-primary, #{$opo-fallback});
}
// ---------- ä¸‹æ‹‰ / å¼¹å‡ºèœå•(只作用于弹出层,避免影响深色侧边栏) ----------
.el-dropdown-menu__item:not(.is-disabled):hover,
.el-dropdown-menu__item:focus {
  color: var(--opo-primary, #{$opo-fallback});
  background-color: var(--opo-primary-light-9, #fff);
}
.el-menu--popup .el-menu-item.is-active,
.el-menu--popup .el-menu-item:hover {
  color: var(--opo-primary, #{$opo-fallback});
}
// ---------- çº§è” ----------
.el-cascader .el-input.is-focus .el-input__inner {
  border-color: var(--opo-primary, #{$opo-fallback});
}
.el-cascader-node.in-active-path,
.el-cascader-node.is-active {
  color: var(--opo-primary, #{$opo-fallback});
}
// ---------- åŠ è½½ / è¿›åº¦ ----------
.el-loading-spinner .path {
  stroke: var(--opo-primary, #{$opo-fallback});
}
.el-progress-bar__inner {
  background-color: var(--opo-primary, #{$opo-fallback});
}
// ---------- æ»‘块 ----------
.el-slider__bar {
  background-color: var(--opo-primary, #{$opo-fallback});
}
.el-slider__button {
  border-color: var(--opo-primary, #{$opo-fallback});
}
src/assets/styles/tokens.css
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,91 @@
/* ============================================================
 * è®¾è®¡ä»¤ç‰Œ Â· CSS è‡ªå®šä¹‰å±žæ€§
 * è¿è¡Œæ—¶æ¢è‚¤åªéœ€æ”¹åЍ --opo-primary åŠå…¶æ´¾ç”Ÿè‰²é˜¶ï¼ˆç”± utils/theme.js æ³¨å…¥ï¼‰
 * è¿™é‡Œçš„值是"品牌青"默认值;JS æ³¨å…¥çš„ inline style ä¼šè¦†ç›– :root
 * ============================================================ */
:root {
  /* å“ç‰Œè‰²ï¼ˆé»˜è®¤ï¼šOPO_Logo åœ†çŽ¯é’ï¼‰ */
  --opo-primary: #0E768F;
  --opo-primary-hover: #0D6A81;
  --opo-primary-active: #0B5E72;
  --opo-primary-light-3: #569FB1;
  --opo-primary-light-5: #87BBC7;
  --opo-primary-light-7: #B7D6DD;
  --opo-primary-light-8: #CFE4E9;
  --opo-primary-light-9: #E7F1F4;
  --opo-primary-light-95: #F3F8F9;
  --opo-primary-dark-2: #0B5E72;
  /* è¯­ä¹‰è‰² */
  --opo-success: #67C23A;
  --opo-success-light: #F0F9EB;
  --opo-warning: #E6A23C;
  --opo-warning-light: #FDF6EC;
  --opo-danger: #CD1D4E;
  --opo-danger-light: #FBEBEF;
  --opo-info: #909399;
  --opo-info-light: #F4F4F5;
  /* ä¸­æ€§è‰² */
  --opo-text-primary: #1F2937;
  --opo-text-regular: #4B5563;
  --opo-text-secondary: #6B7280;
  --opo-text-placeholder: #9CA3AF;
  --opo-border-base: #E5E7EB;
  --opo-border-light: #F0F2F5;
  --opo-border-lighter: #F5F7FA;
  --opo-bg-page: #F5F7FA;
  --opo-bg-card: #FFFFFF;
  --opo-bg-hover: #F5F9FD;
  --opo-bg-stripe: #FAFBFC;
  --opo-bg-thead: #F5F7FA;
  /* è¡¨æ ¼æ¡†çº¿ï¼ˆæ¯”通用边框更深) */
  --opo-table-border: #C8CFD8;
  --opo-table-border-strong: #B7C0CC;
  /* ä¾§è¾¹æ  */
  --opo-sidebar-bg: #0A4655;
  --opo-sidebar-sub-bg: #083B47;
  --opo-sidebar-hover: rgba(14, 118, 143, 0.28);
  --opo-sidebar-text: rgba(255, 255, 255, 0.72);
  --opo-sidebar-text-active: #FFFFFF;
  /* å­—号(2026-10-22 æ•´ä½“上调一号:正文 14→16) */
  --opo-font-size-display: 34px;
  --opo-font-size-h1: 26px;
  --opo-font-size-h2: 20px;
  --opo-font-size-lg: 18px;
  --opo-font-size-base: 16px;
  --opo-font-size-sm: 14px;
  --opo-font-size-xs: 13px;
  --opo-font-size-menu: 16px;
  /* å­—体(不要用 -apple-system æ‰“头,Windows ä¸Šä¼šå˜æˆ Segoe UI,数字显小) */
  --opo-font-family-base: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑",
    "Helvetica Neue", Arial, sans-serif;
  --opo-font-family-number: "DIN Alternate", "SF Mono", "Roboto Mono", Consolas, "Courier New", monospace;
  /* é—´è· */
  --opo-space-1: 4px;
  --opo-space-2: 8px;
  --opo-space-3: 12px;
  --opo-space-4: 16px;
  --opo-space-5: 24px;
  --opo-space-6: 32px;
  /* åœ†è§’ */
  --opo-radius-sm: 2px;
  --opo-radius-base: 4px;
  --opo-radius-lg: 8px;
  --opo-radius-pill: 999px;
  /* é˜´å½± */
  --opo-shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06);
  --opo-shadow-md: 0 4px 12px rgba(16, 24, 40, 0.08);
  --opo-shadow-lg: 0 12px 32px rgba(16, 24, 40, 0.12);
  /* æŽ§ä»¶é«˜åº¦ */
  --opo-control-height-small: 32px;
  --opo-control-height-base: 36px;
}
src/assets/styles/tokens.scss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
// ============================================================
// è®¾è®¡ä»¤ç‰Œï¼ˆDesign Tokens)· SCSS å˜é‡
// å“ç‰Œè‰²å–自 src/assets/logo/OPO_Logo.png åœ†çŽ¯é’ #0E768F
// è¯´æ˜Žè§ï¼šæ–¹æ¡ˆ-前端界面视觉优化方案.md
// çº¦å®šï¼šä¸šåŠ¡æ ·å¼ä¸€å¾‹å¼•ç”¨è¿™é‡Œçš„å˜é‡ï¼Œç¦æ­¢å†å†™è£¸è‰²å€¼
// ============================================================
// ---------- å“ç‰Œè‰²ï¼ˆé’) ----------
$opo-primary:          #0E768F;
$opo-primary-hover:    #0D6A81;
$opo-primary-active:   #0B5E72;
$opo-primary-light-3:  #569FB1;
$opo-primary-light-5:  #87BBC7;
$opo-primary-light-7:  #B7D6DD;
$opo-primary-light-8:  #CFE4E9;
$opo-primary-light-9:  #E7F1F4;
$opo-primary-light-95: #F3F8F9;
$opo-primary-dark-2:   #0B5E72;
// ---------- è¯­ä¹‰è‰² ----------
$opo-success:       #67C23A;
$opo-success-light: #F0F9EB;
$opo-warning:       #E6A23C;
$opo-warning-light: #FDF6EC;
$opo-danger:        #CD1D4E;
$opo-danger-light:  #FBEBEF;
$opo-info:          #909399;
$opo-info-light:    #F4F4F5;
// ---------- ä¸­æ€§è‰² ----------
$opo-text-primary:     #1F2937;
$opo-text-regular:     #4B5563;
$opo-text-secondary:   #6B7280;
$opo-text-placeholder: #9CA3AF;
$opo-border-base:      #E5E7EB;
$opo-border-light:     #F0F2F5;
$opo-border-lighter:   #F5F7FA;
$opo-bg-page:          #F5F7FA;
$opo-bg-card:          #FFFFFF;
$opo-bg-hover:         #F5F9FD;
$opo-bg-stripe:        #FAFBFC;
$opo-bg-thead:         #F5F7FA;
// ---------- è¡¨æ ¼æ¡†çº¿ï¼ˆæ¯”通用边框更深,便于看清分列/分行) ----------
$opo-table-border:        #C8CFD8;
$opo-table-border-strong: #B7C0CC;
// ---------- ä¾§è¾¹æ ï¼ˆä¸Žå“ç‰Œé’同族) ----------
$opo-sidebar-bg:          #0A4655;
$opo-sidebar-sub-bg:      #083B47;
$opo-sidebar-hover:       rgba(14, 118, 143, 0.28);
$opo-sidebar-text:        rgba(255, 255, 255, 0.72);
$opo-sidebar-text-active: #FFFFFF;
// ---------- å­—号(2026-10-22 æ•´ä½“上调一号:正文 14→16) ----------
$opo-font-size-display: 34px;
$opo-font-size-h1:      26px;
$opo-font-size-h2:      20px;
$opo-font-size-lg:      18px;
$opo-font-size-base:    16px;
$opo-font-size-sm:      14px;
$opo-font-size-xs:      13px;
$opo-font-size-menu:    16px;  // ä¾§è¾¹æ èœå•(2026-10-22 ç”± 18px å›žè°ƒä¸€å·ï¼‰
// ---------- è¡Œé«˜ ----------
$opo-line-height-base:    22px;
$opo-line-height-heading: 1.3;
// ---------- å­—体 ----------
// æ³¨æ„ï¼šä¸è¦ç”¨ -apple-system / BlinkMacSystemFont æ‰“头——Windows ä¸Š Chrome ä¼šè§£æžæˆ Segoe UI,
// å¯¼è‡´æ•°å­—/英文走 Segoe UI(比中文细、显小),中文回退微软雅黑,同一行里字号观感不一致。
// è¿™é‡ŒæŒ‰"平台有则用、没有则退到微软雅黑"的顺序,保证中英文同族。
$opo-font-family-base:   "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑",
                         "Helvetica Neue", Arial, sans-serif;
$opo-font-family-number: "DIN Alternate", "SF Mono", "Roboto Mono", Consolas, "Courier New", monospace;
// ---------- é—´è·ï¼ˆ8 ç‚¹æ …格) ----------
$opo-space-1: 4px;
$opo-space-2: 8px;
$opo-space-3: 12px;
$opo-space-4: 16px;
$opo-space-5: 24px;
$opo-space-6: 32px;
// ---------- åœ†è§’ ----------
$opo-radius-sm:   2px;
$opo-radius-base: 4px;
$opo-radius-lg:   8px;
$opo-radius-pill: 999px;
// ---------- é˜´å½± ----------
$opo-shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06);
$opo-shadow-md: 0 4px 12px rgba(16, 24, 40, 0.08);
$opo-shadow-lg: 0 12px 32px rgba(16, 24, 40, 0.12);
// ---------- æŽ§ä»¶é«˜åº¦ ----------
$opo-control-height-small:  32px;
$opo-control-height-base:   36px;
src/assets/styles/variables.scss
@@ -1,3 +1,6 @@
// è®¾è®¡ä»¤ç‰Œï¼ˆé¢œè‰²/字号/间距/圆角/阴影/字体的唯一来源)
@import './tokens.scss';
// base color
$blue:#324157;
$light-blue:#3A71A8;
@@ -10,22 +13,21 @@
// é»˜è®¤èœå•主题风格
//菜单字体颜色
$base-menu-color:#fff;
$base-menu-color:$opo-sidebar-text;
//选定子列表时所在列表的字体颜色
// f4f4f5
$base-menu-color-active:#fff;
//整体背景颜色#0187B1
$base-menu-background:#394d74;
$base-menu-color-active:$opo-sidebar-text-active;
//整体背景颜色(与品牌青同族的深青)
$base-menu-background:$opo-sidebar-bg;
//标题字体颜色
$base-logo-title-color: #ffffff;
//
$base-menu-light-color:rgba(0,0,0,.70);
$base-menu-light-background:#ffffff;
$base-logo-light-title-color: #001529;
//列表背景颜色1f2d3d
$base-sub-menu-background:#394d74;
//
$base-sub-menu-hover:#a76ac3;
//列表背景颜色
$base-sub-menu-background:$opo-sidebar-sub-bg;
//子菜单悬停色(原来是无关联的紫色 #a76ac3)
$base-sub-menu-hover:$opo-sidebar-hover;
// è‡ªå®šä¹‰æš—色菜单风格
/**
@@ -42,7 +44,7 @@
$base-sub-menu-hover:#001528;
*/
$base-sidebar-width: 200px;
$base-sidebar-width: 220px;
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
src/components/ThemePicker/index.vue
@@ -1,7 +1,7 @@
<template>
  <el-color-picker
    v-model="theme"
    :predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
    :predefine="['#0E768F', '#CD1D4E', '#409EFF', '#1B8E70', '#7B61FF', '#E6843C', '#304156', '#212121']"
    class="theme-picker"
    popper-class="theme-picker-dropdown"
  />
@@ -42,8 +42,12 @@
  methods: {
    async setTheme(val) {
      const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
      if (typeof val !== 'string') return
      // å…ˆé€šçŸ¥å¤–层:主题色由 CSS å˜é‡å±‚(utils/theme.js)立即生效。
      // åŽŸæ¥çš„å®žçŽ°è¦ç­‰ CDN æ‹‰å– theme-chalk å®ŒæˆåŽæ‰ emit,内网拉不到 unpkg
      // æ—¶ Promise æ°¸ä¸ resolve,导致"改主题没有任何反应"。
      this.$emit('change', val)
      const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
      const themeCluster = this.getThemeCluster(val.replace('#', ''))
      const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
@@ -81,8 +85,6 @@
        if (typeof innerText !== 'string') return
        style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
      })
      this.$emit('change', val)
    },
    updateStyle(style, oldCluster, newCluster) {
@@ -96,12 +98,19 @@
    getCSSString(url, variable) {
      return new Promise(resolve => {
        const xhr = new XMLHttpRequest()
        const done = () => resolve()
        xhr.onreadystatechange = () => {
          if (xhr.readyState === 4 && xhr.status === 200) {
            this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
          if (xhr.readyState === 4) {
            if (xhr.status === 200) {
              this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
            }
            resolve()
          }
        }
        // å†…网/无外网时快速失败,避免 Promise æ‚¬æŒ‚导致换肤卡住
        xhr.onerror = done
        xhr.ontimeout = done
        xhr.timeout = 4000
        xhr.open('GET', url)
        xhr.send()
      })
src/layout/components/Settings/index.vue
@@ -36,6 +36,21 @@
          <span>主题颜色</span>
          <theme-picker style="float: right;height: 26px;margin: -3px 8px 0 0;" @change="themeChange" />
        </div>
        <div class="drawer-item">
          <span>预设皮肤</span>
          <div class="opo-preset-list">
            <span
              v-for="p in presets"
              :key="p.color"
              class="opo-preset-item"
              :class="{ 'is-active': isCurrent(p.color) }"
              :style="{ backgroundColor: p.color }"
              :title="p.name"
              @click="themeChange(p.color)"
            />
          </div>
        </div>
      </div>
      <el-divider/>
@@ -77,13 +92,15 @@
<script>
import ThemePicker from '@/components/ThemePicker'
import { PRESETS, applyTheme, setStoredTheme } from '@/utils/theme'
export default {
  components: { ThemePicker },
  data() {
    return {
      theme: this.$store.state.settings.theme,
      sideTheme: this.$store.state.settings.sideTheme
      sideTheme: this.$store.state.settings.sideTheme,
      presets: PRESETS
    };
  },
  computed: {
@@ -147,12 +164,18 @@
    },
  },
  methods: {
    isCurrent(color) {
      return !!this.theme && String(this.theme).toUpperCase() === String(color).toUpperCase();
    },
    themeChange(val) {
      this.$store.dispatch('settings/changeSetting', {
        key: 'theme',
        value: val
      })
      this.theme = val;
      // ç«‹å³ç”Ÿæ•ˆå¹¶è®°ä½ï¼šä¸ä¾èµ– ThemePicker ä»Ž CDN æ‹‰å– theme-chalk
      applyTheme(val);
      setStoredTheme(val);
    },
    handleTheme(val) {
      this.$store.dispatch('settings/changeSetting', {
@@ -252,5 +275,29 @@
    .drawer-switch {
      float: right
    }
    // é¢„设皮肤色块
    .opo-preset-list {
      display: flex;
      align-items: center;
      flex-wrap: wrap;
      margin-top: 8px;
    }
    .opo-preset-item {
      display: inline-block;
      width: 22px;
      height: 22px;
      margin-right: 10px;
      border-radius: 4px;
      cursor: pointer;
      border: 2px solid transparent;
      box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08);
      &.is-active {
        border-color: #1F2937;
        box-shadow: 0 0 0 2px rgba(31, 41, 55, 0.18);
      }
    }
  }
</style>
src/layout/index.vue
@@ -1,5 +1,5 @@
<template>
  <div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}">
  <div :class="classObj" class="app-wrapper">
    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
    <sidebar class="sidebar-container"/>
    <div :class="{hasTagsView:needTagsView}" class="main-container">
@@ -21,6 +21,7 @@
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'
import variables from '@/assets/styles/variables.scss'
import { applyTheme, setStoredTheme } from '@/utils/theme'
export default {
  name: 'Layout',
@@ -54,7 +55,20 @@
      return variables;
    }
  },
  created() {
    // è¿›å…¥ Layout æ—¶åº”用当前主题色(并记住选择)
    this.applyCurrentTheme()
  },
  watch: {
    theme(val) {
      this.applyCurrentTheme()
      setStoredTheme(val)
    }
  },
  methods: {
    applyCurrentTheme() {
      applyTheme(this.theme)
    },
    handleClickOutside() {
      this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
    }
src/main.js
@@ -10,10 +10,14 @@
import Cookies from 'js-cookie'
import Element from 'element-ui'
import '@/assets/styles/tokens.css' // è®¾è®¡ä»¤ç‰Œï¼ˆCSS å˜é‡ï¼Œä¾›è¿è¡Œæ—¶æ¢è‚¤ï¼‰
import './assets/styles/element-variables.scss'
import '@/assets/styles/index.scss' // global css
import '@/assets/styles/ruoyi.scss' // ruoyi css
// è¿è¡Œæ—¶ä¸»é¢˜å±‚ + ç»„件默认外观:必须晚于 element-ui ä¸Ž ruoyi.scss,否则覆盖不生效
import '@/assets/styles/theme-runtime.scss'
import '@/assets/styles/element-override.scss'
import App from './App'
import store from './store'
import router from './router'
@@ -27,6 +31,8 @@
import { getConfigKey } from "@/api/system/config";
import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi";
import dataV from '@jiaminghi/data-view';//dataV
// ä¸»é¢˜è‰²è¿è¡Œæ—¶ï¼ˆCSS å˜é‡æ³¨å…¥ï¼‰
import { applyTheme, getStoredTheme, DEFAULT_PRIMARY } from '@/utils/theme'
// åˆ†é¡µç»„ä»¶
import Pagination from "@/components/Pagination";
// è‡ªå®šä¹‰è¡¨æ ¼å·¥å…·ç»„ä»¶
@@ -102,9 +108,12 @@
 */
Vue.use(Element, {
  size: Cookies.get('size') || 'medium' // set element-ui default size
  size: Cookies.get('size') || 'small' // set element-ui default size(原 medium,改为 small æå‡ä¿¡æ¯å¯†åº¦ï¼‰
})
// å¯åŠ¨å³åº”ç”¨å·²ä¿å­˜çš„ä¸»é¢˜è‰²ï¼ˆç™»å½•é¡µç­‰ä¸ä½¿ç”¨ Layout çš„页面也能生效)
applyTheme(getStoredTheme() || DEFAULT_PRIMARY)
Vue.config.productionTip = false
new Vue({
src/settings.js
@@ -5,9 +5,10 @@
  sideTheme: 'theme-dark',
  /**
   * æ˜¯å¦ç³»ç»Ÿå¸ƒå±€é…ç½®
   * æ˜¯å¦ç³»ç»Ÿå¸ƒå±€é…ç½®ï¼ˆå³ä¸‹è§’齿轮面板)
   * åŽŸä¸º false,导致"主题颜色 / é¢„设皮肤"入口对用户不可见 â†’ æ”¹ä¸º true
   */
  showSettings: false,
  showSettings: true,
  /**
   * æ˜¯å¦æ˜¾ç¤ºé¡¶éƒ¨å¯¼èˆª
@@ -40,87 +41,5 @@
   * The default is only used in the production env
   * If you want to also use it in dev, you can pass ['production', 'development']
   */
  errorLog: 'production',
  // vscode默认启用了根据文件类型自动设置tabsize的选项
  "editor.detectIndentation": false,
  // é‡æ–°è®¾å®štabsize
  "editor.tabSize": 2,
  // #每次保存的时候自动格式化
  "editor.formatOnSave": true,
  // #每次保存的时候将代码按eslint格式进行修复
  "eslint.autoFixOnSave": true,
  "tslint.autoFixOnSave": true,
  // æ·»åŠ  vue æ”¯æŒ
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    {
      "language": "vue",
      "autoFix": true
    }
  ],
  // #去掉代码结尾的分号
  "prettier.semi": true,
  // #使用带引号替代双引号
  "prettier.singleQuote": true,
  // #让函数(名)和后面的括号之间加个空格
  "javascript.format.insertSpaceBeforeFunctionParenthesis": true,
  // #这个按用户自身习惯选择
  "vetur.format.defaultFormatter.html": "js-beautify-html",
  // #让vue中的js按编辑器自带的ts格式进行格式化
  "vetur.format.defaultFormatter.js": "vscode-typescript",
  "vetur.format.defaultFormatterOptions": {
    "js-beautify-html": {
      "wrap_attributes": "force-aligned"
      // #vue组件中html代码格式化样式
    }
  },
  // æ ¼å¼åŒ–stylus, éœ€å®‰è£…Manta's Stylus Supremacy插件
  "stylusSupremacy.insertColons": false, // æ˜¯å¦æ’入冒号
  "stylusSupremacy.insertSemicolons": false, // æ˜¯å¦æ’入分好
  "stylusSupremacy.insertBraces": false, // æ˜¯å¦æ’入大括号
  "stylusSupremacy.insertNewLineAroundImports": false, // import之后是否换行
  "stylusSupremacy.insertNewLineAroundBlocks": false,
  "javascript.format.insertSpaceAfterConstructor": true // ä¸¤ä¸ªé€‰æ‹©å™¨ä¸­æ˜¯å¦æ¢è¡Œ
  errorLog: 'production'
}
src/store/modules/settings.js
@@ -1,11 +1,12 @@
import defaultSettings from '@/settings'
import { DEFAULT_PRIMARY, getStoredTheme } from '@/utils/theme'
const { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle } = defaultSettings
const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
const state = {
  title: '',
  theme: storageSetting.theme || '#409EFF',
  theme: storageSetting.theme || getStoredTheme() || DEFAULT_PRIMARY,
  sideTheme: storageSetting.sideTheme || sideTheme,
  showSettings: showSettings,
  topNav:  storageSetting.topNav === undefined ? topNav : storageSetting.topNav,
src/utils/fileNameCheck.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,153 @@
/**
 * æŠ¥é”€å•附件文件名规范校验(2026-10-15)
 *
 * ä¸šåŠ¡å£å¾„ï¼ˆä¸šåŠ¡æ–¹ç¡®è®¤ï¼‰ï¼š
 *   1. å¿…须包含中文(至少 1 ä¸ªæ±‰å­—)——"为方便查验附件,请用中文规范命名附件!"
 *   2. åŽ»æŽ‰æ‰©å±•ååŽçš„ä¸»å¹²ï¼ŒæŒ‰å­—ç¬¦æ•°è®¡ç®—ä¸èƒ½è¶…è¿‡ 20 ä¸ªå­———"为方便查验附件,附件名字长度不能超过20个字!"
 *   3. ä¸èƒ½åŒ…含空格及 / \ : * ? " < > | # % ç­‰ç‰¹æ®Šå­—符
 *   4. åŒä¸€æ˜Žç»†è¡Œå†…不能重名(比较时忽略扩展名大小写与首尾空格)
 *
 * ä½¿ç”¨åœºæ™¯ï¼š
 *   - ä¸Šä¼ æˆåŠŸå›žè°ƒï¼šä¸åˆæ ¼ â†’ å¼¹æç¤ºä¸”不加入列表;
 *   - ä¿å­˜/提交前:调用 checkDetails ç»Ÿä¸€æ ¡éªŒï¼Œä¸åˆæ ¼åˆ™æ‹¦ä¸‹ä¸å…è®¸ä¿å­˜ï¼›
 *   - åŽç«¯ä¿å­˜æŽ¥å£å¦æœ‰åŒå£å¾„的二次校验(防止绕过前端)。
 */
/** ä¸»å¹²ï¼ˆåŽ»æ‰©å±•åï¼‰æœ€å¤§å­—ç¬¦æ•° */
export const MAX_MAIN_LENGTH = 20;
/** æ±‰å­— */
const RE_CHINESE = /[\u4e00-\u9fa5]/;
/** éžæ³•字符:空格(含各类空白)+ / \ : * ? " < > | # % */
const RE_ILLEGAL = /[\s/\\:*?"<>|#%]/;
export const MSG_NO_CHINESE = "为方便查验附件,请用中文规范命名附件!";
export const MSG_TOO_LONG = "为方便查验附件,附件名字长度不能超过20个字!";
export const MSG_ILLEGAL = '为方便查验附件,附件名不能包含空格及 / \\ : * ? " < > | # % ç­‰ç‰¹æ®Šå­—符!';
export const MSG_DUPLICATE = "为方便查验附件,同一行内不能有重名的附件!";
/**
 * å–主干(去掉最后一个扩展名)
 * @param {string} fileName åŽŸå§‹æ–‡ä»¶å
 * @returns {string} ä¸»å¹²
 */
export function mainName(fileName) {
  const s = (fileName || "").trim();
  const i = s.lastIndexOf(".");
  return i > 0 ? s.substring(0, i) : s;
}
/**
 * å•个文件名校验
 * @param {string} fileName åŽŸå§‹æ–‡ä»¶å
 * @returns {{ok: boolean, msg: string, main: string}}
 */
export function checkFileName(fileName) {
  const name = (fileName || "").trim();
  if (!name) {
    return { ok: false, msg: MSG_NO_CHINESE, 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 };
  }
  return { ok: true, msg: "", main };
}
/** å–附件项显示名(不同页面字段名可能是 name / fileName,取不到就返回空串表示"不判断") */
function itemName(item) {
  if (!item) return "";
  if (typeof item === "string") return item;
  return item.name || item.fileName || "";
}
/**
 * ä¸€ç»„附件校验(含同行重名)
 * @param {Array} list é™„件数组,元素形如 {name, url}
 * @returns {{ok: boolean, msg: string, index: number}}
 */
export function checkFileList(list) {
  const arr = list || [];
  const seen = {};
  for (let i = 0; i < arr.length; i++) {
    const name = itemName(arr[i]);
    if (!name) continue; // å–不到原文件名则不判断,避免误拦
    const r = checkFileName(name);
    if (!r.ok) {
      return { ok: false, msg: r.msg, index: i };
    }
    const key = name.trim().toLowerCase();
    if (seen[key]) {
      return { ok: false, msg: MSG_DUPLICATE, index: i };
    }
    seen[key] = true;
  }
  return { ok: true, msg: "", index: -1 };
}
/**
 * ä¿å­˜å‰æ ¡éªŒæ•´å•明细(发票 + é™„件)
 * @param {Array} details æ˜Žç»†è¡Œæ•°ç»„,行内含 invoicefilesList / annexfilesList(数组)
 * @returns {{ok: boolean, msg: string}}
 */
export function checkDetails(details) {
  const rows = details || [];
  for (let i = 0; i < rows.length; i++) {
    const groups = [
      { label: "发票", list: rows[i] && rows[i].invoicefilesList },
      { label: "附件", list: rows[i] && rows[i].annexfilesList }
    ];
    for (let g = 0; g < groups.length; g++) {
      const arr = groups[g].list || [];
      const seen = {};
      for (let j = 0; j < arr.length; j++) {
        const name = itemName(arr[j]);
        if (!name) continue;
        const r = checkFileName(name);
        if (!r.ok) {
          return {
            ok: false,
            msg:
              "附件文件名不符合要求,无法保存:第" + (i + 1) + "行第" + (j + 1) +
              "个(" + name + ")——" + r.msg
          };
        }
        const key = name.trim().toLowerCase();
        if (seen[key]) {
          return {
            ok: false,
            msg:
              "附件文件名不符合要求,无法保存:第" + (i + 1) + "行第" + (j + 1) +
              "个(" + name + ")——" + MSG_DUPLICATE
          };
        }
        seen[key] = true;
      }
    }
  }
  return { ok: true, msg: "" };
}
/**
 * æ•´å•里所有附件(不分行)的扁平校验,供只有单个文件列表的页面使用
 * @param {Array} lists å¤šä¸ªé™„件数组
 * @returns {{ok: boolean, msg: string}}
 */
export function checkAllLists(lists) {
  const all = lists || [];
  for (let i = 0; i < all.length; i++) {
    const r = checkFileList(all[i]);
    if (!r.ok) {
      return { ok: false, msg: r.msg };
    }
  }
  return { ok: true, msg: "" };
}
src/utils/money.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,240 @@
/**
 * é‡‘额计算工具
 *
 * èƒŒæ™¯ï¼šæœ¬ç³»ç»Ÿé‡‘额一律以「元」为单位、保留 2 ä½å°æ•°ï¼Œå±žè´¢åŠ¡æ•°æ®ã€‚
 * JS çš„ Number æ˜¯ IEEE-754 åŒç²¾åº¦æµ®ç‚¹ï¼Œç›´æŽ¥åšåŠ å‡ä¹˜ä¼šå¼•å…¥äºŒè¿›åˆ¶èˆå…¥è¯¯å·®ï¼Œ
 * å…¸åž‹è¡¨çŽ°ï¼š
 *   0.1 + 0.2        -> 0.30000000000000004
 *   1.1 * 3          -> 3.3000000000000003
 *   0.3 - 0.2 - 0.1  -> -2.7755575615628914e-17   ï¼ˆé€€è´¹/扣税相减后残留尾差)
 * è¿™ç±»å°¾å·®å¯¼è‡´ã€Œåˆè®¡å¯¹ä¸ä¸Šã€ã€Œé‡‘额显示为 -0」「相等判断失败」等线上问题,
 * ä¸”会随单据流转被写入数据库,事后难以追溯。
 *
 * å› æ­¤ï¼šæ‰€æœ‰é‡‘额运算统一走本模块,禁止直接对金额使用 + - * / ä¸Ž parseFloat ç´¯åŠ ã€‚
 * å†…部基于 decimal.js(package.json å·²å¼•入),保证十进制精确运算。
 *
 * ç”¨æ³•示例:
 *   import { moneySum, moneyMul, moneySub, moneyRound, moneyEq } from "@/utils/money";
 *
 *   // ç´¯åŠ æ˜Žç»†ï¼ˆè¿‡æ»¤éžæ•°å­—ä¸Žç©ºå€¼ï¼Œä¸ä¼šæŠŠ NaN ä¼ æŸ“给合计)
 *   const total = moneySum(this.rbDetails, "amount");
 *
 *   // å•ä»· Ã— æ•°é‡
 *   const preTax = moneyMul(row.price, row.quantity);
 *
 *   // ç¨Žå‰ - æ‰£ç¨Ž = ç¨ŽåŽ
 *   const afterTax = moneySub(row.amount, row.taxamount);
 *
 *   // è½åº“/展示前统一保留 2 ä½ï¼ˆå››èˆäº”入)
 *   this.form.amountrequested = moneyRound(total);
 *
 *   // é‡‘额比较(勿用 == / === æˆ– parseFloat åŽç›¸å‡ï¼‰
 *   if (!moneyEq(sumOfPayees, this.form.amountrequested)) { ... }
 */
import Decimal from 'decimal.js'
/** é‡‘额保留小数位 */
export const MONEY_SCALE = 2
/**
 * è½¬ä¸º Decimal;无法识别的值(空串/undefined/null/NaN/非数字)一律按 0 å¤„理。
 * æŒ‰ 0 å¤„理而非抛错,是为了保持既有页面「!isNaN(parseFloat(x)) æ‰ç´¯åŠ ã€çš„è¡Œä¸ºï¼Œ
 * åŒæ—¶é¿å… NaN æ±¡æŸ“整条合计链。
 *
 * @param {string|number|Decimal|null|undefined} value
 * @returns {Decimal}
 */
export function toMoney(value) {
  if (value === null || value === undefined || value === '') {
    return new Decimal(0)
  }
  if (value instanceof Decimal) {
    return value
  }
  let d
  try {
    d = new Decimal(value)
  } catch (e) {
    return new Decimal(0)
  }
  return d.isFinite() ? d : new Decimal(0)
}
/**
 * åˆ¤æ–­æ˜¯å¦ä¸ºæœ‰æ•ˆé‡‘额(可参与计算)。用于原本 !isNaN(parseFloat(x)) çš„æ ¡éªŒåœºæ™¯ã€‚
 *
 * @param {*} value
 * @returns {boolean}
 */
export function isValidMoney(value) {
  if (value === null || value === undefined || value === '') {
    return false
  }
  if (value instanceof Decimal) {
    return value.isFinite()
  }
  if (typeof value === 'number') {
    return Number.isFinite(value)
  }
  const s = String(value).trim()
  if (s === '') {
    return false
  }
  try {
    return new Decimal(s).isFinite()
  } catch (e) {
    return false
  }
}
/**
 * ç´¯åŠ æ±‚å’Œã€‚
 *
 * @param {Array} list å¾…累加的数组
 * @param {string|Function} [selector] å–金额的方式:
 *        å­—符串 -> å–该项的该属性名,如 "amount";
 *        å‡½æ•°   -> ç”¨è¿”回值,如 (item) => item.detail && item.detail.amount;
 *        çœç•¥   -> ç›´æŽ¥ç´¯åŠ æ•°ç»„å…ƒç´ æœ¬èº«ã€‚
 * @returns {number} æ±‚和结果(Number,便于赋值给表单字段)
 */
export function moneySum(list, selector) {
  if (!Array.isArray(list) || list.length === 0) {
    return 0
  }
  let acc = new Decimal(0)
  for (let i = 0; i < list.length; i++) {
    const raw =
      selector === undefined
        ? list[i]
        : typeof selector === 'function'
          ? selector(list[i])
          : list[i][selector]
    acc = acc.plus(toMoney(raw))
  }
  return acc.toNumber()
}
/**
 * æ±‚和后保留 2 ä½å°æ•°ï¼ˆä¼˜å…ˆåœ¨å±•示/落库时使用)。
 */
export function moneySumRound(list, selector) {
  return roundNumber(moneySum(list, selector))
}
/**
 * ç²¾ç¡®åŠ æ³•ã€‚æ”¯æŒå¤šå‚æ•°æˆ–æ•°ç»„ï¼šmoneyAdd(1.1, 2.2) / moneyAdd([1.1, 2.2])
 */
export function moneyAdd(...args) {
  const list = args.length === 1 && Array.isArray(args[0]) ? args[0] : args
  let acc = new Decimal(0)
  list.forEach((v) => {
    acc = acc.plus(toMoney(v))
  })
  return acc.toNumber()
}
/**
 * ç²¾ç¡®å‡æ³• a - b(可传多个:a - b - c)
 */
export function moneySub(a, ...rest) {
  let acc = toMoney(a)
  rest.forEach((v) => {
    acc = acc.minus(toMoney(v))
  })
  return acc.toNumber()
}
/**
 * ç²¾ç¡®ä¹˜æ³•(单价 Ã— æ•°é‡ã€é‡‘额 Ã— ç¨ŽçŽ‡ï¼‰
 */
export function moneyMul(a, ...rest) {
  let acc = toMoney(a)
  rest.forEach((v) => {
    acc = acc.times(toMoney(v))
  })
  return acc.toNumber()
}
/**
 * ç²¾ç¡®é™¤æ³•(金额 Ã· æ•°é‡ï¼‰ï¼ŒdivideBy ä¸º 0 æ—¶è¿”回 0,不产生 Infinity
 */
export function moneyDiv(a, b) {
  const divisor = toMoney(b)
  if (divisor.isZero()) {
    return 0
  }
  return toMoney(a).div(divisor).toNumber()
}
/**
 * é‡‘额是否相等(十进制精确比较,不受浮点尾差影响)。
 * ä¼šè‡ªåŠ¨å½’ä¸€åŒ–åˆ° 2 ä½ï¼Œé¿å… 100 ä¸Ž 100.001 è¿™ç±»è¾¹ç•Œè¯¯åˆ¤ã€‚
 *
 * @returns {boolean}
 */
export function moneyEq(a, b) {
  return roundDecimal(a).eq(roundDecimal(b))
}
/**
 * é‡‘额大小比较:a > b è¿”回 1,a < b è¿”回 -1,相等返回 0
 */
export function moneyCmp(a, b) {
  return roundDecimal(a).cmp(roundDecimal(b))
}
/**
 * ä¿ç•™ 2 ä½å°æ•°å¹¶è¿”回 Number(四舍五入,ROUND_HALF_UP)。
 * ç”¨äºŽèµ‹å€¼ç»™è¡¨å•字段、参与后端交互。
 *
 * @param {*} value
 * @param {number} [scale=2]
 * @returns {number}
 */
export function moneyRound(value, scale = MONEY_SCALE) {
  return roundDecimal(value, scale).toNumber()
}
/**
 * ä¿ç•™ 2 ä½å°æ•°å¹¶è¿”回字符串(用于金额展示,避免大额时出现科学计数法)。
 *
 * @param {*} value
 * @param {number} [scale=2]
 * @returns {string}
 */
export function moneyText(value, scale = MONEY_SCALE) {
  return roundDecimal(value, scale).toFixed(scale)
}
/**
 * ä¿ç•™ 2 ä½å°æ•°å¹¶è¿”回 Decimal,供后续继续精确运算
 */
export function moneyDecimal(value, scale = MONEY_SCALE) {
  return roundDecimal(value, scale)
}
function roundDecimal(value, scale = MONEY_SCALE) {
  return toMoney(value).toDecimalPlaces(scale, Decimal.ROUND_HALF_UP)
}
// moneySum å†…部已返回 Number,这里统一走同一套四舍五入
function roundNumber(value) {
  return roundDecimal(value).toNumber()
}
export default {
  MONEY_SCALE,
  toMoney,
  isValidMoney,
  moneySum,
  moneySumRound,
  moneyAdd,
  moneySub,
  moneyMul,
  moneyDiv,
  moneyEq,
  moneyCmp,
  moneyRound,
  moneyText,
  moneyDecimal
}
src/utils/request.js
@@ -104,22 +104,24 @@
    console.log('err' + error)
    let { message } = error;
    if (message == "Network Error") {
      // message = "后端接口连接异常";
      console.log("Network Erro",message);
      message = "后端接口连接异常";
    }
    else if (message.includes("timeout")) {
      // message = "系统接口请求超时";
      console.log("timeout",message);
      message = "系统接口请求超时";
    }
    else if (message.includes("Request failed with status code")) {
      // message = "系统接口" + message.substr(message.length - 3) + "异常";
      console.log("Request failed with status code",message);
      // ä¼˜å…ˆå±•示后端返回的具体原因(如"医院协友系统接收失败,本次审批未生效,请稍后重新审批")
      const data = error.response && error.response.data;
      const backendMsg = data && (data.msg || data.message);
      message = backendMsg || ("系统接口" + message.substr(message.length - 3) + "异常");
    }
    // Message({
    //   message: message,
    //   type: 'error',
    //   duration: 10* 1000
    // })
    // 2026-10-09 æ¢å¤é”™è¯¯æç¤ºï¼šåŽŸæ¥è¿™é‡Œæ˜¯æ³¨é‡ŠçŠ¶æ€ï¼Œå¯¼è‡´è¯·æ±‚å‘ç”Ÿ HTTP çº§é”™è¯¯æ—¶
    // é¡µé¢ä»€ä¹ˆéƒ½ä¸æç¤ºï¼ˆè°ƒç”¨æ–¹çš„ .then ä¸ä¼šæ‰§è¡Œï¼‰ï¼Œç”¨æˆ·çœ‹ä¸åˆ°å¤±è´¥åŽŸå› 
    Message({
      message: message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)
src/utils/theme.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,172 @@
/**
 * ä¸»é¢˜è‰²è¿è¡Œæ—¶è®¡ç®—与应用
 * ---------------------------------------------------------------
 * èƒŒæ™¯ï¼šElement UI 2.x çš„主题色是 SCSS ç¼–译期常量,运行时无法改动;
 *      åŽŸæœ‰ ThemePicker ä¾èµ– unpkg CDN æ‹‰å– theme-chalk å†å­—符串替换,
 *      å†…网环境拉不到 CSS,导致"改主题没反应"。
 * åšæ³•:由任意主色推出一套色阶(hover / active / light-3~95),
 *      å†™å…¥ :root çš„ CSS å˜é‡ï¼›theme-runtime.scss ç”¨ var() æ¶ˆè´¹è¿™äº›å˜é‡ï¼Œ
 *      ä»Žè€Œå®žçް"一次编译、运行时换肤、页面不刷新"。
 */
/** é»˜è®¤ä¸»è‰²ï¼šOPO_Logo.png åœ†çŽ¯é’ */
export const DEFAULT_PRIMARY = '#0E768F'
/** localStorage é”®ï¼ˆä¸Žå¸ƒå±€è®¾ç½®åˆ†å¼€ï¼Œé¿å…äº’相覆盖) */
const STORAGE_KEY = 'opo-theme'
/** é¢„设皮肤 */
export const PRESETS = [
  { name: '品牌青', color: '#0E768F' },
  { name: '玫红', color: '#CD1D4E' },
  { name: 'Element è“', color: '#409EFF' },
  { name: '叶芽绿', color: '#1B8E70' },
  { name: 'ç´«', color: '#7B61FF' },
  { name: '橙', color: '#E6843C' }
]
const WHITE = [255, 255, 255]
const BLACK = [0, 0, 0]
function clamp(v) {
  return Math.max(0, Math.min(255, Math.round(v)))
}
/** #abc / #aabbcc â†’ [r,g,b];非法返回 null */
export function hexToRgb(hex) {
  if (typeof hex !== 'string') return null
  let h = hex.trim().replace(/^#/, '')
  if (h.length === 3) {
    h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]
  }
  if (!/^[0-9a-fA-F]{6}$/.test(h)) return null
  return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)]
}
/** [r,g,b] â†’ #RRGGBB */
export function rgbToHex(rgb) {
  return (
    '#' +
    rgb
      .map(function (v) {
        const s = clamp(v).toString(16)
        return s.length === 1 ? '0' + s : s
      })
      .join('')
      .toUpperCase()
  )
}
/**
 * æ··è‰²ï¼šweight ä¸ºç›®æ ‡è‰²å æ¯”(0~1)
 * mix([14,118,143], WHITE, 0.3) â‰ˆ 30% ç™½ + 70% ä¸»è‰²
 */
export function mix(rgb, target, weight) {
  const w = Math.max(0, Math.min(1, weight))
  return [
    clamp(rgb[0] * (1 - w) + target[0] * w),
    clamp(rgb[1] * (1 - w) + target[1] * w),
    clamp(rgb[2] * (1 - w) + target[2] * w)
  ]
}
function channelLum(v) {
  const c = v / 255
  return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
}
/** ç›¸å¯¹äº®åº¦ï¼ˆWCAG) */
export function luminance(rgb) {
  return 0.2126 * channelLum(rgb[0]) + 0.7152 * channelLum(rgb[1]) + 0.0722 * channelLum(rgb[2])
}
/** ä¸Žç™½è‰²çš„对比度,用于判断白字是否可读 */
export function contrastWithWhite(rgb) {
  return 1.05 / (luminance(rgb) + 0.05)
}
/**
 * ç”±ä¸»è‰²æŽ¨å…¨å¥—色阶
 * æ·±è‰²ä¸»è‰²ï¼ˆäº®åº¦ä½Žï¼‰hover ç”¨"加深",浅色主色 hover ç”¨"变浅",
 * ä¿è¯ hover æ€çš„白字对比度不会掉下去。
 */
export function buildScale(primary) {
  const base = hexToRgb(primary) || hexToRgb(DEFAULT_PRIMARY)
  const isDark = luminance(base) < 0.4
  const hover = isDark ? mix(base, BLACK, 0.1) : mix(base, WHITE, 0.2)
  const active = isDark ? mix(base, BLACK, 0.2) : mix(base, WHITE, 0.32)
  return {
    primary: rgbToHex(base),
    hover: rgbToHex(hover),
    active: rgbToHex(active),
    light3: rgbToHex(mix(base, WHITE, 0.3)),
    light5: rgbToHex(mix(base, WHITE, 0.5)),
    light7: rgbToHex(mix(base, WHITE, 0.7)),
    light8: rgbToHex(mix(base, WHITE, 0.8)),
    light9: rgbToHex(mix(base, WHITE, 0.9)),
    light95: rgbToHex(mix(base, WHITE, 0.95)),
    dark2: rgbToHex(mix(base, BLACK, 0.2)),
    isDark: isDark,
    contrastWithWhite: Number(contrastWithWhite(base).toFixed(2))
  }
}
/** æŠŠè‰²é˜¶å†™å…¥ :root çš„ CSS å˜é‡ï¼ˆtheme-runtime.scss æ¶ˆè´¹ï¼‰ */
export function applyTheme(primary) {
  const scale = buildScale(primary || DEFAULT_PRIMARY)
  if (typeof document === 'undefined') return scale
  const root = document.documentElement
  const map = {
    '--opo-primary': scale.primary,
    '--opo-primary-hover': scale.hover,
    '--opo-primary-active': scale.active,
    '--opo-primary-light-3': scale.light3,
    '--opo-primary-light-5': scale.light5,
    '--opo-primary-light-7': scale.light7,
    '--opo-primary-light-8': scale.light8,
    '--opo-primary-light-9': scale.light9,
    '--opo-primary-light-95': scale.light95,
    '--opo-primary-dark-2': scale.dark2
  }
  Object.keys(map).forEach(function (k) {
    root.style.setProperty(k, map[k])
  })
  // ä¾›ä¸ªåˆ«éœ€è¦çŸ¥é“"当前是否深色主色"的场景使用
  root.setAttribute('data-opo-dark-primary', scale.isDark ? '1' : '0')
  return scale
}
/** è¯»å–上次选择的主色 */
export function getStoredTheme() {
  try {
    return localStorage.getItem(STORAGE_KEY)
  } catch (e) {
    return null
  }
}
/** è®°ä½é€‰æ‹© */
export function setStoredTheme(color) {
  try {
    localStorage.setItem(STORAGE_KEY, color)
  } catch (e) {
    /* å¿½ç•¥éšç§æ¨¡å¼ç­‰å¼‚常 */
  }
}
export default {
  DEFAULT_PRIMARY: DEFAULT_PRIMARY,
  PRESETS: PRESETS,
  applyTheme: applyTheme,
  buildScale: buildScale,
  getStoredTheme: getStoredTheme,
  setStoredTheme: setStoredTheme,
  hexToRgb: hexToRgb,
  rgbToHex: rgbToHex,
  mix: mix,
  luminance: luminance,
  contrastWithWhite: contrastWithWhite
}
src/views/home/components/BarChart/index.vue
@@ -37,8 +37,16 @@
  data() {
    return {
      options: {},
      // è‡ªåŠ¨è½®æ’­å®šæ—¶å™¨å¥æŸ„ï¼›ä¸æ¸…ç†ä¼šåœ¨ç»„ä»¶é”€æ¯åŽç»§ç»­è§¦å‘ï¼Œå¯¼è‡´å¯¹å·²é”€æ¯å®žä¾‹æ“ä½œ
      intervalId: null,
    };
  },
  beforeDestroy() {
    if (this.intervalId !== null) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  },
  components: {
    Echart,
  },
src/views/home/components/BarChart2/index.vue
@@ -37,8 +37,16 @@
  data() {
    return {
      options: {},
      // è‡ªåŠ¨è½®æ’­å®šæ—¶å™¨å¥æŸ„ï¼›ä¸æ¸…ç†ä¼šåœ¨ç»„ä»¶é”€æ¯åŽç»§ç»­è§¦å‘ï¼Œå¯¼è‡´å¯¹å·²é”€æ¯å®žä¾‹æ“ä½œ
      intervalId: null,
    };
  },
  beforeDestroy() {
    if (this.intervalId !== null) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  },
  components: {
    Echart,
  },
src/views/home/components/BarChart3/index.vue
@@ -37,8 +37,16 @@
  data() {
    return {
      options: {},
      // è‡ªåŠ¨è½®æ’­å®šæ—¶å™¨å¥æŸ„ï¼›ä¸æ¸…ç†ä¼šåœ¨ç»„ä»¶é”€æ¯åŽç»§ç»­è§¦å‘ï¼Œå¯¼è‡´å¯¹å·²é”€æ¯å®žä¾‹æ“ä½œ
      intervalId: null,
    };
  },
  beforeDestroy() {
    if (this.intervalId !== null) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  },
  components: {
    Echart,
  },
src/views/home/components/BarChartH/index.vue
@@ -41,8 +41,16 @@
  data() {
    return {
      options: {},
      // è‡ªåŠ¨è½®æ’­å®šæ—¶å™¨å¥æŸ„ï¼›ä¸æ¸…ç†ä¼šåœ¨ç»„ä»¶é”€æ¯åŽç»§ç»­è§¦å‘ï¼Œå¯¼è‡´å¯¹å·²é”€æ¯å®žä¾‹æ“ä½œ
      intervalId: null,
    };
  },
  beforeDestroy() {
    if (this.intervalId !== null) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  },
  components: {
    Echart,
  },
src/views/home/components/MapChart/index.vue
@@ -41,8 +41,16 @@
  data() {
    return {
      options: {},
      // è‡ªåŠ¨è½®æ’­å®šæ—¶å™¨å¥æŸ„ï¼›ä¸æ¸…ç†ä¼šåœ¨ç»„ä»¶é”€æ¯åŽç»§ç»­è§¦å‘ï¼Œå¯¼è‡´å¯¹å·²é”€æ¯å®žä¾‹æ“ä½œ
      intervalId: null,
    };
  },
  beforeDestroy() {
    if (this.intervalId !== null) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  },
  components: {
    Echart,
  },
src/views/home/components/line1Chart/index.vue
@@ -37,8 +37,16 @@
  data() {
    return {
      options: {},
      // è‡ªåŠ¨è½®æ’­å®šæ—¶å™¨å¥æŸ„ï¼›ä¸æ¸…ç†ä¼šåœ¨ç»„ä»¶é”€æ¯åŽç»§ç»­è§¦å‘ï¼Œå¯¼è‡´å¯¹å·²é”€æ¯å®žä¾‹æ“ä½œ
      intervalId: null,
    };
  },
  beforeDestroy() {
    if (this.intervalId !== null) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  },
  components: {
    Echart,
  },
src/views/home/components/line2Chart/index.vue
@@ -37,8 +37,16 @@
  data() {
    return {
      options: {},
      // è‡ªåŠ¨è½®æ’­å®šæ—¶å™¨å¥æŸ„ï¼›ä¸æ¸…ç†ä¼šåœ¨ç»„ä»¶é”€æ¯åŽç»§ç»­è§¦å‘ï¼Œå¯¼è‡´å¯¹å·²é”€æ¯å®žä¾‹æ“ä½œ
      intervalId: null,
    };
  },
  beforeDestroy() {
    if (this.intervalId !== null) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  },
  components: {
    Echart,
  },
src/views/home/components/lineChart/index.vue
@@ -36,9 +36,17 @@
  },
  data() {
    return {
      options: {}
      options: {},
      // è‡ªåŠ¨è½®æ’­å®šæ—¶å™¨å¥æŸ„ï¼›ä¸æ¸…ç†ä¼šåœ¨ç»„ä»¶é”€æ¯åŽç»§ç»­è§¦å‘ï¼Œå¯¼è‡´å¯¹å·²é”€æ¯å®žä¾‹æ“ä½œ
      intervalId: null
    };
  },
  beforeDestroy() {
    if (this.intervalId !== null) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  },
  components: {
    Echart
  },
src/views/monitor/job/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="任务名称" prop="jobName">
        <el-input
          v-model="queryParams.jobName"
src/views/monitor/job/log.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="任务名称" prop="jobName">
        <el-input
          v-model="queryParams.jobName"
src/views/monitor/logininfor/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="登录地址" prop="ipaddr">
        <el-input
          v-model="queryParams.ipaddr"
src/views/monitor/online/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
      <el-form-item label="登录地址" prop="ipaddr">
        <el-input
          v-model="queryParams.ipaddr"
src/views/monitor/operlog/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="系统模块" prop="title">
        <el-input
          v-model="queryParams.title"
src/views/project/DonationEvaluation/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
src/views/project/DonationProcess/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -690,16 +691,10 @@
        this.queryParams.reporterno = this.reportervalue;
      }
      if (this.starttime != "") {
        this.queryParams.starttime = this.starttime;
      } else {
        this.queryParams.starttime = "";
      }
      if (this.endtime != "") {
        this.queryParams.endtime = this.endtime;
      } else {
        this.queryParams.endtime = "";
      }
      // æœªé€‰æ—¶é—´èŒƒå›´æ—¶ä¼  null:空串会让后端拼出 `donatetime >= ''`,
      // MySQL ä¸¥æ ¼æ¨¡å¼ä¼šæŠ¥ Incorrect DATETIME value: ''(见 CHANGELOG 2026-10-22)
      this.queryParams.starttime = this.starttime || null;
      this.queryParams.endtime = this.endtime || null;
      console.log(this.queryParams);
      listDonationProcess(this.queryParams).then(response => {
src/views/project/components/annexupload/index.vue
@@ -123,7 +123,7 @@
              clearable
              size="small"
              v-model="uploadtime"
              type="datetime"
              type="datetime" format="yyyy-MM-dd HH:mm"
              value-format="yyyy-MM-dd HH:mm:ss"
              placeholder="请选择上传时间"
            >
src/views/project/distributedetail/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="收益人姓名 å®¶å±žæˆ–专家" prop="beneficiaryname">
        <el-input
          v-model="queryParams.beneficiaryname"
src/views/project/documentfiles/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -273,7 +274,7 @@
                size="small"
                style="width: 220px"
                v-model="form.uploadtime"
                type="datetime"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
              >
              </el-date-picker>
@@ -331,7 +332,6 @@
              <el-input v-model="form.fileurl" placeholder="请输入文件地址" />
            </el-form-item> -->
            <el-form-item label="文件上传" align="left" prop="fileurl">
              <!-- <file-upload ref="fileUpload" :fileType=fileType :limit="1" :on-success="handleUploadSuccess"></file-upload> -->
              <el-upload
                size="mini"
                class="upload-demo"
@@ -460,7 +460,6 @@
        fileurl: null,
        status: null,
      },
      fileType: ["png", "jpg", "jpeg", "doc", "xls", "ppt", "txt", "pdf"],
      // è¡¨å•参数
      form: {},
      // è¡¨å•校验
@@ -469,8 +468,9 @@
      //文件列表
      fileList: [],
       baseUrl: process.env.VUE_APP_BASE_API,
      // ä¸Šä¼ çš„图片服务器地址
      uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload",
      // æ”¿ç­–文件上传接口(2026-10-09:改用模块专属接口,允许任意文件类型;
      // é€šç”¨ /common/upload ä»æŒ‰æ‰©å±•名白名单校验,不适用于本模块)
      uploadFileUrl: process.env.VUE_APP_BASE_API + "/project/documentfiles/upload",
      //上传附件请求头
      headers: {
        Authorization: "Bearer " + getToken(),
@@ -659,34 +659,100 @@
    download() {
      let list = this.fileList;
      if (list.length > 0) {
        let downUrl = list[0].url;
        const link = document.createElement("a");
        link.href = this.baseUrl + downUrl;
        link.setAttribute("download", this.getFileName(list[0].name));
        document.body.appendChild(link);
        link.click();
        this.saveFile(list[0].url, this.getFileName(list[0].name, list[0].url));
      } else {
        this.$modal.msgWarning("未获取到文件地址,请确认");
      }
    },
    handleDownload(row) {
      if (row.fileurl != "" && row.fileurl != null) {
        const link = document.createElement("a");
        link.href = this.baseUrl + row.fileurl;
        link.setAttribute("download", this.getFileName(row.fileurl));
        document.body.appendChild(link);
        link.click();
        this.saveFile(
          row.fileurl,
          this.getFileName(row.filename, row.fileurl, row.filename)
        );
      } else {
        this.$modal.msgWarning("未获取到文件地址,请确认");
      }
    },
    // èŽ·å–æ–‡ä»¶åç§°
    getFileName(name) {
      if (name.lastIndexOf("/") > -1) {
        return name.slice(name.lastIndexOf("/") + 1).toLowerCase();
      } else {
        return "";
    /**
     * ä¸‹è½½æ–‡ä»¶ï¼ˆå¿…须带鉴权)。
     *
     * èƒŒæ™¯ï¼š/profile/** é™æ€èµ„源未在 SecurityConfig ä¸­æ”¾è¡Œï¼ˆanyRequest().authenticated()),
     * è€ŒåŽç«¯ TokenService åªä»Žè¯·æ±‚头 Authorization å– token。
     * å› æ­¤ç”¨ <a href> ç›´æŽ¥è·³è½¬ä¸‹è½½ä¸ä¼šæºå¸¦ token,服务端返回 401,表现为“点下载没反应/下不到文件”。
     * è¿™é‡Œæ”¹ä¸º fetch å¸¦ Authorization å¤´å–回文件流,再用 blob æœ¬åœ°å¦å­˜ã€‚
     *
     * @param {string} fileUrl æ–‡ä»¶åœ°å€ï¼ˆåº“中存的是 /profile/upload/... ç›¸å¯¹è·¯å¾„)
     * @param {string} fileName å¦å­˜ä¸ºçš„æ–‡ä»¶å
     */
    saveFile(fileUrl, fileName) {
      if (!fileUrl) {
        this.$modal.msgWarning("未获取到文件地址,请确认");
        return;
      }
      // å…¼å®¹åŽ†å²æ•°æ®ä¸­å·²æ˜¯ç»å¯¹åœ°å€çš„æƒ…å†µï¼Œé¿å…é‡å¤æ‹¼æŽ¥
      const isAbsolute = /^https?:\/\//i.test(fileUrl);
      const url = isAbsolute ? fileUrl : this.baseUrl + fileUrl;
      this.$modal.loading("正在下载文件,请耐心等待!");
      fetch(url, {
        method: "get",
        headers: { Authorization: "Bearer " + getToken() },
      })
        .then((response) => {
          if (!response.ok) {
            // 401/403/404 å¿…须显式提示,否则会下到一个内容为错误页的文件
            throw new Error("HTTP " + response.status);
          }
          return response.blob();
        })
        .then((blob) => {
          const blobUrl = window.URL.createObjectURL(blob);
          const link = document.createElement("a");
          link.href = blobUrl;
          link.setAttribute("download", fileName || "下载文件");
          document.body.appendChild(link);
          link.click();
          document.body.removeChild(link);
          window.URL.revokeObjectURL(blobUrl);
        })
        .catch((err) => {
          console.error("文件下载失败:" + url, err);
          this.$modal.msgError("文件下载失败,请确认文件是否存在或登录是否过期");
        })
        .finally(() => {
          this.$modal.closeLoading();
        });
    },
    /**
     * èŽ·å–å¦å­˜ä¸ºçš„æ–‡ä»¶åã€‚
     *
     * ä¼˜å…ˆçº§ï¼šå¸¦æ‰©å±•名的原始文件名 > æ–‡ä»¶åœ°å€æœ«æ®µ > æ–‡æ¡£æ ‡é¢˜ã€‚
     * ä¹‹æ‰€ä»¥ä¸ç›´æŽ¥ç”¨ form.filename:该字段在表单中可手工编辑(如“人体器官移植条例”),
     * ä¸å¸¦æ‰©å±•名,直接用作另存名会导致系统无法识别打开方式。
     *
     * @param {string} storedName åŽŸå§‹æ–‡ä»¶åï¼ˆå¦‚ form.filename ä¸­å­˜çš„ xxx.docx)
     * @param {string} fileUrl æ–‡ä»¶åœ°å€
     * @param {string} title å¤‡ç”¨åç§°ï¼ˆæ–‡æ¡£æ ‡é¢˜ï¼‰
     */
    getFileName(storedName, fileUrl, title) {
      const hasExt = (name) => !!name && name.lastIndexOf(".") > -1;
      if (hasExt(storedName)) {
        return storedName;
      }
      const source = fileUrl || "";
      if (source.lastIndexOf("/") > -1) {
        const fromUrl = source.slice(source.lastIndexOf("/") + 1);
        if (fromUrl) {
          return fromUrl;
        }
      }
      return title || storedName || "下载文件";
    },
    //文件上传成功回调
@@ -698,7 +764,8 @@
        this.$modal.msgSuccess(response.msg);
        this.fileList.push({ name: response.fileName, url: response.fileName });
      } else {
        console.log(response.msg);;
        // 2026-10-09:上传失败必须提示。原来只 console.log,用户看不到失败原因(表现为"点了没反应")
        this.$modal.msgError(response.msg || "上传失败,请重试");
      }
      //console.log("上传文件后:" + JSON.stringify(this.fileList));
    },
@@ -716,9 +783,10 @@
    handleExceed() {
      this.$message.error(`上传文件数量已超过!`);
    },
    // ä¸Šä¼ å¤±è´¥
    // ä¸Šä¼ å¤±è´¥ï¼ˆHTTP å±‚错误,如登录过期、网络异常)
    handleUploadError(err) {
      this.$message.error("上传失败, è¯·é‡è¯•" + JSON.stringify(err));
      const msg = (err && (err.msg || err.message)) || "";
      this.$modal.msgError("上传失败,请重试" + (msg ? ":" + msg : ""));
    },
  },
};
src/views/project/donateannex/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
src/views/project/donateannexrequirement/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-row>
        <el-col :span="6">
          <el-form-item label="附件编号" prop="annexno">
src/views/project/donatebaseinfo/index.vue
@@ -3,16 +3,17 @@
    <el-form
      :model="queryParams"
      ref="queryForm"
      class="opo-query-form"
      :inline="true"
      v-show="showSearch"
      label-width="70px"
    >
      <el-row :gutter="8">
        <el-col :span="5">
          <el-form-item label="姓名" prop="name">
          <el-form-item label="捐献者" prop="name">
            <el-input
              v-model="queryParams.name"
              placeholder="请输入姓名"
              placeholder="请输入捐献者姓名"
              clearable
              size="small"
              @keyup.enter.native="handleQuery"
@@ -46,24 +47,25 @@
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="9">
          <el-form-item label="案例时间">
            <el-date-picker
              style="width: 100%"
              v-model="selecttime"
              type="monthrange"
              range-separator="至"
              start-placeholder="开始月份"
              end-placeholder="结束月份"
              value-format="yyyy-MM-dd"
              @change="getTimeList"
       <el-col :span="6">
          <el-form-item label="报告人">
            <el-select
              v-model="queryParams.reporterno"
              placeholder="请选择报告人"
            >
            </el-date-picker>
              <el-option
                v-for="item in reportlist"
                :key="item.index"
                :label="item.reportername"
                :value="item.reporterno"
              >
              </el-option>
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="8">
        <el-col :span="5">
        <el-col :span="6">
          <el-form-item label="捐献进度" prop="workflow">
            <el-select
              v-model="queryParams.workflow"
@@ -80,23 +82,23 @@
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="5">
          <el-form-item label="报告人">
            <el-select
              v-model="queryParams.reporterno"
              placeholder="请选择报告人"
         <el-col :span="9">
          <el-form-item label="案例时间">
            <el-date-picker
              style="width: 100%"
              v-model="selecttime"
              type="monthrange"
              range-separator="至"
              start-placeholder="开始月份"
              end-placeholder="结束月份"
              value-format="yyyy-MM-dd"
              @change="getTimeList"
            >
              <el-option
                v-for="item in reportlist"
                :key="item.index"
                :label="item.reportername"
                :value="item.reporterno"
              >
              </el-option>
            </el-select>
            </el-date-picker>
          </el-form-item>
        </el-col>
        <el-col :span="5">
        <el-col :span="6">
          <el-form-item label="是否终止">
            <el-select
              v-model="queryParams.terminationCase"
@@ -112,19 +114,8 @@
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="9">
          <el-form-item label="籍贯">
            <div>
              <li_area_select
                ref="areaSelect"
                v-model="searchAddress"
              ></li_area_select>
            </div>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="4">
        <!-- æœç´¢/重置紧跟查询条件(与"案例评估"页一致) -->
        <el-col :span="6">
          <el-form-item>
            <el-button
              type="primary"
@@ -206,7 +197,7 @@
        label="捐献编号"
        align="center"
        prop="donorno"
        width="200"
        width="100"
      />
      <el-table-column label="姓名" align="center" prop="name" width="100" />
      <el-table-column label="性别" align="center" prop="sex" width="100">
@@ -233,9 +224,9 @@
      <el-table-column
        label="医疗机构"
        align="center"
        prop="treatmenthospitalname"
        prop="treatmenthospitalname" width="220"
      />
      <el-table-column label="血型" align="center" prop="bloodtype" width="100">
      <el-table-column label="血型" align="center" prop="bloodtype" width="80">
        <template slot-scope="scope">
          <dict-tag
            :options="dict.type.sys_BloodType"
@@ -263,7 +254,7 @@
        prop="reportername"
        width="100"
      />
      <!--
         <el-table-column
        label="现所在地市"
        align="center"
@@ -283,7 +274,7 @@
          />
        </template>
      </el-table-column>
      -->
      <el-table-column
        label="捐献进度"
        align="center"
@@ -304,7 +295,7 @@
        label="操作"
        align="center"
        class-name="small-padding fixed-width"
        fixed="right"
        fixed="right" width="200"
      >
        <template slot-scope="scope">
          <el-button
@@ -863,7 +854,7 @@
                  size="small"
                  style="width: 190px"
                  v-model="form.contacttime"
                  type="datetime"
                  type="datetime" format="yyyy-MM-dd HH:mm"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择报告时间"
                >
@@ -920,7 +911,7 @@
                  size="small"
                  style="width: 190px"
                  v-model="form.reporttime"
                  type="datetime"
                  type="datetime" format="yyyy-MM-dd HH:mm"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择报告时间"
                >
@@ -1663,19 +1654,16 @@
        this.queryParams.reportno = this.reportervalue;
      }
      if (this.starttime != "") {
        this.queryParams.starttime = this.starttime;
      } else {
        this.queryParams.starttime = "";
      }
      if (this.endtime != "") {
        this.queryParams.endtime = this.endtime;
      } else {
        this.queryParams.endtime = "";
      }
      this.queryParams.residenceprovince = this.$refs.areaSelect.getSheng();
      this.queryParams.residencecity = this.$refs.areaSelect.getShi();
      this.queryParams.residencetown = this.$refs.areaSelect.getQu();
      // æœªé€‰æ‹©æ—¶é—´èŒƒå›´æ—¶å¿…须传 null:传空串会让后端拼出 `donatetime >= ''`,
      // MySQL ä¸¥æ ¼æ¨¡å¼ä¼šæŠ¥ Incorrect DATETIME value: '',导致列表查不出来。
      // request.js åªä¼šæ‹¼æŽ¥éž null/undefined çš„参数,所以 null ç­‰äºŽ"不带该条件"。
      this.queryParams.starttime = this.starttime || null;
      this.queryParams.endtime = this.endtime || null;
      // æŸ¥è¯¢æ¡ä»¶é‡Œçš„"籍贯"已移除,不再从 areaSelect ç»„件取值;
      // ç½®ç©ºä»¥æ¸…掉可能残留的旧条件(参数为 null æ—¶ä¸ä¼šè¢«æ‹¼è¿›è¯·æ±‚)
      this.queryParams.residenceprovince = null;
      this.queryParams.residencecity = null;
      this.queryParams.residencetown = null;
      console.log(this.queryParams);
      listDonatebaseinfo(this.queryParams).then(response => {
@@ -2086,6 +2074,7 @@
</script>
<style scoped>
/* æŸ¥è¯¢åŒºè¡Œè·ã€é¡µé¢å†…边距、工具栏间距已提升为全局规则(.opo-query-form / .app-container),此处不再单独覆盖 */
::v-deep .el-dialog__header {
  padding-top: 40px !important;
  margin: auto !important;
src/views/project/donateconsolationfund/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -635,7 +636,7 @@
                clearable
                style="width: 100%"
                v-model="form.createTime"
                type="date"
                type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="申请日期"
              >
src/views/project/donatefinish/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -308,8 +309,8 @@
                clearable
                size="small"
                v-model="form.completetime"
                type="date"
                value-format="yyyy-MM-dd hh:mm:ss"
                type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择完成时间"
              >
              </el-date-picker>
@@ -378,7 +379,7 @@
                    size="small"
                    style="width: 90%"
                    v-model="scope.row.transplanttime"
                    type="date"
                    type="date" format="yyyy-MM-dd"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    placeholder="选择器官移植时间"
                  >
src/views/project/donatefinishcheck/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -250,8 +251,8 @@
                clearable
                size="small"
                v-model="form.completetime"
                type="date"
                value-format="yyyy-MM-dd hh:mm:ss"
                type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择完成时间"
              >
              </el-date-picker>
@@ -320,7 +321,7 @@
                    size="small"
                    style="width: 90%"
                    v-model="scope.row.transplanttime"
                    type="date"
                    type="date" format="yyyy-MM-dd"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    placeholder="选择器官获取时间"
                  >
@@ -897,16 +898,10 @@
      // this.queryParams.residenceprovince = this.$refs.areaSelect.getSheng();
      // this.queryParams.residencecity = this.$refs.areaSelect.getShi();
      // this.queryParams.residencetown = this.$refs.areaSelect.getQu();
       if (this.starttime != "") {
        this.queryParams.starttime = this.starttime;
      } else {
        this.queryParams.starttime = "";
      }
      if (this.endtime != "") {
        this.queryParams.endtime = this.endtime;
      } else {
        this.queryParams.endtime = "";
      }
      // æœªé€‰æ—¶é—´èŒƒå›´æ—¶ä¼  null:空串会让后端拼出 `donatetime >= ''`,
      // MySQL ä¸¥æ ¼æ¨¡å¼ä¼šæŠ¥ Incorrect DATETIME value: ''(见 CHANGELOG 2026-10-22)
      this.queryParams.starttime = this.starttime || null;
      this.queryParams.endtime = this.endtime || null;
      // this.queryParams.residenceprovince = this.$refs.areaSelect.getSheng();
      // this.queryParams.residencecity = this.$refs.areaSelect.getShi();
      // this.queryParams.residencetown = this.$refs.areaSelect.getQu();
src/views/project/donatefollowup/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -339,8 +340,8 @@
                clearable
                size="small"
                v-model="form.followuptime"
                type="date"
                value-format="yyyy-MM-dd hh:mm:ss"
                type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择随访时间"
              >
              </el-date-picker>
src/views/project/donateorgan/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="70px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="70px">
      <el-row>
        <el-col :span="6">
          <el-form-item label="器官名称" prop="organno">
@@ -360,7 +361,7 @@
        <el-row>
          <el-col :span="12">
            <el-form-item label="获取时间" prop="organgettime">
              <el-date-picker clearable size="medium" v-model="form.organgettime" type="date" disabled
              <el-date-picker clearable size="medium" v-model="form.organgettime" type="date" format="yyyy-MM-dd" disabled
                value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择器官获取时间">
              </el-date-picker>
            </el-form-item>
@@ -410,7 +411,7 @@
          </el-col>
          <el-col :span="12">
            <el-form-item label="移植时间" prop="transplanttime">
              <el-date-picker clearable size="medium" v-model="form.transplanttime" type="date"
              <el-date-picker clearable size="medium" v-model="form.transplanttime" type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择移植时间" disabled>
              </el-date-picker>
            </el-form-item>
src/views/project/donateorganstatics/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
src/views/project/donatereview/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -395,9 +396,9 @@
                clearable
                size="small"
                v-model="form.birthday"
                type="date"
                type="date" format="yyyy-MM-dd"
                style="width: 174px"
                value-format="yyyy:MM:dd HH:mm:ss"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择出生日期"
              >
              </el-date-picker>
@@ -756,8 +757,8 @@
                clearable
                size="small"
                v-model="form.reporttime"
                type="date"
                value-format="yyyy-MM-dd  HH:mm:ss"
                type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择报告时间"
              >
              </el-date-picker>
@@ -1115,16 +1116,10 @@
      // this.queryParams.residenceprovince = this.$refs.areaSelect.getSheng();
      // this.queryParams.residencecity = this.$refs.areaSelect.getShi();
      // this.queryParams.residencetown = this.$refs.areaSelect.getQu();
       if (this.starttime != "") {
        this.queryParams.starttime = this.starttime;
      } else {
        this.queryParams.starttime = "";
      }
      if (this.endtime != "") {
        this.queryParams.endtime = this.endtime;
      } else {
        this.queryParams.endtime = "";
      }
      // æœªé€‰æ—¶é—´èŒƒå›´æ—¶ä¼  null:空串会让后端拼出 `donatetime >= ''`,
      // MySQL ä¸¥æ ¼æ¨¡å¼ä¼šæŠ¥ Incorrect DATETIME value: ''(见 CHANGELOG 2026-10-22)
      this.queryParams.starttime = this.starttime || null;
      this.queryParams.endtime = this.endtime || null;
      listDonatebaseinfo(this.queryParams).then((response) => {
        console.log("getlist", response.rows);
        this.donatebaseinfoList = response.rows;
src/views/project/donationdetails/index.vue
@@ -3,7 +3,7 @@
    <div class="left-course" :class="{ 'left-collapsed': isLeftCollapsed }">
      <div class="postfilx">
        <div class="title">
          å·¥ä½œè¿›ç¨‹
          æ¡ˆä¾‹è¿›ç¨‹
          <el-button
            class="collapse-btn"
            @click="toggleLeftCollapse"
@@ -221,7 +221,7 @@
                    clearable
                    size="small"
                    v-model="form.reporttime"
                    type="datetime"
                    type="datetime" format="yyyy-MM-dd HH:mm"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    placeholder="选择报告日期"
                  >
@@ -267,7 +267,7 @@
                    clearable
                    size="small"
                    v-model="form.birthday"
                    type="date"
                    type="date" format="yyyy-MM-dd"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    placeholder="选择出生日期"
                  >
@@ -779,7 +779,7 @@
                  size="small"
                  style="width: 190px"
                  v-model="form.contacttime"
                  type="datetime"
                  type="datetime" format="yyyy-MM-dd HH:mm"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择报告时间"
                >
@@ -862,7 +862,7 @@
                  clearable
                  size="small"
                  v-model="medicineform.hospitalassesstime"
                  type="date"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择院级评估时间"
                >
@@ -915,7 +915,7 @@
                  clearable
                  size="small"
                  v-model="medicineform.provincialassesstime"
                  type="date"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择省级评估时间"
                >
@@ -969,7 +969,7 @@
                  clearable
                  size="small"
                  v-model="medicineform.coreteamassesstime"
                  type="date"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="评估时间"
                >
@@ -1388,7 +1388,7 @@
                  clearable
                  size="small"
                  v-model="ethicform.conclusiontime"
                  type="date"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择结论时间"
                >
@@ -1478,7 +1478,7 @@
                        size="small"
                        style="width: 100%"
                        v-model="scope.row.applicanttime"
                        type="datetime"
                        type="datetime" format="yyyy-MM-dd HH:mm"
                        value-format="yyyy-MM-dd HH:mm:ss"
                        placeholder="选择分配接收时间"
                      >
@@ -1658,7 +1658,7 @@
                <el-date-picker
                  clearable
                  v-model="witnessform.deathtime"
                  type="datetime"
                  type="datetime" format="yyyy-MM-dd HH:mm"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择死亡时间"
                >
@@ -1697,7 +1697,7 @@
                <el-date-picker
                  clearable
                  v-model="witnessform.operationbegtime"
                  type="datetime"
                  type="datetime" format="yyyy-MM-dd HH:mm"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择手术开始时间"
                >
@@ -1714,7 +1714,7 @@
                <el-date-picker
                  clearable
                  v-model="witnessform.operationendtime"
                  type="datetime"
                  type="datetime" format="yyyy-MM-dd HH:mm"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择手术结束时间"
                >
@@ -1733,7 +1733,7 @@
                <el-date-picker
                  clearable
                  v-model="witnessform.coordinatorInOperating"
                  type="datetime"
                  type="datetime" format="yyyy-MM-dd HH:mm"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择手术开始时间"
                >
@@ -1750,7 +1750,7 @@
                <el-date-picker
                  clearable
                  v-model="witnessform.coordinatorOutOperating"
                  type="datetime"
                  type="datetime" format="yyyy-MM-dd HH:mm"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择手术结束时间"
                >
@@ -1767,7 +1767,7 @@
                <el-date-picker
                  clearable
                  v-model="witnessform.coordinatorSignTime"
                  type="datetime"
                  type="datetime" format="yyyy-MM-dd HH:mm"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择手术结束时间"
                >
@@ -1794,7 +1794,7 @@
                  <el-date-picker
                    clearable
                    v-model="witnessform.abdominalaortacannulatime"
                    type="datetime"
                    type="datetime" format="yyyy-MM-dd HH:mm"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    placeholder="选择腹主动脉插管时间"
                  >
@@ -1811,7 +1811,7 @@
                  <el-date-picker
                    clearable
                    v-model="witnessform.abdominalaortaperfusiontime"
                    type="datetime"
                    type="datetime" format="yyyy-MM-dd HH:mm"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    placeholder="选择腹主动脉灌注时间"
                  >
@@ -1828,7 +1828,7 @@
                  <el-date-picker
                    clearable
                    v-model="witnessform.portalveincannulatime"
                    type="datetime"
                    type="datetime" format="yyyy-MM-dd HH:mm"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    placeholder="选择门静脉脉插管时间"
                  >
@@ -1845,7 +1845,7 @@
                  <el-date-picker
                    clearable
                    v-model="witnessform.portalveinperfusiontime"
                    type="datetime"
                    type="datetime" format="yyyy-MM-dd HH:mm"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    placeholder="选择门静脉灌注时间"
                  >
@@ -1864,7 +1864,7 @@
                  <el-date-picker
                    clearable
                    v-model="witnessform.pulmonaryarterycannulatime"
                    type="datetime"
                    type="datetime" format="yyyy-MM-dd HH:mm"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    placeholder="选择门静脉插管时间"
                  >
@@ -1881,7 +1881,7 @@
                  <el-date-picker
                    clearable
                    v-model="witnessform.pulmonaryarteryperfusiontime"
                    type="datetime"
                    type="datetime" format="yyyy-MM-dd HH:mm"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    placeholder="选择门静脉灌注时间"
                  >
@@ -1898,8 +1898,8 @@
                  <el-date-picker
                    clearable
                    v-model="witnessform.aortacannulatime"
                    type="datetime"
                    value-format="yyyy-MM-dd  HH:mm:ss"
                    type="datetime" format="yyyy-MM-dd HH:mm"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    placeholder="选择主动脉插管时间"
                  >
                  </el-date-picker>
@@ -1915,8 +1915,8 @@
                  <el-date-picker
                    clearable
                    v-model="witnessform.aortaperfusiontime"
                    type="datetime"
                    value-format="yyyy-MM-dd  HH:mm:ss"
                    type="datetime" format="yyyy-MM-dd HH:mm"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    placeholder="选择主动脉灌注时间"
                  >
                  </el-date-picker>
@@ -2028,7 +2028,7 @@
                        size="small"
                        style="width: 100%"
                        v-model="scope.row.organStartTime"
                        type="datetime"
                        type="datetime" format="yyyy-MM-dd HH:mm"
                        value-format="yyyy-MM-dd HH:mm:ss"
                        placeholder="请输入获取开始时间"
                      >
@@ -2048,7 +2048,7 @@
                        size="small"
                        style="width: 100%"
                        v-model="scope.row.organgettime"
                        type="datetime"
                        type="datetime" format="yyyy-MM-dd HH:mm"
                        value-format="yyyy-MM-dd HH:mm:ss"
                        placeholder="请输入器官离体时间"
                      >
@@ -2450,8 +2450,8 @@
                  clearable
                  size="small"
                  v-model="accomplishform.completetime"
                  type="date"
                  value-format="yyyy-MM-dd hh:mm:ss"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择完成时间"
                >
                </el-date-picker>
@@ -3485,7 +3485,12 @@
      this.isLeftCollapsed = !this.isLeftCollapsed;
    },
    /** ä¿å­˜ä¸»è¡¨æŒ‰é’® */
    submitForm() {
    submitForm(fromProcess) {
      // ä¿å­˜é˜²é‡ï¼šéž"流程推进"触发的保存,2 ç§’内的重复点击直接忽略(防止重复插入)
      if (!fromProcess && !this.canSave()) {
        this.$modal.msgWarning("请勿重复提交");
        return;
      }
      this.$refs["form"].validate(valid => {
        console.log("提交的数据们:", this.form);
        if (valid) {
@@ -3495,14 +3500,21 @@
            "YYYY-MM-DD HH:mm:ss"
          );
          this.form.diseasetype = this.form.diseasetype.join(",");
          this.form.infectious = this.form.infectious.join(",");
          this.form.selfwill = this.form.selfwill.join(",");
          this.form.othercases = this.form.othercases.join(",");
          this.form.infosources = this.form.infosources.join(",");
          this.form.kinship = this.form.kinship.join(",");
          this.form.patientstate = this.form.patientstate.join(",");
          // å¤šé€‰å­—段转字符串:幂等处理(已是字符串时保持原样),
          // é¿å…"保存成功后未重新加载再次保存"时抛 join is not a function
          [
            "diseasetype",
            "infectious",
            "selfwill",
            "othercases",
            "infosources",
            "kinship",
            "patientstate"
          ].forEach(key => {
            if (Array.isArray(this.form[key])) {
              this.form[key] = this.form[key].join(",");
            }
          });
          this.form.registerprovince = this.$refs.registerSelect.getSheng();
          this.form.registerprovincename = this.registerAddresss.sheng;
@@ -3584,6 +3596,11 @@
    },
    // ä¿å­˜äºŒçº§è¡¨æŒ‰é’®
    Savethedetails() {
      // ä¿å­˜é˜²é‡ï¼š2 ç§’内的重复点击直接忽略(防止重复插入)
      if (!this.canSave()) {
        this.$modal.msgWarning("请勿重复提交");
        return;
      }
      console.log(this.actives);
      if (this.actives == 1) {
        this.medicineform.infoid = this.infoid;
@@ -3594,14 +3611,19 @@
              updateMedicalevaluation(this.medicineform).then(response => {
                this.$modal.msgSuccess("医学评估信息修改成功");
                this.$modal.closeLoading();
                this.Processvalidation();
              });
            } else {
              addMedicalevaluation(this.medicineform).then(response => {
                // å›žå¡«ä¸»é”®ï¼šå¦åˆ™ä¸‹æ¬¡ä¿å­˜ä»èµ°"新增",会产生重复行
                if (response && response.data && response.data.id) {
                  this.medicineform.id = response.data.id;
                }
                this.$modal.msgSuccess("医学评估信息新增成功");
                this.$modal.closeLoading();
                this.Processvalidation();
              });
            }
            this.Processvalidation();
          }
        });
      } else if (this.actives == 2) {
@@ -3621,17 +3643,22 @@
              updateRelativesconfirmation(this.affirmform).then(response => {
                this.$modal.msgSuccess("捐献确认信息修改成功");
                this.$modal.closeLoading();
                this.Processvalidation();
              });
            } else {
              this.affirmform.residenceprovincename = this.defultAddresss.sheng;
              this.affirmform.residencecityname = this.defultAddresss.shi;
              this.affirmform.residencetownname = this.defultAddresss.qu;
              addRelativesconfirmation(this.affirmform).then(response => {
                // å›žå¡«ä¸»é”®ï¼šå¦åˆ™ä¸‹æ¬¡ä¿å­˜ä»èµ°"新增",会产生重复行
                if (response && response.data && response.data.id) {
                  this.affirmform.id = response.data.id;
                }
                this.$modal.msgSuccess("捐献确认信息新增成功");
                this.$modal.closeLoading();
                this.Processvalidation();
              });
            }
            this.Processvalidation();
          }
        });
      } else if (this.actives == 3) {
@@ -3644,15 +3671,20 @@
              updateEthicalreviewopinions(this.ethicform).then(res => {
                this.$modal.msgSuccess("伦理审查信息修改成功");
                this.$modal.closeLoading();
                this.Processvalidation();
              });
            } else {
              //addDonateflowchart(this.ethicform).then(response => {
              addEthicalreviewopinions(this.ethicform).then(res => {
                // å›žå¡«ä¸»é”®ï¼šå¦åˆ™ä¸‹æ¬¡ä¿å­˜ä»èµ°"新增",会产生重复行
                if (res && res.data && res.data.id) {
                  this.ethicform.id = res.data.id;
                }
                this.$modal.msgSuccess("伦理审查信息新增成功");
                this.$modal.closeLoading();
                this.Processvalidation();
              });
            }
            this.Processvalidation();
          }
        });
      } else if (this.actives == 4) {
@@ -3672,6 +3704,12 @@
            }
          });
          saveDonateorgan(this.allocateddata).then(res => {
            // å›žå¡«ä¸»é”®ï¼šåŽç«¯å·²æŒ‰"同一案例同一器官"做 upsert,回填后前端不会再误判为新增
            if (res && res.data && res.data.length === this.allocateddata.length) {
              res.data.forEach((row, i) => {
                this.allocateddata[i].id = row.id;
              });
            }
            if (res.code == 200) {
              this.$modal.msgSuccess("器官捐献分配信息保存成功!");
              this.$modal.closeLoading();
@@ -3720,7 +3758,12 @@
                });
                //保存器官获取表数据
                saveDonationwitnessorgan(this.procureddata).then(res => {
                  if (res == 200) {
                  if (res && res.data && res.data.length === this.procureddata.length) {
                    res.data.forEach((row, i) => {
                      this.procureddata[i].id = row.id;
                    });
                  }
                  if (res.code == 200) {
                    this.$modal.msgSuccess("捐献器官获取信息保存成功!");
                  }
                  this.$modal.closeLoading();
@@ -3729,6 +3772,10 @@
            } else {
              addDonationwitness(this.witnessform).then(response => {
                if (response.code === 200) {
                  // å›žå¡«ä¸»é”®ï¼šå¦åˆ™ä¸‹æ¬¡ä¿å­˜ä»èµ°"新增"
                  if (response.data && response.data.id) {
                    this.witnessform.id = response.data.id;
                  }
                  this.$modal.msgSuccess("获取见证信息新增成功");
                  this.form.donationcategory = this.witnessform.donationcategory;
                  let data = JSON.parse(JSON.stringify(this.form));
@@ -3747,7 +3794,12 @@
                  });
                  //保存器官获取表数据
                  saveDonationwitnessorgan(this.procureddata).then(res => {
                    if (res == 200) {
                    if (res && res.data && res.data.length === this.procureddata.length) {
                      res.data.forEach((row, i) => {
                        this.procureddata[i].id = row.id;
                      });
                    }
                    if (res.code == 200) {
                      this.$modal.msgSuccess("捐献器官获取信息保存成功!");
                    }
                    this.$modal.closeLoading();
@@ -3782,7 +3834,12 @@
              updateDonatecompletioninfo(this.accomplishform).then(response => {
                this.$modal.msgSuccess("完成登记信息修改成功");
                saveDonatecomporgan(this.transplantdata).then(res => {
                  if (res == 200) {
                  if (res && res.data && res.data.length === this.transplantdata.length) {
                    res.data.forEach((row, i) => {
                      this.transplantdata[i].id = row.id;
                    });
                  }
                  if (res.code == 200) {
                    this.$modal.msgSuccess("捐献器官登记信息保存成功!");
                  } else {
                    this.$modal.msgSuccess(res.msg);
@@ -3792,8 +3849,17 @@
              });
            } else {
              addDonatecompletioninfo(this.accomplishform).then(response => {
                // å›žå¡«ä¸»é”®ï¼šå¦åˆ™ä¸‹æ¬¡ä¿å­˜ä»èµ°"新增"
                if (response && response.data && response.data.id) {
                  this.accomplishform.id = response.data.id;
                }
                this.$modal.msgSuccess("完成登记成功");
                saveDonatecomporgan(this.transplantdata).then(res => {
                  if (res && res.data && res.data.length === this.transplantdata.length) {
                    res.data.forEach((row, i) => {
                      this.transplantdata[i].id = row.id;
                    });
                  }
                  if (res.code == 200) {
                    this.$modal.msgSuccess("捐献器官登记信息保存成功!");
                  } else {
@@ -4130,6 +4196,15 @@
        console.log("已到达最大值");
      }
    },
    // ä¿å­˜é˜²é‡ï¼š2 ç§’内重复点击视为重复提交
    canSave() {
      const now = Date.now();
      if (this.saveLockAt && now - this.saveLockAt < 2000) {
        return false;
      }
      this.saveLockAt = now;
      return true;
    },
    // æ›´æ”¹è¿›ç¨‹æ•ˆéªŒ
    Processvalidation() {
      if (this.form.workflow + 1 == this.actives) {
@@ -4137,15 +4212,12 @@
          this.form.workflow++;
        } else {
        }
        this.submitForm();
        // this.GetAttacheddata();
        // ç”±"保存二级表"触发的保存,不参与防重(否则会被 canSave æ‹¦æŽ‰ï¼‰
        this.submitForm(true);
      } else if (this.form.workflow > this.actives) {
        this.submitForm();
        // Modifydonationattachment(this.donatelist).then(res => {});
        // this.GetAttacheddata();
        this.submitForm(true);
      } else {
        this.submitForm();
        // this.$modal.msgWarning("请先完成前部步骤");
        this.submitForm(true);
        return;
      }
    },
@@ -4462,21 +4534,21 @@
<style lang="scss" scoped>
.particularsdiv {
  display: flex;
  background-color: #f5f7fa;
  background-color: var(--opo-bg-page, #f5f7fa);
  height: 100vh;
  overflow: hidden;
  transition: all 0.3s ease;
  // å·¦ä¾§å·¥ä½œè¿›ç¨‹æ 
  // å·¦ä¾§å·¥ä½œè¿›ç¨‹æ ï¼ˆ2026-10-22:去掉底色,只留一条分隔线;标题文字改品牌青)
  .left-course {
    background: #fff;
    background: transparent;
    width: 220px;
    min-width: 220px;
    text-align: center;
    margin: 0;
    padding: 0;
    height: 100%;
    box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
    border-right: 1px solid var(--opo-border-base, #e5e7eb);
    transition: all 0.3s ease;
    position: relative;
    z-index: 100;
@@ -4511,10 +4583,10 @@
      flex-direction: column;
      .title {
        background: #22a2c3;
        background: transparent;
        margin-bottom: 0;
        padding: 15px 10px;
        color: #fff;
        color: var(--opo-primary, #0e768f);
        font-size: 16px;
        font-weight: 600;
        display: flex;
@@ -4524,11 +4596,13 @@
        transition: all 0.3s ease;
        min-height: 54px; // ç¡®ä¿æ ‡é¢˜åŒºåŸŸæœ‰å›ºå®šé«˜åº¦
        box-sizing: border-box;
        border-bottom: 1px solid var(--opo-border-base, #e5e7eb);
        .collapse-btn {
          background: rgba(255, 255, 255, 0.2);
          border: none;
          color: #fff;
          // æ ‡é¢˜åº•色去掉后,按钮改为浅青描边风格,避免白底白字看不见
          background: var(--opo-primary-light-9, #e7f1f4);
          border: 1px solid var(--opo-primary-light-7, #b7d6dd);
          color: var(--opo-primary, #0e768f);
          width: 32px; // ç¨å¾®å¢žå¤§ç¡®ä¿å¯ç‚¹å‡»åŒºåŸŸ
          height: 32px;
          padding: 0;
@@ -4540,7 +4614,8 @@
          flex-shrink: 0; // é˜²æ­¢æŒ‰é’®è¢«åŽ‹ç¼©
          &:hover {
            background: rgba(255, 255, 255, 0.3);
            background: var(--opo-primary-light-7, #b7d6dd);
            border-color: var(--opo-primary, #0e768f);
            transform: scale(1.1);
          }
@@ -4608,7 +4683,7 @@
  }
}
// æ­¥éª¤æ ·å¼ä¼˜åŒ–
// æ­¥éª¤æ ·å¼ä¼˜åŒ–(配色统一到系统品牌色:teal #0E768F)
::v-deep .el-steps {
  &.el-steps--vertical {
    height: 100%;
@@ -4618,27 +4693,28 @@
      .el-step__head {
        &.is-finish {
          color: #22a2c3;
          border-color: #22a2c3;
          color: var(--opo-primary, #0e768f);
          border-color: var(--opo-primary, #0e768f);
        }
      }
      .el-step__title {
        font-size: 14px;
        font-size: var(--opo-font-size-sm);
        font-weight: 600;
        &.is-finish {
          color: #22a2c3;
          color: var(--opo-primary, #0e768f);
        }
      }
      .el-step__description {
        font-size: 12px;
        color: #666;
        font-size: var(--opo-font-size-xs);
        // æè¿°æ–‡å­—统一用中性灰,避免整块面板被品牌色淹没
        color: var(--opo-text-secondary, #6b7280);
        line-height: 1.5;
        &.is-finish {
          color: #22a2c3;
          color: var(--opo-text-regular, #4b5563);
        }
        p {
src/views/project/donationwitness/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -467,7 +468,7 @@
              <el-date-picker
                clearable
                v-model="form.deathtime"
                type="datetime"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择死亡时间"
              >
@@ -492,7 +493,7 @@
              <el-date-picker
                clearable
                v-model="form.operationbegtime"
                type="datetime"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择手术开始时间"
              >
@@ -504,7 +505,7 @@
              <el-date-picker
                clearable
                v-model="form.operationendtime"
                type="datetime"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择手术结束时间"
              >
@@ -525,7 +526,7 @@
              <el-date-picker
                clearable
                v-model="form.abdominalaortacannulatime"
                type="datetime"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择腹主动脉插管时间"
              >
@@ -541,7 +542,7 @@
              <el-date-picker
                clearable
                v-model="form.abdominalaortaperfusiontime"
                type="datetime"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择腹主动脉灌注时间"
              >
@@ -563,7 +564,7 @@
              <el-date-picker
                clearable
                v-model="form.portalveincannulatime"
                type="datetime"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择门静脉脉插管时间"
              >
@@ -579,7 +580,7 @@
              <el-date-picker
                clearable
                v-model="form.portalveinperfusiontime"
                type="datetime"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择门静脉灌注时间"
              >
@@ -600,7 +601,7 @@
              <el-date-picker
                clearable
                v-model="form.pulmonaryarterycannulatime"
                type="datetime"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择门静脉插管时间"
              >
@@ -616,7 +617,7 @@
              <el-date-picker
                clearable
                v-model="form.pulmonaryarteryperfusiontime"
                type="datetime"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择门静脉灌注时间"
              >
@@ -634,8 +635,8 @@
              <el-date-picker
                clearable
                v-model="form.aortacannulatime"
                type="datetime"
                value-format="yyyy-MM-dd  HH:mm:ss"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择主动脉灌注时间"
              >
              </el-date-picker>
@@ -650,8 +651,8 @@
              <el-date-picker
                clearable
                v-model="form.aortaperfusiontime"
                type="datetime"
                value-format="yyyy-MM-dd  HH:mm:ss"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择主动脉灌注时间"
              >
              </el-date-picker>
@@ -707,7 +708,7 @@
                  size="small"
                  style="width: 100%"
                  v-model="scope.row.organgettime"
                  type="datetime"
                  type="datetime" format="yyyy-MM-dd HH:mm"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择器官获取时间"
                >
src/views/project/donorcharge/donorcharge/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="120px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="120px">
      <el-form-item label="捐献姓名" prop="name">
        <el-input v-model="queryParams.name" placeholder="请输入姓名" clearable size="small"
          @keyup.enter.native="handleQuery" />
@@ -152,7 +153,7 @@
            </el-table-column>
            <el-table-column prop="amounttime" align="center" label="接收时间" width="120">
              <template slot-scope="scope">
                <el-date-picker clearable size="small" style="width: 100%" v-model="scope.row.amounttime" type="date"
                <el-date-picker clearable size="small" style="width: 100%" v-model="scope.row.amounttime" type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss" placeholder="接收时间">
                </el-date-picker> </template>
            </el-table-column>
@@ -163,7 +164,7 @@
            </el-table-column>
            <el-table-column prop="amounttime" align="center" label="收款时间" width="120">
              <template slot-scope="scope">
                <el-date-picker clearable size="small" style="width: 100%" v-model="scope.row.amounttime" type="date"
                <el-date-picker clearable size="small" style="width: 100%" v-model="scope.row.amounttime" 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>
src/views/project/donorcharge/donorchargedetail/index.vue
@@ -179,7 +179,7 @@
                  size="small"
                  style="width: 100%"
                  v-model="scope.row.amounttime"
                  type="date"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="实收时间"
                >
@@ -242,7 +242,7 @@
                  size="small"
                  style="width: 100%"
                  v-model="scope.row.organtime"
                  type="date"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="移植时间"
                >
@@ -294,6 +294,7 @@
import Li_area_select from "@/components/Address";
import { getUserProfile } from "@/api/system/user";
import { getToken } from "@/utils/auth";
import { moneySum, moneyRound } from "@/utils/money";
import debounce from "lodash/debounce";
export default {
  components: {
@@ -667,27 +668,23 @@
    submitForm: debounce(function(data) {
      this.$refs["form"].validate(valid => {
        if (valid) {
          //校验、费用合计
          let ChargeSum = 0;
          let ChargeamountSum = 0;
          //校验、费用合计(金额一律走精确运算)
          let organname = "";
          try {
            for (let i = 0; i < this.donorchargeorgans.length; i++) {
              organname += this.donorchargeorgans[i].organname + ",";
              if (!isNaN(parseFloat(this.donorchargeorgans[i].organcharge))) {
                ChargeSum += parseFloat(this.donorchargeorgans[i].organcharge);
              }
              if (!isNaN(parseFloat(this.donorchargeorgans[i].amount))) {
                ChargeamountSum += parseFloat(this.donorchargeorgans[i].amount);
              }
            }
            this.form.donateorgan = organname.substring(
              0,
              organname.lastIndexOf(",")
            );
            this.form.chargeamount = ChargeSum.toFixed(2);
            this.form.chargeamounted = ChargeamountSum.toFixed(2);
            this.form.chargeamount = moneyRound(
              moneySum(this.donorchargeorgans, "organcharge")
            ).toFixed(2);
            this.form.chargeamounted = moneyRound(
              moneySum(this.donorchargeorgans, "amount")
            ).toFixed(2);
          } catch {}
          //保存
@@ -728,24 +725,16 @@
      });
    }, 500),
    //计算费用合计
    //计算费用合计(金额一律走精确运算)
    chargeSum() {
      let ChargeSum = 0;
      let ChargeamountSum = 0;
      //费用合计
      try {
        for (let i = 0; i < this.donorchargeorgans.length; i++) {
          if (!isNaN(parseFloat(this.donorchargeorgans[i].organcharge))) {
            ChargeSum += parseFloat(this.donorchargeorgans[i].organcharge);
          }
          if (!isNaN(parseFloat(this.donorchargeorgans[i].amount))) {
            ChargeamountSum += parseFloat(this.donorchargeorgans[i].amount);
          }
        }
        this.form.chargeamount = ChargeSum.toFixed(2);
        this.form.chargeamounted = ChargeamountSum.toFixed(2);
        this.form.chargeamount = moneyRound(
          moneySum(this.donorchargeorgans, "organcharge")
        ).toFixed(2);
        this.form.chargeamounted = moneyRound(
          moneySum(this.donorchargeorgans, "amount")
        ).toFixed(2);
      } catch {}
    },
src/views/project/donorcharge/organcharge/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="器官编码" prop="organcode">
        <el-input
          v-model="queryParams.organcode"
src/views/project/ethicalReview/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -292,7 +293,7 @@
                clearable
                size="small"
                v-model="form.createTime"
                type="date"
                type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择结论时间"
              >
src/views/project/externalperson/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="80px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="80px">
      <el-form-item label="账号类别" prop="usertype">
        <el-select v-model="queryParams.usertype" placeholder="请选择账号类别" clearable size="small">
@@ -47,9 +48,10 @@
    <el-table v-loading="loading" :data="externalpersonList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="账号类别" align="center" prop="usertype">
      <el-table-column label="账号类别" align="center" prop="usertype" width="220">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.sys_UserType" :value="scope.row.usertype" />
          <dict-tag v-for="t in splitUserType(scope.row.usertype)" :key="t"
            :options="dict.type.sys_UserType" :value="t" />
        </template>
      </el-table-column>
      <el-table-column label="账号编号" align="center" prop="userno" />
@@ -90,7 +92,7 @@
      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
        <el-row>
          <el-col :span="12"><el-form-item label="账号类别" prop="usertype">
              <el-select v-model="form.usertype" placeholder="请选择账号类别">
              <el-select v-model="form.usertype" multiple collapse-tags placeholder="请选择账号类别(可多选)">
                <el-option v-for="dict in dict.type.sys_UserType" :key="dict.value" :label="dict.label"
                  :value="dict.value"></el-option>
              </el-select>
@@ -109,8 +111,8 @@
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="启用状态" prop="userstatus" :disabled="ismanager == false">
              <el-radio-group v-model="form.userstatus">
            <el-form-item label="启用状态" prop="userstatus" >
              <el-radio-group v-model="form.userstatus" :disabled="!ismanager">
                <el-radio v-for="dict in dict.type.sys_0_1" :key="dict.value" :label="parseInt(dict.value)">{{
                  dict.label
                }}</el-radio>
@@ -118,14 +120,14 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-row v-if="form.usertype != 'org'">
        <el-row v-if="!isUnitForm">
          <el-col :span="24">
            <el-form-item label="身份证号" prop="idcardno">
              <el-input v-model="form.idcardno" placeholder="请输入身份证号" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row v-if="form.usertype != 'org'"><el-col :span="12">
        <el-row v-if="!isUnitForm"><el-col :span="12">
            <el-form-item label="性别" prop="sex">
              <el-select v-model="form.sex" placeholder="请选择性别">
                <el-option v-for="dict in dict.type.sys_user_sex" :key="dict.value" :label="dict.label"
@@ -140,7 +142,7 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-row v-if="form.usertype != 'org'">
        <el-row v-if="!isUnitForm">
          <el-col :span="12">
            <el-form-item label="所在单位" prop="unitname">
              <el-select v-model="form.unitno" placeholder="所在单位" clearable filterable allow-create style="width: 100%">
@@ -253,6 +255,9 @@
        usertype: null
      },
      //是否是专家费的OPO审批人员
      ismanager: false,
      // æŸ¥è¯¢æœ€æ–°åºå·å‚æ•°
      seqnoParams: {
        businessType: "externalperson",
@@ -269,7 +274,19 @@
          { required: true, message: "请输入账户名称", trigger: "blur" }
        ],
        idcardno: [
          { required: true, message: "请输入身份证号码", trigger: "blur" }
          {
            //账号类别为"单位机构"时无需身份证号
            validator: (rule, value, callback) => {
              if (this.hasUserType(this.form.usertype, "org")) {
                return callback();
              }
              if (!value || !String(value).trim()) {
                return callback(new Error("请输入身份证号码"));
              }
              return callback();
            },
            trigger: "blur"
          }
        ],
        bankcardno: [
          { required: true, message: "请输入银行账号", trigger: "blur" }
@@ -277,10 +294,14 @@
        depositbank: [
          { required: true, message: "请输入开户银行", trigger: "blur" }
        ]
      },
      //是否是专家费的OPO审批人员
      ismanager: false
      },
    };
  },
  computed: {
    /** è¡¨å•是否选择了"单位机构"(账号类别为多值,含 org å³ä¸ºå•位) */
    isUnitForm() {
      return this.hasUserType(this.form.usertype, "org");
    }
  },
  created() {
    this.getUserProfile();
@@ -291,17 +312,32 @@
  methods: {
    //获取当前用户信息
    getUserProfile() {
      getUserProfile().then(res => {
        if (
          res.data.userName == "admin" ||
          res.data.userName == "053" ||
          res.data.userName == "047"
        ) {
          this.ismanager = true;
        } else {
          this.ismanager = false;
        }
      getUserProfile().then(res => {
        const managerList = ["admin", "048", "047"];
        this.ismanager = managerList.includes(res.data.userName);
      });
    },
    /** äººå‘˜ç±»åž‹ï¼ˆå¤šå€¼ï¼Œé€—号分隔)转数组 */
    splitUserType(usertype) {
      if (!usertype) {
        return [];
      }
      if (Array.isArray(usertype)) {
        return usertype;
      }
      return String(usertype)
        .split(",")
        .map(t => t.trim())
        .filter(t => t);
    },
    /** äººå‘˜ç±»åž‹æ•°ç»„转逗号分隔字符串 */
    joinUserType(usertype) {
      return Array.isArray(usertype) ? usertype.join(",") : usertype;
    },
    /** äººå‘˜ç±»åž‹æ˜¯å¦åŒ…含指定类型(如 org = å•位机构) */
    hasUserType(usertype, code) {
      return this.splitUserType(usertype).indexOf(code) > -1;
    },
    /** æŸ¥è¯¢å•位、人员账号列表 */
@@ -337,7 +373,7 @@
        userno: null,
        username: null,
        userstatus: 0,
        usertype: null,
        usertype: [],
        personnelunitno: null,
        delFlag: null,
        createBy: null,
@@ -389,6 +425,8 @@
      const id = row.id || this.ids;
      getExternalperson(id).then(response => {
        this.form = response.data;
        //账号类别为多值(逗号分隔),表单用数组回显
        this.form.usertype = this.splitUserType(response.data.usertype);
        this.open = true;
        this.title = "人员/单位账号信息修改";
      });
@@ -402,6 +440,8 @@
            if (res.organizationid == this.form.unitno)
              this.form.unitname = res.organizationname;
          });
          //账号类别为多值:提交前转回逗号分隔的字符串
          this.form.usertype = this.joinUserType(this.form.usertype);
          console.log(this.form);
          if (this.form.id != null) {
            updateExternalperson(this.form).then(response => {
@@ -411,15 +451,8 @@
              this.getList();
            });
          } else {
            //检查身份证唯一
            getInfoByIDCardNo(this.form.idcardno).then(res => {
              if (res.data.length > 1) {
                this.$modal.msgWarning("身份证号信息已被登记,请检查身份证号!"
                );
                return;
              }
              //保存信息
            //保存信息(单位账号没有身份证号,跳过身份证查重)
            const saveNew = () => {
              getOnlyCode(this.seqnoParams).then(res => {
                this.form.userno = res.data.currentvalue;
@@ -430,7 +463,21 @@
                  this.getList();
                });
              });
            };
            if (!this.form.idcardno || !String(this.form.idcardno).trim()) {
              saveNew();
              return;
            }
            //检查身份证唯一
            getInfoByIDCardNo(this.form.idcardno).then(res => {
              if (res.data.length > 1) {
                this.$modal.msgWarning("身份证号信息已被登记,请检查身份证号!"
                );
                return;
              }
              saveNew();
            });
          }
        }
src/views/project/financesubject/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="项目类型" prop="itemtype">
        <el-select v-model="queryParams.itemtype" placeholder="请选择项目类型" clearable size="small">
          <el-option v-for="dict in dict.type.sys_ConsolationType" :key="dict.value" :label="dict.label"
src/views/project/fund/applyDetail/index.vue
@@ -12,7 +12,7 @@
          </el-col>
          <el-col :span="5">
            <el-form-item label="申请日期" prop="applyTime">
              <el-date-picker clearable :disabled="true" style="width: 100%" v-model="form.applyTime" type="date"
              <el-date-picker clearable :disabled="true" style="width: 100%" v-model="form.applyTime" type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss" placeholder="日   æœŸ">
              </el-date-picker>
            </el-form-item>
@@ -68,15 +68,7 @@
            <el-form-item label="捐献案例" prop="name">
              <el-input :disabled="true" v-model="form.donorname" placeholder="请输入捐献者姓名" />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <div style="margin-left: 10px;">
              <dict-tag
                :options="dict.type.sys_DonationStatus"
                :value="form.invoicecount"
              />
            </div>
          </el-col>
          </el-col>
          <el-col :span="10">
            <el-form-item label="申请说明" prop="remark">
              <el-input v-model="form.remark" placeholder="申请说明" />
@@ -229,8 +221,9 @@
              v-if="businessType == '1' || businessType == '2'">
              <template slot-scope="scope">
                <el-form-item label-width="0">
                  <el-input :disabled="userprofile.nickName != '陈慕华'" v-model.number="scope.row.idcardno"
                    @input="tableParValueChange1(scope.row)" placeholder="身份证号" />
                  <el-input :disabled="userprofile.nickName != '陈慕华' || scope.row.isunit == 1"
                    :placeholder="scope.row.isunit == 1 ? '单位无需填写' : '身份证号'"
                    v-model.number="scope.row.idcardno" @input="tableParValueChange1(scope.row)" />
                </el-form-item>
              </template>
            </el-table-column>
@@ -690,13 +683,19 @@
    </el-dialog>
    <!-- æ‰¹é‡é€‰å– -->
    <el-dialog title="批量生成明细信息" :visible.sync="batchdialogVisible" width="30%">
    <el-dialog title="批量生成明细信息" :visible.sync="batchdialogVisible" width="35%">
      <div style="margin-bottom: 12px">
        <el-input v-model="personKeyword" clearable size="small" prefix-icon="el-icon-search"
          placeholder="输入 å§“名 / å•位 / å·¥å· / èº«ä»½è¯åŽ4位 ç­›æŸ¥" style="width: 330px" />
        <span style="margin-left: 10px; color: #909399; font-size: 12px">共 {{ filteredCityOptions.length }} äºº</span>
      </div>
      <div style="max-height: 300px; overflow-y: auto">
        <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange">全选</el-checkbox>
        <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange">全选(当前筛查结果)</el-checkbox>
        <div style="margin: 15px 0;"></div>
        <el-checkbox-group v-model="checkedCities" @change="handleCheckedCitiesChange">
          <el-checkbox border v-for="city in cityOptions" :label="city" :key="city.reportNo">{{ city.reportName
          }}</el-checkbox>
          <el-checkbox border v-for="city in filteredCityOptions" :label="city"
            :key="city.reportNo + '_' + city.reportName">{{ city.reportName
            }}{{ city.unitname ? " Â· " + city.unitname : "" }}</el-checkbox>
        </el-checkbox-group>
      </div>
@@ -736,6 +735,7 @@
  addorupdateFund,
  ManualCalculation,
  getdetailsByItemId,
  validateExpertFee,
  listbypower
} from "@/api/project/fund";
@@ -786,6 +786,8 @@
import Li_area_select from "@/components/Address";
import OrgSelecter from "@/views/project/components/orgselect";
import { getToken } from "@/utils/auth";
import { moneySum, moneyMul, moneySub, moneyRound } from "@/utils/money";
import { checkFileName, checkDetails } from "@/utils/fileNameCheck";
import CustomImageViewer from "@/components/CustomImageViewer"; // æ ¹æ®ä½ çš„路径调整
export default {
@@ -822,9 +824,10 @@
        return callback();
      }
      const regex = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])\d{3}([0-9]|X)$/;
      if (regex.test(value)) {
      } else {
        callback(new Error("身份证号码格式错误"));
      // 2026-10-19:格式不合法立即早退。原来写成 if/else ä½† else é‡Œæ²¡æœ‰ return,
      // ä¼šç»§ç»­å¾€ä¸‹èµ°å¼‚步查重,导致 callback è¢«è°ƒç”¨ä¸¤æ¬¡ã€"身份证号码格式错误"被后续结果覆盖。
      if (!regex.test(value)) {
        return callback(new Error("身份证号码格式错误"));
      }
      // 2. è°ƒç”¨åŽç«¯æŽ¥å£è¿›è¡Œé‡å¤æ€§æ ¡éªŒ
@@ -881,6 +884,8 @@
      // ç½‘络请求头
      Networkheader: null,
      batchdialogVisible: false,
      //批量录入弹框的筛查关键字
      personKeyword: "",
      itemlist: [],
      cityOptions: [],
      checkAll: false,
@@ -955,6 +960,10 @@
      },
      dialogrules: {
        // 2026-10-19:账户名称(账户来源=从账户库 æ—¶çš„下拉)也是必输项
        accountname: [
          { required: true, message: "请选择账户名称", trigger: "change" }
        ],
        usertype: [
          { required: true, message: "请输入账户类型", trigger: "blur" }
        ],
@@ -963,7 +972,23 @@
        ],
        unitTaxNo: [{ required: true, message: "请输入税号", trigger: "blur" }],
        idcardno: [
          { required: true, message: '请输入身份证号', trigger: 'blur' },
          {
            // 2026-10-19:加 required æ ‡è®°åªä¸ºè®© el-form-item æ ‡å‡ºå¿…输星号(Element çš„ isRequired åªè®¤ rule.required)。
            // æœ¬è§„则带 validator,async-validator çš„ getValidationMethod ä¼šç›´æŽ¥è¿”回该 validator、跳过内建 required æ£€æŸ¥ï¼Œ
            // æ‰€ä»¥æ ¡éªŒè¡Œä¸ºä¸Žæ”¹åŠ¨å‰å®Œå…¨ä¸€è‡´ï¼›å¦å¤–**不能加 message**,否则会覆盖下面自定义 validator çš„æŠ¥é”™æ–‡æ¡ˆã€‚
            required: true,
            // è´¦æˆ·ç±»åž‹é€‰æ‹©"单位机构"时不需要身份证号
            validator: (rule, value, callback) => {
              if (this.hasUserType(this.accountselectform.usertype, "org")) {
                return callback();
              }
              if (!value || !String(value).trim()) {
                return callback(new Error("请输入身份证号"));
              }
              return callback();
            },
            trigger: "blur"
          },
          // æ·»åŠ è‡ªå®šä¹‰æ ¡éªŒè§„åˆ™
          { validator: validateIdCard, trigger: 'blur' }
        ],
@@ -1125,7 +1150,16 @@
  },
  //监听属性 ç±»ä¼¼äºŽdata概念
  computed: {},
  computed: {
    //批量录入弹框:按关键字(姓名/单位/工号/身份证后4位)筛查后的候选人
    filteredCityOptions() {
      const kw = (this.personKeyword || "").trim().toLowerCase();
      if (!kw) {
        return this.cityOptions;
      }
      return this.cityOptions.filter(item => this.personMatchKeyword(item, kw));
    }
  },
  //监控data中的数据变化
  watch: {},
  //方法集合
@@ -1529,6 +1563,11 @@
    },
    /** æäº¤æŒ‰é’® */
    submitForm: debounce(function () {
      // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行)
      if (!this.checkDetailFileNames()) {
        return;
      }
      if (!this.randomnumber) {
        this.randomnumber = this.generateRandomString(9);
        this.form.randomnumber = this.randomnumber;
@@ -1537,6 +1576,31 @@
      this.$refs["form"].validate(valid => {
        if (valid) {
          this.formData = this.form;
          // ä¸“家劳务费专用:9 æ¡è´¹ç”¨æŽ§åˆ¶è§„则校验(校验不通过不允许保存)
          if (this.businessType == "1") {
            this.$modal.loading("正在校验专家劳务费用,请稍候!");
            this.checkExpertFeeRules("保存").then((allowed) => {
              this.$modal.closeLoading();
              if (allowed) {
                this.doSubmitForm();
              }
            });
            return;
          }
          this.doSubmitForm();
        }
      });
    }, 500),
    /**
     * ä¿å­˜ç”³è¯·å•的实际落库逻辑。
     *
     * ä»Ž submitForm ä¸­æ‹†å‡ºï¼Œä¾¿äºŽåœ¨ä¸“家劳务费规则校验通过后再执行;
     * å…¶ä»–业务类型由 submitForm ç›´æŽ¥è°ƒç”¨ï¼Œè¡Œä¸ºä¸Žæ‹†åˆ†å‰ä¸€è‡´ã€‚
     */
    doSubmitForm() {
          //保存前校验数据
          for (let k = 0; k < this.rbDetails.length; k++) {
@@ -1607,7 +1671,7 @@
              let tempDetail = this.rbDetails[j];
              if (
                isNaN(parseFloat(tempDetail.amount)) ||
                parseFloat(tempDetail.amount) == 0
                moneyRound(tempDetail.amount) == 0
              ) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
@@ -1622,7 +1686,7 @@
              let tempDetail = this.rbDetails[j];
              if (
                isNaN(parseFloat(tempDetail.amount)) ||
                parseFloat(tempDetail.amount) == 0
                moneyRound(tempDetail.amount) == 0
              ) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
@@ -1633,7 +1697,7 @@
              }
              if (
                isNaN(parseFloat(tempDetail.taxamount)) ||
                parseFloat(tempDetail.taxamount) == 0
                moneyRound(tempDetail.taxamount) == 0
              ) {
                tempDetail.taxedamount = parseFloat(tempDetail.amount);
              } else {
@@ -1735,9 +1799,84 @@
          //关闭窗口
          this.loading = false;
          // this.getfundList();
        }
    },
    /**
     * ä¸“家劳务费 9 æ¡è´¹ç”¨æŽ§åˆ¶è§„则校验(提交前拦截)。
     *
     * é€‚用范围:businessType === '1'(专家劳务费申请单),与后端 applytype === '1' å¯¹åº”。
     * å…¶ä»–业务类型(伦理专家费、医学成本、办公费用)不校验,直接放行。
     *
     * æ ¡éªŒå£å¾„(同一捐献案例 infoid ä¸‹ç´¯è®¡ï¼‰ç”±åŽç«¯ç»Ÿä¸€å®žçŽ°ï¼š
     *   errors   é˜»æ–­åž‹ï¼šæç¤ºè¿åçš„具体规则,不允许继续保存/审核
     *   warnings æç¤ºåž‹ï¼šä»…提醒经办人处理(规则 5、6),需二次确认后才继续
     *
     * @param {string} action å½“前动作,用于拼接提示文案(如“保存”“提交审核”)
     * @returns {Promise<boolean>} true å…è®¸ç»§ç»­ï¼Œfalse é˜»æ­¢ç»§ç»­
     */
    checkExpertFeeRules(action) {
      if (this.businessType != "1") {
        return Promise.resolve(true);
      }
      // åŽç«¯æŒ‰ã€Œæ¡ˆä¾‹å†…已有单据 + æœ¬æ¬¡æäº¤æ˜Žç»†ã€åˆå¹¶è®¡ç®—,确保尚未落库的明细也参与校验
      const payload = Object.assign({}, this.form, {
        applytype: this.form.applytype || "1",
        serviceFunddetails: this.rbDetails,
      });
    }, 500),
      // æŽ’查用:能直接从控制台看到到底送了什么,以及后端判定了什么
      console.log("[专家费校验] æäº¤æ ¡éªŒ", action, {
        businessType: this.businessType,
        applytype: payload.applytype,
        infoid: payload.infoid,
        fundId: payload.id,
        æ˜Žç»†: (this.rbDetails || []).map((d) => ({
          itemid: d.itemid,
          amount: d.amount,
          taxedamount: d.taxedamount,
          åœºæ™¯: d.servicesscopename,
        })),
      });
      return validateExpertFee(payload)
        .then((res) => {
          const errors = (res && res.errors) || [];
          const warnings = (res && res.warnings) || [];
          console.log("[专家费校验] åŽç«¯è¿”回", { errors, warnings });
          // 1) é˜»æ–­åž‹ï¼šåˆ—出违反的具体规则,不允许继续
          //    ç”¨å¼¹çª—而非轻提示:错误可能有多条,轻提示会挤成一行且很快消失
          if (errors.length > 0) {
            this.$modal.alertError(
              "专家劳务费用不符合以下规定,无法" +
                action +
                ":\n\n" +
                errors.map((msg, i) => i + 1 + ". " + msg).join("\n\n")
            );
            return false;
          }
          // 2) æç¤ºåž‹ï¼šéœ€ç»åŠžäººç¡®è®¤åŽæ‰èƒ½ç»§ç»­ï¼ˆå½“å‰è§„åˆ™ 5、6 å·²æ”¹ä¸ºç¡¬æ‹¦æˆªï¼Œæ­¤åˆ†æ”¯ä¿ç•™å¤‡ç”¨ï¼‰
          if (warnings.length > 0) {
            const confirmText =
              "专家劳务费用存在以下问题,请确认是否继续" +
              action +
              "?\n\n" +
              warnings.map((msg, i) => i + 1 + ". " + msg).join("\n\n");
            return this.$modal
              .confirm(confirmText)
              .then(() => true)
              .catch(() => false);
          }
          return true;
        })
        .catch((err) => {
          // æŽ¥å£å¼‚常时按“不阻断”处理,避免校验服务不可用导致完全无法报销;
          // åŽç«¯ addOrUpdateNew / checkFund ä»æœ‰åŒæ ·çš„硬拦截兜底。
          console.error("专家劳务费规则校验接口调用失败", err);
          return true;
        });
    },
    checksubmit() {
      //检查是否收到纸质材料
@@ -1769,8 +1908,19 @@
        return;
      }
      this.Savenow();
      console.log("保存并审批");
      // ä¸“家劳务费专用:9 æ¡è´¹ç”¨æŽ§åˆ¶è§„则校验(仅审核通过时拦截)
      const needExpertFeeCheck = this.checkObj.flowlevelone == 1;
      const expertFeePromise = needExpertFeeCheck
        ? this.checkExpertFeeRules("提交审核")
        : Promise.resolve(true);
      expertFeePromise.then((allowed) => {
        if (!allowed) {
          return;
        }
        this.Savenow();
        console.log("保存并审批");
      });
    },
    // è¿žç»­å®¡æ‰¹ï¼šèŽ·å–å½“å‰ä¸šåŠ¡ç±»åž‹ä¸‹çš„å¾…å®¡æ‰¹åˆ—è¡¨
@@ -1879,6 +2029,19 @@
      });
    },
    /** äººå‘˜ç±»åž‹ï¼ˆå¤šå€¼ï¼Œé€—号分隔)是否包含指定类型;org = å•位机构 */
    hasUserType(usertype, code) {
      if (!usertype) {
        return false;
      }
      return (
        String(usertype)
          .split(",")
          .map(t => t.trim())
          .indexOf(code) > -1
      );
    },
    /** å—益人/账户是否为"单位"(人员类型含 org) */
    //获取专家、单位机构的账户列表
    getAccountList() {
      listExternalperson().then(res => {
@@ -1887,7 +2050,7 @@
        for (let j = 0; j < res.rows.length; j++) {
          //单位账号列表
          if (res.rows[j].userstatus == "1") {
            if (res.rows[j].usertype == "org") {
            if (this.hasUserType(res.rows[j].usertype, "org")) {
              this.accountlist.push({
                id: res.rows[j].id,
                userno: res.rows[j].userno,
@@ -1898,6 +2061,7 @@
                bankcardno: res.rows[j].bankcardno,
                unitno: res.rows[j].unitno,
                unitname: res.rows[j].unitname,
                usertype: res.rows[j].usertype,
                userid:
                  res.rows[j].username + "(" + res.rows[j].bankcardno + ")"
              });
@@ -1912,6 +2076,7 @@
                bankcardno: res.rows[j].bankcardno,
                unitno: res.rows[j].unitno,
                unitname: res.rows[j].unitname,
                usertype: res.rows[j].usertype,
                userid:
                  res.rows[j].username + "(" + res.rows[j].idcardno + ")"
              });
@@ -2105,14 +2270,13 @@
    //计算个税:type(A:已知税前金额,计算税后金额;B:已知税后金额,计算税前金额)
    calculateTax(row, type) {
      let targetValue = 0;
      for (let i = 0; i < this.rbDetails.length; i++) {
        if (!this.rbDetails[i].amount) {
          this.rbDetails[i].amount = 0;
        }
        targetValue += parseFloat(this.rbDetails[i].amount); // å‡è®¾æ¯ä¸ªå¯¹è±¡ä¸­çš„特定值存储在'specificValue'属性下
      }
      this.form.pretaxcost = targetValue;
      // åˆè®¡é‡‘额必须精确累加,否则会写入 form.pretaxcost å¹¶è½åº“
      this.form.pretaxcost = moneySum(this.rbDetails, "amount");
    },
    //导入所有项目
@@ -2186,6 +2350,11 @@
    },
    // æŽ¥å—提醒立刻保存
    Savenow() {
      // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行)
      if (!this.checkDetailFileNames()) {
        return;
      }
      if (!this.randomnumber) {
        this.randomnumber = this.generateRandomString(9);
        this.form.randomnumber = this.randomnumber;
@@ -2193,192 +2362,198 @@
      this.Savereminder = false;
      this.Reminderquantity = 0;
      this.totalquantity = 0;
      this.$refs["form"].validate(valid => {
        if (valid) {
          this.formData = this.form;
          //保存前校验数据
          for (let k = 0; k < this.rbDetails.length; k++) {
            let tempDetail = this.rbDetails[k];
            if (tempDetail.itemid == null) {
              this.$modal.msgWarning(
                "服务项目表中,第" +
                (k + 1).toString() +
                "行的服务项目未选择,请先选择服务项目后再提交保存!"
              );
              return;
            }
            if (this.businessType == "1") {
              if (tempDetail.servicesscope == null) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
                  (k + 1).toString() +
                  "行的费用说明未选择,请选择费用说明后再提交保存!"
                );
                return;
              }
              if (tempDetail.beneficiaryno == null) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
                  (k + 1).toString() +
                  "行的人员姓名为空,请选择人员后再提交保存!"
                );
                return;
              }
              if (tempDetail.unitname == null) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
                  (k + 1).toString() +
                  "行的专家单位为空,请选择单位后再提交保存!"
                );
                return;
              }
            }
          }
          //处理明细
          for (let i = 0; i < this.rbDetails.length; i++) {
            let tempDetail = this.rbDetails[i];
            //获取服务项目明细
            let tempitemlist = tempDetail.itemlist;
            let itemIndex = tempitemlist.findIndex(
              item => tempDetail.itemid == item.id
            );
            if (itemIndex > -1) {
              tempDetail.itemname = tempitemlist[itemIndex].itemName;
              tempDetail.itemid = tempitemlist[itemIndex].id;
            }
            //获取applytypename、服务费用大类
            let applytypeIndex = this.rowfeeItemTypes.findIndex(
              item => tempDetail.applytype == item.value
            );
            if (applytypeIndex > -1) {
              tempDetail.applytypename = this.rowfeeItemTypes[
                applytypeIndex
              ].label;
              tempDetail.subjecttype = this.rowfeeItemTypes[
                applytypeIndex
              ].subjecttype;
              tempDetail.subjecttypename = this.rowfeeItemTypes[
                applytypeIndex
              ].subjecttypename;
            }
            this.rbDetails[i] = tempDetail;
          }
          //计算当前行报销内容费用合计,税前金额(amount)、扣税金额(taxamount)、税后金额(taxedamount)
          if (this.businessType == "2") {
            for (let j = 0; j < this.rbDetails.length; j++) {
              let tempDetail = this.rbDetails[j];
              if (
                isNaN(parseFloat(tempDetail.amount)) ||
                parseFloat(tempDetail.amount) == 0
              ) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
                  (j + 1).toString() +
                  "行的金额未输入,请先输入金额后再提交保存!"
                );
                return;
              }
            }
          } else if (this.businessType == "3" || this.businessType == "4") {
            for (let j = 0; j < this.rbDetails.length; j++) {
              let tempDetail = this.rbDetails[j];
              if (
                isNaN(parseFloat(tempDetail.amount)) ||
                parseFloat(tempDetail.amount) == 0
              ) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
                  (j + 1).toString() +
                  "行的金额未输入,请先输入金额后再提交保存!"
                );
                return;
              }
              if (
                isNaN(parseFloat(tempDetail.taxamount)) ||
                parseFloat(tempDetail.taxamount) == 0
              ) {
                tempDetail.taxedamount = parseFloat(tempDetail.amount);
              } else {
                tempDetail.taxedamount =
                  parseFloat(tempDetail.amount) +
                  parseFloat(tempDetail.taxamount);
              }
            }
          }
          //保存数据
          this.loading = true;
          this.form.serviceFunddetails = 0;
          this.rbDetails.map(item => {
            this.form.serviceFunddetails += item.amount;
          });
          this.form.serviceFunddetails = this.rbDetails;
          if (this.form.id != null) {
          } else {
            this.form.recordstatus = -1;
          }
          addorupdateFund(this.form).then(response => {
            if (response.code === 200) {
              this.$modal.msgSuccess("申请单保存成功!");
              // éžæé†’状态下执行审批解决异步调用延迟问题
              if (!this.Savereminder) {
                let checkFundObj = {
                  // fundid: this.checkObj.fundid,
                  fundid: this.curId,
                  flowconclusion: this.checkObj.flowlevelone,
                  flowcontent: this.checkObj.flowconclusion
                };
                this.$modal.loading("正在提交审核,请稍候!");
                checkfund(checkFundObj).then(res => {
                  if (res.code == 200) {
                    //关闭窗口
                    this.$modal.closeLoading();
                    this.$modal.msgSuccess("审核完成!");
                    // è¿žç»­å®¡æ‰¹ï¼šè¯¢é—®æ˜¯å¦ç»§ç»­å®¡æ‰¹ä¸‹ä¸€å•
                    this.continueOrExit();
                  } else {
                    this.$modal.closeLoading();
                    if (res.msg) {
                      this.$modal.msgError("操作失误");
                    } else {
                      this.$modal.msgError(res.msg);
                    }
                  }
                });
              }
              this.Getnetworkheader();
              this.getroute();
              this.getAccountList();
              this.getUserList();
              this.getUserProfile();
            } else {
              this.$modal.msgError("申请单保存失败:" + response.msg);
            }
          });
          //关闭窗口
          this.loading = false;
          // this.getfundList();
      // ä¸“家劳务费专用:9 æ¡è´¹ç”¨æŽ§åˆ¶è§„则校验(校验不通过不允许落库)
      this.checkExpertFeeRules("保存").then((allowed) => {
        if (!allowed) {
          return;
        }
        this.$refs["form"].validate(valid => {
          if (valid) {
            this.formData = this.form;
            //保存前校验数据
            for (let k = 0; k < this.rbDetails.length; k++) {
              let tempDetail = this.rbDetails[k];
              if (tempDetail.itemid == null) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
                  (k + 1).toString() +
                  "行的服务项目未选择,请先选择服务项目后再提交保存!"
                );
                return;
              }
              if (this.businessType == "1") {
                if (tempDetail.servicesscope == null) {
                  this.$modal.msgWarning(
                    "服务项目表中,第" +
                    (k + 1).toString() +
                    "行的费用说明未选择,请选择费用说明后再提交保存!"
                  );
                  return;
                }
                if (tempDetail.beneficiaryno == null) {
                  this.$modal.msgWarning(
                    "服务项目表中,第" +
                    (k + 1).toString() +
                    "行的人员姓名为空,请选择人员后再提交保存!"
                  );
                  return;
                }
                if (tempDetail.unitname == null) {
                  this.$modal.msgWarning(
                    "服务项目表中,第" +
                    (k + 1).toString() +
                    "行的专家单位为空,请选择单位后再提交保存!"
                  );
                  return;
                }
              }
            }
            //处理明细
            for (let i = 0; i < this.rbDetails.length; i++) {
              let tempDetail = this.rbDetails[i];
              //获取服务项目明细
              let tempitemlist = tempDetail.itemlist;
              let itemIndex = tempitemlist.findIndex(
                item => tempDetail.itemid == item.id
              );
              if (itemIndex > -1) {
                tempDetail.itemname = tempitemlist[itemIndex].itemName;
                tempDetail.itemid = tempitemlist[itemIndex].id;
              }
              //获取applytypename、服务费用大类
              let applytypeIndex = this.rowfeeItemTypes.findIndex(
                item => tempDetail.applytype == item.value
              );
              if (applytypeIndex > -1) {
                tempDetail.applytypename = this.rowfeeItemTypes[
                  applytypeIndex
                ].label;
                tempDetail.subjecttype = this.rowfeeItemTypes[
                  applytypeIndex
                ].subjecttype;
                tempDetail.subjecttypename = this.rowfeeItemTypes[
                  applytypeIndex
                ].subjecttypename;
              }
              this.rbDetails[i] = tempDetail;
            }
            //计算当前行报销内容费用合计,税前金额(amount)、扣税金额(taxamount)、税后金额(taxedamount)
            if (this.businessType == "2") {
              for (let j = 0; j < this.rbDetails.length; j++) {
                let tempDetail = this.rbDetails[j];
                if (
                  isNaN(parseFloat(tempDetail.amount)) ||
                  moneyRound(tempDetail.amount) == 0
                ) {
                  this.$modal.msgWarning(
                    "服务项目表中,第" +
                    (j + 1).toString() +
                    "行的金额未输入,请先输入金额后再提交保存!"
                  );
                  return;
                }
              }
            } else if (this.businessType == "3" || this.businessType == "4") {
              for (let j = 0; j < this.rbDetails.length; j++) {
                let tempDetail = this.rbDetails[j];
                if (
                  isNaN(parseFloat(tempDetail.amount)) ||
                  moneyRound(tempDetail.amount) == 0
                ) {
                  this.$modal.msgWarning(
                    "服务项目表中,第" +
                    (j + 1).toString() +
                    "行的金额未输入,请先输入金额后再提交保存!"
                  );
                  return;
                }
                if (
                  isNaN(parseFloat(tempDetail.taxamount)) ||
                  moneyRound(tempDetail.taxamount) == 0
                ) {
                  tempDetail.taxedamount = parseFloat(tempDetail.amount);
                } else {
                  tempDetail.taxedamount =
                    parseFloat(tempDetail.amount) +
                    parseFloat(tempDetail.taxamount);
                }
              }
            }
            //保存数据
            this.loading = true;
            this.form.serviceFunddetails = 0;
            this.rbDetails.map(item => {
              this.form.serviceFunddetails += item.amount;
            });
            this.form.serviceFunddetails = this.rbDetails;
            if (this.form.id != null) {
            } else {
              this.form.recordstatus = -1;
            }
            addorupdateFund(this.form).then(response => {
              if (response.code === 200) {
                this.$modal.msgSuccess("申请单保存成功!");
                // éžæé†’状态下执行审批解决异步调用延迟问题
                if (!this.Savereminder) {
                  let checkFundObj = {
                    // fundid: this.checkObj.fundid,
                    fundid: this.curId,
                    flowconclusion: this.checkObj.flowlevelone,
                    flowcontent: this.checkObj.flowconclusion
                  };
                  this.$modal.loading("正在提交审核,请稍候!");
                  checkfund(checkFundObj).then(res => {
                    if (res.code == 200) {
                      //关闭窗口
                      this.$modal.closeLoading();
                      this.$modal.msgSuccess("审核完成!");
                      // è¿žç»­å®¡æ‰¹ï¼šè¯¢é—®æ˜¯å¦ç»§ç»­å®¡æ‰¹ä¸‹ä¸€å•
                      this.continueOrExit();
                    } else {
                      this.$modal.closeLoading();
                      this.$modal.msgError(res.msg || "操作失败");
                    }
                  });
                }
                this.Getnetworkheader();
                this.getroute();
                this.getAccountList();
                this.getUserList();
                this.getUserProfile();
              } else {
                this.$modal.msgError("申请单保存失败:" + response.msg);
              }
            });
            //关闭窗口
            this.loading = false;
            // this.getfundList();
          }
        });
      });
    },
    handleCheckAllChange(val) {
      this.checkedCities = val ? this.cityOptions : [];
      this.checkedCities = val ? this.filteredCityOptions.slice() : [];
      this.isIndeterminate = false;
    },
    handleCheckedCitiesChange(value) {
      let checkedCount = value.length;
      this.checkAll = checkedCount === this.cityOptions.length;
      this.checkAll =
        this.filteredCityOptions.length > 0 &&
        checkedCount === this.filteredCityOptions.length;
      this.isIndeterminate =
        checkedCount > 0 && checkedCount < this.cityOptions.length;
        checkedCount > 0 && checkedCount < this.filteredCityOptions.length;
    },
    // æ‰‹åŠ¨è®¡ç®—
    Manualcalculationfn() {
@@ -2414,19 +2589,85 @@
    // æ‰¹é‡å½•入点击
    Batchentry() {
      // ethical
      this.personKeyword = "";
      this.checkedCities = [];
      this.checkAll = false;
      this.isIndeterminate = false;
      listReportname("ethical").then(res => {
        this.cityOptions = res.data;
      });
      this.batchdialogVisible = true;
    },
    /**
     * æ˜Žç»†è¡Œæ˜¯å¦ä¸ºã€Œç©ºè®°å½•」:服务项目、专家、专家所在单位 æ˜¯å¦éƒ½ä¸ºç©º
     * ç”¨äºŽæ‰¹é‡å½•入专家时判断"复用最后一行"还是"追加新行"
     */
    isBlankDetailRow(row) {
      if (!row) {
        return false;
      }
      const isEmpty = v =>
        v === null || v === undefined || String(v).trim() === "";
      return (
        isEmpty(row.itemid) &&
        isEmpty(row.beneficiaryname) &&
        isEmpty(row.unitno) &&
        isEmpty(row.unitname)
      );
    },
    /** æ‰¹é‡å½•入弹框:姓名/单位/工号/身份证后4位 å‘½ä¸­åˆ¤æ–­ */
    personMatchKeyword(item, kw) {
      const idcard = String(item.idcrad || "");
      return (
        String(item.reportName || "")
          .toLowerCase()
          .indexOf(kw) > -1 ||
        String(item.unitname || "")
          .toLowerCase()
          .indexOf(kw) > -1 ||
        String(item.reportNo || "")
          .toLowerCase()
          .indexOf(kw) > -1 ||
        (idcard.length >= 4 && idcard.slice(-4).indexOf(kw) > -1)
      );
    },
    // ç¡®è®¤å½•å…¥
    BatchentryFn() {
      // æ˜Žç»†ä¸­å·²æœ‰çš„人员姓名,用于重复提醒
      const existed = {};
      this.rbDetails.forEach(row => {
        if (row.beneficiaryname) {
          existed[String(row.beneficiaryname).trim()] = true;
        }
      });
      // 2026-10-22:修正"导入后表格多出一行空记录"。
      // è§„则:先看最后一条记录,若它的"服务项目 / ä¸“å®¶ / ä¸“家所在单位"都是空的,
      // è¯´æ˜Žæ˜¯ä¸€æ¡ç©ºè®°å½•,就从这一行开始填;否则追加新行。
      let writeIndex = this.rbDetails.length;
      if (
        writeIndex > 0 &&
        this.isBlankDetailRow(this.rbDetails[writeIndex - 1])
      ) {
        writeIndex -= 1;
      }
      const duplicated = [];
      this.checkedCities.forEach(item => {
        const nameKey = String(item.reportName || "").trim();
        if (nameKey && existed[nameKey]) {
          duplicated.push(item.reportName);
          return;
        }
        if (nameKey) {
          existed[nameKey] = true;
        }
        let rowData = {
          orderno: null,
          fundid: null,
          beneficiaryname: item.reportName,
          beneficiaryno: item.reportNo,
          isunit: 0,
          unitname: item.unitname,
          unitno: null,
          uintuserno: null,
@@ -2469,13 +2710,25 @@
          annexfiles: null
        };
        this.rbDetails.push(rowData);
        if (writeIndex < this.rbDetails.length) {
          // å¤ç”¨æœ€åŽé‚£æ¡ç©ºè®°å½•
          this.rbDetails.splice(writeIndex, 1, rowData);
        } else {
          this.rbDetails.push(rowData);
        }
        writeIndex++;
      });
      for (let i = 0; i < this.checkedCities.length + 1; i++) {
      for (let i = 0; i < this.rbDetails.length; i++) {
        this.rbDetails[i].orderno = i + 1;
      }
      this.calculateTax();
      this.batchdialogVisible = false;
      this.checkedCities = [];
      if (duplicated.length) {
        this.$modal.msgWarning(
          "以下人员在明细中已存在,已跳过重复添加:" + duplicated.join("、")
        );
      }
    },
    //新增一行
    addRow(rowIndex) {
@@ -2485,6 +2738,7 @@
        fundid: null,
        beneficiaryname: null,
        beneficiaryno: null,
        isunit: 0,
        unitname: null,
        itemdescribe: null,
        unitno: null,
@@ -2581,33 +2835,32 @@
        });
      //全部删除后添加一条新行
    },
    //计算总金额
    //计算总金额(金额一律走精确运算,禁止 parseFloat ç´¯åŠ ï¼‰
    sumTotalFee(row) {
      let allSum = 0;
      const detailsArr = [];
      if (this.form.applytype != null) {
        for (let i = 0; i < this.rbDetails.length; i++) {
          const detail = this.rbDetails[i];
          let totalFee = 0;
          let taxFee = 0;
          if (this.form.applytype == 1 || this.form.applytype == 2) {
            //计算当前报销内容费用合计
            if (!isNaN(parseFloat(this.rbDetails[i].amount))) {
              totalFee += parseFloat(this.rbDetails[i].amount);
              taxFee += parseFloat(this.rbDetails[i].amount);
            //计算当前报销内容费用合计:税前 - æ‰£ç¨Ž
            if (!isNaN(parseFloat(detail.amount))) {
              totalFee = moneySub(totalFee, -parseFloat(detail.amount));
              taxFee = moneySub(taxFee, -parseFloat(detail.amount));
            }
            if (!isNaN(parseFloat(this.rbDetails[i].taxamount))) {
              totalFee -= parseFloat(this.rbDetails[i].taxamount);
              taxFee -= parseFloat(this.rbDetails[i].taxamount);
            if (!isNaN(parseFloat(detail.taxamount))) {
              totalFee = moneySub(totalFee, detail.taxamount);
              taxFee = moneySub(taxFee, detail.taxamount);
            }
          } else if (this.form.applytype == 3 || this.form.applytype == 4) {
            //单价*数量=税前金额
            if (
              !isNaN(parseFloat(this.rbDetails[i].price)) &&
              !isNaN(parseFloat(this.rbDetails[i].quantity))
              !isNaN(parseFloat(detail.price)) &&
              !isNaN(parseFloat(detail.quantity))
            ) {
              totalFee +=
                parseFloat(this.rbDetails[i].price) *
                parseFloat(this.rbDetails[i].quantity);
              this.rbDetails[i].amount = totalFee;
              totalFee = moneyMul(detail.price, detail.quantity);
              detail.amount = totalFee;
            }
          }
@@ -2616,9 +2869,10 @@
          //   this.rbDetails[i].taxedamount = taxFee.toFixed(2);
          // }
          allSum += totalFee;
          detailsArr.push(totalFee);
        }
      }
      const allSum = moneyRound(moneySum(detailsArr));
      // this.form.amountrequested = allSum.toFixed(2);
      // this.$forceUpdate();
    },
@@ -2719,7 +2973,43 @@
      }
    },
    /**
     * 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 }
@@ -3093,6 +3383,10 @@
    //专家/医疗机构/费用报销机构选择
    confirmSelectionDialog() {
      this.$refs["accountselectform"].validate(valid => {
        // 2026-10-19:原来没有判断 valid,校验失败也照样往下走、照样保存(星号/红字形同虚设)
        if (!valid) {
          return;
        }
        let tempIndex = this.accountselectform.index;
        if (this.selectionType == "account") {
          if (this.accountfrom == "1") {
@@ -3121,11 +3415,13 @@
                  this.rbDetails[tempIndex].phone = res.telephone;
                  if (res.usertype == "org") {
                    this.$set(this.rbDetails[tempIndex], "isunit", 1);
                    this.rbDetails[tempIndex].idcardno = "";
                    this.rbDetails[tempIndex].title = "";
                    this.rbDetails[tempIndex].unitno = "";
                    this.rbDetails[tempIndex].unitname = "";
                  } else {
                    this.$set(this.rbDetails[tempIndex], "isunit", 0);
                    this.rbDetails[tempIndex].idcardno = res.idcardno;
                    this.rbDetails[tempIndex].title = res.title;
                    this.rbDetails[tempIndex].unitno = parseInt(res.unitno);
@@ -3231,12 +3527,14 @@
                      this.rbDetails[tempIndex].bankcardno = res.bankcardno;
                      this.rbDetails[tempIndex].phone = res.telephone;
                      if (res.usertype == "org") {
                      if (this.hasUserType(res.usertype, "org")) {
                        this.$set(this.rbDetails[tempIndex], "isunit", 1);
                        this.rbDetails[tempIndex].idcardno = "";
                        this.rbDetails[tempIndex].title = "";
                        this.rbDetails[tempIndex].unitno = "";
                        this.rbDetails[tempIndex].unitname = "";
                      } else {
                        this.$set(this.rbDetails[tempIndex], "isunit", 0);
                        this.rbDetails[tempIndex].idcardno = res.idcardno;
                        this.rbDetails[tempIndex].title = res.title;
                        this.rbDetails[tempIndex].unitno = parseInt(res.unitno);
@@ -3321,7 +3619,9 @@
  //生命周期 - æŒ‚载完成(可以访问DOM元素)
  mounted() {
    window.addEventListener("beforeunload", e => this.beforeunloadHandler(e));
    // å­˜ä½åŒä¸€ä¸ªå¼•用,否则 removeEventListener ä¼ æ–°ç®­å¤´å‡½æ•°æ— æ³•真正移除,会泄漏监听器
    this._beforeUnloadHandler = e => this.beforeunloadHandler(e);
    window.addEventListener("beforeunload", this._beforeUnloadHandler);
    this.getUserList();
    let apiaccountselectform = this.accountselectform;
    let apiform = this.form;
@@ -3337,7 +3637,10 @@
  updated() { }, //生命周期 - æ›´æ–°ä¹‹åŽ
  beforeDestroy() { }, //生命周期 - é”€æ¯ä¹‹å‰
  destroyed() {
    window.removeEventListener("beforeunload", e => this.beforeunloadFn(e));
    if (this._beforeUnloadHandler) {
      window.removeEventListener("beforeunload", this._beforeUnloadHandler);
      this._beforeUnloadHandler = null;
    }
  }, //生命周期 - é”€æ¯å®Œæˆ
  activated() { } //如果页面有keep-alive缓存功能,这个函数会触发
};
src/views/project/fund/applyDetail0/index.vue
@@ -11,7 +11,7 @@
                clearable
                style="width: 100%"
                v-model="form.createTime"
                type="date"
                type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="申请日期"
              >
@@ -1227,6 +1227,8 @@
import Li_area_select from "@/components/Address";
import OrgSelecter from "@/views/project/components/orgselect";
import { getToken } from "@/utils/auth";
import { moneySum, moneyAdd, moneySub, moneyMul, moneyRound } from "@/utils/money";
import { checkFileName, checkDetails } from "@/utils/fileNameCheck";
export default {
  //import引入的组件需要注入到对象中才能使用
@@ -1844,6 +1846,11 @@
    },
    /** æäº¤æŒ‰é’® */
    submitForm: debounce(function() {
      // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行)
      if (!this.checkDetailFileNames()) {
        return;
      }
      if (!this.randomnumber) {
        this.randomnumber = this.generateRandomString(9);
        this.form.randomnumber = this.randomnumber;
@@ -2103,11 +2110,7 @@
          this.$store.dispatch("tagsView/delView", this.$route);
          this.$router.go(-1);
        } else {
          if (res.msg) {
            this.$modal.msgError("操作失误");
          } else {
            this.$modal.msgError(res.msg);
          }
          this.$modal.msgError(res.msg || "操作失败");
        }
      });
    },
@@ -2526,6 +2529,11 @@
    },
    // æŽ¥å—提醒立刻保存
    Savenow() {
      // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行)
      if (!this.checkDetailFileNames()) {
        return;
      }
      if (!this.randomnumber) {
        this.randomnumber = this.generateRandomString(9);
        this.form.randomnumber = this.randomnumber;
@@ -2876,32 +2884,28 @@
      }
    },
    //计算总金额
    //计算总金额(金额一律走精确运算)
    sumTotalFee(row) {
      let allSum = 0;
      const detailsArr = [];
      if (this.form.applytype != null) {
        for (let i = 0; i < this.rbDetails.length; i++) {
          const detail = this.rbDetails[i];
          let totalFee = 0;
          let taxFee = 0;
          if (this.form.applytype == 1 || this.form.applytype == 2) {
            //计算当前报销内容费用合计
            if (!isNaN(parseFloat(this.rbDetails[i].amount))) {
              totalFee += parseFloat(this.rbDetails[i].amount);
              taxFee += parseFloat(this.rbDetails[i].amount);
            //计算当前报销内容费用合计:税前 - æ‰£ç¨Ž
            if (!isNaN(parseFloat(detail.amount))) {
              totalFee = moneyAdd(totalFee, detail.amount);
            }
            if (!isNaN(parseFloat(this.rbDetails[i].taxamount))) {
              totalFee -= parseFloat(this.rbDetails[i].taxamount);
              taxFee -= parseFloat(this.rbDetails[i].taxamount);
            if (!isNaN(parseFloat(detail.taxamount))) {
              totalFee = moneySub(totalFee, detail.taxamount);
            }
          } else if (this.form.applytype == 3 || this.form.applytype == 4) {
            //单价*数量=税前金额
            if (
              !isNaN(parseFloat(this.rbDetails[i].price)) &&
              !isNaN(parseFloat(this.rbDetails[i].quantity))
              !isNaN(parseFloat(detail.price)) &&
              !isNaN(parseFloat(detail.quantity))
            ) {
              totalFee +=
                parseFloat(this.rbDetails[i].price) *
                parseFloat(this.rbDetails[i].quantity);
              totalFee = moneyMul(detail.price, detail.quantity);
              this.rbDetails[i].amount = totalFee;
            }
          }
@@ -2911,10 +2915,10 @@
          //   this.rbDetails[i].taxedamount = taxFee.toFixed(2);
          // }
          allSum += totalFee;
          detailsArr.push(totalFee);
        }
      }
      // this.form.amountrequested = allSum.toFixed(2);
      // this.form.amountrequested = moneyRound(moneySum(detailsArr)).toFixed(2);
      // this.$forceUpdate();
    },
@@ -3005,7 +3009,43 @@
    //     console.log(response.msg);
    //   }
    // },
    /**
     * 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;
      //获取票据信息位置
@@ -3279,13 +3319,13 @@
  //生命周期 - æŒ‚载完成(可以访问DOM元素)
  mounted() {
    window.addEventListener("beforeunload", e => this.beforeunloadHandler(e));
    // å­˜ä½åŒä¸€ä¸ªå¼•用,否则 removeEventListener ä¼ æ–°ç®­å¤´å‡½æ•°æ— æ³•真正移除,会泄漏监听器
    this._beforeUnloadHandler = e => this.beforeunloadHandler(e);
    window.addEventListener("beforeunload", this._beforeUnloadHandler);
    this.getUserList();
    this.getUserProfile();
    let apifunddetailForm = this.funddetailForm;
    let apiform = this.form;
    console.log(apiform);
    console.log(apifunddetailForm);
    // this.getFeeUnitList();
  },
@@ -3294,7 +3334,12 @@
  beforeMount() {}, //生命周期 - æŒ‚载之前
  beforeUpdate() {}, //生命周期 - æ›´æ–°ä¹‹å‰
  updated() {}, //生命周期 - æ›´æ–°ä¹‹åŽ
  beforeDestroy() {}, //生命周期 - é”€æ¯ä¹‹å‰
  beforeDestroy() {
    if (this._beforeUnloadHandler) {
      window.removeEventListener("beforeunload", this._beforeUnloadHandler);
      this._beforeUnloadHandler = null;
    }
  }, //生命周期 - é”€æ¯ä¹‹å‰
  destroyed() {}, //生命周期 - é”€æ¯å®Œæˆ
  activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
};
src/views/project/fund/ethicalExpertFeeApply/index.vue
@@ -1,122 +1,67 @@
<!--  -->
<template>
  <div class="app-container">
    <el-form
      :model="queryParams"
      ref="queryForm"
      :inline="true"
      label-width="70px"
    >
      <el-row align="left">
        <el-col :span="6">
          <el-form-item label="姓名" prop="name">
            <el-input
              v-model="queryParams.name"
              placeholder="请输入姓名"
              clearable
              size="small"
              @keyup.enter.native="handleQuery"
            />
          </el-form-item>
        </el-col>
        <el-col :span="6">
          <el-form-item label="捐献进度" prop="recordstate">
            <el-select
              v-model="queryParams.recordstate"
              placeholder="请选择记录状态"
              clearable
              size="small"
            >
              <el-option
                v-for="dict in dict.type.sys_DonationStatus"
                :key="dict.value"
                :label="dict.label"
                :value="dict.value"
              />
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="6">
          <el-form-item label="捐献者" prop="name">
            <el-input
              v-model="queryParams.donorname"
              placeholder="请输入捐献者姓名"
              clearable
              size="small"
              @keyup.enter.native="handleQuery"
            />
          </el-form-item>
        </el-col>
        <el-col :span="6">
          <el-button
            type="primary"
            icon="el-icon-search"
            size="mini"
            @click="handleQuery"
            >搜索</el-button
          >
          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery"
            >重置</el-button
          >
        </el-col>
      </el-row>
    </el-form>
    <el-row>
      <el-col :span="6">
        <el-card shadow="never">
          <!-- æçŒ®æ¡ˆä¾‹åˆ—表 -->
          <el-row>
            <el-col :span="10">
              <el-row type="flex" :gutter="10" align="right" class="mb8">
              </el-row>
            </el-col>
          </el-row>
          <el-table
            v-loading="loading"
            border
            highlight-current-row
            :data="donatebaseinfoList"
            @row-click="selectDonotor"
          >
            <!-- <el-table-column label="捐献编号" align="center" prop="donorno" /> -->
            <el-table-column label="捐献进度" align="center" prop="recordstate">
              <template slot-scope="scope">
                <dict-tag
                  :options="dict.type.sys_DonationStatus"
                  :value="scope.row.recordstate"
                />
              </template>
            </el-table-column>
            <el-table-column label="姓名" align="center" prop="name" />
          </el-table>
          <pagination
            v-show="total > 0"
            :total="total"
            small
            :page.sync="queryParams.pageNum"
            :limit.sync="queryParams.pageSize"
            @pagination="getBaseInfoList"
          />
        </el-card>
      </el-col>
      <el-col :span="18">
      <el-col :span="24">
        <el-card shadow="naver">
          <el-form ref="infoform" label-width="100px">
            <el-row :gutter="4" align="right" class="mb8">
              <el-col :span="9">
                <el-form-item label="捐献编号">
                  <el-input
                    v-model="curCase.donorno"
                    :disabled="true"
                  ></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="9">
                <el-form-item label="姓名">
                  <el-input v-model="curCase.name" :disabled="true"></el-input>
                </el-form-item>
              </el-col>
            </el-row>
            <!-- æŠ¥é”€å•筛选:捐献者、审批状态 -->
            <el-form
              class="opo-query-form"
              :model="fundQueryParam"
              ref="fundQueryForm"
              :inline="true"
              label-width="82px"
            >
              <el-row>
                <el-col :span="6">
                  <el-form-item label="捐献者" prop="donorname">
                    <el-input
                      v-model="fundQueryParam.donorname"
                      placeholder="请输入捐献者姓名"
                      clearable
                      size="small"
                      @keyup.enter.native="handleQueryFund"
                    />
                  </el-form-item>
                </el-col>
                <el-col :span="6">
                  <el-form-item label="审批状态" prop="recordstatus">
                    <el-select
                      v-model="fundQueryParam.recordstatus"
                      placeholder="请选择审批状态"
                      clearable
                      size="small"
                      style="width: 100%"
                    >
                      <el-option
                        v-for="dict in dict.type.sys_recordstatus"
                        :key="dict.value"
                        :label="dict.label"
                        :value="Number(dict.value)"
                      />
                    </el-select>
                  </el-form-item>
                </el-col>
                <el-col :span="6">
                  <el-form-item label-width="10px">
                    <el-button
                      type="primary"
                      icon="el-icon-search"
                      size="small"
                      @click="handleQueryFund"
                      >搜索</el-button
                    >
                    <el-button
                      icon="el-icon-refresh"
                      size="small"
                      @click="resetFundQuery"
                      >重置</el-button
                    >
                  </el-form-item>
                </el-col>
              </el-row>
            </el-form>
            <el-row :gutter="10" class="mb8">
              <el-col :span="1.5">
                <el-button
@@ -198,6 +143,13 @@
                    />
                  </template>
                </el-table-column>
                <el-table-column
                  label="捐献者"
                  align="center"
                  prop="donorname"
                  width="110px"
                  show-overflow-tooltip
                />
                <!--
                <el-table-column label="联系电话" align="center" prop="phone" width="150px" />
                <el-table-column label="工作单位" align="center" prop="unitname" width="220px" />
@@ -261,11 +213,119 @@
                  </template>
                </el-table-column>
              </el-table>
              <!-- æœªé€‰æ‹©æ¡ˆä¾‹æ—¶å±•示该登录人全部伦理审查单据,超过一页可用分页翻看 -->
              <pagination
                v-show="fundTotal > 0"
                :total="fundTotal"
                small
                :page.sync="fundQueryParam.pageNum"
                :limit.sync="fundQueryParam.pageSize"
                @pagination="listFundData"
              />
            </el-row>
          </el-form>
        </el-card>
      </el-col>
    </el-row>
    <!-- æ–°å¢žç”³è¯·å•前先选择捐献案例(原左侧案例列表移到这里) -->
    <el-dialog
      title="选择捐献案例"
      :visible.sync="caseDialogVisible"
      width="760px"
      append-to-body
    >
      <el-form
        class="opo-query-form"
        :model="queryParams"
        ref="queryForm"
        :inline="true"
        label-width="70px"
      >
        <el-row>
          <el-col :span="8">
            <el-form-item label="姓名" prop="name">
              <el-input
                v-model="queryParams.name"
                placeholder="请输入姓名"
                clearable
                size="small"
                @keyup.enter.native="handleQuery"
              />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="捐献进度" prop="recordstate">
              <el-select
                v-model="queryParams.recordstate"
                placeholder="请选择记录状态"
                clearable
                size="small"
                style="width: 100%"
              >
                <el-option
                  v-for="dict in dict.type.sys_DonationStatus"
                  :key="dict.value"
                  :label="dict.label"
                  :value="dict.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label-width="10px">
              <el-button
                type="primary"
                icon="el-icon-search"
                size="small"
                @click="handleQuery"
                >搜索</el-button
              >
              <el-button icon="el-icon-refresh" size="small" @click="resetQuery"
                >重置</el-button
              >
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <el-table
        ref="caseTable"
        v-loading="loading"
        border
        height="380"
        highlight-current-row
        :data="donatebaseinfoList"
        @row-click="selectDonotor"
      >
        <el-table-column label="捐献进度" align="center" prop="recordstate">
          <template slot-scope="scope">
            <dict-tag
              :options="dict.type.sys_DonationStatus"
              :value="scope.row.recordstate"
            />
          </template>
        </el-table-column>
        <el-table-column label="姓名" align="center" prop="name" />
        <el-table-column label="操作" align="center" width="90">
          <template slot-scope="scope">
            <el-button type="text" size="mini" @click="selectDonotor(scope.row)"
              >选择</el-button
            >
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total > 0"
        :total="total"
        small
        :page.sync="queryParams.pageNum"
        :limit.sync="queryParams.pageSize"
        @pagination="getBaseInfoList"
      />
      <div slot="footer" class="dialog-footer">
        <el-button @click="caseDialogVisible = false">取 æ¶ˆ</el-button>
      </div>
    </el-dialog>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹è´¹ç”¨ç”³è¯·å• -->
    <el-dialog
@@ -284,7 +344,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="申请日期"
              >
@@ -960,6 +1020,7 @@
import { getUserProfile } from "@/api/system/user";
import { listFundflow } from "@/api/project/fundflow";
import { getToken } from "@/utils/auth";
import { moneySum, moneyAdd, moneySub, moneyMul, moneyRound } from "@/utils/money";
export default {
  //import引入的组件需要注入到对象中才能使用
@@ -1011,11 +1072,19 @@
      },
      fundQueryParam: {
        pageNum: 1,
        pageSize: 100,
        pageSize: 10,
        infoid: null,
        applytype: "2",
        createBy: null
        createBy: null,
        // æŠ¥é”€å•筛选条件
        donorname: null,
        recordstatus: null
      },
      // å³ä¾§æŠ¥é”€å•总数(用于分页)
      fundTotal: 0,
      // ã€Œé€‰æ‹©æçŒ®æ¡ˆä¾‹ã€å¯¹è¯æ¡†ï¼ˆæ–°å¢žç”³è¯·å•时使用)
      caseDialogVisible: false,
      casePickerMode: "",
      // è¡¨å•参数
      form: {},
      // è¡¨å•校验
@@ -1102,55 +1171,23 @@
    },
    /** æ–°å¢žæŒ‰é’®æ“ä½œ */
    handleAdd() {
      if (this.curCase.id) {
        this.$router.push({
          path: "/finance/applyDetail/",
          query: {
            id: 0,
            businessType: "2",
            operationType: "add",
            curCase: this.curCase
          }
        });
      } else {
        const h = this.$createElement;
        this.$message({
          message: h("p", null, [
            h("span", null, "请在左边的捐献案例表选择需要操作的案例!")
          ])
        });
      }
      return;
      // æ–°å¢žç”³è¯·å•:先弹出「选择捐献案例」对话框
      this.casePickerMode = "add";
      this.caseDialogVisible = true;
      this.getBaseInfoList();
    },
      if (this.curCase.id) {
        this.istrue += 2;
        this.reset();
        this.dialogOpen = true;
        this.title = "添加费用申请";
        this.dialogType = "edit";
        this.queryParams.params = {};
        this.form.infoid = this.curCase.id;
        this.form.donorno = this.curCase.donorno;
        this.form.donorname = this.curCase.name;
        this.form.username = this.defaultperson.nickName;
        this.form.userno = this.defaultperson.userName;
        this.form.deptmentname = this.defaultperson.dept.deptName;
        this.form.deptmentno = this.defaultperson.dept.deptId;
        this.form.managername = this.defaultperson.dept.leader;
        // this.form.createTime = nowdate;
        //this.open = true;
        //this.initFundApplyForm();
        this.fundDetailArr = [];
        this.fundflowList = [];
        this.addRow();
      } else {
        const h = this.$createElement;
        this.$message({
          message: h("p", null, [h("span", null, "请先选择左方捐献案例 ")])
        });
      }
    /** æºå¸¦å·²é€‰æ¡ˆä¾‹è·³è½¬åˆ°ç”³è¯·å•填写页 */
    goApplyAdd() {
      this.$router.push({
        path: "/finance/applyDetail/",
        query: {
          id: 0,
          businessType: "2",
          operationType: "add",
          curCase: this.curCase
        }
      });
    },
    /** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
    handleUpdate(row) {
@@ -1360,15 +1397,11 @@
            // } catch {
            //   tempDetail.unitname = tempDetail.unitno;
            // }
            if (!isNaN(parseFloat(tempDetail.amount))) {
              totalPreTaxAmount += parseFloat(tempDetail.amount);
              totalAmount += parseFloat(tempDetail.amount);
            }
            if (!isNaN(parseFloat(tempDetail.taxedamount))) {
              totalAfterTaxAmount += parseFloat(tempDetail.taxedamount);
              totalAmount += parseFloat(tempDetail.taxedamount);
            }
          }
          //金额一律走精确运算,禁止 parseFloat ç´¯åŠ 
          totalPreTaxAmount = moneySum(this.fundDetailArr, "amount");
          totalAfterTaxAmount = moneySum(this.fundDetailArr, "taxedamount");
          totalAmount = moneyAdd(totalPreTaxAmount, totalAfterTaxAmount);
          try {
            this.form.unitname = this.$refs.orgSelecter.getOptionByValue(
              this.form.unitno
@@ -1498,18 +1531,60 @@
    //点击捐献案例列表触发方法
    selectDonotor(row, column, event) {
      this.curCase = row;
      this.getfundList();
      if (this.casePickerMode === "add") {
        // ä»Žã€Œæ–°å¢žç”³è¯·å•」进入:选完案例直接跳到申请单填写页
        this.casePickerMode = "";
        this.caseDialogVisible = false;
        this.goApplyAdd();
      }
    },
    /** æŒ‰å½“前 fundQueryParam æŸ¥è¯¢å³ä¾§æŠ¥é”€å•列表 */
    listFundData() {
      this.loading = true;
      listFund(this.fundQueryParam).then(response => {
        this.loading = false;
        this.donateconsolationfundList = response.rows;
        this.fundTotal = response.total;
      });
    },
    /** æŠ¥é”€å•筛选:搜索 */
    handleQueryFund() {
      this.fundQueryParam.pageNum = 1;
      this.listFundData();
    },
    /** æŠ¥é”€å•筛选:重置(清空条件与已选案例,回到该登录人全部单据) */
    resetFundQuery() {
      this.fundQueryParam.donorname = null;
      this.fundQueryParam.recordstatus = null;
      this.getAllfundList();
    },
    /** æ£€ç´¢å…¨éƒ¨ï¼šå–消案例选择与筛选条件,查询当前登录人的全部伦理审查单据 */
    getAllfundList() {
      this.curCase = {};
      if (this.$refs.caseTable) {
        this.$refs.caseTable.setCurrentRow();
      }
      this.reset();
      this.fundQueryParam.infoid = null;
      this.fundQueryParam.donorname = null;
      this.fundQueryParam.recordstatus = null;
      this.fundQueryParam.pageNum = 1;
      this.listFundData();
    },
    //获取案例专家费用申请单
    getfundList() {
      this.loading = true;
      this.reset();
      // é€‰ä¸­æ¡ˆä¾‹åŽæŒ‰æ¡ˆä¾‹å±•示,同时清掉列表筛选条件,避免两个条件叠加导致空列表
      this.fundQueryParam.donorname = null;
      this.fundQueryParam.recordstatus = null;
      this.fundQueryParam.infoid = this.curCase.id;
      listFund(this.fundQueryParam).then(response => {
        this.loading = false;
        this.donateconsolationfundList = response.rows;
      });
      this.fundQueryParam.pageNum = 1;
      this.listFundData();
    },
    select(row) {
@@ -1736,49 +1811,45 @@
        });
    },
    //计算总金额
    //计算总金额(金额一律走精确运算,禁止 parseFloat ç´¯åŠ ï¼‰
    sumTotalFee() {
      let allSum = 0;
      const detailsArr = [];
      let preTaxSum = 0;
      let taxedSum = 0;
      if (this.form.applytype != null) {
        for (let i = 0; i < this.fundDetailArr.length; i++) {
          const detail = this.fundDetailArr[i];
          let totalFee = 0;
          let taxFee = 0;
          if (this.form.applytype == 1 || this.form.applytype == 2) {
            //计算当前报销内容费用合计
            if (!isNaN(parseFloat(this.fundDetailArr[i].amount))) {
              totalFee += parseFloat(this.fundDetailArr[i].amount);
              taxFee += parseFloat(this.fundDetailArr[i].amount);
              preTaxSum += parseFloat(this.fundDetailArr[i].amount);
            //计算当前报销内容费用合计:税前 - æ‰£ç¨Ž
            if (!isNaN(parseFloat(detail.amount))) {
              totalFee = moneyAdd(totalFee, detail.amount);
              preTaxSum = moneyAdd(preTaxSum, detail.amount);
            }
            if (!isNaN(parseFloat(this.fundDetailArr[i].taxamount))) {
              totalFee -= parseFloat(this.fundDetailArr[i].taxamount);
              taxFee -= parseFloat(this.fundDetailArr[i].taxamount);
            if (!isNaN(parseFloat(detail.taxamount))) {
              totalFee = moneySub(totalFee, detail.taxamount);
            }
            //taxedamount
            if (!isNaN(parseFloat(this.fundDetailArr[i].taxedamount))) {
              taxedSum += parseFloat(this.fundDetailArr[i].taxedamount);
            if (!isNaN(parseFloat(detail.taxedamount))) {
              taxedSum = moneyAdd(taxedSum, detail.taxedamount);
            }
          } else if (this.form.applytype == 3) {
            if (
              !isNaN(parseFloat(this.fundDetailArr[i].price)) &&
              !isNaN(parseFloat(this.fundDetailArr[i].quantity))
              !isNaN(parseFloat(detail.price)) &&
              !isNaN(parseFloat(detail.quantity))
            ) {
              totalFee +=
                parseFloat(this.fundDetailArr[i].price) *
                parseFloat(this.fundDetailArr[i].quantity);
              totalFee = moneyMul(detail.price, detail.quantity);
            }
          }
          // if (!isNaN(parseFloat(taxFee))) {
          //   this.fundDetailArr[i].taxedamount = taxFee.toFixed(2);
          // }
          allSum += totalFee;
          detailsArr.push(totalFee);
        }
      }
      this.form.amountrequested = allSum.toFixed(2);
      this.form.pretaxcost = preTaxSum.toFixed(2);
      this.form.taxedcost = taxedSum.toFixed(2);
      this.form.amountrequested = moneyRound(moneySum(detailsArr)).toFixed(2);
      this.form.pretaxcost = moneyRound(preTaxSum).toFixed(2);
      this.form.taxedcost = moneyRound(taxedSum).toFixed(2);
      this.$forceUpdate();
    },
@@ -2064,10 +2135,11 @@
  //生命周期 - åˆ›å»ºå®Œæˆï¼ˆå¯ä»¥è®¿é—®å½“前this实例)
  created() {
    this.getUnitList();
    this.getList();
    this.getBaseInfoList();
    // æ¡ˆä¾‹åˆ—表已移入「选择捐献案例」对话框,打开对话框时再查询
    this.getlistUser();
    this.getCurFundType();
    // é¦–屏展示当前登录人的全部伦理审查单据
    this.getAllfundList();
  },
  //生命周期 - æŒ‚载完成(可以访问DOM元素)
  mounted() {
src/views/project/fund/expertFeeApply/index.vue
@@ -2,119 +2,66 @@
<template>
  <div class="app-container">
    <el-row>
      <el-col :span="6">
        <el-card shadow="never">
          <!-- æçŒ®æ¡ˆä¾‹åˆ—表 -->
          <el-row>
            <el-col :span="10">
              <el-row type="flex" :gutter="10" align="right" class="mb8">
              </el-row>
            </el-col>
          </el-row>
          <div>
            <el-form
              :model="queryParams"
              ref="queryForm"
              :inline="true"
              label-width="70px"
            >
              <el-form-item label="姓名" prop="name">
                <el-input
                  v-model="queryParams.name"
                  placeholder="请输入姓名"
                  clearable
                  size="small"
                  @keyup.enter.native="handleQuery"
                />
              </el-form-item>
              <el-form-item label="捐献进度" prop="recordstate">
                <el-select
                  v-model="queryParams.recordstate"
                  placeholder="请选择记录状态"
                  clearable
                  size="small"
                >
                  <el-option
                    v-for="dict in dict.type.sys_DonationStatus"
                    :key="dict.value"
                    :label="dict.label"
                    :value="dict.value"
                  />
                </el-select>
              </el-form-item>
              <div style="float: right; margin-bottom: 10px;">
                <el-button
                  type="primary"
                  icon="el-icon-search"
                  size="mini"
                  @click="handleQuery"
                  >搜索</el-button
                >
                <el-button
                  icon="el-icon-refresh"
                  size="mini"
                  @click="resetQuery"
                  >重置</el-button
                >
              </div>
            </el-form>
          </div>
          <el-table
            v-loading="loading"
            highlight-current-row
            :data="donatebaseinfoList"
            @row-click="selectDonotor"
          >
            <!-- <el-table-column label="捐献编号" align="center" prop="donorno" /> -->
            <el-table-column label="捐献进度" align="center" prop="recordstate">
              <template slot-scope="scope">
                <dict-tag
                  :options="dict.type.sys_DonationStatus"
                  :value="scope.row.recordstate"
                  :key="dict.value"
                />
                <!-- :label="dict.label" -->
              </template>
            </el-table-column>
            <el-table-column label="姓名" align="center" prop="name" />
          </el-table>
          <pagination
            v-show="total > 0"
            :total="total"
            small
            :page.sync="queryParams.pageNum"
            :limit.sync="queryParams.pageSize"
            @pagination="getBaseInfoList"
          />
        </el-card>
      </el-col>
      <el-col :span="18">
      <el-col :span="24">
        <el-card shadow="naver">
          <el-form ref="infoform" label-width="100px">
            <el-row :gutter="4" align="right" class="mb8">
              <el-col :span="7">
                <el-form-item label="捐献编号">
                  <el-input
                    v-model="curCase.donorno"
                    :disabled="true"
                  ></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="8">
                <el-form-item label="姓名">
                  <el-input v-model="curCase.name" :disabled="true"></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="3">
                <el-button
                  type="primary"
                  icon="el-icon-search"
                  size="mini"
                  @click="allgetfundList"
                  >检索全部</el-button
                >
              </el-col>
            </el-row>
            <!-- æŠ¥é”€å•筛选:捐献者、审批状态 -->
            <el-form
              class="opo-query-form"
              :model="fundQueryParam"
              ref="fundQueryForm"
              :inline="true"
              label-width="82px"
            >
              <el-row>
                <el-col :span="6">
                  <el-form-item label="捐献者" prop="donorname">
                    <el-input
                      v-model="fundQueryParam.donorname"
                      placeholder="请输入捐献者姓名"
                      clearable
                      size="small"
                      @keyup.enter.native="handleQueryFund"
                    />
                  </el-form-item>
                </el-col>
                <el-col :span="6">
                  <el-form-item label="审批状态" prop="recordstatus">
                    <el-select
                      v-model="fundQueryParam.recordstatus"
                      placeholder="请选择审批状态"
                      clearable
                      size="small"
                      style="width: 100%"
                    >
                      <el-option
                        v-for="dict in dict.type.sys_recordstatus"
                        :key="dict.value"
                        :label="dict.label"
                        :value="Number(dict.value)"
                      />
                    </el-select>
                  </el-form-item>
                </el-col>
                <el-col :span="6">
                  <el-form-item label-width="10px">
                    <el-button
                      type="primary"
                      icon="el-icon-search"
                      size="small"
                      @click="handleQueryFund"
                      >搜索</el-button
                    >
                    <el-button
                      icon="el-icon-refresh"
                      size="small"
                      @click="resetFundQuery"
                      >重置</el-button
                    >
                  </el-form-item>
                </el-col>
              </el-row>
            </el-form>
            <el-row :gutter="10" class="mb8">
              <el-col :span="1.5">
                <el-button
@@ -268,6 +215,8 @@
                    <!-- v-hasPermi="['project:expretfund:download']" -->
                  </template>
                </el-table-column>
                <!-- 2026-10-22:按要求隐藏「功能」列(线下报销),需要时取消注释即可恢复 -->
                <!--
                <el-table-column
                  fixed="right"
                  align="center"
@@ -287,12 +236,121 @@
                    >
                  </template>
                </el-table-column>
                -->
              </el-table>
              <!-- æœªé€‰æ‹©æ¡ˆä¾‹æ—¶å±•示该登录人全部专家劳务单据,超过一页可用分页翻看 -->
              <pagination
                v-show="fundTotal > 0"
                :total="fundTotal"
                small
                :page.sync="fundQueryParam.pageNum"
                :limit.sync="fundQueryParam.pageSize"
                @pagination="listFundData"
              />
            </el-row>
          </el-form>
        </el-card>
      </el-col>
    </el-row>
    <!-- æ–°å¢žç”³è¯·å•前先选择捐献案例(原左侧案例列表移到这里) -->
    <el-dialog
      title="选择捐献案例"
      :visible.sync="caseDialogVisible"
      width="760px"
      append-to-body
    >
      <el-form
        class="opo-query-form"
        :model="queryParams"
        ref="queryForm"
        :inline="true"
        label-width="70px"
      >
        <el-row>
          <el-col :span="8">
            <el-form-item label="姓名" prop="name">
              <el-input
                v-model="queryParams.name"
                placeholder="请输入姓名"
                clearable
                size="small"
                @keyup.enter.native="handleQuery"
              />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="捐献进度" prop="recordstate">
              <el-select
                v-model="queryParams.recordstate"
                placeholder="请选择记录状态"
                clearable
                size="small"
                style="width: 100%"
              >
                <el-option
                  v-for="dict in dict.type.sys_DonationStatus"
                  :key="dict.value"
                  :label="dict.label"
                  :value="dict.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label-width="10px">
              <el-button
                type="primary"
                icon="el-icon-search"
                size="small"
                @click="handleQuery"
                >搜索</el-button
              >
              <el-button icon="el-icon-refresh" size="small" @click="resetQuery"
                >重置</el-button
              >
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <el-table
        ref="caseTable"
        v-loading="loading"
        border
        height="380"
        highlight-current-row
        :data="donatebaseinfoList"
        @row-click="selectDonotor"
      >
        <el-table-column label="捐献进度" align="center" prop="recordstate">
          <template slot-scope="scope">
            <dict-tag
              :options="dict.type.sys_DonationStatus"
              :value="scope.row.recordstate"
            />
          </template>
        </el-table-column>
        <el-table-column label="姓名" align="center" prop="name" />
        <el-table-column label="操作" align="center" width="90">
          <template slot-scope="scope">
            <el-button type="text" size="mini" @click="selectDonotor(scope.row)"
              >选择</el-button
            >
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total > 0"
        :total="total"
        small
        :page.sync="queryParams.pageNum"
        :limit.sync="queryParams.pageSize"
        @pagination="getBaseInfoList"
      />
      <div slot="footer" class="dialog-footer">
        <el-button @click="caseDialogVisible = false">取 æ¶ˆ</el-button>
      </div>
    </el-dialog>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹è´¹ç”¨ç”³è¯·å• -->
    <el-dialog
@@ -317,7 +375,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="申请日期"
              >
@@ -1112,7 +1170,8 @@
  getdownloadYX,
  getdownloadBX,
  addorupdateFund,
  getdetailsByItemId
  getdetailsByItemId,
  validateExpertFee
} from "@/api/project/fund";
import {
@@ -1159,6 +1218,7 @@
import OrgSelecter from "@/views/project/components/orgselect";
import elementIcons from "../../../components/icons/element-icons";
import { getToken } from "@/utils/auth";
import { moneySum, moneyAdd, moneySub, moneyMul, moneyRound } from "@/utils/money";
export default {
  //import引入的组件需要注入到对象中才能使用
@@ -1212,11 +1272,16 @@
      },
      fundQueryParam: {
        pageNum: 1,
        pageSize: 100,
        pageSize: 10,
        infoid: null,
        applytype: "1",
        createBy: null
        createBy: null,
        // æŠ¥é”€å•筛选条件
        donorname: null,
        recordstatus: null
      },
      // å³ä¾§æŠ¥é”€å•总数(用于分页)
      fundTotal: 0,
      // è¡¨å•参数
      form: {},
      //所有人员表
@@ -1265,6 +1330,9 @@
      donatebaseinfoList: [],
      //当前选中捐献案例
      curCase: {},
      // ã€Œé€‰æ‹©æçŒ®æ¡ˆä¾‹ã€å¯¹è¯æ¡†ï¼ˆæ–°å¢žç”³è¯·å•时使用)
      caseDialogVisible: false,
      casePickerMode: "",
      //是否显示费用申请弹窗
      dialogOpen: false,
      // title: "",
@@ -1360,11 +1428,12 @@
  watch: {},
  //生命周期 - åˆ›å»ºå®Œæˆï¼ˆå¯ä»¥è®¿é—®å½“前this实例)
  created() {
    this.getList();
    this.getBaseInfoList();
    // æ¡ˆä¾‹åˆ—表已移入「选择捐献案例」对话框,打开对话框时再查询
    this.getCurFundType();
    this.getUserList();
    this.getUnitList();
    // é¦–屏展示当前登录人的全部专家劳务单据
    this.allgetfundList();
  },
  //生命周期 - æŒ‚载完成(可以访问DOM元素)
@@ -1408,57 +1477,24 @@
      });
    },
    /** æ–°å¢žæŒ‰é’®æ“ä½œ */
    /** æ–°å¢žæŒ‰é’®æ“ä½œï¼šå…ˆå¼¹å‡ºã€Œé€‰æ‹©æçŒ®æ¡ˆä¾‹ã€å¯¹è¯æ¡† */
    handleAdd() {
      if (this.curCase.id) {
        this.$router.push({
          path: "/finance/applyDetail/",
          query: {
            id: 0,
            businessType: "1",
            operationType: "add",
            curCase: this.curCase
          }
        });
      } else {
        const h = this.$createElement;
        this.$message({
          message: h("p", null, [
            h("span", null, "请在左边的捐献案例表选择需要操作的案例!")
          ])
        });
      }
      return;
      this.casePickerMode = "add";
      this.caseDialogVisible = true;
      this.getBaseInfoList();
    },
      if (this.curCase.id) {
        this.istrue += 2;
        this.reset();
        this.queryParams.params = {};
        this.form.infoid = this.curCase.id;
        this.form.donorno = this.curCase.donorno;
        this.form.donorname = this.curCase.name;
        this.form.username = this.defaultperson.nickName;
        this.form.userno = this.defaultperson.userName;
        this.form.phone = this.defaultperson.phonenumber;
        this.form.deptmentname = this.defaultperson.dept.deptName;
        this.form.deptmentno = this.defaultperson.dept.deptId;
        this.form.managername = this.defaultperson.dept.leader;
        // this.form.createTime = nowdate;
        //this.open = true;
        //this.initFundApplyForm();
        this.fundDetails = [];
        this.addRow();
        this.dialogOpen = true;
        this.dialogType = "edit";
        this.title = "费用申请";
      } else {
        const h = this.$createElement;
        this.$message({
          message: h("p", null, [h("span", null, "请先选择左方捐献案例 ")])
        });
      }
    /** æºå¸¦å·²é€‰æ¡ˆä¾‹è·³è½¬åˆ°ç”³è¯·å•填写页 */
    goApplyAdd() {
      this.$router.push({
        path: "/finance/applyDetail/",
        query: {
          id: 0,
          businessType: "1",
          operationType: "add",
          curCase: this.curCase
        }
      });
    },
    /** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
@@ -1824,21 +1860,37 @@
            // });
          }
          addorupdateFund(this.form).then(response => {
            if (response.code === 200) {
              this.selectDonotor(this.curCase);
              this.$modal.msgSuccess("专家劳务费保存成功!");
            } else {
              this.$modal.msgError("专家劳务费保存失败:" + response.msg);
            }
          });
          // ä¿å­˜å‰å…ˆåšä¸“家劳务费 9 æ¡è´¹ç”¨æŽ§åˆ¶æ ¡éªŒï¼ˆåŒä¸€æ¡ˆä¾‹ç´¯è®¡ï¼‰
          validateExpertFee(this.form).then(valid => {
            const errors = (valid && valid.errors) || [];
            const warnings = (valid && valid.warnings) || [];
          //关闭窗口
          this.loading = false;
          this.dialogOpen = false;
          // this.getfundList();
          this.getList();
          this.selectDonotor(this.curCase);
            // warnings(规则 5、6)仅提示,不阻断保存
            if (warnings.length > 0) {
              this.$modal.msgWarning(warnings.join(";"));
            }
            // errors é˜»æ–­ä¿å­˜ï¼Œä¿ç•™çª—口让经办人先修正
            if (errors.length > 0) {
              this.$modal.msgError(errors.join(";"));
              this.loading = false;
              return;
            }
            addorupdateFund(this.form).then(response => {
              if (response.code === 200) {
                //关闭窗口
                this.loading = false;
                this.dialogOpen = false;
                this.selectDonotor(this.curCase);
                this.$modal.msgSuccess("专家劳务费保存成功!");
                this.getList();
              } else {
                this.$modal.msgError("专家劳务费保存失败:" + response.msg);
                this.loading = false;
              }
            });
          });
        }
      });
    },
@@ -2152,25 +2204,57 @@
    //点击捐献案例列表触发方法
    selectDonotor(row, column, event) {
      this.curCase = row;
      this.getfundList();
      if (this.casePickerMode === "add") {
        // ä»Žã€Œæ–°å¢žç”³è¯·å•」进入:选完案例直接跳到申请单填写页
        this.casePickerMode = "";
        this.caseDialogVisible = false;
        this.goApplyAdd();
      }
    },
    allgetfundList() {
      this.loading = true;
      // æ£€ç´¢å…¨éƒ¨ï¼šå–消左侧案例选择与筛选条件,查询当前登录人的全部专家劳务单据
      this.curCase = {};
      if (this.$refs.caseTable) {
        this.$refs.caseTable.setCurrentRow();
      }
      this.reset();
      this.fundQueryParam.infoid = null;
      this.fundQueryParam.donorname = null;
      this.fundQueryParam.recordstatus = null;
      this.fundQueryParam.pageNum = 1;
      this.listFundData();
    },
    /** æŠ¥é”€å•筛选:搜索 */
    handleQueryFund() {
      this.fundQueryParam.pageNum = 1;
      this.listFundData();
    },
    /** æŠ¥é”€å•筛选:重置(清空条件与已选案例,回到该登录人全部单据) */
    resetFundQuery() {
      this.fundQueryParam.donorname = null;
      this.fundQueryParam.recordstatus = null;
      this.allgetfundList();
    },
    /**
     * æŒ‰å½“前 fundQueryParam æŸ¥è¯¢å³ä¾§æŠ¥é”€å•列表
     * infoid ä¸ºç©º = å½“前登录人的全部该类型单据;有值 = æŒ‡å®šæ¡ˆä¾‹
     */
    listFundData() {
      this.loading = true;
      listFund(this.fundQueryParam).then(response => {
        this.loading = false;
        this.donateconsolationfundList = response.rows;
        this.fundTotal = response.total;
      });
    },
    getfundList() {
      this.loading = true;
      this.reset();
      // é€‰ä¸­æ¡ˆä¾‹åŽæŒ‰æ¡ˆä¾‹å±•示,同时清掉列表筛选条件,避免两个条件叠加导致空列表
      this.fundQueryParam.donorname = null;
      this.fundQueryParam.recordstatus = null;
      this.fundQueryParam.infoid = this.curCase.id;
      listFund(this.fundQueryParam).then(response => {
        this.loading = false;
        this.donateconsolationfundList = response.rows;
      });
      this.fundQueryParam.pageNum = 1;
      this.listFundData();
    },
    //导入所有项目
@@ -2451,43 +2535,34 @@
    //计算个税
    calculateTax() {},
    //计算总金额
    //计算总金额(金额一律走精确运算)
    sumTotalFee(row) {
      let allSum = 0;
      const detailsArr = [];
      if (this.form.applytype != null) {
        for (let i = 0; i < this.fundDetails.length; i++) {
          const detail = this.fundDetails[i];
          let totalFee = 0;
          let taxFee = 0;
          if (this.form.applytype == 1 || this.form.applytype == 2) {
            //计算当前报销内容费用合计
            if (!isNaN(parseFloat(this.fundDetails[i].amount))) {
              totalFee += parseFloat(this.fundDetails[i].amount);
              taxFee += parseFloat(this.fundDetails[i].amount);
            //计算当前报销内容费用合计:税前 - æ‰£ç¨Ž
            if (!isNaN(parseFloat(detail.amount))) {
              totalFee = moneyAdd(totalFee, detail.amount);
            }
            if (!isNaN(parseFloat(this.fundDetails[i].taxamount))) {
              totalFee -= parseFloat(this.fundDetails[i].taxamount);
              taxFee -= parseFloat(this.fundDetails[i].taxamount);
            if (!isNaN(parseFloat(detail.taxamount))) {
              totalFee = moneySub(totalFee, detail.taxamount);
            }
          } else if (this.form.applytype == 3) {
            if (
              !isNaN(parseFloat(this.fundDetails[i].price)) &&
              !isNaN(parseFloat(this.fundDetails[i].quantity))
              !isNaN(parseFloat(detail.price)) &&
              !isNaN(parseFloat(detail.quantity))
            ) {
              totalFee +=
                parseFloat(this.fundDetails[i].price) *
                parseFloat(this.fundDetails[i].quantity);
              totalFee = moneyMul(detail.price, detail.quantity);
            }
          }
          //税前、税后需要独立录入
          // if (!isNaN(parseFloat(taxFee))) {
          //   this.fundDetails[i].taxedamount = taxFee.toFixed(2);
          // }
          allSum += totalFee;
          detailsArr.push(totalFee);
        }
      }
      // this.form.amountrequested = allSum.toFixed(2);
      // åˆè®¡ä»åœ¨ç¡®è®¤å£å¾„后启用;先保证计算本身是精确的
      // this.form.amountrequested = moneyRound(moneySum(detailsArr)).toFixed(2);
      // this.$forceUpdate();
    },
src/views/project/fund/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="捐献者编号" prop="donorno">
        <el-input v-model="queryParams.donorno" placeholder="请输入捐献者编号" clearable size="small"
          @keyup.enter.native="handleQuery" />
src/views/project/fund/medicalCostApply/index.vue
@@ -2,253 +2,94 @@
<template>
  <div class="app-container">
    <el-row>
      <el-col :span="6">
        <el-card shadow="never">
          <!-- æçŒ®æ¡ˆä¾‹åˆ—表 -->
          <el-row>
            <el-col :span="10">
              <el-row type="flex" :gutter="10" align="right" class="mb8">
              </el-row>
            </el-col>
          </el-row>
          <div>
            <el-form
              :model="queryParams"
              ref="queryForm"
              :inline="true"
              label-width="70px"
            >
              <el-form-item label="姓名" prop="name">
                <el-input
                  v-model="queryParams.name"
                  placeholder="请输入姓名"
                  clearable
                  size="small"
                  @keyup.enter.native="handleQuery"
                />
              </el-form-item>
              <el-form-item label="捐献进度" prop="recordstate">
                <el-select
                  v-model="queryParams.recordstate"
                  placeholder="请选择记录状态"
                  clearable
                  size="small"
                >
                  <el-option
                    v-for="dict in dict.type.sys_DonationStatus"
                    :key="dict.value"
                    :label="dict.label"
                    :value="dict.value"
                  />
                </el-select>
              </el-form-item>
              <div style="float: right; margin-bottom: 10px;">
                <el-button
                  type="primary"
                  icon="el-icon-search"
                  size="mini"
                  @click="handleQuery"
                  >搜索</el-button
                >
                <el-button
                  icon="el-icon-refresh"
                  size="mini"
                  @click="resetQuery"
                  >重置</el-button
                >
              </div>
            </el-form>
          </div>
          <el-table
            v-loading="loading"
            border
            highlight-current-row
            :data="donatebaseinfoList"
            @row-click="selectDonotor"
          >
            <!-- <el-table-column label="捐献编号" align="center" prop="donorno" /> -->
            <el-table-column label="捐献进度" align="center" prop="recordstate">
              <template slot-scope="scope">
                <dict-tag
                  :options="dict.type.sys_DonationStatus"
                  :value="scope.row.recordstate"
                />
              </template>
            </el-table-column>
            <el-table-column label="姓名" align="center" prop="name" />
          </el-table>
          <pagination
            v-show="total > 0"
            :total="total"
            small
            :page.sync="queryParams.pageNum"
            :limit.sync="queryParams.pageSize"
            @pagination="getBaseInfoList"
          />
        </el-card>
      </el-col>
      <el-col :span="18">
      <el-col :span="24">
        <el-card shadow="naver">
          <el-form ref="infoform" label-width="100px">
            <el-row :gutter="4" align="right" class="mb8">
              <el-col :span="9">
                <el-form-item label="捐献编号">
                  <el-input
                    v-model="curCase.donorno"
                    :disabled="true"
                  ></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="9">
                <el-form-item label="姓名">
                  <el-input v-model="curCase.name" :disabled="true"></el-input>
                </el-form-item>
              </el-col>
              <el-col> </el-col>
            </el-row>
            <!-- æŠ¥é”€å•筛选:捐献者、审批状态 -->
            <el-form class="opo-query-form" :model="fundQueryParam" ref="fundQueryForm" :inline="true"
              label-width="82px">
              <el-row>
                <el-col :span="6">
                  <el-form-item label="捐献者" prop="donorname">
                    <el-input v-model="fundQueryParam.donorname" placeholder="请输入捐献者姓名" clearable size="small"
                      @keyup.enter.native="handleQueryFund" />
                  </el-form-item>
                </el-col>
                <el-col :span="6">
                  <el-form-item label="审批状态" prop="recordstatus">
                    <el-select v-model="fundQueryParam.recordstatus" placeholder="请选择审批状态" clearable size="small"
                      style="width: 100%">
                      <el-option v-for="dict in dict.type.sys_recordstatus" :key="dict.value" :label="dict.label"
                        :value="Number(dict.value)" />
                    </el-select>
                  </el-form-item>
                </el-col>
                <el-col :span="6">
                  <el-form-item label-width="10px">
                    <el-button type="primary" icon="el-icon-search" size="small" @click="handleQueryFund">搜索</el-button>
                    <el-button icon="el-icon-refresh" size="small" @click="resetFundQuery">重置</el-button>
                  </el-form-item>
                </el-col>
              </el-row>
            </el-form>
            <el-row :gutter="10" class="mb8">
              <el-col :span="1.5">
                <el-button
                  type="primary"
                  plain
                  icon="el-icon-plus"
                  size="mini"
                  @click="handleAdd"
                  v-hasPermi="['project:medicalfund:add']"
                  >新增申请单</el-button
                >
                <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
                  v-hasPermi="['project:medicalfund:add']">新增申请单</el-button>
              </el-col>
            </el-row>
            <el-row>
              <el-table
                v-loading="loading"
                border
                :data="donateconsolationfundList"
                :row-class-name="rowClassName"
              >
                <el-table-column
                  label="申请日期"
                  align="center"
                  prop="applyTime"
                  width="150px"
                >
              <el-table v-loading="loading" border :data="donateconsolationfundList" :row-class-name="rowClassName">
                <el-table-column label="申请日期" align="center" prop="applyTime" width="150px">
                  <template slot-scope="scope">
                    <span>{{
                      parseTime(scope.row.applyTime, "{y}-{m}-{d}")
                    }}</span>
                  </template>
                </el-table-column>
                <el-table-column
                  label="申请人"
                  align="center"
                  prop="username"
                  width="120px"
                />
                <el-table-column
                  label="申请金额"
                  align="center"
                  prop="pretaxcost"
                  width="200px"
                />
                <el-table-column
                  label="所属组别"
                  align="center"
                  prop="deptmentname"
                  width="220px"
                />
                <el-table-column
                  label="组长"
                  align="center"
                  prop="managername"
                  width="200px"
                />
                <el-table-column
                  label="审核状态"
                  align="center"
                  prop="recordstatus"
                >
                <el-table-column label="经办人" align="center" prop="username" width="120px" />
                <el-table-column label="申请金额" align="center" prop="pretaxcost" width="200px" />
                <el-table-column label="审核状态" align="center" prop="recordstatus">
                  <template slot-scope="scope">
                    <dict-tag
                      :options="dict.type.sys_recordstatus"
                      :value="scope.row.recordstatus"
                    />
                    <dict-tag :options="dict.type.sys_recordstatus" :value="scope.row.recordstatus" />
                  </template>
                </el-table-column>
                <el-table-column
                  label="材料预审状态"
                  width="140"
                  align="center"
                  prop="checkstatus"
                >
                <el-table-column label="材料预审状态" width="140" align="center" prop="checkstatus">
                  <template slot-scope="scope">
                    <dict-tag
                      :options="dict.type.sys_stage_type"
                      :value="scope.row.checkstatus"
                    />
                    <dict-tag :options="dict.type.sys_stage_type" :value="scope.row.checkstatus" />
                  </template>
                </el-table-column>
                <el-table-column label="捐献者" align="center" prop="donorname" show-overflow-tooltip />
                <el-table-column label="所属组别" align="center" prop="deptmentname"/>
                <el-table-column label="组长" align="center" prop="managername" />
                <!--
                  <el-table-column label="联系电话" align="center" prop="phone" width="150px" />
                <el-table-column label="工作单位" align="center" prop="unitname" width="150px" />
                -->
                <el-table-column
                  label="操作"
                  fixed="right"
                  align="center"
                  class-name="small-padding fixed-width"
                  width="280px"
                >
                <el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width"
                  width="280px">
                  <template slot-scope="scope">
                    <el-button
                      v-if="
                        scope.row.recordstatus == -1 ||
                          scope.row.recordstatus == 1
                      "
                      size="mini"
                      type="text"
                      icon="el-icon-edit"
                      @click="handleup(scope.row)"
                      >上报</el-button
                    >
                    <el-button
                      v-if="
                        scope.row.recordstatus == -1 ||
                          scope.row.recordstatus == 1
                      "
                      size="mini"
                      type="text"
                      icon="el-icon-edit"
                      @click="handleUpdate(scope.row)"
                      >修改</el-button
                    >
                    <el-button
                      size="mini"
                      type="text"
                      icon="el-icon-view"
                      @click="handleDetail(scope.row)"
                      >查看</el-button
                    >
                    <el-button
                      v-if="
                        scope.row.recordstatus == -1 ||
                          scope.row.recordstatus == 1
                      "
                      size="mini"
                      type="text"
                      @click="handleDelete(scope.row)"
                      ><span class="button-delete"
                        ><i class="el-icon-delete"></i>删除</span
                      ></el-button
                    >
                    <el-button
                      size="mini"
                      type="text"
                      icon="el-icon-download"
                      @click="mixExport(scope.row.id)"
                      >下载</el-button
                    >
                    <el-button v-if="
                      scope.row.recordstatus == -1 ||
                      scope.row.recordstatus == 1
                    " size="mini" type="text" icon="el-icon-edit" @click="handleup(scope.row)">上报</el-button>
                    <el-button v-if="
                      scope.row.recordstatus == -1 ||
                      scope.row.recordstatus == 1
                    " size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
                    <el-button size="mini" type="text" icon="el-icon-view"
                      @click="handleDetail(scope.row)">查看</el-button>
                    <el-button v-if="
                      scope.row.recordstatus == -1 ||
                      scope.row.recordstatus == 1
                    " size="mini" type="text" @click="handleDelete(scope.row)"><span class="button-delete"><i
                          class="el-icon-delete"></i>删除</span></el-button>
                    <el-button size="mini" type="text" icon="el-icon-download"
                      @click="mixExport(scope.row.id)">下载</el-button>
                  </template>
                </el-table-column>
                <!-- 2026-10-22:按要求隐藏「功能」列(线下报销),需要时取消注释即可恢复 -->
                <!--
                <el-table-column
                  fixed="right"
                  align="center"
@@ -268,12 +109,64 @@
                    >
                  </template>
                </el-table-column>
                -->
              </el-table>
              <!-- æœªé€‰æ‹©æ¡ˆä¾‹æ—¶å±•示该登录人全部医学成本单据,超过一页可用分页翻看 -->
              <pagination v-show="fundTotal > 0" :total="fundTotal" small :page.sync="fundQueryParam.pageNum"
                :limit.sync="fundQueryParam.pageSize" @pagination="listFundData" />
            </el-row>
          </el-form>
        </el-card>
      </el-col>
    </el-row>
    <!-- æ–°å¢žç”³è¯·å•前先选择捐献案例(原左侧案例列表移到这里) -->
    <el-dialog title="选择捐献案例" :visible.sync="caseDialogVisible" width="760px" append-to-body>
      <el-form class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" label-width="70px">
        <el-row>
          <el-col :span="8">
            <el-form-item label="姓名" prop="name">
              <el-input v-model="queryParams.name" placeholder="请输入姓名" clearable size="small"
                @keyup.enter.native="handleQuery" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="捐献进度" prop="recordstate">
              <el-select v-model="queryParams.recordstate" placeholder="请选择记录状态" clearable size="small"
                style="width: 100%">
                <el-option v-for="dict in dict.type.sys_DonationStatus" :key="dict.value" :label="dict.label"
                  :value="dict.value" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label-width="10px">
              <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
              <el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <el-table ref="caseTable" v-loading="loading" border height="380" highlight-current-row :data="donatebaseinfoList"
        @row-click="selectDonotor">
        <el-table-column label="捐献进度" align="center" prop="recordstate">
          <template slot-scope="scope">
            <dict-tag :options="dict.type.sys_DonationStatus" :value="scope.row.recordstate" />
          </template>
        </el-table-column>
        <el-table-column label="姓名" align="center" prop="name" />
        <el-table-column label="操作" align="center" width="90">
          <template slot-scope="scope">
            <el-button type="text" size="mini" @click="selectDonotor(scope.row)">选择</el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" small :page.sync="queryParams.pageNum"
        :limit.sync="queryParams.pageSize" @pagination="getBaseInfoList" />
      <div slot="footer" class="dialog-footer">
        <el-button @click="caseDialogVisible = false">取 æ¶ˆ</el-button>
      </div>
    </el-dialog>
  </div>
</template>
@@ -335,6 +228,7 @@
import { getUserProfile } from "@/api/system/user";
import { listFundflow } from "@/api/project/fundflow";
import { getToken } from "@/utils/auth";
import { moneySum, moneyAdd, moneySub, moneyMul, moneyRound } from "@/utils/money";
export default {
  //import引入的组件需要注入到对象中才能使用
@@ -385,10 +279,13 @@
      fundQueryParam: {
        pageNum: 1,
        pageSize: 100,
        pageSize: 10,
        infoid: null,
        applytype: "3",
        createBy: null
        createBy: null,
        // æŠ¥é”€å•筛选条件
        donorname: null,
        recordstatus: null
      },
      // è¡¨å•参数
      form: {},
@@ -410,6 +307,9 @@
      donatebaseinfoList: [],
      //当前选中捐献案例
      curCase: {},
      // ã€Œé€‰æ‹©æçŒ®æ¡ˆä¾‹ã€å¯¹è¯æ¡†ï¼ˆæ–°å¢žç”³è¯·å•时使用)
      caseDialogVisible: false,
      casePickerMode: "",
      //是否显示费用申请弹窗
      dialogOpen: false,
      // title: "",
@@ -418,6 +318,8 @@
      // æçŒ®äººé“慰问金表格数据
      donateconsolationfundList: [],
      // å³ä¾§æŠ¥é”€å•总数(用于分页)
      fundTotal: 0,
      //明细记录
      fundDetails: [],
@@ -471,11 +373,12 @@
  watch: {},
  //生命周期 - åˆ›å»ºå®Œæˆï¼ˆå¯ä»¥è®¿é—®å½“前this实例)
  created() {
    this.getList();
    this.getBaseInfoList();
    // æ¡ˆä¾‹åˆ—表已移入「选择捐献案例」对话框,打开对话框时再查询
    this.getCurFundType();
    this.getlistUser();
    // this.getUnitList();
    // é¦–屏展示当前登录人的全部医学成本单据
    this.getAllfundList();
  },
  //生命周期 - æŒ‚载完成(可以访问DOM元素)
@@ -484,13 +387,13 @@
    this.getUsermsg();
  },
  beforeCreate() {}, //生命周期 - åˆ›å»ºä¹‹å‰
  beforeMount() {}, //生命周期 - æŒ‚载之前
  beforeUpdate() {}, //生命周期 - æ›´æ–°ä¹‹å‰
  updated() {}, //生命周期 - æ›´æ–°ä¹‹åŽ
  beforeDestroy() {}, //生命周期 - é”€æ¯ä¹‹å‰
  destroyed() {}, //生命周期 - é”€æ¯å®Œæˆ
  activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
  beforeCreate() { }, //生命周期 - åˆ›å»ºä¹‹å‰
  beforeMount() { }, //生命周期 - æŒ‚载之前
  beforeUpdate() { }, //生命周期 - æ›´æ–°ä¹‹å‰
  updated() { }, //生命周期 - æ›´æ–°ä¹‹åŽ
  beforeDestroy() { }, //生命周期 - é”€æ¯ä¹‹å‰
  destroyed() { }, //生命周期 - é”€æ¯å®Œæˆ
  activated() { }, //如果页面有keep-alive缓存功能,这个函数会触发
  //方法集合
  methods: {
@@ -499,7 +402,7 @@
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** é‡ç½®æŒ‰é’®æ“ä½œ */
    /** é‡ç½®æŒ‰é’®æ“ä½œï¼ˆæ¡ˆä¾‹é€‰æ‹©å¯¹è¯æ¡†å†…的案例查询) */
    resetQuery() {
      this.daterangeReporttime = [];
@@ -536,55 +439,24 @@
      });
    },
    /** æ–°å¢žæŒ‰é’®æ“ä½œ */
    /** æ–°å¢žæŒ‰é’®æ“ä½œï¼šå…ˆå¼¹å‡ºã€Œé€‰æ‹©æçŒ®æ¡ˆä¾‹ã€å¯¹è¯æ¡† */
    handleAdd() {
      if (this.curCase.id) {
        this.$router.push({
          path: "/finance/applyDetail/",
          query: {
            id: 0,
            businessType: "3",
            operationType: "add",
            curCase: this.curCase,
          }
        });
      } else {
        const h = this.$createElement;
        this.$message({
          message: h("p", null, [
            h("span", null, "请在左边的捐献案例表选择需要操作的案例!")
          ])
        });
      }
      return;
      this.casePickerMode = "add";
      this.caseDialogVisible = true;
      this.getBaseInfoList();
    },
      if (this.curCase.id) {
        this.istrue += 2;
        this.reset();
        this.queryParams.params = {};
        this.form.donorno = this.curCase.donorno;
        this.form.name = this.curCase.name;
        this.form.infoid = this.curCase.id;
        this.form.username = this.defaultperson.nickName;
        this.form.userno = this.defaultperson.userName;
        this.form.deptmentname = this.defaultperson.dept.deptName;
        this.form.deptmentno = this.defaultperson.dept.deptId;
        this.form.managername = this.defaultperson.dept.leader;
        // this.form.createTime = nowdate;
        //this.open = true;
        //this.initFundApplyForm();
        this.fundDetails = [];
        this.addRow();
        this.dialogOpen = true;
        this.title = "添加费用申请";
        this.dialogType = "edit";
      } else {
        const h = this.$createElement;
        this.$message({
          message: h("p", null, [h("span", null, "请先选择左方捐献案例 ")])
        });
      }
    /** æºå¸¦å·²é€‰æ¡ˆä¾‹è·³è½¬åˆ°ç”³è¯·å•填写页 */
    goApplyAdd() {
      this.$router.push({
        path: "/finance/applyDetail/",
        query: {
          id: 0,
          businessType: "3",
          operationType: "add",
          curCase: this.curCase,
        }
      });
    },
    /** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
    handleUpdate(row) {
@@ -615,8 +487,8 @@
        //附件处理
        this.fileList = this.form.annexbankcard
          ? this.form.annexbankcard
              .split(",")
              .map(item => ({ url: item, name: item }))
            .split(",")
            .map(item => ({ url: item, name: item }))
          : [];
        getownFundDetail(id).then(async res => {
@@ -662,8 +534,8 @@
        //附件处理
        this.fileList = this.form.annexbankcard
          ? this.form.annexbankcard
              .split(",")
              .map(item => ({ url: item, name: item }))
            .split(",")
            .map(item => ({ url: item, name: item }))
          : [];
        listFundflow(listFundflowparams).then(res => {
@@ -691,7 +563,7 @@
      const ids = row.id || this.ids;
      this.$modal
        .confirm("是否确认删除该记录?")
        .then(function() {
        .then(function () {
          return delFund(ids);
        })
        .then(() => {
@@ -705,7 +577,7 @@
          this.getfundList();
          this.$modal.msgSuccess("删除成功");
        })
        .catch(() => {});
        .catch(() => { });
    },
    /** ä¸ŠæŠ¥æŒ‰é’®æ“ä½œ */
@@ -884,20 +756,68 @@
      });
    },
    //点击捐献案例列表触发方法
    //点击捐献案例列表触发方法(案例列表已移入「选择捐献案例」对话框)
    selectDonotor(row, column, event) {
      this.curCase = row;
      this.getfundList();
      if (this.casePickerMode === "add") {
        // ä»Žã€Œæ–°å¢žç”³è¯·å•」进入:选完案例直接跳到申请单填写页
        this.casePickerMode = "";
        this.caseDialogVisible = false;
        this.goApplyAdd();
      }
    },
    getfundList() {
      this.loading = true;
      this.reset();
      // é€‰ä¸­æ¡ˆä¾‹åŽæŒ‰æ¡ˆä¾‹å±•示,同时清掉列表筛选条件,避免两个条件叠加导致空列表
      this.fundQueryParam.donorname = null;
      this.fundQueryParam.recordstatus = null;
      this.fundQueryParam.infoid = this.curCase.id;
      this.fundQueryParam.pageNum = 1;
      this.listFundData();
    },
    /**
     * æŒ‰å½“前 fundQueryParam æŸ¥è¯¢å³ä¾§æŠ¥é”€å•列表
     * infoid ä¸ºç©º = å½“前登录人的全部该类型单据;有值 = æŒ‡å®šæ¡ˆä¾‹
     */
    listFundData() {
      this.loading = true;
      listFund(this.fundQueryParam).then(response => {
        this.loading = false;
        this.donateconsolationfundList = response.rows;
        this.fundTotal = response.total;
      });
    },
    /** æŠ¥é”€å•筛选:搜索 */
    handleQueryFund() {
      this.fundQueryParam.pageNum = 1;
      this.listFundData();
    },
    /** æŠ¥é”€å•筛选:重置(清空条件与已选案例,回到该登录人全部单据) */
    resetFundQuery() {
      this.fundQueryParam.donorname = null;
      this.fundQueryParam.recordstatus = null;
      this.getAllfundList();
    },
    /**
     * æ£€ç´¢å…¨éƒ¨ï¼šå–消左侧案例选择与筛选条件,查询当前登录人的全部医学成本单据
     */
    getAllfundList() {
      this.curCase = {};
      // åŒæ—¶æ¸…掉左侧案例列表的高亮选中态
      if (this.$refs.caseTable) {
        this.$refs.caseTable.setCurrentRow();
      }
      this.reset();
      this.fundQueryParam.infoid = null;
      this.fundQueryParam.donorname = null;
      this.fundQueryParam.recordstatus = null;
      this.fundQueryParam.pageNum = 1;
      this.listFundData();
    },
    // å–消按钮
@@ -1091,9 +1011,9 @@
                  tempData.itemid = tempItemArr[itemIndex].id;
                }
                if (tempData.id > 0) {
                  updateFunddetail(tempData).then(response2 => {});
                  updateFunddetail(tempData).then(response2 => { });
                } else {
                  addFunddetail(tempData).then(response2 => {});
                  addFunddetail(tempData).then(response2 => { });
                }
              }
            });
@@ -1190,41 +1110,40 @@
        });
    },
    //计算总金额
    //计算总金额(金额一律走精确运算,禁止 parseFloat ç´¯åŠ ï¼‰
    sumTotalFee(row) {
      let allSum = 0;
      const detailsArr = [];
      if (this.form.applytype != null) {
        for (let i = 0; i < this.fundDetails.length; i++) {
          const detail = this.fundDetails[i];
          let totalFee = 0;
          let taxFee = 0;
          if (this.form.applytype == 1 || this.form.applytype == 2) {
            //计算当前报销内容费用合计
            if (!isNaN(parseFloat(this.fundDetails[i].amount))) {
              totalFee += parseFloat(this.fundDetails[i].amount);
              taxFee += parseFloat(this.fundDetails[i].amount);
            //计算当前报销内容费用合计:税前 - æ‰£ç¨Ž
            if (!isNaN(parseFloat(detail.amount))) {
              totalFee = moneyAdd(totalFee, detail.amount);
              taxFee = moneyAdd(taxFee, detail.amount);
            }
            if (!isNaN(parseFloat(this.fundDetails[i].taxamount))) {
              totalFee -= parseFloat(this.fundDetails[i].taxamount);
              taxFee -= parseFloat(this.fundDetails[i].taxamount);
            if (!isNaN(parseFloat(detail.taxamount))) {
              totalFee = moneySub(totalFee, detail.taxamount);
              taxFee = moneySub(taxFee, detail.taxamount);
            }
          } else if (this.form.applytype == 3) {
            if (
              !isNaN(parseFloat(this.fundDetails[i].price)) &&
              !isNaN(parseFloat(this.fundDetails[i].quantity))
              !isNaN(parseFloat(detail.price)) &&
              !isNaN(parseFloat(detail.quantity))
            ) {
              totalFee +=
                parseFloat(this.fundDetails[i].price) *
                parseFloat(this.fundDetails[i].quantity);
              totalFee = moneyMul(detail.price, detail.quantity);
            }
          }
          if (!isNaN(parseFloat(taxFee))) {
            this.fundDetails[i].taxedamount = taxFee.toFixed(2);
            this.fundDetails[i].taxedamount = moneyRound(taxFee).toFixed(2);
          }
          allSum += totalFee;
          detailsArr.push(totalFee);
        }
      }
      //this.form.amountrequested = allSum.toFixed(2);
      this.$set(this.form, "amountrequested", allSum.toFixed(2));
      this.$set(this.form, "amountrequested", moneyRound(moneySum(detailsArr)).toFixed(2));
      //this.$forceUpdate();
    },
@@ -1397,7 +1316,7 @@
    },
    //文件上传
    handleUploadError() {},
    handleUploadError() { },
    handleremove(file) {
      this.fileList.splice(this.fileList.indexOf(file), 1);
@@ -1479,6 +1398,7 @@
.button-delete {
  color: rgb(236, 69, 69);
}
::v-deep.el-table .error-row {
  background: #fcebeb;
}
src/views/project/fund/officeExpenseApply/index.vue
@@ -2,6 +2,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -225,7 +226,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="申请日期"
              >
@@ -1709,41 +1710,40 @@
        });
    },
    //计算总金额
    //计算总金额(金额一律走精确运算,禁止 parseFloat ç´¯åŠ ï¼‰
    sumTotalFee(row) {
      let allSum = 0;
      const detailsArr = [];
      if (this.form.applytype != null) {
        for (let i = 0; i < this.fundDetails.length; i++) {
          const detail = this.fundDetails[i];
          let totalFee = 0;
          let taxFee = 0;
          if (this.form.applytype == 1 || this.form.applytype == 2) {
            //计算当前报销内容费用合计
            if (!isNaN(parseFloat(this.fundDetails[i].amount))) {
              totalFee += parseFloat(this.fundDetails[i].amount);
              taxFee += parseFloat(this.fundDetails[i].amount);
            //计算当前报销内容费用合计:税前 - æ‰£ç¨Ž
            if (!isNaN(parseFloat(detail.amount))) {
              totalFee = moneyAdd(totalFee, detail.amount);
              taxFee = moneyAdd(taxFee, detail.amount);
            }
            if (!isNaN(parseFloat(this.fundDetails[i].taxamount))) {
              totalFee -= parseFloat(this.fundDetails[i].taxamount);
              taxFee -= parseFloat(this.fundDetails[i].taxamount);
            if (!isNaN(parseFloat(detail.taxamount))) {
              totalFee = moneySub(totalFee, detail.taxamount);
              taxFee = moneySub(taxFee, detail.taxamount);
            }
          } else if (this.form.applytype == 3) {
            if (
              !isNaN(parseFloat(this.fundDetails[i].price)) &&
              !isNaN(parseFloat(this.fundDetails[i].quantity))
              !isNaN(parseFloat(detail.price)) &&
              !isNaN(parseFloat(detail.quantity))
            ) {
              totalFee +=
                parseFloat(this.fundDetails[i].price) *
                parseFloat(this.fundDetails[i].quantity);
              totalFee = moneyMul(detail.price, detail.quantity);
            }
          }
          if (!isNaN(parseFloat(taxFee))) {
            this.fundDetails[i].taxedamount = taxFee.toFixed(2);
            this.fundDetails[i].taxedamount = moneyRound(taxFee).toFixed(2);
          }
          allSum += totalFee;
          detailsArr.push(totalFee);
        }
      }
      //this.form.amountrequested = allSum.toFixed(2);
      this.$set(this.form, "amountrequested", allSum.toFixed(2));
      this.$set(this.form, "amountrequested", moneyRound(moneySum(detailsArr)).toFixed(2));
      //this.$forceUpdate();
    },
@@ -1929,7 +1929,9 @@
      if (response.code == 200) {
        this.form.filename = file.raw.name;
        this.$modal.msgSuccess(response.msg);
        this.fileList.push({ name: response.fileName, url: response.fileName });
        // 2026-10-15:列表里的 name ç”¨æ“ä½œäººçš„原始文件名,不要用 response.fileName(那是存储路径)。
        // ä¿å­˜æ—¶åªå›žå†™ url(form.annexbankcard å­˜çš„æ˜¯é€—号拼接的 url),所以这里只影响上传框的显示。
        this.fileList.push({ name: file.raw.name, url: response.fileName });
      } else {
        console.log(response.msg);
      }
src/views/project/fund/performanceApply/index.vue
@@ -2,6 +2,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -53,7 +54,7 @@
          <el-form-item label="申请日期" prop="applyTime">
            <el-date-picker
              format="yyyy-MM-dd"
              value-format="yyyy-MM-dd"
              value-format="yyyy-MM-dd HH:mm:ss"
              v-model="queryParams.value1"
              type="daterange"
              range-separator="至"
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"
@@ -115,7 +115,7 @@
                icon="el-icon-download"
                size="small"
                @click="Batchentry"
                >批量录入人员</el-button
                >批量导入绩效人员</el-button
              >
            </div>
          </el-col>
@@ -266,7 +266,7 @@
            <el-table-column
              prop="beneficiaryname"
              align="center"
              label="绩效人员或单位"
              label="绩效人员/单位"
              width="250"
            >
              <template slot-scope="scope">
@@ -283,13 +283,33 @@
              </template>
            </el-table-column>
            <el-table-column
              prop="isunit"
              align="center"
              label="人员类别"
              width="100"
            >
              <template slot-scope="scope">
                <el-tag
                  :type="scope.row.isunit == 1 ? 'warning' : 'success'"
                  size="mini"
                  >{{ scope.row.isunit == 1 ? "单位" : "个人" }}</el-tag
                >
              </template>
            </el-table-column>
            <el-table-column
              prop="idcardno"
              align="center"
              label="身份证号"
              width="200"
            >
              <template slot-scope="scope">
                <el-input v-model="scope.row.idcardno" placeholder="身份证号" />
                <el-input
                  v-model="scope.row.idcardno"
                  :disabled="scope.row.isunit == 1"
                  :placeholder="
                    scope.row.isunit == 1 ? '单位无需填写' : '身份证号'
                  "
                />
              </template>
            </el-table-column>
@@ -470,34 +490,43 @@
      :visible.sync="showSelectionDialog"
      :close-on-click-modal="false"
      :title="'绩效人员(含所在单位)账号选择'"
      width="500px"
      width="560px"
      style="text-align: center"
      v-loading="loading"
    >
      <el-form ref="funddetailForm" :model="funddetailForm">
        <el-row>
          <el-form-item label="请输入选择:" prop="unitno" label-width="120px">
          <el-form-item label="筛查人员:" prop="userKey" label-width="120px">
            <el-select
              v-model="funddetailForm.userName"
              v-model="funddetailForm.userKey"
              filterable
              remote
              reserve-keyword
              clearable
              allow-create
              default-first-option
              placeholder="请输入姓名或单位名称"
              :remote-method="remoteMethod"
              :loading="loadingperformance"
              :filter-method="filterPersonOption"
              placeholder="输入 å§“名 / å•位 / å·¥å· / èº«ä»½è¯åŽ4位 ç­›æŸ¥ï¼Œä¹Ÿå¯ç›´æŽ¥è¾“入姓名"
              style="width: 100%"
            >
              <el-option
                v-for="item in optionsperformance"
                :key="item.value"
                :label="item.label"
                :value="item.value"
                :key="item.id"
                :label="personOptionLabel(item)"
                :value="item.id"
              >
                <span style="float: left">{{ item.username }}</span>
                <span style="float: right; color: #8492a6; font-size: 13px">
                  {{
                    item.isUnitPerson ? "单位" : "工号 " + (item.userno || "-")
                  }}{{ item.unitname ? " Â· " + item.unitname : "" }}
                </span>
              </el-option>
            </el-select>
          </el-form-item>
        </el-row>
        <el-row style="text-align: left; padding-left: 120px">
          <span style="color: #909399; font-size: 12px">
            å€™é€‰ä¸ºäººå‘˜ç±»åž‹å«ã€Œå‚与绩效」的人员;选中后身份证号、银行账号等自动带出,单位为单位的行不校验身份证号。
          </span>
        </el-row>
      </el-form>
      <span slot="footer" class="dialog-footer">
@@ -798,12 +827,25 @@
          <el-radio-button :label="70">年度绩效</el-radio-button>
        </el-radio-group>
      </div>
      <div style="margin-bottom: 12px; margin-left: 10px;">
        <el-input
          v-model="personKeyword"
          clearable
          size="small"
          prefix-icon="el-icon-search"
          placeholder="输入 å§“名 / å•位 / å·¥å· / èº«ä»½è¯åŽ4位 ç­›æŸ¥"
          style="width: 330px"
        />
        <span style="margin-left: 10px; color: #909399; font-size: 12px">
          å…± {{ filteredCityOptions.length }} äºº
        </span>
      </div>
      <div style="max-height: 330px; overflow-y: auto">
        <el-checkbox
          :indeterminate="isIndeterminate"
          v-model="checkAll"
          @change="handleCheckAllChange"
          >全选</el-checkbox
          >全选(当前筛查结果)</el-checkbox
        >
        <div style="margin: 15px 0;"></div>
        <el-checkbox-group
@@ -812,10 +854,11 @@
        >
          <el-checkbox
            border
            v-for="city in cityOptions"
            v-for="city in filteredCityOptions"
            :label="city"
            :key="city.userNo"
            >{{ city.userName }}</el-checkbox
            :key="city.id"
            >{{ city.username }}{{ city.isUnitPerson ? "(单位)" : ""
            }}{{ city.unitname ? " Â· " + city.unitname : "" }}</el-checkbox
          >
        </el-checkbox-group>
      </div>
@@ -859,7 +902,6 @@
  listOrganization,
  getOrganization,
  listReportname,
  listperformance,
  listUser
} from "@/api/project/organization";
@@ -876,6 +918,8 @@
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 {
@@ -930,7 +974,10 @@
      feeUnitList: [],
      batchdialogVisible: false,
      itemlist: [],
      //绩效人员候选(人员类型含「参与绩效」的人员,来自 service_externalperson)
      cityOptions: [],
      //批量录入弹框的筛查关键字
      personKeyword: "",
      checkAll: false,
      checkedCities: [],
      Personnelforname: null,
@@ -1063,6 +1110,7 @@
      funddetailForm: {
        index: null,
        userKey: null,
        userName: null,
        beneficiaryname: null,
        beneficiaryno: null,
@@ -1198,7 +1246,16 @@
  },
  //监听属性 ç±»ä¼¼äºŽdata概念
  computed: {},
  computed: {
    //批量录入弹框:按关键字(姓名/单位/工号/身份证后4位)筛查后的候选人
    filteredCityOptions() {
      const kw = (this.personKeyword || "").trim().toLowerCase();
      if (!kw) {
        return this.cityOptions;
      }
      return this.cityOptions.filter(item => this.personMatchKeyword(item, kw));
    }
  },
  //监控data中的数据变化
  watch: {},
  //方法集合
@@ -1604,6 +1661,11 @@
    },
    /** æäº¤æŒ‰é’® */
    submitForm: debounce(function() {
      // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行)
      if (!this.checkDetailFileNames()) {
        return;
      }
      if (!this.randomnumber) {
        this.randomnumber = this.generateRandomString(9);
        this.form.randomnumber = this.randomnumber;
@@ -1624,7 +1686,12 @@
              );
              return;
            }
            if (tempDetail.idcardno == null) {
            // å•位行(isunit=1)不需要身份证号
            if (
              tempDetail.isunit != 1 &&
              (tempDetail.idcardno == null ||
                String(tempDetail.idcardno).trim() === "")
            ) {
              this.$modal.msgWarning(
                "服务项目表中,第" +
                  (k + 1).toString() +
@@ -1652,6 +1719,22 @@
                return;
              }
            }
          }
          //明细重复校验:同一人员/单位不允许出现多行
          const detailNameMap = {};
          for (let k = 0; k < this.rbDetails.length; k++) {
            const nm = String(this.rbDetails[k].beneficiaryname || "").trim();
            if (!nm) {
              continue;
            }
            if (detailNameMap[nm]) {
              this.$modal.msgWarning(
                "服务项目表中,人员名称:" + nm + " é‡å¤ï¼Œè¯·æ ¸å¯¹åŽå†æäº¤ä¿å­˜ï¼"
              );
              return;
            }
            detailNameMap[nm] = true;
          }
          //处理明细
@@ -1808,11 +1891,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 || "操作失败");
        }
      });
    },
@@ -1865,7 +1944,7 @@
        this.personlist = res.rows;
        for (let j = 0; j < this.personlist.length; j++) {
          //获取费用报销机构列表
          if (this.personlist[j].usertype == "org") {
          if (this.hasUserType(this.personlist[j].usertype, "org")) {
            this.feeUnitList.push({
              id: this.personlist[j].id,
              reportNo: this.personlist[j].userno,
@@ -1876,7 +1955,7 @@
            });
          }
          //专家人员列表
          else if (res.rows[j].usertype == "expert") {
          else if (this.hasUserType(res.rows[j].usertype, "expert")) {
            this.expertlist.push({
              expertNo: this.personlist[j].id,
              exportName: this.personlist[j].username
@@ -2065,9 +2144,8 @@
      }
    },
    //税前扣税算税后
    //税前扣税算税后(金额一律走精确运算)
    chargeSum(row, type) {
      let targetValue = 0;
      for (let i = 0; i < this.rbDetails.length; i++) {
        if (!this.rbDetails[i].amount) {
          this.rbDetails[i].amount = 0;
@@ -2075,17 +2153,16 @@
        if (!this.rbDetails[i].taxamount) {
          this.rbDetails[i].taxamount = 0;
        }
        this.rbDetails[i].taxedamount = (
          parseFloat(this.rbDetails[i].amount) -
          parseFloat(this.rbDetails[i].taxamount)
        //税前 - æ‰£ç¨Ž = ç¨ŽåŽï¼ˆæµ®ç‚¹ç›¸å‡ä¼šæ®‹ç•™æžå°å°¾å·®ï¼Œå¯¼è‡´å‡ºçް -0.00)
        this.rbDetails[i].taxedamount = moneySub(
          this.rbDetails[i].amount,
          this.rbDetails[i].taxamount
        ).toFixed(2);
        targetValue += parseFloat(this.rbDetails[i].amount); // å‡è®¾æ¯ä¸ªå¯¹è±¡ä¸­çš„特定值存储在'specificValue'属性下
        console.log(this.rbDetails[i].taxedamount, "合计");
      }
      console.log(targetValue, "合计");
      this.form.pretaxcost = targetValue;
      //合计必须精确累加,否则会写入 form.pretaxcost å¹¶è½åº“
      this.form.pretaxcost = moneySum(this.rbDetails, "amount");
    },
    //税后扣税算税前
    //税后扣税算税前(金额一律走精确运算)
    queenchargeSum(row, type) {
      for (let i = 0; i < this.rbDetails.length; i++) {
        if (!this.rbDetails[i].amount) {
@@ -2094,9 +2171,10 @@
        if (!this.rbDetails[i].taxamount) {
          this.rbDetails[i].taxamount = 0;
        }
        this.rbDetails[i].amount =
          parseFloat(this.rbDetails[i].taxamount) +
          parseFloat(this.rbDetails[i].taxedamount);
        //扣税 + ç¨ŽåŽ = ç¨Žå‰ï¼ˆå¿…须精确相加并归一化到 2 ä½ï¼Œå¦åˆ™ä¼šæ®‹ç•™é•¿å°æ•°ï¼‰
        this.rbDetails[i].amount = moneyRound(
          moneyAdd(this.rbDetails[i].taxamount, this.rbDetails[i].taxedamount)
        );
      }
    },
@@ -2106,6 +2184,11 @@
    },
    // æŽ¥å—提醒立刻保存
    Savenow() {
      // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行)
      if (!this.checkDetailFileNames()) {
        return;
      }
      if (!this.randomnumber) {
        this.randomnumber = this.generateRandomString(9);
        this.form.randomnumber = this.randomnumber;
@@ -2151,6 +2234,22 @@
                return;
              }
            }
          }
          //明细重复校验:同一人员/单位不允许出现多行
          const detailNameMap = {};
          for (let k = 0; k < this.rbDetails.length; k++) {
            const nm = String(this.rbDetails[k].beneficiaryname || "").trim();
            if (!nm) {
              continue;
            }
            if (detailNameMap[nm]) {
              this.$modal.msgWarning(
                "服务项目表中,人员名称:" + nm + " é‡å¤ï¼Œè¯·æ ¸å¯¹åŽå†æäº¤ä¿å­˜ï¼"
              );
              return;
            }
            detailNameMap[nm] = true;
          }
          //处理明细
@@ -2239,16 +2338,18 @@
        }
      });
    },
    // å…¨é€‰
    // å…¨é€‰ï¼ˆæŒ‰å½“前筛查结果)
    handleCheckAllChange(val) {
      this.checkedCities = val ? this.cityOptions : [];
      this.checkedCities = val ? this.filteredCityOptions.slice() : [];
      this.isIndeterminate = false;
    },
    handleCheckedCitiesChange(value) {
      let checkedCount = value.length;
      this.checkAll = checkedCount === this.cityOptions.length;
      this.checkAll =
        this.filteredCityOptions.length > 0 &&
        checkedCount === this.filteredCityOptions.length;
      this.isIndeterminate =
        checkedCount > 0 && checkedCount < this.cityOptions.length;
        checkedCount > 0 && checkedCount < this.filteredCityOptions.length;
    },
    // æ‰‹åŠ¨è®¡ç®—
    Manualcalculationfn() {
@@ -2273,18 +2374,107 @@
    },
    // æ‰¹é‡å½•入点击
    Batchentry() {
      // ethical
      this.listperformance();
      this.personKeyword = "";
      this.checkedCities = [];
      this.checkAll = false;
      this.isIndeterminate = false;
      this.batchdialogVisible = true;
    },
    /**
     * è½½å…¥ç»©æ•ˆäººå‘˜å€™é€‰ã€‚
     * æ”¹é€ å‰å–自人员分组表(service_externalperson_group,group_id=1);
     * 2026-09-27 èµ·æ”¹ä¸ºã€Œäººå‘˜ç±»åž‹ï¼ˆUserType,可多选)含 å‚与绩效」的外围人员账户,
     * ä¸å†ä¾èµ–分组表。
     */
    listperformance() {
      listperformance(1).then(res => {
        this.cityOptions = res.data;
        this.listperformancea = [];
        this.listperformancea = this.cityOptions.map(item => {
          return { value: item.userName, label: item.userName };
        });
      listExternalperson({
        usertype: "performance",
        pageNum: 1,
        pageSize: 10000
      }).then(res => {
        const rows = res.rows || [];
        this.cityOptions = rows
          //只保留启用账号(与改造前分组表接口的 userstatus=1 å£å¾„一致)。
          //注意:ServiceExternalperson å®žä½“没有 del_flag å­—段,接口不返回该字段,
          //不能用它做过滤,否则 undefined == 0 ä¼šæŠŠå…¨éƒ¨å€™é€‰äººæ»¤ç©ºã€‚
          .filter(
            item =>
              item.userstatus === undefined ||
              item.userstatus === null ||
              item.userstatus == 1
          )
          .map(item => {
            return Object.assign({}, item, {
              isUnitPerson: this.hasUserType(item.usertype, "org")
            });
          });
      });
    },
    /** äººå‘˜ç±»åž‹ï¼ˆå¤šå€¼ï¼Œé€—号分隔)是否包含指定类型 */
    hasUserType(usertype, code) {
      if (!usertype) {
        return false;
      }
      return (
        String(usertype)
          .split(",")
          .map(t => t.trim())
          .indexOf(code) > -1
      );
    },
    /** å…³é”®å­—命中判断:姓名 / å•位 / å·¥å· / èº«ä»½è¯åŽ4位 */
    personMatchKeyword(item, kw) {
      const idcard = String(item.idcardno || "");
      return (
        String(item.username || "")
          .toLowerCase()
          .indexOf(kw) > -1 ||
        String(item.unitname || "")
          .toLowerCase()
          .indexOf(kw) > -1 ||
        String(item.userno || "")
          .toLowerCase()
          .indexOf(kw) > -1 ||
        (idcard.length >= 4 && idcard.slice(-4).indexOf(kw) > -1)
      );
    },
    /** è¯†åˆ«äººå‘˜ç±»åˆ«ï¼šå•位(人员类型含 org)还是个人 */
    isUnitPerson(item) {
      return !!(item && this.hasUserType(item.usertype, "org"));
    },
    /** é€‰æ‹©æ¡†ä¸‹æ‹‰é¡¹æ–‡æ¡ˆï¼šå§“名(单位) */
    personOptionLabel(item) {
      return (
        (item.username || "") +
        (item.unitname ? "(" + item.unitname + ")" : "") +
        (item.isUnitPerson ? " - å•位" : "")
      );
    },
    /** é€‰æ‹©æ¡†å€™é€‰ï¼šç©ºå…³é”®å­—返回全部,否则按关键字筛查 */
    filterPersonCandidates(kw) {
      if (!kw) {
        return this.cityOptions.slice();
      }
      return this.cityOptions.filter(item => this.personMatchKeyword(item, kw));
    },
    /** é€‰æ‹©æ¡†æœ¬åœ°ç­›æŸ¥ï¼ˆå§“名/单位/工号/身份证后4位) */
    filterPersonOption(query) {
      const kw = (query || "").trim().toLowerCase();
      this.optionsperformance = this.filterPersonCandidates(kw);
      // å·²é€‰ä¸­é¡¹è¦ä¿ç•™åœ¨ä¸‹æ‹‰é‡Œï¼Œå¦åˆ™å›žæ˜¾ä¼šç©ºç™½
      const picked = this.getPickedPerson(this.funddetailForm.userKey);
      if (picked && this.optionsperformance.indexOf(picked) === -1) {
        this.optionsperformance = [picked].concat(this.optionsperformance);
      }
    },
    /** æŒ‰å”¯ä¸€é”®ï¼ˆäººå‘˜ ID)取候选;手工输入的姓名返回 null */
    getPickedPerson(key) {
      if (key === null || key === undefined || key === "") {
        return null;
      }
      return (
        this.cityOptions.find(item => String(item.id) === String(key)) || null
      );
    },
    // ç¡®è®¤å½•å…¥
    BatchentryFn() {
@@ -2292,23 +2482,42 @@
      if (!this.rbDetails[0].applytype) {
        this.rbDetails = [];
      }
      // æ˜Žç»†ä¸­å·²æœ‰çš„人员姓名,用于重复提醒(与伦理专家费页的判重口径一致)
      const existed = {};
      this.rbDetails.forEach(row => {
        if (row.beneficiaryname) {
          existed[String(row.beneficiaryname).trim()] = true;
        }
      });
      const duplicated = [];
      this.checkedCities.forEach(item => {
        const nameKey = String(item.username || "").trim();
        if (nameKey && existed[nameKey]) {
          duplicated.push(item.username);
          return;
        }
        if (nameKey) {
          existed[nameKey] = true;
        }
        const isunit = this.isUnitPerson(item);
        let rowData = {
          orderno: null,
          fundid: null,
          beneficiaryname: item.userName,
          beneficiaryno: item.usertNo,
          beneficiaryname: item.username,
          beneficiaryno: item.userno,
          isunit: isunit ? 1 : 0,
          unitname: item.unitname,
          unitno: null,
          unitno: item.unitno,
          uintuserno: null,
          title: item.title,
          idcardtype: null,
          idcardno: item.idcrad,
          idcardno: isunit ? "" : item.idcardno,
          sex: null,
          familyrelations: null,
          phone: null,
          depositbank: item.depositBank,
          bankcardno: item.bankCardNo,
          phone: item.telephone,
          depositbank: item.depositbank,
          bankcardno: item.bankcardno,
          branchbankname: item.branchbankname,
          annexbankcard: null,
          annexregistform: null,
@@ -2351,6 +2560,11 @@
      this.calculateTax();
      this.batchdialogVisible = false;
      this.checkedCities = [];
      if (duplicated.length) {
        this.$modal.msgWarning(
          "以下人员在明细中已存在,已跳过重复添加:" + duplicated.join("、")
        );
      }
      console.log(this.rowfeeItemTypes);
    },
    //新增一行
@@ -2361,6 +2575,7 @@
        fundid: null,
        beneficiaryname: null,
        beneficiaryno: null,
        isunit: 0,
        unitname: null,
        unitno: null,
        uintuserno: null,
@@ -2460,64 +2675,78 @@
        });
      //全部删除后添加一条新行
    },
    //专家/医疗机构/费用报销机构选择
    //绩效人员/单位选择:打开时预填当前行已选人员,避免"直接确定"把已有姓名清空
    ShowDetailDialog(spoce, showType) {
      this.spocewl = spoce;
      this.selectionType = showType;
      this.selectionType = this.experthear;
      this.expertfrom = "1";
      this.funddetailForm.index = spoce.$index;
      this.funddetailForm.beneficiaryno = spoce.row.beneficiaryno;
      this.funddetailForm.beneficiaryname = spoce.row.beneficiaryname;
      this.funddetailForm.unitno = spoce.row.unitno;
      this.funddetailForm.unitname = spoce.row.unitname;
      this.funddetailForm.expertname = "";
      this.funddetailForm.expertidcardno = "";
      this.funddetailForm.expertunitno = "";
      this.funddetailForm.expertunitname = "";
      this.funddetailForm.experttitle = "";
      this.funddetailForm.expertdepositbank = "";
      this.funddetailForm.expertbankcardno = "";
      //当前行已选人员的回填:先按人员编号匹配,匹配不到再按姓名匹配
      let cur =
        this.cityOptions.find(
          item => String(item.userno) === String(spoce.row.beneficiaryno)
        ) || null;
      if (!cur && spoce.row.beneficiaryname) {
        cur =
          this.cityOptions.find(
            item => item.username === spoce.row.beneficiaryname
          ) || null;
      }
      this.funddetailForm.userKey = cur
        ? cur.id
        : spoce.row.beneficiaryname || null;
      this.funddetailForm.userName = spoce.row.beneficiaryname || null;
      //打开时展示全量候选,便于直接浏览筛查
      this.optionsperformance = this.cityOptions.slice();
      this.showSelectionDialog = true;
    },
    //专家/医疗机构/费用报销机构选择
    /**
     * ç¡®å®šé€‰æ‹©ï¼šæŒ‰å”¯ä¸€é”®ï¼ˆäººå‘˜ ID)匹配候选,
     * è‡ªåŠ¨å¸¦å‡ºèº«ä»½è¯å·ã€é“¶è¡Œè´¦å·ã€å¼€æˆ·é“¶è¡Œã€ç”µè¯ã€èŒç§°ã€äººå‘˜ç¼–å·ã€å•ä½ï¼›
     * å•位(人员类型含 org)不写身份证号;手工输入的姓名不做带出。
     */
    ConfirmDetailDialog() {
      let tempIndex = this.funddetailForm.index;
      let tempExpertName = this.funddetailForm.userName;
      let singleDetail = this.rbDetails[tempIndex];
      const tempIndex = this.funddetailForm.index;
      const singleDetail = this.rbDetails[tempIndex];
      const key = this.funddetailForm.userKey;
      //从专家列表获取信息
      this.cityOptions.map(res => {
        if (tempExpertName == res.userName) {
          singleDetail.beneficiaryno = res.userno;
          singleDetail.bankcardno = res.bankCardNo;
          singleDetail.branchbankname = res.branchbankname;
          singleDetail.depositbank = res.depositBank;
          singleDetail.idcardno = res.idcardNo;
          singleDetail.phone = res.telephone;
          singleDetail.title = res.title;
          singleDetail.unitname = res.unitname;
          singleDetail.unitno = parseInt(res.unitno);
        }
      });
      singleDetail.beneficiaryname = tempExpertName;
      this.rbDetails[tempIndex] = singleDetail;
      this.showSelectionDialog = false;
    },
    remoteMethod(query) {
      console.log(this.listperformancea);
      if (query !== "") {
        this.loadingperformance = true;
        setTimeout(() => {
          this.loadingperformance = false;
          this.optionsperformance = this.listperformancea.filter(item => {
            return item.label.toLowerCase().indexOf(query.toLowerCase()) > -1;
          });
        }, 200);
      } else {
        this.optionsperformance = [];
      if (key === null || key === undefined || key === "") {
        this.$modal.msgWarning("请先筛查并选择绩效人员,或直接输入人员姓名!");
        return;
      }
      const person = this.getPickedPerson(key);
      if (person) {
        // å‘½ä¸­äººå‘˜åº“:自动带出全部信息
        const isunit = this.isUnitPerson(person);
        singleDetail.beneficiaryname = person.username;
        singleDetail.beneficiaryno = person.userno;
        singleDetail.isunit = isunit ? 1 : 0;
        singleDetail.idcardno = isunit ? "" : person.idcardno;
        singleDetail.bankcardno = person.bankcardno;
        singleDetail.branchbankname = person.branchbankname;
        singleDetail.depositbank = person.depositbank;
        singleDetail.phone = person.telephone;
        singleDetail.title = person.title;
        singleDetail.unitname = person.unitname;
        singleDetail.unitno = person.unitno;
      } else {
        // æ‰‹å·¥è¾“入的人员:只写姓名,身份证号/银行账号由经办人填写
        singleDetail.beneficiaryname = String(key);
        singleDetail.isunit = 0;
        this.$modal.msgWarning(
          "人员库中未找到「" +
            key +
            "」,已按手工录入处理,请自行填写身份证号、银行账号等信息。"
        );
      }
      this.$set(this.rbDetails, tempIndex, singleDetail);
      this.showSelectionDialog = false;
    },
    handleUploadError() {},
    remove(file, fileList) {
@@ -2541,7 +2770,43 @@
        this.Networkheader = string.slice(0, index + 4); // æˆªå–8032及其前部字符
      }
    },
    /**
     * 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 }
src/views/project/fundcheck/ethicalExpertFeeCheck/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
      <el-row>
        <el-col :span="5">
          <el-form-item label="捐献案例" prop="donorname" style="float: left">
@@ -41,7 +42,7 @@
        </el-col>
        <el-col :span="8">
          <el-form-item label="申请日期" prop="applyTime">
            <el-date-picker format="yyyy-MM-dd" @change="selectapplication" value-format="yyyy-MM-dd"
            <el-date-picker format="yyyy-MM-dd" @change="selectapplication" value-format="yyyy-MM-dd HH:mm:ss"
              v-model="applyTime" type="daterange" range-separator="至" start-placeholder="申请开始日期"
              end-placeholder="申请结束日期" @keyup.enter.native="flowconclusion">
            </el-date-picker>
@@ -311,7 +312,7 @@
          <el-col :span="7">
            <el-form-item label="申请日期" prop="applyTime">
              <!-- <el-input v-model="form.donorno" placeholder="请输入申请日期" /> -->
              <el-date-picker clearable style="width: 100%" v-model="form.applyTime" type="date"
              <el-date-picker clearable style="width: 100%" v-model="form.applyTime" type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss" placeholder="申请日期">
              </el-date-picker>
            </el-form-item>
@@ -362,7 +363,7 @@
        </el-row>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="dundcheck" v-if="dialogType == 'edit'">审核</el-button>
        <el-button type="primary" :loading="checkSubmitLoading" :disabled="checkSubmitLoading" @click="dundcheck" v-if="dialogType == 'edit'">审核</el-button>
        <el-button @click="cancel">取消</el-button>
      </div>
    </el-dialog>
@@ -437,6 +438,7 @@
// å¯¼å…¥æœ¬åœ°å­˜å‚¨çš„æ–¹æ³•
import { setItem, grtItem, removeItem } from "@/utils/storage.js";
import { moneySum, moneyAdd, moneySub, moneyMul, moneyRound } from "@/utils/money";
export default {
  components: {
@@ -460,6 +462,7 @@
  ],
  data() {
    return {
      checkSubmitLoading: false,
      arrr2: [],
      arrr3: [],
      //费用类型数组
@@ -692,6 +695,7 @@
      });
    },
    dundcheck() {
      if (this.checkSubmitLoading) return;
      // this.$em
      //console.log("1234567", this.form.flowlevelone, this.form.flowconclusion);
@@ -708,7 +712,9 @@
        this.$modal.msgError("请选择审核结论!");
        return;
      }
      this.checkSubmitLoading = true;
      checkfund(checkfundobj).then(res => {
        this.checkSubmitLoading = false;
        if (res.code == 200) {
          console.log("审核费用", res);
          this.dialogOpen = false;
@@ -717,11 +723,7 @@
          this.getlistbypower();
        } else {
          this.dialogOpen = false;
          if (res.msg) {
            this.$modal.msgError("操作失误");
          } else {
            this.$modal.msgError(res.msg);
          }
          this.$modal.msgError(res.msg || "操作失败");
        }
      });
    },
@@ -1469,49 +1471,45 @@
      });
    },
    //计算总金额
    //计算总金额(金额一律走精确运算,禁止 parseFloat ç´¯åŠ ï¼‰
    sumTotalFee() {
      let allSum = 0;
      const detailsArr = [];
      let preTaxSum = 0;
      let taxedSum = 0;
      if (this.form.applytype != null) {
        for (let i = 0; i < this.fundDetailArr.length; i++) {
          const detail = this.fundDetailArr[i];
          let totalFee = 0;
          let taxFee = 0;
          if (this.form.applytype == 1 || this.form.applytype == 2) {
            //计算当前报销内容费用合计
            if (!isNaN(parseFloat(this.fundDetailArr[i].amount))) {
              totalFee += parseFloat(this.fundDetailArr[i].amount);
              taxFee += parseFloat(this.fundDetailArr[i].amount);
              preTaxSum += parseFloat(this.fundDetailArr[i].amount);
            //计算当前报销内容费用合计:税前 - æ‰£ç¨Ž
            if (!isNaN(parseFloat(detail.amount))) {
              totalFee = moneyAdd(totalFee, detail.amount);
              preTaxSum = moneyAdd(preTaxSum, detail.amount);
            }
            if (!isNaN(parseFloat(this.fundDetailArr[i].taxamount))) {
              totalFee -= parseFloat(this.fundDetailArr[i].taxamount);
              taxFee -= parseFloat(this.fundDetailArr[i].taxamount);
            if (!isNaN(parseFloat(detail.taxamount))) {
              totalFee = moneySub(totalFee, detail.taxamount);
            }
            //taxedamount
            if (!isNaN(parseFloat(this.fundDetailArr[i].taxedamount))) {
              taxedSum += parseFloat(this.fundDetailArr[i].taxedamount);
            if (!isNaN(parseFloat(detail.taxedamount))) {
              taxedSum = moneyAdd(taxedSum, detail.taxedamount);
            }
          } else if (this.form.applytype == 3) {
            if (
              !isNaN(parseFloat(this.fundDetailArr[i].price)) &&
              !isNaN(parseFloat(this.fundDetailArr[i].quantity))
              !isNaN(parseFloat(detail.price)) &&
              !isNaN(parseFloat(detail.quantity))
            ) {
              totalFee +=
                parseFloat(this.fundDetailArr[i].price) *
                parseFloat(this.fundDetailArr[i].quantity);
              totalFee = moneyMul(detail.price, detail.quantity);
            }
          }
          // if (!isNaN(parseFloat(taxFee))) {
          //   this.fundDetailArr[i].taxedamount = taxFee.toFixed(2);
          // }
          allSum += totalFee;
          detailsArr.push(totalFee);
        }
      }
      this.form.amountrequested = allSum.toFixed(2);
      this.form.pretaxcost = preTaxSum.toFixed(2);
      this.form.taxedcost = taxedSum.toFixed(2);
      this.form.amountrequested = moneyRound(moneySum(detailsArr)).toFixed(2);
      this.form.pretaxcost = moneyRound(preTaxSum).toFixed(2);
      this.form.taxedcost = moneyRound(taxedSum).toFixed(2);
      this.$forceUpdate();
      console.log(this.form);
    },
src/views/project/fundcheck/expertFeeCheck/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="80px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="80px">
      <el-row>
        <el-col :span="5">
          <el-form-item label="捐献案例" prop="donorname" style="float: left">
@@ -48,7 +49,7 @@
        </el-col>
        <el-col :span="8">
          <el-form-item label="申请日期" prop="createTime">
            <el-date-picker format="yyyy-MM-dd" @change="selectapplication" value-format="yyyy-MM-dd"
            <el-date-picker format="yyyy-MM-dd" @change="selectapplication" value-format="yyyy-MM-dd HH:mm:ss"
              v-model="createTime" type="daterange" range-separator="至" start-placeholder="申请开始日期"
              end-placeholder="申请结束日期" @keyup.enter.native="flowconclusion">
            </el-date-picker>
@@ -157,7 +158,7 @@
          <el-col :span="5">
            <el-form-item label="申请日期" prop="createTime">
              <!-- <el-input v-model="form.donorno" placeholder="请输入申请日期" /> -->
              <el-date-picker clearable style="width: 100%" v-model="form.createTime" type="date"
              <el-date-picker clearable style="width: 100%" v-model="form.createTime" type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss" placeholder="申请日期">
              </el-date-picker>
            </el-form-item>
@@ -397,7 +398,7 @@
        </el-row>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="dundcheck" v-if="dialogType == 'edit'">保存</el-button>
        <el-button type="primary" :loading="checkSubmitLoading" :disabled="checkSubmitLoading" @click="dundcheck" v-if="dialogType == 'edit'">保存</el-button>
        <el-button @click="cancel">取消</el-button>
      </div>
    </el-dialog>
@@ -421,7 +422,8 @@
  checkfund,
  listbypower,
  addFundSharedInfo,
  TotalcalculationFn
  TotalcalculationFn,
  validateExpertFee
} from "@/api/project/fund";
import {
  onelistFunds,
@@ -494,6 +496,7 @@
  ],
  data() {
    return {
      checkSubmitLoading: false,
      arrr2: [],
      arrr3: [],
      personlist: [],
@@ -723,6 +726,7 @@
      });
    },
    dundcheck() {
      if (this.checkSubmitLoading) return;
      // this.$em
      let checkfundobj = {
@@ -738,18 +742,38 @@
        this.$modal.msgError("请选择审核结论!");
        return;
      }
      // å®¡æ ¸â€œé€šè¿‡â€å‰ï¼šå…ˆåšä¸“家劳务费 9 æ¡è´¹ç”¨æŽ§åˆ¶æ ¡éªŒï¼ˆä¸ŽåŽç«¯æ‹¦æˆªåŒå£å¾„)
      if (checkfundobj.flowconclusion == 1) {
        validateExpertFee({ id: this.form.id }).then(valid => {
          const errors = (valid && valid.errors) || [];
          const warnings = (valid && valid.warnings) || [];
          if (warnings.length > 0) {
            this.$modal.msgWarning(warnings.join(";"));
          }
          if (errors.length > 0) {
            this.$modal.msgError("不能审核通过:" + errors.join(";"));
            return;
          }
          this.doCheckfund(checkfundobj);
        });
        return;
      }
      // ä¸é€šè¿‡ï¼šæ— éœ€æ ¡éªŒé™é¢
      this.doCheckfund(checkfundobj);
    },
    doCheckfund(checkfundobj) {
      this.checkSubmitLoading = true;
      checkfund(checkfundobj).then(res => {
        this.checkSubmitLoading = false;
        if (res.code == 200) {
          this.reset();
          this.$modal.msgSuccess("审核完成!");
          this.getlistbypower();
          this.dialogOpen = false;
        } else {
          if (res.msg) {
            this.$modal.msgError("操作失误");
          } else {
            this.$modal.msgError(res.msg);
          }
          // åŽç«¯è¿”回的具体原因(如超限额)优先展示
          this.$modal.msgError(res.msg || "操作失误");
        }
      });
    },
src/views/project/fundcheck/medicalCostCheck/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
      <el-row align="left">
        <el-col :span="5">
          <el-form-item label="捐献案例" prop="donorname" style="float: left">
@@ -48,7 +49,7 @@
        </el-col>
        <el-col :span="8">
          <el-form-item label="申请日期" prop="createTime">
            <el-date-picker format="yyyy-MM-dd" @change="selectapplication" value-format="yyyy-MM-dd"
            <el-date-picker format="yyyy-MM-dd" @change="selectapplication" value-format="yyyy-MM-dd HH:mm:ss"
              v-model="createTime" type="daterange" range-separator="至" start-placeholder="申请开始日期"
              end-placeholder="申请结束日期" @keyup.enter.native="flowconclusion">
            </el-date-picker>
@@ -145,7 +146,7 @@
          <el-col :span="5">
            <el-form-item label="申请日期" prop="createTime">
              <!-- <el-input v-model="form.donorno" placeholder="请输入申请日期" /> -->
              <el-date-picker clearable style="width: 100%" v-model="form.createTime" type="date"
              <el-date-picker clearable style="width: 100%" v-model="form.createTime" type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss" placeholder="申请日期">
              </el-date-picker>
            </el-form-item>
@@ -421,7 +422,7 @@
        </el-row>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="dundcheck" v-if="dialogType == 'edit'">确认</el-button>
        <el-button type="primary" :loading="checkSubmitLoading" :disabled="checkSubmitLoading" @click="dundcheck" v-if="dialogType == 'edit'">确认</el-button>
        <el-button @click="cancel">取消</el-button>
      </div>
    </el-dialog>
@@ -496,6 +497,7 @@
import { listDept } from "@/api/system/dept";
import { getToken } from "@/utils/auth";
import { setItem, grtItem, removeItem } from "@/utils/storage.js";
import { moneySum, moneyAdd, moneySub, moneyMul, moneyRound } from "@/utils/money";
export default {
  components: {
@@ -519,6 +521,7 @@
  ],
  data() {
    return {
      checkSubmitLoading: false,
      arrr2: [],
      arrr3: [],
      //费用类型数组
@@ -730,6 +733,7 @@
      });
    },
    dundcheck() {
      if (this.checkSubmitLoading) return;
      // this.$em
      this.dialogOpen = false;
      let checkfundobj = {
@@ -737,17 +741,15 @@
        flowcontent: this.newobj.flowconclusion,
        fundid: this.newobj.fundid
      };
      this.checkSubmitLoading = true;
      checkfund(checkfundobj).then(res => {
        this.checkSubmitLoading = false;
        if (res.code == 200) {
          this.reset();
          this.$modal.msgSuccess("审核完成!");
          this.getlistbypower();
        } else {
          if (res.msg) {
            this.$modal.msgError("操作失误");
          } else {
            this.$modal.msgError(res.msg);
          }
          this.$modal.msgError(res.msg || "操作失败");
        }
      });
    },
@@ -1443,41 +1445,40 @@
      // });
    },
    //计算总金额
    //计算总金额(金额一律走精确运算,禁止 parseFloat ç´¯åŠ ï¼‰
    sumTotalFee() {
      let allSum = 0;
      const detailsArr = [];
      if (this.form.applytype != null) {
        for (let i = 0; i < this.fundDetailArr.length; i++) {
          const detail = this.fundDetailArr[i];
          let totalFee = 0;
          let taxFee = 0;
          if (this.form.applytype == 1 || this.form.applytype == 2) {
            //计算当前报销内容费用合计
            if (!isNaN(parseFloat(this.fundDetailArr[i].amount))) {
              totalFee += parseFloat(this.fundDetailArr[i].amount);
              taxFee += parseFloat(this.fundDetailArr[i].amount);
            //计算当前报销内容费用合计:税前 - æ‰£ç¨Ž
            if (!isNaN(parseFloat(detail.amount))) {
              totalFee = moneyAdd(totalFee, detail.amount);
              taxFee = moneyAdd(taxFee, detail.amount);
            }
            if (!isNaN(parseFloat(this.fundDetailArr[i].taxamount))) {
              totalFee -= parseFloat(this.fundDetailArr[i].taxamount);
              taxFee -= parseFloat(this.fundDetailArr[i].taxamount);
            if (!isNaN(parseFloat(detail.taxamount))) {
              totalFee = moneySub(totalFee, detail.taxamount);
              taxFee = moneySub(taxFee, detail.taxamount);
            }
          } else if (this.form.applytype == 3) {
            if (
              !isNaN(parseFloat(this.fundDetailArr[i].price)) &&
              !isNaN(parseFloat(this.fundDetailArr[i].quantity))
              !isNaN(parseFloat(detail.price)) &&
              !isNaN(parseFloat(detail.quantity))
            ) {
              totalFee +=
                parseFloat(this.fundDetailArr[i].price) *
                parseFloat(this.fundDetailArr[i].quantity);
              totalFee = moneyMul(detail.price, detail.quantity);
            }
          }
          if (!isNaN(parseFloat(taxFee))) {
            this.fundDetailArr[i].taxedamount = taxFee.toFixed(2);
            this.fundDetailArr[i].taxedamount = moneyRound(taxFee).toFixed(2);
          }
          allSum += totalFee;
          detailsArr.push(totalFee);
        }
      }
      //this.form.amountrequested = allSum.toFixed(2);
      this.$set(this.form, "amountrequested", allSum.toFixed(2));
      this.$set(this.form, "amountrequested", moneyRound(moneySum(detailsArr)).toFixed(2));
      //this.$forceUpdate();
    },
src/views/project/fundcheck/officeExpenseCheck/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
      <el-row align="left">
        <el-col :span="5">
          <el-form-item label="捐献案例" prop="donorname" style="float: left">
@@ -48,7 +49,7 @@
        </el-col>
        <el-col :span="8">
          <el-form-item label="申请日期" prop="createTime">
            <el-date-picker format="yyyy-MM-dd" @change="selectapplication" value-format="yyyy-MM-dd"
            <el-date-picker format="yyyy-MM-dd" @change="selectapplication" value-format="yyyy-MM-dd HH:mm:ss"
              v-model="createTime" type="daterange" range-separator="至" start-placeholder="申请开始日期"
              end-placeholder="申请结束日期" @keyup.enter.native="flowconclusion">
            </el-date-picker>
@@ -148,7 +149,7 @@
          <el-col :span="5">
            <el-form-item label="申请日期" prop="createTime">
              <!-- <el-input v-model="form.donorno" placeholder="请输入申请日期" /> -->
              <el-date-picker clearable style="width: 100%" v-model="form.createTime" type="date"
              <el-date-picker clearable style="width: 100%" v-model="form.createTime" type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss" placeholder="申请日期">
              </el-date-picker>
            </el-form-item>
@@ -424,7 +425,7 @@
        </el-row>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="dundcheck" v-if="dialogType == 'edit'">确认</el-button>
        <el-button type="primary" :loading="checkSubmitLoading" :disabled="checkSubmitLoading" @click="dundcheck" v-if="dialogType == 'edit'">确认</el-button>
        <el-button @click="cancel">取消</el-button>
      </div>
    </el-dialog>
@@ -499,6 +500,7 @@
import { listDept } from "@/api/system/dept";
import { getToken } from "@/utils/auth";
import { setItem, grtItem, removeItem } from "@/utils/storage.js";
import { moneySum, moneyAdd, moneySub, moneyMul, moneyRound } from "@/utils/money";
export default {
  components: {
@@ -522,6 +524,7 @@
  ],
  data() {
    return {
      checkSubmitLoading: false,
      arrr2: [],
      arrr3: [],
      //费用类型数组
@@ -735,6 +738,7 @@
      });
    },
    dundcheck() {
      if (this.checkSubmitLoading) return;
      // this.$em
      this.dialogOpen = false;
      let checkfundobj = {
@@ -742,17 +746,15 @@
        flowcontent: this.newobj.flowconclusion,
        fundid: this.newobj.fundid
      };
      this.checkSubmitLoading = true;
      checkfund(checkfundobj).then(res => {
        this.checkSubmitLoading = false;
        if (res.code == 200) {
          this.reset();
          this.$modal.msgSuccess("审核完成!");
          this.getlistbypower();
        } else {
          if (res.msg) {
            this.$modal.msgError("操作失误");
          } else {
            this.$modal.msgError(res.msg);
          }
          this.$modal.msgError(res.msg || "操作失败");
        }
      });
    },
@@ -1448,41 +1450,40 @@
      // });
    },
    //计算总金额
    //计算总金额(金额一律走精确运算,禁止 parseFloat ç´¯åŠ ï¼‰
    sumTotalFee() {
      let allSum = 0;
      const detailsArr = [];
      if (this.form.applytype != null) {
        for (let i = 0; i < this.fundDetailArr.length; i++) {
          const detail = this.fundDetailArr[i];
          let totalFee = 0;
          let taxFee = 0;
          if (this.form.applytype == 1 || this.form.applytype == 2) {
            //计算当前报销内容费用合计
            if (!isNaN(parseFloat(this.fundDetailArr[i].amount))) {
              totalFee += parseFloat(this.fundDetailArr[i].amount);
              taxFee += parseFloat(this.fundDetailArr[i].amount);
            //计算当前报销内容费用合计:税前 - æ‰£ç¨Ž
            if (!isNaN(parseFloat(detail.amount))) {
              totalFee = moneyAdd(totalFee, detail.amount);
              taxFee = moneyAdd(taxFee, detail.amount);
            }
            if (!isNaN(parseFloat(this.fundDetailArr[i].taxamount))) {
              totalFee -= parseFloat(this.fundDetailArr[i].taxamount);
              taxFee -= parseFloat(this.fundDetailArr[i].taxamount);
            if (!isNaN(parseFloat(detail.taxamount))) {
              totalFee = moneySub(totalFee, detail.taxamount);
              taxFee = moneySub(taxFee, detail.taxamount);
            }
          } else if (this.form.applytype == 3) {
            if (
              !isNaN(parseFloat(this.fundDetailArr[i].price)) &&
              !isNaN(parseFloat(this.fundDetailArr[i].quantity))
              !isNaN(parseFloat(detail.price)) &&
              !isNaN(parseFloat(detail.quantity))
            ) {
              totalFee +=
                parseFloat(this.fundDetailArr[i].price) *
                parseFloat(this.fundDetailArr[i].quantity);
              totalFee = moneyMul(detail.price, detail.quantity);
            }
          }
          if (!isNaN(parseFloat(taxFee))) {
            this.fundDetailArr[i].taxedamount = taxFee.toFixed(2);
            this.fundDetailArr[i].taxedamount = moneyRound(taxFee).toFixed(2);
          }
          allSum += totalFee;
          detailsArr.push(totalFee);
        }
      }
      //this.form.amountrequested = allSum.toFixed(2);
      this.$set(this.form, "amountrequested", allSum.toFixed(2));
      this.$set(this.form, "amountrequested", moneyRound(moneySum(detailsArr)).toFixed(2));
      //this.$forceUpdate();
    },
src/views/project/fundcheck/performanceCheck/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
      <el-row align="left">
        <el-col :span="5">
          <el-form-item label="预审状态" prop="checkstatus" style="float: left">
@@ -34,7 +35,7 @@
        </el-col>
        <el-col :span="8">
          <el-form-item label="申请日期" prop="createTime">
            <el-date-picker format="yyyy-MM-dd" @change="selectapplication" value-format="yyyy-MM-dd" v-model="createTime"
            <el-date-picker format="yyyy-MM-dd" @change="selectapplication" value-format="yyyy-MM-dd HH:mm:ss" v-model="createTime"
              type="daterange" range-separator="至" start-placeholder="申请开始日期" end-placeholder="申请结束日期"
              @keyup.enter.native="flowconclusion">
            </el-date-picker>
src/views/project/funddetail/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="专家姓名" prop="beneficiaryname">
        <el-input v-model="queryParams.beneficiaryname" placeholder="请输入专家姓名" clearable size="small"
          @keyup.enter.native="handleQuery" />
src/views/project/fundflow/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <!-- <el-form-item label="费用编号,差旅费关联表service_reimbursement ID; è´¹ç”¨å…³è”费用表service_fund的ID" prop="fundid">
        <el-input
          v-model="queryParams.fundid"
@@ -31,7 +32,7 @@
        <el-form-item label="申请日期" prop="createTime">
        <el-date-picker
          format="yyyy-MM-dd"
          value-format="yyyy-MM-dd hh:mm:ss"
          value-format="yyyy-MM-dd HH:mm:ss"
          v-model="queryParams.createTime"
          type="date"
          placeholder="请选择申请日期"
src/views/project/fundflowrule/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
src/views/project/fundstatistics/index.vue
@@ -3,7 +3,8 @@
  <div class="app-container">
    <el-col :span="24" v-if="showType == 'list'">
      <el-card shadow="never">
        <el-form :model="queryParams" ref="queryForm" :inline="true">
        <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true">
          <el-form-item label="姓名" prop="name">
            <el-input
              v-model="queryParams.name"
src/views/project/itemexpense/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="服务项目ID" prop="itemid">
        <el-input
          v-model="queryParams.itemid"
@@ -97,6 +98,13 @@
      <el-table-column label="费用金额" align="center" prop="expense" />
      <el-table-column label="费用说明" align="center" prop="expensedescribe" />
      <el-table-column label="启用状态" align="center" prop="usestate" />
      <el-table-column label="校验模式" align="center" prop="validateMode" width="110">
        <template slot-scope="scope">
          <el-tag v-if="scope.row.validateMode == 3" type="danger">不校验</el-tag>
          <el-tag v-else-if="scope.row.validateMode == 2" type="warning">校验提醒</el-tag>
          <el-tag v-else>强制校验</el-tag>
        </template>
      </el-table-column>
      <el-table-column label="备注" align="center" prop="remark" />
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
@@ -140,6 +148,13 @@
        </el-form-item>
        <el-form-item label="启用状态" prop="usestate">
          <el-input v-model="form.usestate" placeholder="请输入启用状态" />
        </el-form-item>
        <el-form-item label="校验模式" prop="validateMode">
          <el-select v-model="form.validateMode" placeholder="请选择专家费校验模式" style="width: 100%">
            <el-option label="强制校验(不通过不能保存)" :value="1" />
            <el-option label="校验提醒(仅提醒,允许保存)" :value="2" />
            <el-option label="不校验(跳过金额类校验)" :value="3" />
          </el-select>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" placeholder="请输入备注" />
@@ -222,6 +237,7 @@
        expense: null,
        expensedescribe: null,
        usestate: null,
        validateMode: 1,
        remark: null,
        delFlag: null,
        createBy: null,
src/views/project/medicalevaluation/index.vue
@@ -2,6 +2,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -410,7 +411,7 @@
                  clearable
                  size="small"
                  v-model="form.hospitalassesstime"
                  type="date"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择院级评估时间"
                >
@@ -463,7 +464,7 @@
                  clearable
                  size="small"
                  v-model="form.provincialassesstime"
                  type="date"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择省级评估时间"
                >
@@ -517,7 +518,7 @@
                  clearable
                  size="small"
                  v-model="form.coreteamassesstime"
                  type="date"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="评估时间"
                >
src/views/project/organallocation/index.vue
@@ -3,6 +3,7 @@
  <div class="app-container">
    <!-- æœç´¢æ¡ä»¶ -->
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -337,7 +338,7 @@
                size="small"
                style="width: 100%"
                v-model="form.applicanttime"
                type="datetime"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择接收时间"
              >
src/views/project/organallocation/index0.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="70px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="70px">
      <el-row>
        <el-col :span="6">
          <el-form-item label="捐献者" prop="name">
@@ -198,7 +199,7 @@
        <el-row>
          <el-col :span="12">
            <el-form-item label="获取时间" prop="organgettime">
              <el-date-picker clearable size="medium" v-model="form.organgettime" type="date" disabled
              <el-date-picker clearable size="medium" v-model="form.organgettime" type="date" format="yyyy-MM-dd" disabled
                value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择器官获取时间">
              </el-date-picker>
            </el-form-item>
@@ -248,7 +249,7 @@
          </el-col>
          <el-col :span="12">
            <el-form-item label="移植时间" prop="transplanttime">
              <el-date-picker clearable size="medium" v-model="form.transplanttime" type="date"
              <el-date-picker clearable size="medium" v-model="form.transplanttime" type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择移植时间" disabled>
              </el-date-picker>
            </el-form-item>
src/views/project/organization/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -272,7 +273,7 @@
                clearable
                size="small"
                v-model="form.registertime"
                type="datetime"
                type="datetime" format="yyyy-MM-dd HH:mm"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择注册时间"
              >
src/views/project/register/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,351 @@
<template>
  <div class="app-container">
    <!-- æŸ¥è¯¢æ¡ä»¶ï¼šæŒ‰é’®ç´§è·Ÿæ¡ä»¶ä¹‹åŽï¼ˆä¸Žæ¡ˆä¾‹è¯„估页风格一致) -->
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
      v-show="showSearch"
      label-width="82px"
    >
      <el-row>
        <el-col :span="6">
          <el-form-item label="捐献者" prop="name">
            <el-input
              v-model="queryParams.name"
              placeholder="请输入捐献者姓名"
              clearable
              size="small"
              @keyup.enter.native="handleQuery"
            />
          </el-form-item>
        </el-col>
        <el-col :span="6">
          <el-form-item label="证件号码" prop="idcardno">
            <el-input
              v-model="queryParams.idcardno"
              placeholder="请输入证件号码"
              clearable
              size="small"
              @keyup.enter.native="handleQuery"
            />
          </el-form-item>
        </el-col>
        <el-col :span="6">
          <el-form-item label="器官类别" prop="organno">
            <el-select
              v-model="queryParams.organno"
              placeholder="请选择器官类别"
              clearable
              size="small"
              style="width: 100%"
            >
              <el-option
                v-for="dict in dict.type.sys_Organ"
                :key="dict.value"
                :label="dict.label"
                :value="dict.value"
              />
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="6">
          <el-form-item label="获取状态" prop="organstate">
            <el-select
              v-model="queryParams.organstate"
              placeholder="请选择获取状态"
              clearable
              size="small"
              style="width: 100%"
            >
              <el-option
                v-for="dict in dict.type.sys_organstate"
                :key="dict.value"
                :label="dict.label"
                :value="dict.value"
              />
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="6">
          <el-form-item label="器官编号" prop="organnumber">
            <el-input
              v-model="queryParams.organnumber"
              placeholder="请输入器官编号"
              clearable
              size="small"
              @keyup.enter.native="handleQuery"
            />
          </el-form-item>
        </el-col>
        <el-col :span="6">
          <el-form-item label="移植机构" prop="transplanthospitalname">
            <el-input
              v-model="queryParams.transplanthospitalname"
              placeholder="请输入移植机构名称"
              clearable
              size="small"
              @keyup.enter.native="handleQuery"
            />
          </el-form-item>
        </el-col>
        <el-col :span="6">
          <el-form-item label="移植人" prop="acceptname">
            <el-input
              v-model="queryParams.acceptname"
              placeholder="请输入移植人姓名"
              clearable
              size="small"
              @keyup.enter.native="handleQuery"
            />
          </el-form-item>
        </el-col>
        <el-col :span="6">
          <el-form-item label-width="10px">
            <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">搜索</el-button>
            <el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button>
            <el-button
              type="warning"
              plain
              icon="el-icon-download"
              size="small"
              :loading="exportLoading"
              @click="handleExport"
              v-hasPermi="['project:register:export']"
            >导出</el-button>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row class="mb8">
        <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
    </el-form>
    <el-table v-loading="loading" :data="registerList" :border="true">
      <el-table-column label="捐献者编号" align="center" prop="donorno" width="200" show-overflow-tooltip />
      <el-table-column label="姓名" align="center" prop="name" width="90" show-overflow-tooltip />
      <el-table-column label="性别" align="center" prop="sex" width="70">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.sys_user_sex" :value="scope.row.sex" />
        </template>
      </el-table-column>
      <el-table-column label="年龄" align="center" width="80">
        <template slot-scope="scope">
          <span v-if="scope.row.age">{{ scope.row.age }}{{ scope.row.ageunit }}</span>
        </template>
      </el-table-column>
      <el-table-column label="器官类别" align="center" prop="organname" width="90" show-overflow-tooltip />
      <el-table-column label="器官编号" align="center" prop="organnumber" width="130" show-overflow-tooltip />
      <el-table-column label="获取状态" align="center" prop="organstate" width="100">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.sys_organstate" :value="scope.row.organstate" />
        </template>
      </el-table-column>
      <el-table-column label="获取时间" align="center" prop="organgettime" width="150">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.organgettime) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="获取机构" align="center" prop="gainhospitalname" min-width="180" show-overflow-tooltip />
      <el-table-column label="移植机构" align="center" prop="transplanthospitalname" min-width="180" show-overflow-tooltip />
      <el-table-column label="移植医生" align="center" prop="transplantdoct" width="100" show-overflow-tooltip />
      <el-table-column label="移植时间" align="center" prop="transplanttime" width="150">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.transplanttime) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" width="90" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-view"
            @click="handleDetail(scope.row)"
            v-hasPermi="['project:register:query']"
          >详情</el-button>
        </template>
      </el-table-column>
    </el-table>
    <pagination
      v-show="total > 0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />
    <!-- è¯¦æƒ…:只读展示全部字段 -->
    <el-dialog title="器官去处详情" :visible.sync="open" width="900px" append-to-body>
      <div v-loading="detailLoading" class="register-detail">
        <el-divider content-position="left">捐献者信息</el-divider>
        <el-descriptions :column="3" size="small" border>
          <el-descriptions-item label="姓名">{{ detail.name }}</el-descriptions-item>
          <el-descriptions-item label="性别">{{ dictLabel("sys_user_sex", detail.sex) }}</el-descriptions-item>
          <el-descriptions-item label="年龄">
            <span v-if="detail.age">{{ detail.age }}{{ detail.ageunit }}</span>
          </el-descriptions-item>
          <el-descriptions-item label="证件号码">{{ detail.idcardno }}</el-descriptions-item>
          <el-descriptions-item label="出生日期">{{ parseTime(detail.birthday, "{y}-{m}-{d}") }}</el-descriptions-item>
          <el-descriptions-item label="联系电话">{{ detail.phone }}</el-descriptions-item>
          <el-descriptions-item label="现住地址" :span="3">{{ detail.residenceaddress }}</el-descriptions-item>
          <el-descriptions-item label="捐献者编号">{{ detail.donorno }}</el-descriptions-item>
        </el-descriptions>
        <el-divider content-position="left">器官信息</el-divider>
        <el-descriptions :column="3" size="small" border>
          <el-descriptions-item label="器官类别">{{ detail.organname }}</el-descriptions-item>
          <el-descriptions-item label="器官编号">{{ detail.organnumber }}</el-descriptions-item>
          <el-descriptions-item label="获取状态">
            {{ dictLabel("sys_organstate", detail.organstate) }}
          </el-descriptions-item>
          <el-descriptions-item label="获取时间">{{ parseTime(detail.organgettime) }}</el-descriptions-item>
          <el-descriptions-item label="获取医生" :span="2">{{ detail.organgetdoct }}</el-descriptions-item>
          <el-descriptions-item label="获取机构" :span="2">{{ detail.gainhospitalname }}</el-descriptions-item>
          <el-descriptions-item label="移植机构" :span="2">{{ detail.transplanthospitalname }}</el-descriptions-item>
          <el-descriptions-item label="移植医生">{{ detail.transplantdoct }}</el-descriptions-item>
          <el-descriptions-item label="移植时间" :span="2">{{ parseTime(detail.transplanttime) }}</el-descriptions-item>
        </el-descriptions>
        <el-divider content-position="left">申请与审核</el-divider>
        <el-descriptions :column="3" size="small" border>
          <el-descriptions-item label="申请人">{{ detail.applicantusername }}</el-descriptions-item>
          <el-descriptions-item label="申请时间" :span="2">{{ parseTime(detail.applicanttime) }}</el-descriptions-item>
          <el-descriptions-item label="审核人">{{ detail.checkusername }}</el-descriptions-item>
          <el-descriptions-item label="审核时间" :span="2">{{ parseTime(detail.checktime) }}</el-descriptions-item>
          <el-descriptions-item label="审核意见" :span="3">{{ detail.checksuggestion }}</el-descriptions-item>
        </el-descriptions>
        <el-divider content-position="left">移植人信息</el-divider>
        <el-descriptions :column="3" size="small" border>
          <el-descriptions-item label="姓名">{{ detail.acceptname }}</el-descriptions-item>
          <el-descriptions-item label="性别">{{ dictLabel("sys_user_sex", detail.acceptsex) }}</el-descriptions-item>
          <el-descriptions-item label="年龄">
            <span v-if="detail.acceptage">{{ detail.acceptage }}{{ detail.acceptageunit }}</span>
          </el-descriptions-item>
          <el-descriptions-item label="证件号码">{{ detail.acceptidcard }}</el-descriptions-item>
          <el-descriptions-item label="联系电话" :span="2">{{ detail.acceptphone }}</el-descriptions-item>
          <el-descriptions-item label="现住地址" :span="3">{{ detail.acceptresidenceaddress }}</el-descriptions-item>
        </el-descriptions>
      </div>
      <div slot="footer" class="dialog-footer">
        <el-button @click="open = false">关 é—­</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import { listRegister, getRegister, exportRegister } from "@/api/project/register";
export default {
  name: "Register",
  dicts: ["sys_Organ", "sys_organstate", "sys_user_sex", "sys_AgeUnit"],
  data() {
    return {
      // é®ç½©å±‚
      loading: true,
      // è¯¦æƒ…加载
      detailLoading: false,
      // å¯¼å‡ºé®ç½©å±‚
      exportLoading: false,
      // æ˜¾ç¤ºæœç´¢æ¡ä»¶
      showSearch: true,
      // æ€»æ¡æ•°
      total: 0,
      // å™¨å®˜åŽ»å¤„åˆ—è¡¨
      registerList: [],
      // è¯¦æƒ…弹窗
      open: false,
      detail: {},
      // æŸ¥è¯¢å‚数(均需与后端 queryList / Mapper æ”¯æŒçš„æ¡ä»¶ä¸€è‡´ï¼‰
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        name: null,
        idcardno: null,
        organno: null,
        organstate: null,
        organnumber: null,
        transplanthospitalname: null,
        acceptname: null
      }
    };
  },
  created() {
    this.getList();
  },
  methods: {
    /** æŸ¥è¯¢å™¨å®˜åŽ»å¤„åˆ—è¡¨ */
    getList() {
      this.loading = true;
      listRegister(this.queryParams).then(response => {
        this.registerList = response.rows;
        this.total = response.total;
        this.loading = false;
      });
    },
    /** æœç´¢æŒ‰é’®æ“ä½œ */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** é‡ç½®æŒ‰é’®æ“ä½œ */
    resetQuery() {
      this.resetForm("queryForm");
      this.handleQuery();
    },
    /** è¯¦æƒ…按钮操作 */
    handleDetail(row) {
      this.detail = {};
      this.open = true;
      this.detailLoading = true;
      getRegister(row.id).then(response => {
        this.detail = response.data || {};
        this.detailLoading = false;
      }).catch(() => {
        this.detailLoading = false;
      });
    },
    /** å­—典翻译(模板里用,避免多个 dict-tag åµŒå¥—) */
    dictLabel(type, value) {
      const list = this.dict.type[type] || [];
      const item = list.find(d => String(d.value) === String(value));
      return item ? item.label : value;
    },
    /** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
    handleExport() {
      this.$modal
        .confirm("是否确认导出器官去处数据?")
        .then(() => {
          this.exportLoading = true;
          return exportRegister(this.queryParams);
        })
        .then(response => {
          this.$download.name(response.msg);
          this.exportLoading = false;
        })
        .catch(() => {
          this.exportLoading = false;
        });
    }
  }
};
</script>
<style scoped>
.register-detail >>> .el-divider--horizontal {
  margin: 12px 0 14px;
}
.register-detail >>> .el-divider__text {
  color: var(--opo-primary, #0e768f);
  font-size: var(--opo-font-size-sm, 14px);
}
.register-detail >>> .el-descriptions-item__label {
  color: var(--opo-text-secondary, #6b7280);
  width: 90px;
}
</style>
src/views/project/reimbursement/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="创建者" prop="createBy">
        <el-input
          v-model="queryParams.createBy"
src/views/project/reimbursementdetail/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="创建人" prop="createBy">
        <el-input
          v-model="queryParams.createBy"
src/views/project/reimbursementpayee/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="资金申请主表ID" prop="rbid">
        <el-input
          v-model="queryParams.rbid"
src/views/project/relativesconfirmation/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -588,8 +589,8 @@
                  clearable
                  size="small"
                  v-model="form.signdate"
                  type="date"
                  value-format="yyyy-MM-dd hh:mm:ss"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="选择签字日期"
                >
                </el-date-picker>
@@ -745,8 +746,8 @@
                size="small"
                style="width: 310px"
                v-model="evaluationForm.hospitalassesstime"
                type="date"
                value-format="yyyy-MM-dd hh:mm:ss"
                type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择院级评估时间"
              >
              </el-date-picker>
@@ -792,8 +793,8 @@
                size="small"
                style="width: 310px"
                v-model="evaluationForm.provincialassesstime"
                type="date"
                value-format="yyyy-MM-dd hh:mm:ss"
                type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="选择省级评估时间"
              >
              </el-date-picker>
@@ -840,8 +841,8 @@
                size="small"
                style="width: 310px"
                v-model="evaluationForm.coreteamassesstime"
                type="date"
                value-format="yyyy-MM-dd hh:mm:ss"
                type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="评估时间"
              >
              </el-date-picker>
src/views/project/statistics/applyStatistics/index.vue
@@ -2,6 +2,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -265,48 +266,14 @@
        this.queryParams.pabegtime = this.selecttime[0];
        this.queryParams.paendtime = this.selecttime[1];
        // if (this.queryParams.paendtime == this.queryParams.pabegtime) {
        let num = Number(this.queryParams.paendtime.slice(5, 7));
        if (num < 9) {
          let mon = Number(this.queryParams.paendtime.slice(6, 7));
          this.queryParams.paendtime =
            this.queryParams.paendtime.slice(0, 5) +
            "0" +
            (mon + 1) +
            "-" +
            "01" +
            " " +
            "00" +
            ":" +
            "00" +
            ":" +
            "00";
        }
        // this.queryParams.paendtime=this.queryParams.paendtime.slice(0,5)å¹´
        else if (num >= 10) {
          this.queryParams.paendtime =
            this.queryParams.paendtime.slice(0, 5) +
            (num + 1) +
            "-" +
            "01" +
            " " +
            "00" +
            ":" +
            "00" +
            ":" +
            "00";
        } else {
          this.queryParams.paendtime =
            this.queryParams.paendtime.slice(0, 5) +
            "10" +
            "-" +
            "01" +
            " " +
            "00" +
            ":" +
            "00" +
            ":" +
            "00";
        }
        // ç»“束时间取"所选结束月的次月 1 å·",以包含整个结束月。
        // åŽŸå®žçŽ°æŠŠæœˆä»½ +1 åŽç›´æŽ¥æ‹¼å­—符串,12 æœˆä¼šå¾—到 13 æœˆï¼ˆ2026-13-01),
        // MySQL æŠ¥ Incorrect datetime value,存储过程直接 500。
        this.queryParams.paendtime = this.$moment(this.queryParams.paendtime)
          .startOf("month")
          .add(1, "month")
          .format("YYYY-MM")
          .concat("-01 00:00:00");
        this.queryParams.pabegtime =
          this.queryParams.pabegtime + " " + "00" + ":" + "00" + ":" + "00";
        // }
src/views/project/statistics/caseStatistics/index.vue
@@ -2,6 +2,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -271,48 +272,14 @@
        this.queryParams.pabegtime = this.selecttime[0];
        this.queryParams.paendtime = this.selecttime[1];
        // if (this.queryParams.paendtime == this.queryParams.pabegtime) {
        let num = Number(this.queryParams.paendtime.slice(5, 7));
        if (num < 9) {
          let mon = Number(this.queryParams.paendtime.slice(6, 7));
          this.queryParams.paendtime =
            this.queryParams.paendtime.slice(0, 5) +
            "0" +
            (mon + 1) +
            "-" +
            "01" +
            " " +
            "00" +
            ":" +
            "00" +
            ":" +
            "00";
        }
        // this.queryParams.paendtime=this.queryParams.paendtime.slice(0,5)å¹´
        else if (num >= 10) {
          this.queryParams.paendtime =
            this.queryParams.paendtime.slice(0, 5) +
            (num + 1) +
            "-" +
            "01" +
            " " +
            "00" +
            ":" +
            "00" +
            ":" +
            "00";
        } else {
          this.queryParams.paendtime =
            this.queryParams.paendtime.slice(0, 5) +
            "10" +
            "-" +
            "01" +
            " " +
            "00" +
            ":" +
            "00" +
            ":" +
            "00";
        }
        // ç»“束时间取"所选结束月的次月 1 å·",以包含整个结束月。
        // åŽŸå®žçŽ°æŠŠæœˆä»½ +1 åŽç›´æŽ¥æ‹¼å­—符串,12 æœˆä¼šå¾—到 13 æœˆï¼ˆ2026-13-01),
        // MySQL æŠ¥ Incorrect datetime value,存储过程直接 500。
        this.queryParams.paendtime = this.$moment(this.queryParams.paendtime)
          .startOf("month")
          .add(1, "month")
          .format("YYYY-MM")
          .concat("-01 00:00:00");
        this.queryParams.pabegtime =
          this.queryParams.pabegtime + " " + "00" + ":" + "00" + ":" + "00";
        // }
src/views/project/statistics/chargeStatistics/index.vue
@@ -2,6 +2,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -289,48 +290,14 @@
        this.queryParams.pabegtime = this.selecttime[0];
        this.queryParams.paendtime = this.selecttime[1];
        // if (this.queryParams.paendtime == this.queryParams.pabegtime) {
        let num = Number(this.queryParams.paendtime.slice(5, 7));
        if (num < 9) {
          let mon = Number(this.queryParams.paendtime.slice(6, 7));
          this.queryParams.paendtime =
            this.queryParams.paendtime.slice(0, 5) +
            "0" +
            (mon + 1) +
            "-" +
            "01" +
            " " +
            "00" +
            ":" +
            "00" +
            ":" +
            "00";
        }
        // this.queryParams.paendtime=this.queryParams.paendtime.slice(0,5)å¹´
        else if (num >= 10) {
          this.queryParams.paendtime =
            this.queryParams.paendtime.slice(0, 5) +
            (num + 1) +
            "-" +
            "01" +
            " " +
            "00" +
            ":" +
            "00" +
            ":" +
            "00";
        } else {
          this.queryParams.paendtime =
            this.queryParams.paendtime.slice(0, 5) +
            "10" +
            "-" +
            "01" +
            " " +
            "00" +
            ":" +
            "00" +
            ":" +
            "00";
        }
        // ç»“束时间取"所选结束月的次月 1 å·",以包含整个结束月。
        // åŽŸå®žçŽ°æŠŠæœˆä»½ +1 åŽç›´æŽ¥æ‹¼å­—符串,12 æœˆä¼šå¾—到 13 æœˆï¼ˆ2026-13-01),
        // MySQL æŠ¥ Incorrect datetime value,存储过程直接 500。
        this.queryParams.paendtime = this.$moment(this.queryParams.paendtime)
          .startOf("month")
          .add(1, "month")
          .format("YYYY-MM")
          .concat("-01 00:00:00");
        this.queryParams.pabegtime =
          this.queryParams.pabegtime + " " + "00" + ":" + "00" + ":" + "00";
        // }
src/views/project/statistics/performanceStaristics/index.vue
@@ -2,6 +2,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -277,48 +278,14 @@
        this.queryParams.pabegtime = this.selecttime[0];
        this.queryParams.paendtime = this.selecttime[1];
        // if (this.queryParams.paendtime == this.queryParams.pabegtime) {
        let num = Number(this.queryParams.paendtime.slice(5, 7));
        if (num < 9) {
          let mon = Number(this.queryParams.paendtime.slice(6, 7));
          this.queryParams.paendtime =
            this.queryParams.paendtime.slice(0, 5) +
            "0" +
            (mon + 1) +
            "-" +
            "01" +
            " " +
            "00" +
            ":" +
            "00" +
            ":" +
            "00";
        }
        // this.queryParams.paendtime=this.queryParams.paendtime.slice(0,5)å¹´
        else if (num >= 10) {
          this.queryParams.paendtime =
            this.queryParams.paendtime.slice(0, 5) +
            (num + 1) +
            "-" +
            "01" +
            " " +
            "00" +
            ":" +
            "00" +
            ":" +
            "00";
        } else {
          this.queryParams.paendtime =
            this.queryParams.paendtime.slice(0, 5) +
            "10" +
            "-" +
            "01" +
            " " +
            "00" +
            ":" +
            "00" +
            ":" +
            "00";
        }
        // ç»“束时间取"所选结束月的次月 1 å·",以包含整个结束月。
        // åŽŸå®žçŽ°æŠŠæœˆä»½ +1 åŽç›´æŽ¥æ‹¼å­—符串,12 æœˆä¼šå¾—到 13 æœˆï¼ˆ2026-13-01),
        // MySQL æŠ¥ Incorrect datetime value,存储过程直接 500。
        this.queryParams.paendtime = this.$moment(this.queryParams.paendtime)
          .startOf("month")
          .add(1, "month")
          .format("YYYY-MM")
          .concat("-01 00:00:00");
        this.queryParams.pabegtime =
          this.queryParams.pabegtime + " " + "00" + ":" + "00" + ":" + "00";
        // }
src/views/project/tallage/computingBill/index.vue
@@ -43,7 +43,7 @@
              <el-date-picker
                v-model="form.taxedtime"
                value-format="yyyy-MM-dd HH:mm:ss"
                type="datetime"
                type="datetime" format="yyyy-MM-dd HH:mm"
                :disabled="true"
                placeholder="选择时间"
              >
@@ -298,6 +298,7 @@
            <el-row :gutter="20">
              <!--用户数据-->
              <el-form
                class="opo-query-form"
                :model="patientqueryParams"
                ref="queryForm"
                size="small"
@@ -570,6 +571,7 @@
import Li_area_select from "@/components/Address";
import { getUserProfile } from "@/api/system/user";
import { getToken } from "@/utils/auth";
import { moneySum, moneyRound } from "@/utils/money";
import {
  ExpertfeeListnb,
  getFundtax,
@@ -1110,23 +1112,16 @@
        this.$modal.msgError("注意实收金额不等于应收金额,请核对数据!");
      }
    },
    //修改实收或应收后触发
    //修改实收或应收后触发(金额一律走精确运算)
    chargeSum() {
      let ChargeSum = 0;
      let ChargeamountSum = 0;
      //费用合计
      try {
        for (let i = 0; i < this.donorchargeorgans.length; i++) {
          if (!isNaN(parseFloat(this.donorchargeorgans[i].organcharge))) {
            ChargeSum += parseFloat(this.donorchargeorgans[i].organcharge);
          }
          if (!isNaN(parseFloat(this.donorchargeorgans[i].amount))) {
            ChargeamountSum += parseFloat(this.donorchargeorgans[i].amount);
          }
        }
        this.form.receivableamount = ChargeSum.toFixed(2);
        this.form.receivedamount = ChargeamountSum.toFixed(2);
        this.form.receivableamount = moneyRound(
          moneySum(this.donorchargeorgans, "organcharge")
        ).toFixed(2);
        this.form.receivedamount = moneyRound(
          moneySum(this.donorchargeorgans, "amount")
        ).toFixed(2);
      } catch {}
    },
src/views/project/tallage/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
@@ -82,7 +83,7 @@
        label="申请日期"
        align="center"
        prop="createTime"
        width="180"
        width="140"
      >
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.createTime, "{y}-{m}-{d}") }}</span>
@@ -91,16 +92,16 @@
      <!-- <el-table-column label="经办人" align="center" prop="username" /> -->
      <el-table-column
        label="申请单号"
        width="180"
        width="160"
        align="center"
        prop="applyno"
      />
      <el-table-column label="税前金额" align="center" prop="pretaxamount">
      <el-table-column label="税前金额" align="center" prop="pretaxamount" width="100" >
      </el-table-column>
      <el-table-column label="扣税金额" align="center" prop="taxMoney" />
      <el-table-column label="税后金额" align="center" prop="taxedamount" />
      <el-table-column label="是否算税" width="180" align="center" prop="istax">
      <el-table-column label="扣税金额" align="center" prop="taxMoney" width="100" />
      <el-table-column label="税后金额" align="center" prop="taxedamount" width="100" />
      <el-table-column label="是否算税" align="center" prop="istax" width="90" >
        <template slot-scope="scope">
          <dict-tag
            :options="dict.type.sys_0_1"
@@ -111,12 +112,12 @@
      <!-- <el-table-column label="总金额" align="center" prop="taxamount" /> -->
      <el-table-column label="算税时间" align="center" prop="taxedtime" />
      <el-table-column label="付款状态" align="center" prop="isdistribute">
      <el-table-column label="付款状态" align="center" prop="isdistribute" width="100" >
        <template slot-scope="scope">
          <span>{{ scope.row.isdistribute == "0" ? "未付款" : "已付款" }}</span>
        </template>
      </el-table-column>
      <el-table-column label="付款日期" align="center" prop="riqi" />
      <el-table-column label="付款日期" align="center" prop="riqi" width="120" />
      <el-table-column label="操作" width="280" align="center">
        <template slot-scope="scope">
          <el-button
src/views/project/travelexpenseapply/edit.vue
@@ -63,7 +63,7 @@
                </el-col>
                <el-col :span="7">
                    <el-form-item label="申请日期" prop="applyTime">
                        <el-date-picker style="width: 100%" v-model="form.applyTime" type="date"
                        <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="申请日期">
                        </el-date-picker>
                    </el-form-item>
@@ -101,7 +101,7 @@
                    <el-table-column prop="starttime" slot="" label="出发日期" fixed align="center" width="150">
                        <template slot-scope="scope">
                            <el-date-picker clearable size="small" style="width: 100%" v-model="scope.row.starttime"
                                type="date" value-format="yyyy-MM-dd HH:mm:ss" placeholder="出发日期"
                                type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd HH:mm:ss" placeholder="出发日期"
                                @change="getStandard(scope.row)">
                            </el-date-picker>
                        </template>
@@ -115,7 +115,7 @@
                    <el-table-column prop="endtime" slot="" label="到达日期" align="center" width="150" fixed>
                        <template slot-scope="scope">
                            <el-date-picker clearable size="small" style="width: 100%" v-model="scope.row.endtime"
                                type="date" value-format="yyyy-MM-dd HH:mm:ss" placeholder="到达日期">
                                type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd HH:mm:ss" placeholder="到达日期">
                            </el-date-picker>
                        </template>
                    </el-table-column>
@@ -340,6 +340,7 @@
} from "@/api/project/organization";
import { listDonatebaseinfo } from "@/api/project/donatebaseinfo";
import { getToken } from "@/utils/auth";
import { moneySum } from "@/utils/money";
export default {
    data() {
        return {
@@ -673,41 +674,25 @@
                    //几点取消的提示
                });
        },
        //计算费用合计
        //计算费用合计(金额一律走精确运算,禁止 parseFloat ç´¯åŠ ï¼‰
        sumTotalFee() {
            let allSum = 0;
            try {
                for (let i = 0; i < this.applicationDetailArr.length; i++) {
                    let totalFee = 0;
                    const row = this.applicationDetailArr[i];
                    //计算当前报销内容费用合计
                    if (!isNaN(parseFloat(this.applicationDetailArr[i].trafficexpense))) {
                        totalFee += parseFloat(this.applicationDetailArr[i].trafficexpense);
                    }
                    if (!isNaN(parseFloat(this.applicationDetailArr[i].cityfee))) {
                        totalFee += parseFloat(this.applicationDetailArr[i].cityfee);
                    }
                    if (!isNaN(parseFloat(this.applicationDetailArr[i].hotelexpense))) {
                        totalFee += parseFloat(this.applicationDetailArr[i].hotelexpense);
                    }
                    if (!isNaN(parseFloat(this.applicationDetailArr[i].otherexpense))) {
                        totalFee += parseFloat(this.applicationDetailArr[i].otherexpense);
                    }
                    if (!isNaN(parseFloat(this.applicationDetailArr[i].foodexpenses))) {
                        totalFee += parseFloat(this.applicationDetailArr[i].foodexpenses);
                    }
                    if (!isNaN(parseFloat(this.applicationDetailArr[i].foodallowance))) {
                        totalFee += parseFloat(this.applicationDetailArr[i].foodallowance);
                    }
                    if (!isNaN(parseFloat(this.applicationDetailArr[i].otherfeeamount))) {
                        totalFee += parseFloat(this.applicationDetailArr[i].otherfeeamount);
                    }
                    this.applicationDetailArr[i].total = parseFloat(totalFee).toFixed(2);
                    this.$set(this.applicationDetailArr, i, this.applicationDetailArr[i]);
                    if (!isNaN(parseFloat(this.applicationDetailArr[i].total))) {
                        allSum += parseFloat(this.applicationDetailArr[i].total);
                    }
                    const totalFee = moneySum([
                        row.trafficexpense,
                        row.cityfee,
                        row.hotelexpense,
                        row.otherexpense,
                        row.foodexpenses,
                        row.foodallowance,
                        row.otherfeeamount
                    ]);
                    row.total = totalFee.toFixed(2);
                    this.$set(this.applicationDetailArr, i, row);
                }
                this.form.amountrequested = allSum.toFixed(2);
                this.form.amountrequested = moneySum(this.applicationDetailArr, "total").toFixed(2);
                this.form.bigstrmoney = this.convertCurrency(this.form.amountrequested);
            } catch { }
        },
src/views/project/travelexpenseapply/index.vue
@@ -1,12 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      :model="queryParams"
      ref="queryForm"
      :inline="true"
      v-show="showSearch"
      label-width="68px"
    >
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-row>
        <!--
        <el-col :span="6">
@@ -18,53 +13,25 @@
-->
        <el-col :span="6">
          <el-form-item label="经办人" prop="username">
            <el-input
              v-model="queryParams.username"
              placeholder="请输入经办人"
              clearable
              size="small"
              @keyup.enter.native="handleQuery"
            />
            <el-input v-model="queryParams.username" placeholder="请输入经办人" clearable size="small"
              @keyup.enter.native="handleQuery" />
          </el-form-item>
        </el-col>
        <el-col :span="8">
          <el-form-item label="报销日期" prop="applyTime">
            <el-date-picker
              format="yyyy-MM-dd"
              value-format="yyyy-MM-dd"
              v-model="value1"
              type="daterange"
              @blur="onpick"
              range-separator="至"
              start-placeholder="报销开始日期"
              end-placeholder="报销结束日期"
              @keyup.enter.native="handleQuery"
            >
            <el-date-picker format="yyyy-MM-dd" value-format="yyyy-MM-dd HH:mm:ss" v-model="value1" type="daterange"
              @change="selecttime" range-separator="至" start-placeholder="报销开始日期" end-placeholder="报销结束日期"
              @keyup.enter.native="handlQuery">
            </el-date-picker>
          </el-form-item>
        </el-col>
        </el-col>
        <el-col :span="6">
          <el-form-item label="审核状态" prop="recordstatus">
            <el-input
              v-model="queryParams.recordstatus"
              placeholder="请选择审核状态"
              clearable
              size="small"
              v-if="0"
              @keyup.enter.native="handleQuery"
            />
            <el-select
              v-model="queryParams.recordstatus"
              placeholder="请选择审核状态"
              clearable
              size="small"
            >
              <el-option
                v-for="dict in dict.type.sys_recordstatus"
                :key="dict.value"
                :label="dict.label"
                :value="dict.value"
              />
            <el-input v-model="queryParams.recordstatus" placeholder="请选择审核状态" clearable size="small" v-if="0"
              @keyup.enter.native="handleQuery" />
            <el-select v-model="queryParams.recordstatus" placeholder="请选择审核状态" clearable 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>
@@ -72,27 +39,14 @@
      <el-row>
        <el-col :span="6">
          <el-form-item label="捐献者" prop="name">
            <el-input
              v-model="queryParams.donorname"
              placeholder="请输入捐献者姓名"
              clearable
              size="small"
              @keyup.enter.native="handleQuery"
            />
            <el-input v-model="queryParams.donorname" placeholder="请输入捐献者姓名" clearable size="small"
              @keyup.enter.native="handleQuery" />
          </el-form-item>
        </el-col>
        <el-col :span="4">
          <el-form-item>
            <el-button
              type="primary"
              icon="el-icon-search"
              size="mini"
              @click="handleQuery"
              >搜索</el-button
            >
            <el-button icon="el-icon-refresh" size="mini" @click="resetQuery"
              >重置</el-button
            >
            <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
            <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-col>
      </el-row>
@@ -100,104 +54,48 @@
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button type="primary" plain icon="el-icon-plus" @click="handleAdd"
          >新增</el-button
        >
        <el-button type="primary" plain icon="el-icon-plus" @click="handleAdd">新增</el-button>
        <!-- v-hasPermi="['project:funddetail:add']" -->
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="el-icon-download"
          :loading="exportLoading"
          @click="handleExport"
          >导出</el-button
        >
        <el-button type="warning" plain icon="el-icon-download" :loading="exportLoading"
          @click="handleExport">导出</el-button>
      </el-col>
      <!-- v-hasPermi="['project:funddetail:export']" -->
      <right-toolbar
        :showSearch.sync="showSearch"
        @queryTable="getList"
      ></right-toolbar>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>
    <el-table
      :row-class-name="rowClassName"
      v-loading="loading"
      :data="reimbursementList"
      @selection-change="handleSelectionChange"
    >
    <el-table :row-class-name="rowClassName" v-loading="loading" :data="reimbursementList"
      @selection-change="handleSelectionChange">
      <!-- <el-table-column type="selection" width="55" align="center" /> 
      <el-table-column label="报销人类别" width="120" align="center" prop="costtype">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.sys_travelexpensebelong" :value="scope.row.costtype" />
        </template>
      </el-table-column>-->
      <el-table-column
        label="申请时间"
        width="150"
        align="center"
        prop="applyTime"
      >
</el-table-column>-->
      <el-table-column label="申请时间" width="120" align="center" prop="applyTime">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.applyTime, "{y}-{m}-{d}") }}</span>
        </template>
      </el-table-column>
      <el-table-column
        label="审核状态"
        align="center"
        prop="recordstatus"
        width="160"
      >
      <el-table-column label="审核状态" align="center" prop="recordstatus" width="140">
        <template slot-scope="scope">
          <dict-tag
            :options="dict.type.sys_recordstatus"
            :value="scope.row.recordstatus"
          />
          <dict-tag :options="dict.type.sys_recordstatus" :value="scope.row.recordstatus" />
        </template>
      </el-table-column>
      <el-table-column
        label="材料预审状态"
        width="140"
        align="center"
        prop="checkstatus"
      >
      <el-table-column label="材料预审状态" width="140" align="center" prop="checkstatus">
        <template slot-scope="scope">
          <dict-tag
            :options="dict.type.sys_stage_type"
            :value="scope.row.checkstatus"
          />
          <dict-tag :options="dict.type.sys_stage_type" :value="scope.row.checkstatus" />
        </template>
      </el-table-column>
      <el-table-column
        label="经办人"
        width="150"
        align="center"
        prop="username"
      />
      <el-table-column label="经办人" width="100" align="center" prop="username" />
      <!--
      <el-table-column label="出差人" width="180" align="center" prop="travelers">
      </el-table-column>-->
      <el-table-column
        label="报销金额"
        width="150"
        align="center"
        prop="amountrequested"
      >
      <el-table-column label="报销金额" width="120" align="center" prop="amountrequested">
      </el-table-column>
      <el-table-column
        label="所属组别"
        width="150"
        align="center"
        prop="deptmentname"
      />
      <el-table-column
        label="组长"
        width="150"
        align="center"
        prop="managername"
      />
      <el-table-column label="所属组别" width="120" align="center" prop="deptmentname" />
      <el-table-column label="组长" width="100" align="center" prop="managername" />
      <el-table-column label="出差事由" align="center" prop="reason" />
      <!-- <el-table-column label="联系电话" width="120" align="center" prop="phone">
      </el-table-column>
@@ -207,130 +105,55 @@
      </el-table-column>
      <el-table-column label="报销合计" align="center" prop="financechecher"/>-->
      <!-- <el-table-column label="财务审核" width="120" align="center" prop="financechecher" /> -->
      <el-table-column
        label="捐献者"
        width="150"
        align="center"
        prop="donorname"
      />
      <el-table-column
        label="操作"
        align="center"
        fixed="right"
        width="300"
        class-name="small-padding fixed-width"
      >
      <el-table-column label="捐献者" width="100" align="center" prop="donorname" />
      <el-table-column label="操作" align="center" fixed="right" width="300" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            v-if="scope.row.recordstatus == -1 || scope.row.recordstatus == 1"
            size="mini"
            type="text"
            icon="el-icon-upload2"
            @click="handleup(scope.row)"
            >上报</el-button
          >
          <el-button
            v-if="scope.row.recordstatus == -1 || scope.row.recordstatus == 1"
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            >修改</el-button
          >
          <el-button
            size="mini"
            type="text"
            icon="el-icon-view"
            @click="handleDetail(scope.row)"
            >查看</el-button
          >
          <el-button v-if="scope.row.recordstatus == -1 || scope.row.recordstatus == 1" size="mini" type="text"
            icon="el-icon-upload2" @click="handleup(scope.row)">上报</el-button>
          <el-button v-if="scope.row.recordstatus == -1 || scope.row.recordstatus == 1" size="mini" type="text"
            icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
          <el-button size="mini" type="text" icon="el-icon-view" @click="handleDetail(scope.row)">查看</el-button>
          <!-- v-hasPermi="['project:funddetail:edit']" -->
          <el-button
            size="mini"
            type="text"
            icon="el-icon-download"
            @click="gettable(scope.row)"
          >
          <el-button size="mini" type="text" icon="el-icon-download" @click="gettable(scope.row)">
            ä¸‹è½½
          </el-button>
          <!-- v-hasPermi="['project:funddetail:edit']" -->
          <el-button
            v-if="scope.row.recordstatus == -1 || scope.row.recordstatus == 1"
            size="mini"
            type="text"
            @click="handleDelete(scope.row)"
            ><span class="button-delete"
              ><i class="el-icon-delete"></i>删除</span
            ></el-button
          >
          <el-button v-if="scope.row.recordstatus == -1 || scope.row.recordstatus == 1" size="mini" type="text"
            @click="handleDelete(scope.row)"><span class="button-delete"><i
                class="el-icon-delete"></i>删除</span></el-button>
          <!-- v-hasPermi="['project:funddetail:remove']" -->
        </template>
      </el-table-column>
      <el-table-column fixed="right" align="center" width="120" label="功能">
      <el-table-column fixed="right" align="center" width="120" label="功能" v-if="false">
        <template slot-scope="scope">
          <el-button size="mini" v-if="scope.row.uploadStates == 1"
            >线下已报销</el-button
          >
          <el-button
            size="mini"
            type="success"
            v-else-if="scope.row.uploadStates == 0"
            @click="Dooffline('1', scope.row)"
            >线下报销</el-button
          >
          <el-button size="mini" v-if="scope.row.uploadStates == 1">线下已报销</el-button>
          <el-button size="mini" type="success" v-else-if="scope.row.uploadStates == 0"
            @click="Dooffline('1', scope.row)">线下报销</el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- v-hasPermi="['project:donateconsolationfund:edit']" -->
    <pagination
      v-show="total > 0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />
    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
      @pagination="getList" />
    <!-- æ·»åŠ æˆ–ä¿®æ”¹è´¹ç”¨ç”³è¯·æ˜Žç»†å¯¹è¯æ¡† -->
    <el-dialog
      :title="title"
      :visible.sync="open"
      :close-on-click-modal="false"
      width="1400px"
      append-to-body
      @close="reset"
    >
    <el-dialog :title="title" :visible.sync="open" :close-on-click-modal="false" width="1400px" append-to-body
      @close="reset">
      <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"
                value-format="yyyy-MM-dd HH:mm:ss"
                placeholder="申请日期"
              >
              <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="申请日期">
              </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 in personsList" :key="item.reportNo" :label="item.reportName"
                  :value="item.reportName">
                </el-option>
              </el-select>
            </el-form-item>
@@ -355,18 +178,8 @@
          </el-col>
          <el-col :span="5">
            <el-form-item label="捐献者" prop="infoid">
              <el-select
                v-model="form.infoid"
                @change="selectbaseinfo"
                filterable
                placeholder="请选择捐献者"
              >
                <el-option
                  v-for="item in baselist"
                  :key="item.index"
                  :label="item.name"
                  :value="item.infoid"
                >
              <el-select v-model="form.infoid" @change="selectbaseinfo" filterable placeholder="请选择捐献者">
                <el-option v-for="item in baselist" :key="item.index" :label="item.name" :value="item.infoid">
                </el-option>
              </el-select>
            </el-form-item>
@@ -375,36 +188,17 @@
        <el-row style="margin-top: 10px">
          <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="10">
            <el-form-item label="大写金额" prop="bigstrmoney">
              <el-input
                :disabled="true"
                v-model="form.bigstrmoney"
                placeholder="报销大写金额"
              />
            </el-form-item> </el-col
          ><el-col :span="5">
              <el-input :disabled="true" v-model="form.bigstrmoney" placeholder="报销大写金额" />
            </el-form-item> </el-col><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>
@@ -511,263 +305,117 @@
        </el-row>
        <el-row>
          <el-table :data="rbDetails" border highlight-current-row>
            <el-table-column
              prop="orderno"
              slot=""
              label="序号"
              align="center"
              fixed
              width="65"
            >
            <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="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="请选择"
                  @focus1="getPersons(scope.row)"
                >
                  <el-option
                    v-for="item in personsSel"
                    :key="item.reportNo"
                    :label="item.reportName"
                    :value="item.reportName"
                  >
                <el-select v-model="scope.row.personname" clearable filterable allow-create default-first-option
                  placeholder="请选择" @focus1="getPersons(scope.row)">
                  <el-option v-for="item in personsSel" :key="item.reportNo" :label="item.reportName"
                    :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"
                  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"
                  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="65" 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="80"
            >
            <el-table-column prop="trafficexpense" label="交通费" align="center" width="80">
              <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>
            <!--
@@ -782,144 +430,70 @@
              </template>
            </el-table-column>
                                                                                                                                      -->
            <el-table-column
              prop="hotelexpense"
              label="住宿费"
              align="center"
              width="80"
            >
            <el-table-column prop="hotelexpense" label="住宿费" align="center" width="80">
              <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>
            <el-table-column
              prop="foodexpenses"
              label="餐费报销"
              align="center"
              width="80"
            >
            <el-table-column prop="foodexpenses" label="餐费报销" align="center" width="80">
              <template slot-scope="scope">
                <el-input
                  v-model="scope.row.foodexpenses"
                  placeholder="伙食费报销"
                  @blur="
                    val => {
                      sumRowFee(scope.row);
                    }
                  "
                />
                <el-input v-model="scope.row.foodexpenses" placeholder="伙食费报销" @blur="
                  val => {
                    sumRowFee(scope.row);
                  }
                " />
              </template>
            </el-table-column>
            <el-table-column
              prop="foodallowance"
              label="伙食补助"
              align="center"
              width="80"
            >
            <el-table-column prop="foodallowance" label="伙食补助" align="center" width="80">
              <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="80"
            >
            <el-table-column prop="otherexpense" label="公杂费补助" align="center" width="80">
              <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="90"
            >
            <el-table-column prop="otherfeeamount" label="其他费用" align="center" width="90">
              <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"
            >
            <el-table-column prop="total" label="合计" align="center" width="110">
              <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"
              v-if="dialogType == 'edit'"
            >
            <el-table-column fixed="right" label="操作" align="center" width="120" v-if="dialogType == 'edit'">
              <template slot-scope="scope">
                <el-button
                  type="text"
                  size="mini"
                  @click="addDetailRow(scope.$index)"
                  >新增</el-button
                >
                <el-button
                  @click.native.prevent="
                    deleteDetailRow(scope.$index, rbDetails, scope.row)
                  "
                  type="text"
                  size="small"
                >
                <el-button type="text" size="mini" @click="addDetailRow(scope.$index)">新增</el-button>
                <el-button @click.native.prevent="
                  deleteDetailRow(scope.$index, rbDetails, scope.row)
                  " type="text" size="small">
                  åˆ é™¤
                </el-button>
              </template>
@@ -931,192 +505,75 @@
        </el-row>
        <el-row style="margin-top: 30px">
          <el-table :data="rbPayees" border highlight-current-row>
            <el-table-column
              prop="orderno"
              slot=""
              label="序号"
              align="center"
              fixed
              width="65"
            >
            <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="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
                  ref="getReportname"
                  default-first-option
                  placeholder="请选择"
                  @change="getAccountinfo2(scope.row, scope.row.persontype)"
                >
                  <el-option
                    v-for="item in personsSel"
                    :key="item.reportNo"
                    :label="item.reportName"
                    :value="item.reportName"
                  >
                <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.reportNo" :label="item.reportName"
                    :value="item.reportName">
                  </el-option>
                </el-select>
              </template>
            </el-table-column>
            <el-table-column
              prop="bankname"
              slot=""
              label="银行名称"
              align="center"
              fixed
              width="200"
            >
            <el-table-column prop="bankname" slot="" label="银行名称" align="center" fixed width="200">
              <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
              width="280"
            >
            <el-table-column prop="remark" slot="" label="请款说明" align="center" fixed width="280">
              <template slot-scope="scope">
                <el-input v-model="scope.row.remark" placeholder="说明" />
              </template> </el-table-column
            ><el-table-column
              prop="paiddate"
              slot=""
              label="打款日期"
              fixed
              align="center"
              width="150"
            >
              </template> </el-table-column><el-table-column prop="paiddate" slot="" label="打款日期" fixed align="center"
              width="150">
              <template slot-scope="scope">
                <el-date-picker
                  clearable
                  size="small"
                  style="width: 100%"
                  v-model="scope.row.paiddate"
                  type="date"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="打款日期"
                  :disabled="true"
                >
                <el-date-picker clearable size="small" style="width: 100%" v-model="scope.row.paiddate" type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss" placeholder="打款日期" :disabled="true">
                </el-date-picker>
              </template>
            </el-table-column>
            <el-table-column
              label="操作"
              align="center"
              width="100"
              v-if="dialogType == 'edit'"
            >
            <el-table-column label="操作" align="center" width="100" v-if="dialogType == '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>
@@ -1163,76 +620,30 @@
                                                                                              -->
            <el-form-item label="文件上传" align="left" prop="annexfiles">
              <!-- <file-upload ref="fileUpload" :fileType=fileType :limit="1" :on-success="handleUploadSuccess"></file-upload> -->
              <el-upload
                size="mini"
                class="upload-demo"
                :action="uploadFileUrl"
                :file-list="fileList"
                multiple
                :limit="20"
                :headers="headers"
                :on-success="
                  (response, file, fileList) =>
              <el-upload size="mini" class="upload-demo" :action="uploadFileUrl" :file-list="fileList" multiple
                :limit="20" :headers="headers" :on-success="(response, file, fileList) =>
                    uploadSccess(response, file, fileList)
                "
                :on-preview="downFile"
                :disabled="dialogType == 'detail'"
                :on-error="handleUploadError"
                :on-exceed="handleExceed"
                :on-remove="remove"
                accept="image/*,.pdf"
              >
                <el-button
                  :disabled="dialogType == 'detail'"
                  size="small"
                  type="primary"
                  >上传</el-button
                >
                  " :on-preview="downFile" :disabled="dialogType == 'detail'" :on-error="handleUploadError"
                :on-exceed="handleExceed" :on-remove="remove" accept="image/*,.pdf">
                <el-button :disabled="dialogType == 'detail'" size="small" type="primary">上传</el-button>
              </el-upload>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row v-if="dialogType == 'detail'">
          <el-table :data="fundflowList" border>
            <el-table-column
              label="审核人"
              align="center"
              width="120"
              prop="checkusername"
            />
            <el-table-column
              label="审核日期"
              align="center"
              width="160"
              prop="createTime"
            />
            <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 label="审核人" align="center" width="120" prop="checkusername" />
            <el-table-column label="审核日期" align="center" width="160" prop="createTime" />
            <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-form>
      <div slot="footer" class="dialog-footer">
        <el-button
          type="primary"
          @click="submitForm"
          v-if="dialogType == 'edit'"
          >保 å­˜</el-button
        >
        <el-button type="primary" :loading="saveLoading" @click="submitForm" v-if="dialogType == 'edit'">保 å­˜</el-button>
        <el-button @click="cancel">取 æ¶ˆ</el-button>
      </div>
    </el-dialog>
@@ -1277,6 +688,7 @@
import { addorupdateFund } from "@/api/project/fund";
import { listReportname, listUser } from "@/api/project/organization";
import { getToken } from "@/utils/auth";
import { moneySum, moneyAdd } from "@/utils/money";
export default {
  components: {
    Treeselect,
@@ -1328,6 +740,8 @@
      loading: true,
      // å¯¼å‡ºé®ç½©å±‚
      exportLoading: false,
      // ä¿å­˜æŒ‰é’® loading,防止重复提交
      saveLoading: false,
      // é€‰ä¸­æ•°ç»„
      ids: [],
      // éžå•个禁用
@@ -1348,17 +762,10 @@
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        itemname: null,
        itemtype: null,
        costtype: null,
        donorname: null,
        starttime: null,
        remark: null,
        departure: null,
        endtime: null,
        destination: null,
        uploadFlag: null,
        uploadTime: null,
        // orderByColumn: "createTime",
        orderByColumn: "id",
        isAsc: "desc"
      },
      queryParams2: {
@@ -1457,7 +864,9 @@
  },
  methods: {
    handleUploadError() {},
    handleUploadError() { },
    remove(file) {
      this.fileList.splice(this.fileList.indexOf(file), 1);
@@ -1596,7 +1005,7 @@
        row.days = (
          Math.floor(
            Math.abs(Date.parse(row.starttime) - Date.parse(row.endtime)) /
              (1000 * 3600 * 24)
            (1000 * 3600 * 24)
          ) + 1
        ).toString();
      } else {
@@ -1618,9 +1027,9 @@
        if (tempStr == "杭州") {
          row.otherexpense = "";
        } else if (
          tempStr == "杭州-桐庐" ||
          tempStr == "杭州-建德" ||
          tempStr == "杭州-淳安"
          tempStr.includes("桐庐") ||
          tempStr.includes("建德") ||
          tempStr.includes("淳安")
        ) {
          row.otherexpense = (parseInt(row.days) * 60).toString();
        } else {
@@ -1751,11 +1160,7 @@
        }
      });
    },
    onpick(e) {
      // console.log(e,this.value1)
    },
    gettable(e) {
      const id = e.id;
      getdownload(e.id).then(res => {
@@ -1934,19 +1339,26 @@
      //获取报销主信息
      const id = row.id || this.ids;
      getReimbursement(id).then(response => {
        this.form = response.data;
        this.fileList = this.form.annexfiles
          ? this.form.annexfiles
      getReimbursement(id)
        .then(async response => {
          this.form = response.data;
          this.fileList = this.form.annexfiles
            ? this.form.annexfiles
              .split(",")
              .map(item => ({ url: item, name: item }))
          : [];
        this.form.recordstatus = response.data.recordstatus + "";
            : [];
          this.form.recordstatus = response.data.recordstatus + "";
        let ids = response.data.id;
        //获取报销明细信息
        getReimbursementdetaillist(ids).then(res => {
          this.rbDetails = res.data;
          const ids = response.data.id;
          this.queryParamsPayee.rbid = ids;
          //明细与打款信息互不依赖,并发获取,减少等待时间
          const [detailRes, payeeRes] = await Promise.all([
            getReimbursementdetaillist(ids),
            listReimbursementpayee(this.queryParamsPayee)
          ]);
          this.rbDetails = detailRes.data;
          if (this.rbDetails.length == 0) {
            this.addDetailRow(0);
          }
@@ -1965,17 +1377,15 @@
          //实时合计金额
          //this.sumTotalFee();
        });
        //获取报销打款信息
        this.queryParamsPayee.rbid = ids;
        listReimbursementpayee(this.queryParamsPayee).then(res => {
          this.rbPayees = res.rows;
          this.rbPayees = payeeRes.rows;
          if (this.rbPayees.length == 0) {
            this.addPayeeRow(0);
          }
        })
        .catch(() => {
          this.$modal.msgError("报销单信息加载失败,请重试!");
        });
      });
    },
    /** æŸ¥çœ‹æŒ‰é’®æ“ä½œ */
@@ -1992,59 +1402,56 @@
      this.open = true;
      const id = row.id || this.ids;
      getReimbursement(id).then(response => {
        this.form = response.data;
        //附件
        this.fileList = this.form.annexfiles
          ? this.form.annexfiles
      getReimbursement(id)
        .then(async response => {
          this.form = response.data;
          //附件
          this.fileList = this.form.annexfiles
            ? this.form.annexfiles
              .split(",")
              .map(item => ({ url: item, name: item }))
          : [];
            : [];
        //审核状态
        this.form.recordstatus = response.data.recordstatus + "";
          //审核状态
          this.form.recordstatus = response.data.recordstatus + "";
        let ids = response.data.id;
        let listFundflowparams = {
          fundid: row.id,
          fundtype: 1
        };
        listFundflow(listFundflowparams).then(res => {
          this.fundflowList = res.rows;
        });
          const ids = response.data.id;
          const listFundflowparams = {
            fundid: row.id,
            fundtype: 1
          };
          this.queryParamsPayee.rbid = ids;
        //报销明细
        getReimbursementdetaillist(ids).then(res => {
          this.rbDetails = res.data;
          /*
          for (let i = 0; i < this.rbDetails.length; i++) {
            this.rbDetails[i].index = i;
            this.rbDetails[i].searchAddress = {
              sheng: this.rbDetails[i].travelprovincename,
              shi: this.rbDetails[i].travelcityname,
              qu: this.rbDetails[i].traveltownname,
            };
          }
          */
        });
          //资金流水、报销明细、打款信息互不依赖,并发获取
          const [fundflowRes, detailRes, payeeRes] = await Promise.all([
            listFundflow(listFundflowparams),
            getReimbursementdetaillist(ids),
            listReimbursementpayee(this.queryParamsPayee)
          ]);
        if (this.rbDetails.length > 0) this.sumTotalFee();
          this.fundflowList = fundflowRes.rows;
          this.rbDetails = detailRes.data;
        //付款信息
        this.queryParamsPayee.rbid = ids;
        listReimbursementpayee(this.queryParamsPayee).then(res => {
          this.rbPayees = res.rows;
          //明细加载完成后再计算合计,修复原代码在明细返回前就 sumTotalFee çš„竞态问题
          if (this.rbDetails.length > 0) this.sumTotalFee();
          //付款信息
          this.rbPayees = payeeRes.rows;
          if (this.rbPayees.length == 0) {
            this.addPayeeRow(0);
          }
        })
        .catch(() => {
          this.$modal.msgError("报销单信息加载失败,请重试!");
        });
      });
    },
    /** æäº¤æŒ‰é’® */
    submitForm() {
      this.$refs["form"].validate(valid => {
        if (valid) {
      //防止重复提交:保存中直接忽略再次点击
      if (this.saveLoading) return;
      this.$refs["form"].validate(async valid => {
        if (!valid) return;
          /**
           let indexErrTime = this.rbDetails.findIndex((item) => {
             if (item.endtime && item.starttime) {
@@ -2101,6 +1508,8 @@
          }
          */
          try {
          this.saveLoading = true;
          //附件文件处理
          let list = this.fileList;
          if (list.length > 0) {
@@ -2126,83 +1535,90 @@
          const rbDetails = [...this.rbDetails];
          const rbPayees = [...this.rbPayees];
          if (this.form.id != null) {
            updateReimbursement(this.form)
              .then(res => {
                //保存明细
                for (let i = 0; i < rbDetails.length; i++) {
                  if (rbDetails[i].rbid != null) {
                    updateReimbursementdetail(rbDetails[i]);
                  } else {
                    //rbid关联主表Id
                    rbDetails[i].rbid = this.form.id;
                    addReimbursementdetail(rbDetails[i]);
                  }
                }
            //第1步:更新主表,成功后再处理子表
            await updateReimbursement(this.form);
                //保存账户
                for (let i = 0; i < rbPayees.length; i++) {
                  if (rbPayees[i].rbid != null) {
                    updateReimbursementpayee(rbPayees[i]);
                  } else {
                    //rbid关联主表Id
                    rbPayees[i].rbid = this.form.id;
                    addReimbursementpayee(rbPayees[i]);
                  }
            //第2步:并发保存明细,等待全部完成,任一失败则走 catch æç¤º
            await Promise.all(
              rbDetails.map(detail => {
                if (detail.rbid != null) {
                  return updateReimbursementdetail(detail);
                }
                this.$modal.msgSuccess("修改报销单保存成功!");
                this.open = false;
                this.getList();
                //rbid关联主表Id
                detail.rbid = this.form.id;
                return addReimbursementdetail(detail);
              })
              .catch(err => {
                this.$modal.msgError("修改报销单保存失败!");
              });
            );
            //第3步:并发保存账户
            await Promise.all(
              rbPayees.map(payee => {
                if (payee.rbid != null) {
                  return updateReimbursementpayee(payee);
                }
                //rbid关联主表Id
                payee.rbid = this.form.id;
                return addReimbursementpayee(payee);
              })
            );
            this.$modal.msgSuccess("修改报销单保存成功!");
          } else {
            this.form.recordstatus = -1;
            addReimbursement(this.form)
              .then(response => {
                //保存明细
                for (let i = 0; i < rbDetails.length; i++) {
                  //rbid关联主表Id
                  rbDetails[i].rbid = response.data;
                  addReimbursementdetail(rbDetails[i]);
                }
            //第1步:新增主表,返回主表Id
            const response = await addReimbursement(this.form);
                //保存账号
                for (let i = 0; i < rbPayees.length; i++) {
                  //rbid关联主表Id
                  rbPayees[i].rbid = response.data;
                  addReimbursementpayee(rbPayees[i]);
                }
                this.$modal.msgSuccess("新增报销单保存成功!");
                this.open = false;
                this.getList();
            //第2步:并发保存明细
            await Promise.all(
              rbDetails.map(detail => {
                //rbid关联主表Id
                detail.rbid = response.data;
                return addReimbursementdetail(detail);
              })
              .catch(err => {
                this.$modal.msgError("新增报销单保存失败!");
              });
            );
            //第3步:并发保存账号
            await Promise.all(
              rbPayees.map(payee => {
                //rbid关联主表Id
                payee.rbid = response.data;
                return addReimbursementpayee(payee);
              })
            );
            this.$modal.msgSuccess("新增报销单保存成功!");
          }
          //所有保存步骤全部成功后才关闭弹窗并刷新列表
          this.open = false;
          this.getList();
          } catch (err) {
            this.$modal.msgError("报销单保存失败,请重试!");
          } finally {
            this.saveLoading = false;
          }
        }
      });
      );
    },
    /** åˆ é™¤æŒ‰é’®æ“ä½œ */
    handleDelete(row) {
      const ids = row.id || this.ids;
      console.log(ids);
      this.$modal
        .confirm("是否确认删除该条记录?")
        .then(function() {
        .then(function () {
          // åˆ é™¤details内的数据
          return delReimbursement(ids);
        })
        .then(() => {
          delReimbursementdetail(ids);
          //等待明细删除完成后再刷新列表,避免列表刷新时明细尚未删除
          return delReimbursementdetail(ids);
        })
        .then(() => {
          this.getList();
          this.$modal.msgSuccess("删除成功");
        })
        .catch(() => {});
        .catch(() => { });
    },
    /** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
@@ -2218,7 +1634,10 @@
          this.$download.name(response.msg);
          this.exportLoading = false;
        })
        .catch(() => {});
        .catch(() => {
          //失败时也要复位 loading,否则按钮永久转圈
          this.exportLoading = false;
        });
    },
    //新增一行
@@ -2399,109 +1818,32 @@
    },
    //计算一行合计
    //计算当前行报销内容费用合计
    //计算当前行报销内容费用合计(金额一律走精确运算)
    sumRowFee(row) {
      let totalFee = 0;
      if (!isNaN(parseFloat(row.trafficexpense))) {
        totalFee += parseFloat(row.trafficexpense);
      }
      if (!isNaN(parseFloat(row.cityfee))) {
        totalFee += parseFloat(row.cityfee);
      }
      if (!isNaN(parseFloat(row.hotelexpense))) {
        totalFee += parseFloat(row.hotelexpense);
      }
      if (!isNaN(parseFloat(row.otherexpense))) {
        totalFee += parseFloat(row.otherexpense);
      }
      if (!isNaN(parseFloat(row.foodexpenses))) {
        totalFee += parseFloat(row.foodexpenses);
      }
      if (!isNaN(parseFloat(row.foodallowance))) {
        totalFee += parseFloat(row.foodallowance);
      }
      if (!isNaN(parseFloat(row.otherfeeamount))) {
        totalFee += parseFloat(row.otherfeeamount);
      }
      row.total = totalFee;
      row.total = moneySum([
        row.trafficexpense,
        row.cityfee,
        row.hotelexpense,
        row.otherexpense,
        row.foodexpenses,
        row.foodallowance,
        row.otherfeeamount
      ]);
      /*
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].trafficexpense))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].trafficexpense);
      }
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].cityfee))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].cityfee);
      }
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].hotelexpense))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].hotelexpense);
      }
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].otherexpense))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].otherexpense);
      }
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].foodexpenses))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].foodexpenses);
      }
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].foodallowance))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].foodallowance);
      }
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].otherfeeamount))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].otherfeeamount);
      }
      this.rbDetails[rowIndex].total = parseFloat(totalFee).toFixed(2);
      this.$set(this.rbDetails, rowIndex, this.rbDetails[rowIndex]);
      */
      //重新计算整个报销单的合计
      this.sumTotalFee();
    },
    //计算费用合计
    sumTotalFee() {
      let allSum = 0;
      //捐献者家属费用
      if (!isNaN(parseFloat(this.form.donoramount))) {
        allSum += parseFloat(this.form.donoramount);
      }
      //报销明细费用
      try {
        for (let i = 0; i < this.rbDetails.length; i++) {
          /*
          let totalFee = 0;
          //计算当前报销内容费用合计
          if (!isNaN(parseFloat(this.rbDetails[i].total))) {
            totalFee += parseFloat(this.rbDetails[i].total);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].cityfee))) {
            totalFee += parseFloat(this.rbDetails[i].cityfee);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].hotelexpense))) {
            totalFee += parseFloat(this.rbDetails[i].hotelexpense);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].otherexpense))) {
            totalFee += parseFloat(this.rbDetails[i].otherexpense);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].foodexpenses))) {
            totalFee += parseFloat(this.rbDetails[i].foodexpenses);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].foodallowance))) {
            totalFee += parseFloat(this.rbDetails[i].foodallowance);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].otherfeeamount))) {
            totalFee += parseFloat(this.rbDetails[i].otherfeeamount);
          }
          this.rbDetails[i].total = parseFloat(totalFee).toFixed(2);
          this.$set(this.rbDetails, i, this.rbDetails[i]);
          */
          if (!isNaN(parseFloat(this.rbDetails[i].total))) {
            allSum += parseFloat(this.rbDetails[i].total);
          }
        }
        this.form.amountrequested = allSum.toFixed(2);
        this.form.amountrequested = moneyAdd(
          moneySum(this.rbDetails, "total"),
          this.form.donoramount
        ).toFixed(2);
        this.form.bigstrmoney = this.convertCurrency(this.form.amountrequested);
      } catch {}
      } catch { }
    },
    //金额数字转换成大写
@@ -2623,9 +1965,11 @@
  background: #e8f4ff !important;
  /* color:whitesmoke; */
}
.button-delete {
  color: rgb(236, 69, 69);
}
::v-deep.el-table .error-row {
  background: #fcebeb;
}
src/views/project/travelexpenseapply/travelexpensedetail/index.vue
@@ -9,7 +9,7 @@
              <el-date-picker
                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"
@@ -369,7 +369,7 @@
                  size="small"
                  style="width: 100%"
                  v-model="scope.row.starttime"
                  type="date"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="开始日期"
                  @blur="getdays(scope.row)"
@@ -401,7 +401,7 @@
                  size="small"
                  style="width: 100%"
                  v-model="scope.row.endtime"
                  type="date"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="结束日期"
                  @blur="getdays(scope.row)"
@@ -848,7 +848,7 @@
            <!--
            <el-table-column prop="paiddate" slot="" label="打款日期" fixed align="center" width="150">
              <template slot-scope="scope">
                <el-date-picker clearable size="small" style="width: 100%" v-model="scope.row.paiddate" type="date"
                <el-date-picker clearable size="small" style="width: 100%" v-model="scope.row.paiddate" type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss" placeholder="打款日期" :disabled="true">
                </el-date-picker>
              </template>
@@ -1305,6 +1305,8 @@
import { listReportname, listUser } from "@/api/project/organization";
import { getToken } from "@/utils/auth";
import debounce from "lodash/debounce";
import { moneySum, moneyAdd, moneyRound } from "@/utils/money";
import { checkFileName, checkDetails } from "@/utils/fileNameCheck";
export default {
  components: {
    Treeselect,
@@ -1689,7 +1691,43 @@
    },
    //文件上传成功回调
    /**
     * 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 }
@@ -1710,14 +1748,11 @@
                name: file.name,
                url: URL.createObjectURL(blob)
              });
              // console.log(file.response.url);
              // console.log(file);
              this.pdfimgsrcList.push(file.response.url);
              this.fileListto.push({
                name: file.response.fileName,
                url: file.response.url
              });
              // 2026-10-15:删除这里的第二次 push。原来一次上传会向 fileListto / pdfimgsrcList
              // å„写两条,且第二条的 name å–的是 file.response.fileName(存储路径);而 remove()
              // ä¼šæŠŠæ•´ä¸ª fileListto å›žå†™åˆ° rbDetails[atpresent].annexfilesList,导致明细附件里
              // æ··å…¥è·¯å¾„形态的 name,被附件文件名校验误拦(前端没有"路径形态跳过"豁免),
              // åŒæ—¶ç¥¨æ®åˆ—表会多出一行。
              console.log(URL.createObjectURL(blob), "ur转换");
            })
            .catch(error => {
@@ -2146,9 +2181,9 @@
          if (tempStr == "杭州") {
            row.otherexpense = "";
          } else if (
            tempStr == "杭州-桐庐" ||
            tempStr == "杭州-建德" ||
            tempStr == "杭州-淳安"
            tempStr.includes("桐庐") ||
          tempStr.includes("建德") ||
          tempStr.includes("淳安")
          ) {
            row.otherexpense = (parseInt(row.days) * 60).toString();
          } else {
@@ -2537,7 +2572,7 @@
            .map(item => ({ url: item, name: item }))
          : [];
        //审核状态
        this.form.recordstatus = response.data.recordstatus + "";
        this.form.recordstatus = this.toStatusText(response.data.recordstatus, "-1");
        let ids = response.data.id;
        let listFundflowparams = {
          fundid: ids,
@@ -2548,14 +2583,19 @@
        });
        //报销明细
        getReimbursementdetaillist(ids).then(res => {
          this.rbDetails = res.data;
          //合计费用
          // if (this.rbDetails.length > 0) this.sumTotalFee();
          for (let i = 0; i < this.rbDetails.length; i++) {
            this.sumRowFee(this.rbDetails[i]);
          }
        });
        getReimbursementdetaillist(ids)
          .then(res => {
            this.rbDetails = this.normalizeDetailList(res);
            //合计费用
            // if (this.rbDetails.length > 0) this.sumTotalFee();
            for (let i = 0; i < this.rbDetails.length; i++) {
              this.sumRowFee(this.rbDetails[i]);
            }
          })
          .catch(err => {
            this.rbDetails = [];
            console.error("获取报销明细失败,id=" + ids, err);
          });
        //付款信息
        this.queryParamsPayee.rbid = ids;
@@ -2591,7 +2631,8 @@
      getReimbursement(id).then(response => {
        this.form = response.data;
        console.log(this.form.checkstatus, "checkstatus");
        this.form.checkstatus = this.form.checkstatus.toString();
        // checkstatus åœ¨åº“中可为空(预审状态尚未产生时),直接 toString ä¼šæŠ› TypeError ä¸­æ–­åŽç»­å–æ•°
        this.form.checkstatus = this.toStatusText(this.form.checkstatus, "-1");
        //票据
        this.fileListto = this.form.annexfiles
@@ -2618,16 +2659,22 @@
        });
        //报销明细
        getReimbursementdetaillist(ids).then(res => {
          this.rbDetails = res.data;
          console.log(this.rbDetails);
        getReimbursementdetaillist(ids)
          .then(res => {
            this.rbDetails = this.normalizeDetailList(res);
            console.log("报销明细行数:", this.rbDetails.length, this.rbDetails);
          //合计费用
          // if (this.rbDetails.length > 0) this.sumTotalFee();
          for (let i = 0; i < this.rbDetails.length; i++) {
            this.sumRowFee(this.rbDetails[i]);
          }
        });
            //合计费用
            // if (this.rbDetails.length > 0) this.sumTotalFee();
            for (let i = 0; i < this.rbDetails.length; i++) {
              this.sumRowFee(this.rbDetails[i]);
            }
          })
          .catch(err => {
            // å–数失败时不再静默:置空并打出原因,避免“接口有数据但页面空白”无从排查
            this.rbDetails = [];
            console.error("获取报销明细失败,id=" + ids, err);
          });
        //付款信息
        this.queryParamsPayee.rbid = ids;
@@ -2680,11 +2727,7 @@
          this.continueOrExit();
        } else {
          this.$modal.closeLoading();
          if (res.msg) {
            this.$modal.msgError("操作失误");
          } else {
            this.$modal.msgError(res.msg);
          }
          this.$modal.msgError(res.msg || "操作失败");
        }
      });
    },
@@ -2751,6 +2794,11 @@
    /** æäº¤ä¿å­˜æŒ‰é’® */
    submitForm: debounce(function (data) {
      // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行)
      if (!this.checkDetailFileNames()) {
        return;
      }
      this.$refs["form"].validate(valid => {
        if (valid) {
          //票据文件处理
@@ -2974,6 +3022,11 @@
    },
    // æŽ¥å—提醒立刻保存
    Savenow() {
      // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行)
      if (!this.checkDetailFileNames()) {
        return;
      }
      this.Savereminder = false;
      this.Reminderquantity = 0;
      this.totalquantity = 0;
@@ -3250,108 +3303,71 @@
        });
    },
    //计算一行合计
    //计算当前行报销内容费用合计
    sumRowFee(row) {
      let totalFee = 0;
      if (!isNaN(parseFloat(row.trafficexpense))) {
        totalFee += parseFloat(row.trafficexpense);
    /**
     * çŠ¶æ€ç±»å­—æ®µè½¬å­—ç¬¦ä¸²ï¼ˆæŽ¥å£å¯èƒ½è¿”å›ž null)。
     *
     * èƒŒæ™¯ï¼šcheckstatus、recordstatus ç­‰åœ¨åº“中可空,早期直接调用 .toString()
     * ä¼šæŠ› TypeError å¹¶ä¸­æ–­å…¶åŽçš„取数逻辑,表现为“接口已有数据但明细区空白”。
     *
     * @param {*} value åŽŸå§‹çŠ¶æ€å€¼
     * @param {string} fallback ä¸ºç©ºæ—¶çš„兜底值
     * @returns {string}
     */
    toStatusText(value, fallback) {
      if (value === null || value === undefined || value === "") {
        return fallback;
      }
      if (!isNaN(parseFloat(row.cityfee))) {
        totalFee += parseFloat(row.cityfee);
      }
      if (!isNaN(parseFloat(row.hotelexpense))) {
        totalFee += parseFloat(row.hotelexpense);
      }
      if (!isNaN(parseFloat(row.otherexpense))) {
        totalFee += parseFloat(row.otherexpense);
      }
      if (!isNaN(parseFloat(row.foodexpenses))) {
        totalFee += parseFloat(row.foodexpenses);
      }
      if (!isNaN(parseFloat(row.foodallowance))) {
        totalFee += parseFloat(row.foodallowance);
      }
      if (!isNaN(parseFloat(row.otherfeeamount))) {
        totalFee += parseFloat(row.otherfeeamount);
      }
      row.total = totalFee.toFixed(2);
      return String(value);
    },
      /*
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].trafficexpense))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].trafficexpense);
    /**
     * ç»Ÿä¸€ä»Žæ˜Žç»†æŽ¥å£å“åº”中取出数组。
     *
     * æ­£å¸¸ä¸º AjaxResult çš„ data æ•°ç»„;若后端改为分页结构(total + rows),
     * res.data å°†ä¸æ˜¯æ•°ç»„,直接绑定到 el-table ä¼šå¯¼è‡´â€œæœ‰æ•°æ®ä½†ä¸æ˜¾ç¤ºâ€ã€‚
     *
     * @param {Object} res æŽ¥å£åŽŸå§‹å“åº”
     * @returns {Array} æ˜Žç»†æ•°ç»„(异常时为空数组,不会让表格拿到非数组)
     */
    normalizeDetailList(res) {
      if (!res) {
        return [];
      }
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].cityfee))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].cityfee);
      if (Array.isArray(res.data)) {
        return res.data;
      }
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].hotelexpense))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].hotelexpense);
      if (Array.isArray(res.rows)) {
        return res.rows;
      }
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].otherexpense))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].otherexpense);
      }
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].foodexpenses))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].foodexpenses);
      }
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].foodallowance))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].foodallowance);
      }
      if (!isNaN(parseFloat(this.rbDetails[rowIndex].otherfeeamount))) {
        totalFee += parseFloat(this.rbDetails[rowIndex].otherfeeamount);
      }
      this.rbDetails[rowIndex].total = parseFloat(totalFee).toFixed(2);
      this.$set(this.rbDetails, rowIndex, this.rbDetails[rowIndex]);
      */
      return [];
    },
    //计算一行合计
    //计算当前行报销内容费用合计(金额一律走精确运算)
    sumRowFee(row) {
      const totalFee = moneySum([
        row.trafficexpense,
        row.cityfee,
        row.hotelexpense,
        row.otherexpense,
        row.foodexpenses,
        row.foodallowance,
        row.otherfeeamount
      ]);
      row.total = moneyRound(totalFee).toFixed(2);
      //重新计算整个报销单的合计
      this.sumTotalFee();
    },
    //计算费用合计
    sumTotalFee() {
      let allSum = 0;
      //捐献者家属费用
      if (!isNaN(parseFloat(this.form.donoramount))) {
        allSum += parseFloat(this.form.donoramount);
      }
      //报销明细费用
      try {
        for (let i = 0; i < this.rbDetails.length; i++) {
          /*
          let totalFee = 0;
          //计算当前报销内容费用合计
          if (!isNaN(parseFloat(this.rbDetails[i].total))) {
            totalFee += parseFloat(this.rbDetails[i].total);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].cityfee))) {
            totalFee += parseFloat(this.rbDetails[i].cityfee);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].hotelexpense))) {
            totalFee += parseFloat(this.rbDetails[i].hotelexpense);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].otherexpense))) {
            totalFee += parseFloat(this.rbDetails[i].otherexpense);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].foodexpenses))) {
            totalFee += parseFloat(this.rbDetails[i].foodexpenses);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].foodallowance))) {
            totalFee += parseFloat(this.rbDetails[i].foodallowance);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].otherfeeamount))) {
            totalFee += parseFloat(this.rbDetails[i].otherfeeamount);
          }
          this.rbDetails[i].total = parseFloat(totalFee).toFixed(2);
          this.$set(this.rbDetails, i, this.rbDetails[i]);
          */
          if (!isNaN(parseFloat(this.rbDetails[i].total))) {
            allSum += parseFloat(this.rbDetails[i].total);
          }
        }
        this.form.amountrequested = allSum.toFixed(2);
        this.form.amountrequested = moneyAdd(
          moneySum(this.rbDetails, "total"),
          this.form.donoramount
        ).toFixed(2);
        this.form.bigstrmoney = this.convertCurrency(this.form.amountrequested);
      } catch { }
    },
src/views/project/travelexpensecheck/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="80px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="80px">
      <el-row>
        <el-col :span="5">
          <el-form-item label="捐献案例" prop="name">
@@ -33,7 +34,16 @@
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
      <el-row> <el-col :span="5">
          <el-form-item label="报销类别" prop="costtype">
            <el-input v-model="queryParams.costtype" placeholder="请选择报销类别" clearable size="small" v-if="0"
              @keyup.enter.native="handleQuery" />
            <el-select v-model="queryParams.costtype" placeholder="请选择报销类别" clearable size="small">
              <el-option v-for="dict in dict.type.sys_travelexpensebelong" :key="dict.value" :label="dict.label"
                :value="dict.label" />
            </el-select>
          </el-form-item>
        </el-col>
        <el-col :span="5">
          <el-form-item label="经办人" prop="username">
            <el-input v-model="queryParams.username" placeholder="请输入报销人" clearable size="small"
@@ -46,9 +56,11 @@
              @keyup.enter.native="handleQuery" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="8">
          <el-form-item label="申请日期" prop="applyTime">
            <el-date-picker format="yyyy-MM-dd" value-format="yyyy-MM-dd" v-model="value1" type="daterange"
            <el-date-picker format="yyyy-MM-dd" value-format="yyyy-MM-dd HH:mm:ss" v-model="value1" type="daterange"
              @blur="onpick" range-separator="至" start-placeholder="报销申请开始日期" end-placeholder="报销申请结束日期"
              @keyup.enter.native="handleQuery">
            </el-date-picker>
@@ -145,7 +157,7 @@
        <el-row>
          <el-col :span="5">
            <el-form-item label="申请日期" prop="applyTime">
              <el-date-picker style="width: 100%" v-model="form.applyTime" :disabled="true" type="date"
              <el-date-picker style="width: 100%" v-model="form.applyTime" :disabled="true" type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss" placeholder="申请日期">
              </el-date-picker>
            </el-form-item>
@@ -293,7 +305,7 @@
                      );
                    }
                  }
                }" clearable size="small" style="width: 100%" v-model="scope.row.starttime" type="date"
                }" 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="getallowance(scope.row)">
                </el-date-picker>
              </template>
@@ -309,7 +321,7 @@
                      );
                    }
                  }
                }" clearable size="small" style="width: 100%" v-model="scope.row.endtime" type="date"
                }" 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="getallowance(scope.row)">
                </el-date-picker>
              </template>
@@ -537,7 +549,7 @@
              </template> </el-table-column><el-table-column prop="paiddate" slot="" label="打款日期" fixed align="center"
              width="150">
              <template slot-scope="scope">
                <el-date-picker clearable size="small" style="width: 100%" v-model="scope.row.paiddate" type="date"
                <el-date-picker clearable size="small" style="width: 100%" v-model="scope.row.paiddate" type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss" placeholder="打款日期" :disabled="true"
                  @blur="getallowance(scope.row)">
                </el-date-picker>
@@ -564,7 +576,7 @@
              <!-- <file-upload ref="fileUpload" :fileType=fileType :limit="1" :on-success="handleUploadSuccess"></file-upload> -->
              <el-upload size="mini" class="upload-demo" :action="uploadFileUrl" :file-list="fileList" multiple
                :limit="20" :headers="headers" :on-success="(response, file, fileList) =>
                    uploadSccess(response, file, fileList)
                  uploadSccess(response, file, fileList)
                  " :on-preview="downFile" :disabled="dialogType == 'detail'" :on-error="handleUploadError"
                :on-exceed="handleExceed" :on-remove="remove" accept="image/*,.pdf">
                <el-button :disabled="dialogType == 'detail'" size="small" type="primary">上传</el-button>
@@ -606,7 +618,7 @@
        </el-row>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="checksubmit" v-if="dialogType == 'edit'">保 å­˜</el-button>
        <el-button type="primary" :loading="checkSubmitLoading" :disabled="checkSubmitLoading" @click="checksubmit" v-if="dialogType == 'edit'">保 å­˜</el-button>
        <el-button @click="cancel">取 æ¶ˆ</el-button>
      </div>
    </el-dialog>
@@ -667,6 +679,7 @@
import { getUserProfile } from "@/api/system/user";
import { getToken } from "@/utils/auth";
import { setItem, grtItem, removeItem } from "@/utils/storage.js";
import { moneySum } from "@/utils/money";
export default {
  components: {
@@ -684,6 +697,7 @@
  ],
  data() {
    return {
      checkSubmitLoading: false,
      checkObj: {
        flowlevelone: null,
        flowconclusion: null,
@@ -834,6 +848,7 @@
    },
    checksubmit() {
      if (this.checkSubmitLoading) return;
      let checkfundobj = {
        flowconclusion: this.checkObj.flowlevelone,
        flowcontent: this.checkObj.flowconclusion,
@@ -849,7 +864,9 @@
        return;
      }
      this.checkSubmitLoading = true;
      checkfund(checkfundobj).then(res => {
        this.checkSubmitLoading = false;
        if (res.code == 200) {
          this.reset();
          this.open = false;
@@ -857,11 +874,7 @@
          this.$modal.msgSuccess("审核完成!");
        } else {
          this.open = false;
          if (res.msg) {
            this.$modal.msgError("操作失误");
          } else {
            this.$modal.msgError(res.msg);
          }
          this.$modal.msgError(res.msg || "操作失败");
        }
      });
    },
@@ -1283,41 +1296,25 @@
        });
    },
    //计算费用合计
    //计算费用合计(金额一律走精确运算,禁止 parseFloat ç´¯åŠ ï¼‰
    sumTotalFee(scope) {
      let allSum = 0;
      try {
        for (let i = 0; i < this.rbDetails.length; i++) {
          let totalFee = 0;
          const row = this.rbDetails[i];
          //计算当前报销内容费用合计
          if (!isNaN(parseFloat(this.rbDetails[i].trafficexpense))) {
            totalFee += parseFloat(this.rbDetails[i].trafficexpense);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].cityfee))) {
            totalFee += parseFloat(this.rbDetails[i].cityfee);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].hotelexpense))) {
            totalFee += parseFloat(this.rbDetails[i].hotelexpense);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].otherexpense))) {
            totalFee += parseFloat(this.rbDetails[i].otherexpense);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].foodexpenses))) {
            totalFee += parseFloat(this.rbDetails[i].foodexpenses);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].foodallowance))) {
            totalFee += parseFloat(this.rbDetails[i].foodallowance);
          }
          if (!isNaN(parseFloat(this.rbDetails[i].otherfeeamount))) {
            totalFee += parseFloat(this.rbDetails[i].otherfeeamount);
          }
          this.rbDetails[i].total = parseFloat(totalFee).toFixed(2);
          this.$set(this.rbDetails, scope.$index, this.rbDetails[i]);
          if (!isNaN(parseFloat(this.rbDetails[i].total))) {
            allSum += parseFloat(this.rbDetails[i].total);
          }
          const totalFee = moneySum([
            row.trafficexpense,
            row.cityfee,
            row.hotelexpense,
            row.otherexpense,
            row.foodexpenses,
            row.foodallowance,
            row.otherfeeamount
          ]);
          row.total = totalFee.toFixed(2);
          this.$set(this.rbDetails, scope.$index, row);
        }
        this.form.amountrequested = allSum.toFixed(2);
        this.form.amountrequested = moneySum(this.rbDetails, "total").toFixed(2);
        this.form.bigstrmoney = this.convertCurrency(this.form.amountrequested);
      } catch { }
    },
src/views/project/travelexpensedeal/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
src/views/project/unitCost/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="120px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="120px">
      <el-form-item label="医疗机构" prop="name">
        <el-input v-model="queryParams.name" placeholder="请输入医疗机构" clearable size="small"
          @keyup.enter.native="handleQuery" />
src/views/project/unitCost/particulars/index.vue
@@ -198,7 +198,7 @@
                  style="width: 100%"
                  v-model="scope.row.receiveTime"
                  :disabled="true"
                  type="date"
                  type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  placeholder="接收日期"
                >
@@ -257,7 +257,7 @@
            </el-table-column>
            <el-table-column prop="amounttime" align="center" label="收款日期" width="200">
              <template slot-scope="scope">
                <el-date-picker clearable size="small" style="width: 100%" v-model="scope.row.amounttime" type="date"
                <el-date-picker clearable size="small" style="width: 100%" v-model="scope.row.amounttime" type="date" format="yyyy-MM-dd"
                  value-format="yyyy-MM-dd HH:mm:ss" placeholder="收款日期">
                </el-date-picker>
              </template>
@@ -329,6 +329,7 @@
            <el-row :gutter="20">
              <!--用户数据-->
              <el-form
                class="opo-query-form"
                :model="patientqueryParams"
                ref="queryForm"
                size="small"
@@ -575,6 +576,7 @@
import Li_area_select from "@/components/Address";
import { getUserProfile } from "@/api/system/user";
import { getToken } from "@/utils/auth";
import { moneySum, moneyRound } from "@/utils/money";
import debounce from "lodash/debounce";
export default {
  components: {
@@ -1089,23 +1091,16 @@
        this.$modal.msgError("注意实收金额不等于应收金额,请核对数据!");
      }
    },
    //修改实收或应收后触发
    //修改实收或应收后触发(金额一律走精确运算)
    chargeSum() {
      let ChargeSum = 0;
      let ChargeamountSum = 0;
      //费用合计
      try {
        for (let i = 0; i < this.donorchargeorgans.length; i++) {
          if (!isNaN(parseFloat(this.donorchargeorgans[i].organcharge))) {
            ChargeSum += parseFloat(this.donorchargeorgans[i].organcharge);
          }
          if (!isNaN(parseFloat(this.donorchargeorgans[i].amount))) {
            ChargeamountSum += parseFloat(this.donorchargeorgans[i].amount);
          }
        }
        this.form.receivableamount = ChargeSum.toFixed(2);
        this.form.receivedamount = ChargeamountSum.toFixed(2);
        this.form.receivableamount = moneyRound(
          moneySum(this.donorchargeorgans, "organcharge")
        ).toFixed(2);
        this.form.receivedamount = moneyRound(
          moneySum(this.donorchargeorgans, "amount")
        ).toFixed(2);
      } catch {}
    },
src/views/system/class/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="班级名称" prop="className">
        <el-input
          v-model="queryParams.className"
src/views/system/config/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="参数名称" prop="configName">
        <el-input
          v-model="queryParams.configName"
src/views/system/costmaintenance/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form
      class="opo-query-form"
      :model="queryParams"
      ref="queryForm"
      :inline="true"
src/views/system/dept/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
      <el-form-item label="部门名称" prop="deptName">
        <el-input
          v-model="queryParams.deptName"
src/views/system/dict/data.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="字典名称" prop="dictType">
        <el-select v-model="queryParams.dictType" size="small">
          <el-option
src/views/system/dict/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="字典名称" prop="dictName">
        <el-input
          v-model="queryParams.dictName"
src/views/system/grade/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="年级名称" prop="gradeName">
        <el-input
          v-model="queryParams.gradeName"
src/views/system/medicalevalustion/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="姓名" prop="name">
        <el-input
          v-model="queryParams.name"
src/views/system/menu/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
      <el-form-item label="菜单名称" prop="menuName">
        <el-input
          v-model="queryParams.menuName"
src/views/system/notice/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="公告标题" prop="noticeTitle">
        <el-input
          v-model="queryParams.noticeTitle"
src/views/system/post/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="岗位编码" prop="postCode">
        <el-input
          v-model="queryParams.postCode"
src/views/system/role/authUser.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
     <el-form :model="queryParams" ref="queryForm" v-show="showSearch" :inline="true">
     <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" v-show="showSearch" :inline="true">
      <el-form-item label="用户名称" prop="userName">
        <el-input
          v-model="queryParams.userName"
src/views/system/role/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" v-show="showSearch" :inline="true">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" v-show="showSearch" :inline="true">
      <el-form-item label="角色名称" prop="roleName">
        <el-input
          v-model="queryParams.roleName"
src/views/system/role/selectUser.vue
@@ -1,7 +1,8 @@
<template>
  <!-- æŽˆæƒç”¨æˆ· -->
  <el-dialog title="选择用户" :visible.sync="visible" width="800px" top="5vh" append-to-body>
    <el-form :model="queryParams" ref="queryForm" :inline="true">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true">
      <el-form-item label="用户名称" prop="userName">
        <el-input
          v-model="queryParams.userName"
src/views/system/student/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="学生名称" prop="studentName">
        <el-input
          v-model="queryParams.studentName"
src/views/system/user/index.vue
@@ -27,7 +27,8 @@
      </el-col>
      <!--用户数据-->
      <el-col :span="20" :xs="24">
        <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
        <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
          <el-form-item label="用户名称" prop="userName">
            <el-input
              v-model="queryParams.userName"
src/views/tool/gen/importTable.vue
@@ -1,7 +1,8 @@
<template>
  <!-- å¯¼å…¥è¡¨ -->
  <el-dialog title="导入表" :visible.sync="visible" width="800px" top="5vh" append-to-body>
    <el-form :model="queryParams" ref="queryForm" :inline="true">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true">
      <el-form-item label="表名称" prop="tableName">
        <el-input
          v-model="queryParams.tableName"
src/views/tool/gen/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
    <el-form
      class="opo-query-form" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="表名称" prop="tableName">
        <el-input
          v-model="queryParams.tableName"
vue.config.js
@@ -33,8 +33,8 @@
    proxy: {
      // detail: https://cli.vuejs.org/config/#devserver-proxy
      [process.env.VUE_APP_BASE_API]: {
        // target:`http://localhost:8080`,
        target:`http://192.168.100.10:8081`,
        target:`http://localhost:8080`,
        // target:`http://192.168.100.110:8080`,
        // target:`http://192.168.100.137:8080`,
        // target: `https://slb.hospitalstar.com:9093`,
        changeOrigin: true,