From b1f39acb9771b4c0bf32215177ff945237fe6a3c Mon Sep 17 00:00:00 2001 From: yueli Date: Thu, 17 Sep 2026 09:45:34 +0800 Subject: [PATCH] =?UTF-8?q?style(borrow):=20=E6=8C=81=E6=9C=89=E4=BA=BA?= =?UTF-8?q?=E9=AB=98=E4=BA=AE=E7=BB=9F=E4=B8=80=E4=B8=BA=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E8=93=9D=E8=89=B2=EF=BC=8C=E5=A7=93=E5=90=8D=E6=8C=89=E4=B8=A4?= =?UTF-8?q?=E5=AD=97=E8=A1=A5=E5=85=A8=E8=A7=92=E7=A9=BA=E6=A0=BC=E7=AD=89?= =?UTF-8?q?=E5=AE=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一、当前持有人高亮策略统一 ---- 还库页(return.vue)该列原为橙色标签(type="warning"),与列表页在上一轮 已完成的条件高亮不一致。现统一为: · 当前持有人 ≠ 借用人(确曾转交)→ 主题色 var(--el-color-primary) 加粗 · 两者同一人(常态)→ 普通深色文本,不做任何高亮 比对前把两侧都归一化到「斜杠前段」:历史数据可能混入「姓名/拼音」的完整 username 写法,不归一化会把同一人误判成转交,让整列无故飘蓝。 (records.vue 该列在 ba5394f 已是此策略,本轮仅同步调用 formatName。) 二、姓名两字补全角空格,实现等宽对齐 ---- 新增 formatName()(放在 src/utils/format.ts,而非各组件内复制 —— 列表页与 还库页都要用,规则改动只需改一处): '高雪' -> '高 雪' U+3000 全角空格,占一个汉字宽 '郭俊玥' -> '郭俊玥' '欧阳娜娜' -> '欧阳娜娜' 'AB' -> 'AB' 纯拉丁不补,否则渲染出 'A B' 怪相 null/'测试01'/混合字符 -> 原样 ★ 纯展示层:只在模板 {{ formatName(row.borrower_name) }} 中调用, row.borrower_name 等底层对象**一个字节都没动**。 姓名是转交/归还责任链的匹配键(records.vue 的 currentHolderList、 return.vue 的 isTransferred),往数据里插不可见字符会让比对、检索、 导出、复制全部带上它 —— 这正是上一轮拒绝「插空格」的原因,本轮改用 渲染期格式化,视觉要求与数据纯洁性同时满足。 ★ 为什么用 U+3000 而非普通空格:普通空格宽度随字体浮动(约半个汉字), 补进去反而难对齐;U+3000 稳定占一个汉字宽,与「1 汉字 ≈ 1em」一致。 三、配套 ---- · 归还人 / 当前持有人 / 借用人三列(含转交弹窗的持有人列)统一走 formatName; · .name-fixed 增加 margin-right,避免同一格内多个姓名(同一单持有人/归还人 不同)紧贴成「高 雪张 三」。 --- inventory-web/src/utils/format.ts | 34 ++++++++++++ .../src/views/transaction/records.vue | 12 +++-- .../src/views/transaction/return.vue | 54 +++++++++++++++++-- 3 files changed, 91 insertions(+), 9 deletions(-) diff --git a/inventory-web/src/utils/format.ts b/inventory-web/src/utils/format.ts index 1a84029..42edfcb 100644 --- a/inventory-web/src/utils/format.ts +++ b/inventory-web/src/utils/format.ts @@ -34,3 +34,37 @@ export function normalizeQty(value: any, fallback = 0): number { const n = Number(value) return Number.isFinite(n) ? Number(n.toFixed(4)) : fallback } + +// 中日韩统一表意文字。用于判断一个字符是否占一个「汉字宽」。 +const CJK_CHAR = /[㐀-䶿一-鿿豈-﫿]/ + +/** + * 姓名等宽显示格式化 —— 两个字的中文姓名在中间补一个全角空格, + * 视觉上撑成三个字宽,与三字姓名纵向对齐。 + * + * '高雪' -> '高 雪' (中间是 U+3000,占一个汉字宽) + * '郭俊玥' -> '郭俊玥' + * '欧阳娜娜' -> '欧阳娜娜' (4 字不动) + * + * ★ 纯展示层函数:只在模板渲染时调用,**绝不写回数据**。 + * 姓名是转交/归还责任链的匹配键(见 records.vue 的 currentHolderList + * 与 return.vue 的 isTransferred),往数据里插不可见字符会让比对、检索、 + * 导出、复制全部带上它。本函数返回的是「显示用副本」, + * row.borrower_name 始终是原值。 + * + * ★ 为什么用 U+3000 而不是普通空格: + * 普通空格宽度随字体变化(通常约半个汉字),补进去反而更难对齐; + * U+3000 是东亚字体的全角空格,稳定占一个汉字宽 —— 与「1 个汉字 ≈ 1em」 + * 的等宽前提一致。 + * + * ★ 为什么限定两个字符都是汉字: + * 这条规则的目的是「与三字中文姓名对齐」。若姓名为两个拉丁字符(如 'AB'), + * 补一个全角空格并不会让它获得汉字宽度,只会渲染出 'A B' 这样的怪相。 + * 故非汉字组合一律原样返回。 + */ +export function formatName(name: any): string { + if (name === null || name === undefined) return '' + const s = String(name) + if (s.length !== 2) return s + return CJK_CHAR.test(s[0]) && CJK_CHAR.test(s[1]) ? `${s[0]} ${s[1]}` : s +} diff --git a/inventory-web/src/views/transaction/records.vue b/inventory-web/src/views/transaction/records.vue index 776da43..df97ee8 100644 --- a/inventory-web/src/views/transaction/records.vue +++ b/inventory-web/src/views/transaction/records.vue @@ -138,7 +138,7 @@ - + + + + + @@ -225,6 +235,7 @@ import { ref, nextTick, onMounted, onUnmounted } from 'vue' import request from '@/utils/request' import { getBorrowUsers } from '@/api/transaction' +import { formatName } from '@/utils/format' import { uploadFile } from '@/api/common/upload' import { ElMessage, ElMessageBox } from 'element-plus' import { Scissor, EditPen, Delete, CameraFilled, Close, Refresh, Select } from '@element-plus/icons-vue' @@ -257,6 +268,16 @@ const loading = ref(false) const showCamera = ref(false) const barcodeRef = ref() +// ★ 是否发生过转交:当前持有人 ≠ 借用人。 +// 两者都是后端 to_dict 的展示名口径,仍归一化到「斜杠前段」再比对 —— +// 历史数据里可能混入「姓名/拼音」的完整 username 写法,不归一化会把同一人 +// 误判成转交,让整列无故飘蓝。 +const isTransferred = (row: any): boolean => { + const holder = String(row.current_holder_name || '').split('/')[0].trim() + const borrower = String(row.borrower_name || '').split('/')[0].trim() + return !!holder && holder !== borrower +} + // ★ 实际归还人(一期转交改造):必须等于物品的当前持有人,后端强校验。 // 与借出页的借用人下拉共用同一份公司隔离名单。 const returnerId = ref(null) @@ -630,4 +651,27 @@ onUnmounted(() => { .sidebar-actions { flex-direction: row; width: 100%; gap: 10px; } .sidebar-actions .el-button { flex: 1; height: 40px; } } + +/* ========================================================================== + ★ 姓名等宽对齐 + 中文 1 字 ≈ 1em。formatName() 已把两字姓名补成全角三字宽,这里再加一个 + 最小宽度兜底,保证单字姓名及后续可能出现的异常长度也不破坏列对齐。 + (视觉对齐只做在样式与展示副本上,row.borrower_name 始终是原值) + ========================================================================== */ +.name-fixed { + display: inline-block; + min-width: 3em; +} + +/* 发生过转交的当前持有人:主题色加粗,替代原先的橙色标签 */ +.text-primary { + color: var(--el-color-primary); + font-weight: 600; +} + +/* 中性/次要信息:灰字,不抢视觉重心 */ +.text-info { + color: #909399; + font-size: 12px; +}