已添加1个文件
已修改25个文件
已删除3个文件
2345 ■■■■■ 文件已修改
JHHG.sh 467 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WTXC.bat 451 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
YZFX.bat 451 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ZSJC.bat 451 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/erp/purchase/request/index.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/business/data.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/business/detail/data.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/in/data.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/in/modules/item-form.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/components/select-dialog.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/components/select.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/data.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/detail/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/modules/item-form.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/data.ts 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/detail/index.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/modules/item-form.vue 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/return/data.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/return/modules/item-form.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/components/select-dialog.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/components/select.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/data.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/detail/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/modules/item-form.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/modules/print.vue 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/out/data.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/out/modules/item-form.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/return/data.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/return/modules/item-form.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
JHHG.sh
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,467 @@
#!/usr/bin/env bash
# ============================================================================
#  Vue å‰ç«¯éƒ¨ç½²è„šæœ¬ï¼ˆmacOS / Linux ç‰ˆï¼‰â€”—  é“¶å·ä¹é¸¿åŒ–工(JHHG)
#
#  ç”±åŒç›®å½•çš„ JHHG.bat è½¬å†™è€Œæ¥ï¼šæ­¥éª¤ã€æç¤ºè¯­ã€æœåŠ¡å™¨ã€è´¦å·ã€è¿œç«¯ç›®å½•ã€
#  è¶…时参数与 .bat å®Œå…¨ä¸€è‡´ï¼ŒWindows ä¸Šæ€Žä¹ˆç”¨ï¼Œè¿™é‡Œå°±æ€Žä¹ˆç”¨ã€‚
#
#  ä¸Ž .bat çš„全部差别:
#    1. ä¸ä¾èµ– sshpass.exe。系统里有 sshpass å°±ç”¨ sshpass,没有就退回 macOS
#       è‡ªå¸¦çš„ /usr/bin/expect æ¥å–‚密码 â€”— æ‰€ä»¥è¿™å° Mac ä¸ç”¨è£…任何东西也能跑。
#    2. è„šæœ¬å¼€å¤´ä¼š cd åˆ°è‡ªå·±æ‰€åœ¨ç›®å½•,从任何位置调用都能正确构建和上传。
#    3. ç»“尾的 pause åªåœ¨äº¤äº’式终端里生效,被其它脚本/管道调用时不会卡住。
#    4. ã€Œæ¯”较文件大小」这一步按标题的原意真正做了比对(.bat åªæ‰“印不比对),
#       å¤§å°ä¸ä¸€è‡´ç›´æŽ¥æŠ¥é”™ï¼Œé¿å…æŠŠä¸€ä¸ªä¸Šä¼ æ®‹ç¼ºçš„包解压到线上目录。
#
#  ç”¨æ³•:
#      ./JHHG.sh                 # é¦–次可能需要 chmod +x JHHG.sh
#      bash JHHG.sh
#
#  å¯é€‰çŽ¯å¢ƒå˜é‡ï¼ˆä¸€èˆ¬ç”¨ä¸åˆ°ï¼‰ï¼š
#      JHHG_TRANSPORT=sshpass|expect    å¼ºåˆ¶æŒ‡å®šå–‚密码的方式
# ============================================================================
set -uo pipefail
# =========================
# é…ç½®ï¼ˆä¸Ž JHHG.bat ä¸€å­—不差)
# =========================
readonly COMPANY='JHHG'
readonly REMOTE_USER='root'
readonly REMOTE_HOST='42.63.70.90'
readonly REMOTE_PORT='22'
# å¯†ç å« ! å’Œ #:必须用单引号;不要 source åˆ°äº¤äº’式 shell é‡Œè·‘(那里会做 ! åŽ†å²å±•å¼€ï¼‰ï¼Œ
# ç”¨ ./JHHG.sh æˆ– bash JHHG.sh æ‰§è¡Œã€‚
# 2026-10-12 å®žæµ‹è¿‡ï¼šæœåŠ¡å™¨æŽ¥å—çš„å°±æ˜¯è¿™ä¸ªå®Œæ•´å­—ç¬¦ä¸²ï¼ˆåŽ»æŽ‰ !@##@! çš„ Nxjhhg ä¼šè¢«æ‹’)。
readonly PASSWORD='Nxjhhg!@##@!'
readonly ZIP_FILE='dist.zip'
readonly REMOTE_DIR='/home/vue/dist'
readonly SSH_OPTS=(-o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=15 -o ServerAliveCountMax=3)
readonly REMOTE_TARGET="${REMOTE_USER}@${REMOTE_HOST}"
readonly REMOTE_TMP="${REMOTE_DIR}/.dist.zip.uploading"
# è„šæœ¬è‡ªèº«æ‰€åœ¨ç›®å½• = é¡¹ç›®æ ¹ç›®å½•
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# =========================
# è¾“出helper(与 .bat çš„æç¤ºæ ¼å¼ä¸€è‡´ï¼‰
# =========================
log()   { printf '%s\n' "$*"; }
ok()    { printf '[OK] %s\n' "$*"; }
info()  { printf '[INFO] %s\n' "$*"; }
warn()  { printf '[WARN] %s\n' "$*"; }
err()   { printf '[ERROR] %s\n' "$*"; }
# ä¸Ž .bat çš„ :END ä¸€æ ·ï¼Œäº¤äº’式终端里停一下再退出
pause_if_interactive() {
    if [[ -t 0 ]]; then
        printf '\n按回车键退出...'
        read -r _ || true
        printf '\n'
    fi
}
fail() {
    log ''
    log '============================================================'
    log '                  DEPLOY FAILED'
    log '============================================================'
    log ''
    log 'Please check the error message above.'
    log ''
    pause_if_interactive
    exit 1
}
# æ²¡è¢«æ˜¾å¼å¤„理就失败的命令,也走到 DEPLOY FAILED,不留半截输出
on_unexpected_error() {
    err "命令失败(退出码 $1,脚本第 $2 è¡Œï¼‰ã€‚"
    fail
}
trap 'on_unexpected_error $? $LINENO' ERR
# =========================
# ä¼ è¾“层:把密码喂给 ssh/scp
# =========================
# ä¼˜å…ˆ sshpass(和 .bat ä¸€æ ·ï¼‰ï¼Œæ²¡æœ‰å°±ç”¨ macOS è‡ªå¸¦çš„ expect
TRANSPORT="${JHHG_TRANSPORT:-}"
if [[ -z "$TRANSPORT" ]]; then
    if command -v sshpass >/dev/null 2>&1; then
        TRANSPORT='sshpass'
    elif command -v expect >/dev/null 2>&1; then
        TRANSPORT='expect'
    fi
