/** * 单行长文本省略的**唯一一份契约**(票 37)。 * * 维护者 2026-10-13 反馈:「文本过长显示不全,需要一个**共用方法**在所有类似位置统一处理 * (溢出省略成 `…`,hover 显示完整)。」这句话里其实是两件事,各自只能有一处实现: * * ① **画省略标记** —— 归 CSS 的 `text-overflow: ellipsis`,声明在 * `components/EllipsisText.vue` 的 `.ellipsis-text` 里(全仓面板文字仅此一份)。 * 为什么**不**用 JS 按字符数预截:字符数 ≠ 像素宽度(中英文混排、等宽数字都不同), * 两份截断规则必然出现「JS 已截掉但 CSS 还能显示更多」或反过来的不一致 —— * 那正是本票要根除的「早晚不一致」。标记让平台按**真实宽度**画,永远只有一套。 * * ② **留完整原文** —— 归本文件的 `ellipsisTitle()`:`title` 里放的是**未截断的原文**, * 所以无论列宽多窄,鼠标悬停都能读到全(这才是维护者要的「hover 显示完整」)。 * * 两半合起来即「对超长文本给出省略标记、同时保留完整原文」:标记由平台画, * 原文由 title 承载,两者不会各自漂移。 * * 本文件是纯函数层(票 05 的静态守卫:不 import service、不碰 DOM、不发请求)。 */ /** 省略标记字符(与 CSS `text-overflow: ellipsis` 画出的那一个一致,供断言与文案对齐)。 */ export const ELLIPSIS_MARK = '…' /** 任意值 → 一行纯文本;`null`/`undefined` 回空串(绝不把 `null` 字面量画到界面上)。 */ export function plainText(value) { if (value === null || value === undefined) return '' return String(value) } /** * `title` 的取值:**完整原文**,一个字符都不截。 * 空文本回空串 —— 空 title 会让浏览器弹一个空提示框,那不是「显示完整」。 */ export function ellipsisTitle(value) { return plainText(value) } /** * 省略展示契约:`text` 是**显示串**(原样,省略标记归 CSS 按真实宽度画), * `title` 是**完整原文**(hover 读全)。两个字段同源,因此不会出现「显示的是甲、提示的是乙」。 * @param {*} value 待显示文本(数字/字符串/空值都收) * @returns {{ text: string, title: string }} */ export function ellipsisView(value) { const text = plainText(value) return { text, title: text } } /** * 地图标签的省略(批次 3 走查接通,2026-10-12)。 * * 与上面 DOM 契约是**同一份语义**、不同渲染媒介: * · DOM 侧(`EllipsisText.vue`)—— 省略标记归 CSS `text-overflow`,title 归属性; * · Canvas 侧(Cesium billboard label)—— CSS 够不着画布内部,截断归 * `services/cesiumEngine.js` 的估算字宽逻辑(`mapTextLabel` 的 `maxWidthPx`), * 完整原文归实体 properties(`pointName`/`deviceName`)+ mapService 的 * hover tooltip(`data-map-marker-tooltip`)。 * 两边共用 `ELLIPSIS_MARK`,保证省略标记字符全仓一致;任何一侧改省略字符都要过这里。 * * @param {string} text 完整原文 * @param {number} maxWidthPx 屏显限宽(px) * @param {number} fontSizePx 字号(估算字宽用:全角 ≈ fontSize、半角 ≈ 一半) * @returns {{ shown: string, truncated: boolean, title: string }} shown=截断显示串(含省略号) */ export function ellipsisForMapLabel(text, maxWidthPx, fontSizePx = 12) { const full = plainText(text) if (!full || maxWidthPx <= 0) return { shown: full, truncated: false, title: full } const charW = (ch) => (ch.charCodeAt(0) > 0x2e7f ? fontSizePx : fontSizePx / 2) const ellipsisW = fontSizePx // 先量全文:放得下就不截(临界文本不被省略号预留宽度误伤)。 let fullW = 0 for (let i = 0; i < full.length; i++) fullW += charW(full[i]) if (fullW <= maxWidthPx) return { shown: full, truncated: false, title: full } // 放不下:截到 (limit - 省略号宽) 再补 … let acc = 0 let cut = 0 for (let i = 0; i < full.length; i++) { acc += charW(full[i]) if (acc > maxWidthPx - ellipsisW) { cut = i; break } } return { shown: `${full.slice(0, cut)}${ELLIPSIS_MARK}`, truncated: true, title: full } }