fi
# ç”¨å½“前 TRANSPORT æ‰§è¡Œä¸€æ¡ ssh/scp å‘½ä»¤ï¼Œå‚数原样透传
# å¯†ç é€šè¿‡çŽ¯å¢ƒå˜é‡ä¼ é€’ï¼Œä¸å‡ºçŽ°åœ¨å‘½ä»¤è¡Œå‚æ•°é‡Œï¼ˆä¸ä¼šè¿› ps è¾“出)
run_remote() {
    if [[ "$TRANSPORT" == 'sshpass' ]]; then
        SSHPASS="$PASSWORD" sshpass -e "$@"
        return $?
    fi
    # expect åˆ†æ”¯ï¼šç¨‹åºå¿…须写成文件再用 -f è°ƒç”¨ï¼Œå› ä¸º `expect -c è„šæœ¬ å‚æ•°...`
    # ä¸ä¼šæŠŠå‚数放进 $argv(会把它当脚本文件名)。
    local exp_file rc
    exp_file="$(mktemp "${TMPDIR:-/tmp}/jhhg-deploy.XXXXXX")" || {
        err 'temp file creation failed.'
        return 1
    }
    cat >"$exp_file" <<'EXPECT_PROGRAM'
set timeout 300
log_user 1
if {[catch {spawn {*}$argv} spawn_err]} {
    puts stderr "spawn failed: $spawn_err"
    exit 127
}
expect {
    -re "(?i)are you sure you want to continue connecting" { send "yes\r"; exp_continue }
    -re "(?i)password:" { send "$env(JHHG_PW)\r"; exp_continue }
    eof
}
# æŠŠå­è¿›ç¨‹çš„退出码原样带出去(密码错时 ssh è¿”回 255,必须让上层感知到)
set wait_result {}
catch wait wait_result
set rc [lindex $wait_result 3]
if {![string is integer -strict $rc]} { set rc 1 }
exit $rc
EXPECT_PROGRAM
    JHHG_PW="$PASSWORD" expect -f "$exp_file" "$@"
    rc=$?
    rm -f "$exp_file"
    return $rc
}
ssh_remote() {                       # ssh_remote "远端命令"
    run_remote ssh -p "$REMOTE_PORT" "${SSH_OPTS[@]}" "$REMOTE_TARGET" "$1"
}
scp_up() {                           # scp_up æœ¬åœ°æ–‡ä»¶ è¿œç«¯è·¯å¾„
    run_remote scp -P "$REMOTE_PORT" "${SSH_OPTS[@]}" "$1" "${REMOTE_TARGET}:$2"
}
# =========================
# å¼€å§‹
# =========================
log ''
log '============================================================'
log '             Vue Dist Auto Deploy'
log '============================================================'
log ''
log "Company    : $COMPANY"
log "Server     : $REMOTE_USER@$REMOTE_HOST"
log "Port       : $REMOTE_PORT"
log "Remote Dir : $REMOTE_DIR"
log "Package    : $ZIP_FILE"
log ''
# =========================
# [1/7] å¯†ç å·¥å…·
# =========================
log '[1/7] Checking password tool...'
if [[ -z "$TRANSPORT" ]]; then
    err 'sshpass å’Œ expect éƒ½æ²¡æœ‰ï¼Œæ— æ³•自动输入密码。'
    info 'macOS ä¸€èˆ¬è‡ªå¸¦ /usr/bin/expect,正常不会走到这里;'
    info '如果确实没有,装 sshpass å³å¯ï¼ˆå¯èƒ½éœ€å…ˆåŠ ç¬¬ä¸‰æ–¹ tap):brew install sshpass'
    info 'Linux: apt install sshpass  /  yum install sshpass'
    fail
fi
if [[ "$TRANSPORT" == 'sshpass' ]]; then
    ok "sshpass found: $(command -v sshpass)"
else
    ok "expect found: $(command -v expect)(macOS è‡ªå¸¦ï¼Œæ— éœ€å®‰è£… sshpass)"
fi
# =========================
# [2/7] pnpm
# =========================
log ''
log '[2/7] Checking pnpm...'
if ! command -v pnpm >/dev/null 2>&1; then
    err 'pnpm not found.'
    info 'HINT: npm install -g pnpm'
    fail
fi
if ! pnpm --version; then
    err 'pnpm execution failed.'
    fail
fi
# =========================
# [3/7] node
# =========================
log ''
log '[3/7] Checking Node.js...'
if ! command -v node >/dev/null 2>&1; then
    err 'Node.js not found.'
    fail
fi
if ! node --version; then
    err 'Node.js execution failed.'
    fail
fi
# =========================
# node_modules
# =========================
if [[ ! -d node_modules ]]; then
    log ''
    info 'node_modules not found.'
    info 'Running pnpm install...'
    if ! pnpm install; then
        err 'pnpm install failed.'
        fail
    fi
    ok 'pnpm install completed.'
else
    ok 'node_modules exists.'
fi
# =========================
# [4/7] æž„建
# =========================
log ''
log '[4/7] Building project...'
SKIP_BUILD=''
if [[ -f "$ZIP_FILE" ]]; then
    log ''
    warn "$ZIP_FILE already exists."
    printf 'Rebuild? Y/N (default Y): '
    read -r REBUILD_ANSWER || true
    case "$(printf '%s' "${REBUILD_ANSWER:-}" | tr '[:lower:]' '[:upper:]')" in
        N) info 'Skip build.'; SKIP_BUILD='1' ;;
    esac
fi
if [[ -z "$SKIP_BUILD" ]]; then
    info "Company: $COMPANY"
    info 'Command: pnpm run build'
    log ''
    export VITE_COMPANY="$COMPANY"
    if ! pnpm run build; then
        log ''
        err 'Build failed.'
        fail
    fi
    log ''
    ok 'Build completed.'
fi
# =========================
# [5/7] æ£€æŸ¥åŒ…
# =========================
log ''
log '[5/7] Checking package...'
if [[ ! -f "$ZIP_FILE" ]]; then
    err "$ZIP_FILE not found."
    info 'HINT: Please check whether your build generates dist.zip.'
    fail
fi
LOCAL_ZIP_BYTES="$(wc -c < "$ZIP_FILE" | tr -d ' ')"
LOCAL_ZIP_MB="$(awk -v b="$LOCAL_ZIP_BYTES" 'BEGIN { printf "%.2f", b / 1048576 }')"
ok 'Package found.'
info "File size: $LOCAL_ZIP_BYTES bytes"
info "File size: $LOCAL_ZIP_MB MB"
# =========================
# [6/7] æµ‹è¯• SSH
# =========================
log ''
log '[6/7] Testing SSH connection...'
if ! ssh_remote 'echo SSH_OK'; then
    err 'SSH connection failed.'
    fail
fi
ok 'SSH connection successful.'
# =========================
# å»ºè¿œç«¯ç›®å½•
# =========================
log ''
info 'Creating remote directory...'
if ! ssh_remote "mkdir -p '$REMOTE_DIR'"; then
    err 'Failed to create remote directory.'
    fail
fi
ok 'Remote directory ready.'
# =========================
# ä¸Šä¼ ä¸´æ—¶æ–‡ä»¶
# =========================
log ''
info 'Uploading package...'
ssh_remote "rm -f '$REMOTE_TMP'" || true
# è¿œç«¯è·¯å¾„不能加引号:ssh é‚£æ¡çš„单引号是给远端 shell å‰¥çš„,而 scp çš„参数不经 shell,
# å•引号会变成文件名的真实字符(远端会去开 "'/home/vue/dist/..." è¿™ä¸ªç›¸å¯¹è·¯å¾„)
if ! scp_up "$ZIP_FILE" "$REMOTE_TMP"; then
    err 'Upload failed.'
    fail
fi
ok 'Upload completed.'
# =========================
# æ ¡éªŒè¿œç«¯æ–‡ä»¶
# =========================
log ''
info 'Checking remote package...'
if ! ssh_remote "test -f '$REMOTE_TMP'"; then
    err 'Remote package does not exist.'
    fail
fi
ok 'Remote package exists.'
# =========================
# æ¯”较文件大小
# =========================
log ''
info 'Checking package size...'
log ''
# å¤§å°æ¯”对放在远端做、只取退出码。
# ä¸è¦ç”¨ $(run_remote ...) åŽ»æˆªå–è¿œç«¯è¾“å‡ºï¼šexpect ä¼šæŠŠæ•´æ®µäº¤äº’回显(spawn å‘½ä»¤è¡Œã€
# å¯†ç æç¤ºï¼‰ä¸€èµ·å†™è¿› stdout,截出来的不是命令结果,比对必然失败。
if ! ssh_remote "test \"\$(stat -c %s '$REMOTE_TMP')\" -eq $LOCAL_ZIP_BYTES"; then
    # å˜é‡ä¸€å¾‹åŠ  ${}:bash 3.2 ä¼šæŠŠç´§è·Ÿå…¶åŽçš„全角字符(如「(」)当成变量名的一部分,
    # äºŽæ˜¯ $ZIP_FILE( ä¼šè¢«è§£æžæˆåä¸º "ZIP_FILE(" çš„变量,配 set -u ç›´æŽ¥æŠ¥ unbound variable
    err "包大小不一致:远端 ${REMOTE_TMP} ä¸Žæœ¬åœ° ${ZIP_FILE}(${LOCAL_ZIP_BYTES} å­—节)不符,上传可能被截断。"
    info "远端临时文件已保留。可在服务器上执行 stat -c %s '${REMOTE_TMP}' æ ¸å¯¹ï¼Œç¡®è®¤åŽåˆ é™¤ã€‚"
    fail
fi
ok "包大小一致:${LOCAL_ZIP_BYTES} å­—节。"
# =========================
# æ”¹åç”Ÿæ•ˆ
# =========================
log ''
info 'Activating package...'
if ! ssh_remote "mv -f '$REMOTE_TMP' '$REMOTE_DIR/$ZIP_FILE'"; then
    err 'Failed to activate package.'
    fail
fi
ok 'Package activated.'
# =========================
# æ£€æŸ¥ unzip
# =========================
log ''
info 'Checking unzip...'
if ! ssh_remote 'command -v unzip'; then
    err 'unzip is not installed on server.'
    log ''
    log 'CentOS/RHEL:'
    log 'yum install -y unzip'
    log ''
    log 'Ubuntu/Debian:'
    log 'apt install -y unzip'
    fail
fi
ok 'unzip found.'
# =========================
# æµ‹è¯• ZIP
# =========================
log ''
info 'Testing remote ZIP...'
if ! ssh_remote "cd '$REMOTE_DIR' && unzip -t '$ZIP_FILE'"; then
    log ''
    err 'ZIP test failed.'
    info 'Remote ZIP has been kept.'
    fail
fi
ok 'ZIP test passed.'
# =========================
# è§£åŽ‹
# =========================
log ''
info 'Extracting package...'
if ! ssh_remote "cd '$REMOTE_DIR' && unzip -o '$ZIP_FILE'"; then
    log ''
    err 'Remote unzip failed.'
    info 'Remote ZIP has been kept.'
    fail
fi
log ''
ok 'Extract completed.'
# =========================
# åˆ é™¤è¿œç«¯ ZIP
# =========================
log ''
info 'Removing remote ZIP...'
if ssh_remote "rm -f '$REMOTE_DIR/$ZIP_FILE'"; then
    ok 'Remote ZIP removed.'
else
    warn 'Failed to remove remote ZIP.'
fi
# =========================
# [7/7] å®Œæˆ
# =========================
log ''
log '============================================================'
log '                 DEPLOY SUCCESS'
log '============================================================'
log ''
log "Company    : $COMPANY"
log "Server     : $REMOTE_HOST:$REMOTE_PORT"
log "Remote Dir : $REMOTE_DIR"
log "Package    : $ZIP_FILE"
log "Size       : $LOCAL_ZIP_MB MB"
log ''
log 'Deployment completed successfully.'
log ''
pause_if_interactive
WTXC.bat
ÎļþÒÑɾ³ý
YZFX.bat
ÎļþÒÑɾ³ý
ZSJC.bat
ÎļþÒÑɾ³ý
src/api/erp/purchase/request/index.ts
@@ -38,11 +38,15 @@
    productUnitId?: number; // äº§å“å•位编号
    productUnitName?: string; // äº§å“å•位名称
    productBarCode?: string; // äº§å“æ¡ç 
    productPrice?: number; // äº§å“å•价(参考价)
    productPrice?: number; // å«ç¨Žå•价(参考价)
    count: number; // æ•°é‡
    taxPercent?: number; // ç¨Žçއ
    totalPrice?: number; // ä¸å«ç¨Žé‡‘额
    taxPrice?: number; // ç¨Žé¢
    demandTime?: string; // éœ€æ±‚日期
    remark?: string; // å¤‡æ³¨
    qcCheckFlag?: boolean; // æ˜¯å¦è´¨æ£€
    inCount?: number; // å…¥åº“数量
  }
}
src/views/crm/business/data.ts
@@ -82,14 +82,13 @@
        api: getBusinessStatusTypeSimpleList,
        labelField: 'name',
        valueField: 'id',
        placeholder: '请选择商机状态组',
        placeholder: '请选择商机状态组(选填)',
        allowClear: true,
      },
      dependencies: {
        triggerFields: ['id'],
        disabled: (values) => values.id,
      },
      rules: 'required',
    },
    {
      fieldName: 'dealTime',
src/views/crm/business/detail/data.ts
@@ -99,19 +99,29 @@
      dependencies: {
        triggerFields: [''],
        async componentProps() {
          const statusList = await getBusinessStatusSimpleList(
            formData.value?.statusTypeId ?? 0,
          );
          const statusTypeId = formData.value?.statusTypeId;
          // å•†æœºçŠ¶æ€ç»„ä¸ºé€‰å¡«ï¼šæœªè®¾ç½®çŠ¶æ€ç»„æ—¶æ²¡æœ‰å¯é€‰é˜¶æ®µï¼Œç¦ç”¨å¹¶è¯´æ˜ŽåŽŸå› 
          if (!statusTypeId) {
            return {
              disabled: true,
              options: [],
              placeholder: '该商机未设置「商机状态组」,无可选商机阶段',
            };
          }
          const statusList = await getBusinessStatusSimpleList(statusTypeId);
          const options = statusList.map((item) => ({
            label: `${item.name}(赢单率:${item.percent}%)`,
            value: item.id,
          }));
          return {
            disabled: false,
            options,
            placeholder: '请选择商机阶段',
          };
        },
        // æœªè®¾ç½®å•†æœºçŠ¶æ€ç»„æ—¶ä¸åšå¿…å¡«æ ¡éªŒï¼ˆæ­¤æ—¶ä¹Ÿæ²¡æœ‰é˜¶æ®µå¯ä¾›é€‰æ‹©ï¼‰
        rules: () => (formData.value?.statusTypeId ? 'required' : null),
      },
      rules: 'required',
    },
  ];
}
src/views/erp/purchase/in/data.ts
@@ -132,11 +132,11 @@
    },
    {
      fieldName: 'discountedPrice',
      label: '优惠后金额',
      label: '优惠后含税金额',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        placeholder: '优惠后金额',
        placeholder: '优惠后含税金额',
        precision: 2,
        formatter: erpPriceInputFormatter,
        disabled: true,
@@ -256,17 +256,10 @@
    },
    {
      field: 'productPrice',
      title: '产品单价',
      title: '含税单价(元)',
      fixed: 'right',
      minWidth: 120,
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '产品金额',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
@@ -276,16 +269,23 @@
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '不含税金额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
      field: 'taxPrice',
      title: '税额',
      title: '税额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      field: 'totalPrice',
      fixed: 'right',
      title: '合计金额',
      title: '含税合计(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
@@ -603,7 +603,7 @@
    },
    {
      field: 'totalProductPrice',
      title: '金额合计',
      title: '不含税金额合计',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/purchase/in/modules/item-form.vue
@@ -152,6 +152,7 @@
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  // TODO èŠ‹è‰¿
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
@@ -161,13 +162,28 @@
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
function initRow(row: ErpPurchaseInApi.PurchaseInItem) {
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpPurchaseInApi.PurchaseInItem) {
  if (row.productPrice && row.count) {
    row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = row.totalProductPrice + row.taxPrice;
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalProductPrice = taxInclusivePrice - row.taxPrice;
    row.totalPrice = taxInclusivePrice;
  }
}
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpPurchaseInApi.PurchaseInItem) {
  if (!row.id) {
    recalcRow(row);
  }
}
@@ -280,11 +296,11 @@
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>
              é‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
              ä¸å«ç¨Žé‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
            </span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>
              ç¨Žé¢åˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
              å«ç¨Žåˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
            </span>
          </div>
        </div>
src/views/erp/purchase/order/components/select-dialog.vue
@@ -93,7 +93,7 @@
    { field: 'supplierName', title: '供应商', minWidth: 120 },
    { field: 'orderTime', title: '订单时间', width: 120, formatter: 'formatDate' },
    { field: 'totalCount', title: '合计数量', width: 100 },
    { field: 'totalPrice', title: '合计金额', width: 100 },
    { field: 'totalPrice', title: '含税合计', width: 100 },
  ];
}
src/views/erp/purchase/order/components/select.vue
@@ -116,7 +116,7 @@
          <div>订单号:{{ selectedItem.no || '-' }}</div>
          <div>供应商:{{ selectedItem.supplierName || '-' }}</div>
          <div>订单时间:{{ selectedItem.orderTime || '-' }}</div>
          <div>合计金额:{{ selectedItem.totalPrice || '-' }} å…ƒ</div>
          <div>含税合计:{{ selectedItem.totalPrice || '-' }} å…ƒ</div>
        </div>
      </template>
      <Input
src/views/erp/purchase/order/data.ts
@@ -81,7 +81,7 @@
            return z
              .number()
              .min(0, '订金不能小于0')
              .max(values.totalPrice, '订金不能大于优惠后金额');
              .max(values.totalPrice, '订金不能大于优惠后含税金额');
          }
          return z.number().min(0, '订金不能小于0');
        },
@@ -145,11 +145,11 @@
    },
    {
      fieldName: 'totalPrice',
      label: '优惠后金额',
      label: '优惠后含税金额',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        placeholder: '优惠后金额',
        placeholder: '优惠后含税金额',
        precision: 2,
        formatter: erpPriceInputFormatter,
        disabled: true,
@@ -210,17 +210,10 @@
    },
    {
      field: 'productPrice',
      title: '产品单价',
      title: '含税单价(元)',
      minWidth: 120,
      fixed: 'right',
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalProductPrice',
      title: '金额',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'taxPercent',
@@ -230,15 +223,22 @@
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalProductPrice',
      title: '不含税金额(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'taxPrice',
      title: '税额',
      title: '税额(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'totalPrice',
      title: '税额合计',
      title: '含税合计(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
@@ -404,7 +404,7 @@
    },
    {
      field: 'totalProductPrice',
      title: '金额合计',
      title: '不含税金额合计',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/purchase/order/detail/index.vue
@@ -60,7 +60,7 @@
      render: (val) => erpCountInputFormatter(val) || '-',
    },
    {
      label: '合计金额',
      label: '含税合计金额',
      field: 'totalPrice',
      render: (val) => erpPriceInputFormatter(val) || '-',
    },
src/views/erp/purchase/order/modules/item-form.vue
@@ -166,6 +166,7 @@
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
@@ -175,13 +176,28 @@
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
function initRow(row: ErpPurchaseOrderApi.PurchaseOrderItem) {
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpPurchaseOrderApi.PurchaseOrderItem) {
  if (row.productPrice && row.count) {
    row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = row.totalProductPrice + row.taxPrice;
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalProductPrice = taxInclusivePrice - row.taxPrice;
    row.totalPrice = taxInclusivePrice;
  }
}
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpPurchaseOrderApi.PurchaseOrderItem) {
  if (!row.id) {
    recalcRow(row);
  }
}
@@ -301,11 +317,11 @@
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>
              é‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
              ä¸å«ç¨Žé‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
            </span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>
              ç¨Žé¢åˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
              å«ç¨Žåˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
            </span>
          </div>
        </div>
src/views/erp/purchase/request/data.ts
@@ -167,17 +167,10 @@
    },
    {
      field: 'productPrice',
      title: '单价(参考价)',
      title: '含税单价(元)',
      minWidth: 120,
      fixed: 'right',
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalPrice',
      title: '预估金额',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'taxPercent',
@@ -185,6 +178,26 @@
      minWidth: 100,
      fixed: 'right',
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalPrice',
      title: '不含税金额(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'taxPrice',
      title: '税额(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      title: '含税合计(元)',
      minWidth: 120,
      fixed: 'right',
      slots: { default: 'totalTaxInclusivePrice' },
    },
    {
      field: 'demandTime',
@@ -339,7 +352,7 @@
    },
    {
      field: 'totalPrice',
      title: '预估金额',
      title: '含税金额',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/purchase/request/detail/index.vue
@@ -46,13 +46,7 @@
    },
    {
      field: 'productPrice',
      title: '单价(参考价)',
      minWidth: 120,
      formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-',
    },
    {
      field: 'totalPrice',
      title: '预估金额',
      title: '含税单价(元)',
      minWidth: 120,
      formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-',
    },
@@ -61,6 +55,25 @@
      title: '税率(%)',
      minWidth: 90,
      formatter: ({ cellValue }) => (cellValue ? `${cellValue}%` : '-'),
    },
    {
      field: 'totalPrice',
      title: '不含税金额(元)',
      minWidth: 120,
      formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-',
    },
    {
      field: 'taxPrice',
      title: '税额(元)',
      minWidth: 120,
      formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-',
    },
    {
      field: 'totalTaxInclusivePrice',
      title: '含税合计(元)',
      minWidth: 120,
      formatter: ({ row }) =>
        erpPriceInputFormatter((row.totalPrice || 0) + (row.taxPrice || 0)) || '-',
    },
    { field: 'demandTime', title: '需求日期', minWidth: 110 },
    {
@@ -123,7 +136,7 @@
      render: (val) => erpCountInputFormatter(val) || '-',
    },
    {
      label: '合计金额',
      label: '含税合计金额',
      field: 'totalPrice',
      render: (val) => erpPriceInputFormatter(val) || '-',
    },
src/views/erp/purchase/request/modules/item-form.vue
@@ -30,19 +30,22 @@
const tableData = ref<ErpPurchaseRequestApi.PurchaseRequestItem[]>([]);
/** èŽ·å–è¡¨æ ¼åˆè®¡æ•°æ® */
/** èŽ·å–è¡¨æ ¼åˆè®¡æ•°æ®ï¼ˆè¡Œä¸Š totalPrice ä¸ºä¸å«ç¨Žé‡‘额,含税合计需加上税额) */
const summaries = computed(() => {
  return {
    count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0),
    totalPrice: tableData.value.reduce(
      (sum, item) => {
        const price = erpPriceMultiply(item.productPrice || 0, item.count || 0) ?? 0;
        return sum + price;
      },
      (sum, item) => sum + (item.totalPrice || 0),
      0,
    ),
    taxPrice: tableData.value.reduce((sum, item) => sum + (item.taxPrice || 0), 0),
  };
});
/** å«ç¨Žåˆè®¡ = ä¸å«ç¨Žé‡‘额 + ç¨Žé¢ */
const totalTaxInclusivePrice = computed(
  () => summaries.value.totalPrice + summaries.value.taxPrice,
);
/** è¡¨æ ¼é…ç½® */
const [Grid, gridApi] = useVbenVxeGrid({
@@ -126,25 +129,39 @@
  handleRowChange(row);
}
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpPurchaseRequestApi.PurchaseRequestItem) {
  if (row.productPrice && row.count) {
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = taxInclusivePrice - row.taxPrice;
  }
}
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
  } else {
    tableData.value[index] = row;
  }
  // è®¡ç®—预估金额
  if (row.productPrice && row.count) {
    row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
  }
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpPurchaseRequestApi.PurchaseRequestItem) {
  if (row.productPrice && row.count) {
    row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
  if (!row.id) {
    recalcRow(row);
  }
}
@@ -218,6 +235,9 @@
      />
      <span v-else>{{ row.taxPercent ? `${row.taxPercent}%` : '-' }}</span>
    </template>
    <template #totalTaxInclusivePrice="{ row }">
      <span>{{ erpPriceInputFormatter((row.totalPrice || 0) + (row.taxPrice || 0)) }}</span>
    </template>
    <template #demandTime="{ row }">
      <DatePicker
        v-if="!disabled"
@@ -264,7 +284,9 @@
          <span class="font-medium text-foreground">合计:</span>
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>预估金额:{{ erpPriceInputFormatter(summaries.totalPrice) }}</span>
            <span>不含税金额:{{ erpPriceInputFormatter(summaries.totalPrice) }}</span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>含税合计:{{ erpPriceInputFormatter(totalTaxInclusivePrice) }}</span>
          </div>
        </div>
      </div>
src/views/erp/purchase/return/data.ts
@@ -131,11 +131,11 @@
    },
    {
      fieldName: 'discountedPrice',
      label: '优惠后金额',
      label: '优惠后含税金额',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        placeholder: '优惠后金额',
        placeholder: '优惠后含税金额',
        precision: 2,
        formatter: erpPriceInputFormatter,
        disabled: true,
@@ -256,17 +256,10 @@
    },
    {
      field: 'productPrice',
      title: '产品单价',
      title: '含税单价(元)',
      fixed: 'right',
      minWidth: 120,
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '产品金额',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
@@ -276,16 +269,23 @@
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '不含税金额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
      field: 'taxPrice',
      title: '税额',
      title: '税额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      field: 'totalPrice',
      fixed: 'right',
      title: '合计金额',
      title: '含税合计(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
@@ -590,7 +590,7 @@
    },
    {
      field: 'totalProductPrice',
      title: '金额合计',
      title: '不含税金额合计',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/purchase/return/modules/item-form.vue
@@ -155,6 +155,7 @@
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  // TODO èŠ‹è‰¿
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
@@ -164,13 +165,28 @@
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
function initRow(row: ErpPurchaseReturnApi.PurchaseReturnItem) {
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpPurchaseReturnApi.PurchaseReturnItem) {
  if (row.productPrice && row.count) {
    row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = row.totalProductPrice + row.taxPrice;
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalProductPrice = taxInclusivePrice - row.taxPrice;
    row.totalPrice = taxInclusivePrice;
  }
}
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpPurchaseReturnApi.PurchaseReturnItem) {
  if (!row.id) {
    recalcRow(row);
  }
}
@@ -283,11 +299,11 @@
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>
              é‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
              ä¸å«ç¨Žé‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
            </span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>
              ç¨Žé¢åˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
              å«ç¨Žåˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
            </span>
          </div>
        </div>
src/views/erp/sale/order/components/select-dialog.vue
@@ -95,7 +95,7 @@
    { field: 'customerName', title: '客户', minWidth: 120 },
    { field: 'orderTime', title: '订单时间', width: 120, formatter: 'formatDate' },
    { field: 'totalCount', title: '合计数量', width: 100 },
    { field: 'totalPrice', title: '合计金额', width: 100 },
    { field: 'totalPrice', title: '含税合计', width: 100 },
  ];
}
src/views/erp/sale/order/components/select.vue
@@ -119,7 +119,7 @@
          <div>订单号:{{ selectedItem.no || '-' }}</div>
          <div>客户:{{ selectedItem.customerName || '-' }}</div>
          <div>订单时间:{{ selectedItem.orderTime || '-' }}</div>
          <div>合计金额:{{ selectedItem.totalPrice || '-' }} å…ƒ</div>
          <div>含税合计:{{ selectedItem.totalPrice || '-' }} å…ƒ</div>
        </div>
      </template>
      <Input
src/views/erp/sale/order/data.ts
@@ -156,11 +156,11 @@
    },
    {
      fieldName: 'totalPrice',
      label: '优惠后金额',
      label: '优惠后含税金额',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        placeholder: '优惠后金额',
        placeholder: '优惠后含税金额',
        precision: 2,
        formatter: erpPriceInputFormatter,
        disabled: true,
@@ -197,7 +197,7 @@
            return z
              .number()
              .min(0, '订金不能小于0')
              .max(values.totalPrice, '收取订金不能大于优惠后金额');
              .max(values.totalPrice, '收取订金不能大于优惠后含税金额');
          }
          return z.number().min(0, '订金不能小于0').optional();
        },
@@ -270,17 +270,10 @@
    },
    {
      field: 'productPrice',
      title: '产品单价',
      title: '含税单价(元)',
      minWidth: 120,
      fixed: 'right',
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalProductPrice',
      title: '金额',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'taxPercent',
@@ -290,15 +283,22 @@
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalProductPrice',
      title: '不含税金额(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'taxPrice',
      title: '税额',
      title: '税额(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'totalPrice',
      title: '税额合计',
      title: '含税合计(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
@@ -482,7 +482,7 @@
    },
    {
      field: 'totalProductPrice',
      title: '金额合计',
      title: '不含税金额合计',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/sale/order/detail/index.vue
@@ -75,7 +75,7 @@
      render: (val) => erpCountInputFormatter(val) || '-',
    },
    {
      label: '合计金额',
      label: '含税合计金额',
      field: 'totalPrice',
      render: (val) => erpPriceInputFormatter(val) || '-',
    },
src/views/erp/sale/order/modules/item-form.vue
@@ -172,6 +172,7 @@
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
@@ -181,13 +182,28 @@
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
function initRow(row: ErpSaleOrderApi.SaleOrderItem) {
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpSaleOrderApi.SaleOrderItem) {
  if (row.productPrice && row.count) {
    row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = row.totalProductPrice + row.taxPrice;
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalProductPrice = taxInclusivePrice - row.taxPrice;
    row.totalPrice = taxInclusivePrice;
  }
}
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpSaleOrderApi.SaleOrderItem) {
  if (!row.id) {
    recalcRow(row);
  }
  // é»˜è®¤éœ€è¦ç”Ÿäº§
  if (row.needProduction === undefined) {
@@ -311,11 +327,11 @@
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>
              é‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
              ä¸å«ç¨Žé‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
            </span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>
              ç¨Žé¢åˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
              å«ç¨Žåˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
            </span>
          </div>
        </div>
src/views/erp/sale/order/modules/print.vue
@@ -99,21 +99,21 @@
}
/**
 * æ˜Žç»†è¡Œï¼ˆåŽç«¯ä¸è¿”回明细行的 totalProductPrice/totalPrice,需按单价×数量计算:
 * é‡‘额 = å•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = é‡‘额 Ã— ç¨ŽçŽ‡ï¼›å«ç¨Žé‡‘é¢ = é‡‘额 + ç¨Žé¢ï¼‰
 * æ˜Žç»†è¡Œï¼ˆä¼˜å…ˆç”¨åŽç«¯è¿”回值;缺省时按「单价为含税单价」计算:
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢ï¼‰
 */
const printRows = computed(() =>
  (printData.value.items || []).map((item) => {
    const totalProductPrice =
      item.totalProductPrice ?? multiplyPrice(item.productPrice, item.count) ?? 0;
    const taxInclusivePrice = multiplyPrice(item.productPrice, item.count);
    const taxPrice =
      item.taxPrice ?? multiplyPrice(totalProductPrice, (item.taxPercent ?? 0) / 100) ?? 0;
    const totalPrice = item.totalPrice ?? totalProductPrice + taxPrice;
      item.taxPrice ?? multiplyPrice(taxInclusivePrice, (item.taxPercent ?? 0) / 100) ?? 0;
    const totalPrice = item.totalPrice ?? taxInclusivePrice ?? 0;
    const totalProductPrice = item.totalProductPrice ?? totalPrice - taxPrice;
    return { ...item, totalProductPrice, taxPrice, totalPrice };
  }),
);
/** æ˜Žç»†åˆè®¡ï¼šæ•°é‡/金额优先取订单头(后端权威值),税额与含税金额按明细累加(订单头 totalPrice ä¸ºä¼˜æƒ åŽé‡‘额,另在金额信息区展示) */
/** æ˜Žç»†åˆè®¡ï¼šæ•°é‡/金额优先取订单头(后端权威值),税额与含税金额按明细累加(订单头 totalPrice ä¸ºä¼˜æƒ åŽå«ç¨Žé‡‘额,另在金额信息区展示) */
const summaries = computed(() => {
  const rows = printRows.value;
  const count = rows.reduce((sum, item) => sum + (item.count || 0), 0);
@@ -262,17 +262,17 @@
            <th
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold"
            >
              äº§å“å•ä»·(元)
            </th>
            <th
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold"
            >
              é‡‘额(元)
              å«ç¨Žå•ä»·(元)
            </th>
            <th
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold"
            >
              ç¨Žçއ(%)
            </th>
            <th
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold"
            >
              ä¸å«ç¨Žé‡‘额(元)
            </th>
            <th
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold"
@@ -312,10 +312,10 @@
              {{ formatPrice(item.productPrice) }}
            </td>
            <td class="border border-solid border-[#dcdfe6] p-2 text-right">
              {{ formatPrice(item.totalProductPrice) }}
              {{ formatPrice(item.taxPercent) }}
            </td>
            <td class="border border-solid border-[#dcdfe6] p-2 text-right">
              {{ formatPrice(item.taxPercent) }}
              {{ formatPrice(item.totalProductPrice) }}
            </td>
            <td class="border border-solid border-[#dcdfe6] p-2 text-right">
              {{ formatPrice(item.taxPrice) }}
@@ -348,13 +348,13 @@
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2"
            ></td>
            <td
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2"
            ></td>
            <td
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right"
            >
              {{ formatPrice(summaries.totalProductPrice) }}
            </td>
            <td
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2"
            ></td>
            <td
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right"
            >
@@ -386,7 +386,7 @@
      >
        <div>优惠率(%):{{ formatPrice(printData.discountPercent) }}</div>
        <div>付款优惠(元):{{ formatPrice(printData.discountPrice) }}</div>
        <div>优惠后金额(元):{{ formatPrice(printData.totalPrice) }}</div>
        <div>优惠后含税金额(元):{{ formatPrice(printData.totalPrice) }}</div>
        <div>收取订金(元):{{ formatPrice(printData.depositPrice) }}</div>
      </div>
src/views/erp/sale/out/data.ts
@@ -149,11 +149,11 @@
    },
    {
      fieldName: 'discountedPrice',
      label: '优惠后金额',
      label: '优惠后含税金额',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        placeholder: '优惠后金额',
        placeholder: '优惠后含税金额',
        precision: 2,
        formatter: erpPriceInputFormatter,
        disabled: true,
@@ -277,17 +277,10 @@
    },
    {
      field: 'productPrice',
      title: '产品单价',
      title: '含税单价(元)',
      fixed: 'right',
      minWidth: 120,
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '产品金额',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
@@ -297,16 +290,23 @@
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '不含税金额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
      field: 'taxPrice',
      title: '税额',
      title: '税额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      field: 'totalPrice',
      fixed: 'right',
      title: '合计金额',
      title: '含税合计(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
@@ -617,7 +617,7 @@
    },
    {
      field: 'totalProductPrice',
      title: '金额合计',
      title: '不含税金额合计',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/sale/out/modules/item-form.vue
@@ -153,6 +153,7 @@
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
@@ -162,13 +163,28 @@
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
function initRow(row: ErpSaleOutApi.SaleOutItem) {
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpSaleOutApi.SaleOutItem) {
  if (row.productPrice && row.count) {
    row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = row.totalProductPrice + row.taxPrice;
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalProductPrice = taxInclusivePrice - row.taxPrice;
    row.totalPrice = taxInclusivePrice;
  }
}
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpSaleOutApi.SaleOutItem) {
  if (!row.id) {
    recalcRow(row);
  }
}
@@ -282,11 +298,11 @@
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>
              é‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
              ä¸å«ç¨Žé‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
            </span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>
              ç¨Žé¢åˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
              å«ç¨Žåˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
            </span>
          </div>
        </div>
src/views/erp/sale/return/data.ts
@@ -144,11 +144,11 @@
    },
    {
      fieldName: 'discountedPrice',
      label: '优惠后金额',
      label: '优惠后含税金额',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        placeholder: '优惠后金额',
        placeholder: '优惠后含税金额',
        precision: 2,
        formatter: erpPriceInputFormatter,
        disabled: true,
@@ -270,17 +270,10 @@
    },
    {
      field: 'productPrice',
      title: '产品单价',
      title: '含税单价(元)',
      fixed: 'right',
      minWidth: 120,
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '产品金额',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
@@ -290,16 +283,23 @@
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '不含税金额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
      field: 'taxPrice',
      title: '税额',
      title: '税额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      field: 'totalPrice',
      fixed: 'right',
      title: '合计金额',
      title: '含税合计(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
@@ -604,7 +604,7 @@
    },
    {
      field: 'totalProductPrice',
      title: '金额合计',
      title: '不含税金额合计',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/sale/return/modules/item-form.vue
@@ -207,6 +207,7 @@
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
@@ -216,13 +217,28 @@
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
function initRow(row: ErpSaleReturnApi.SaleReturnItem) {
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpSaleReturnApi.SaleReturnItem) {
  if (row.productPrice && row.count) {
    row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = row.totalProductPrice + row.taxPrice;
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalProductPrice = taxInclusivePrice - row.taxPrice;
    row.totalPrice = taxInclusivePrice;
  }
}
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpSaleReturnApi.SaleReturnItem) {
  if (!row.id) {
    recalcRow(row);
  }
}
@@ -340,11 +356,11 @@
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>
              é‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
              ä¸å«ç¨Žé‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
            </span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>
              ç¨Žé¢åˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
              å«ç¨Žåˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
            </span>
          </div>
        </div>