news 2026/9/9 20:17:34

React Native鸿蒙跨端开发:统一货币与百分比格式化工具实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
React Native鸿蒙跨端开发:统一货币与百分比格式化工具实践

前阵子在把项目从 Android/iOS 双端往鸿蒙端迁移时,团队里吵了三次架,核心原因全是“数字显示不一致”:同一个 1234567.8,iOS 上显示¥1,234,567.80,Android 上显示¥1,234,567.8,同一个 0.126,有的端是 12.6%,有的端是 13%。问题的根子不是 UI 没对齐,而是每个端各自维护了一套格式化逻辑,格式规则、舍入策略、空值兜底全不统一。

后来我们借着 React Native 鸿蒙适配的契机,把货币格式化(formatCurrency)和百分比格式化(formatPercent)两个工具函数彻底收敛到了 JS 层,做成了一套跨端共用的核心资产。这篇文章就把当时的完整设计思路、实现代码、踩坑过程和测试方案都放出来,给同样在搞 React Native 鸿蒙跨端、尤其是金融类应用的团队一个可以直接参考的样板。

1. 为什么金融应用的“数字显示”值得专门抽一层工具沉淀

很多团队一开始都觉得格式化不就是toFixed(2)加个逗号,不值得单独做一层。这个想法在普通工具类 App 里问题不大,但在金融类应用里,数字显示绝不是 UI 小事,它是数据正确性的最后一道防线。

1.1 金额和百分比出错不是 UI 瑕疵,是事故

金融应用里,金额和百分比直接关联到用户资产、收益、费率、还款计划等关键信息。一个¥1,234,567.80如果被格式化成¥1,234,567.8,用户可能觉得只是显示风格不一致;但如果是利率从3.45%被显示成3.5%,用户会认为平台擅自改了他的利率,这事直接上升到客诉和合规层面。

更隐蔽的是舍入规则不一致。同一笔0.005元的利息,A 端四舍五入成0.01,B 端直接截断成0.00,两边对账对不上,查半天才发现是格式化层的差异。这类问题在金融项目里就不是“显示 bug”,而是资金对账事故。所以把格式化逻辑收敛到一处,首要动机是保证跨端行为一致

1.2 多端重复实现带来的连锁问题

在没有鸿蒙之前,不少金融团队的现状是:Android 用DecimalFormat,iOS 用NumberFormatter,Web 端用Intl.NumberFormat,三段代码各自维护,规则还经常不一样。

Android 的DecimalFormat("#,##0.00")和 iOS 的NumberFormatter在舍入行为上虽然都遵循四舍五入,但遇到浮点精度问题、负数格式(括号还是负号)、货币符号前后置这些细节,很容易各搞各的。再加上不同产品经理在不同时期提出不同需求,比如 A 端要求-$100.00,B 端要求($100.00),两边代码越来越分叉。

鸿蒙端加入之后,这个问题被放大了三倍:鸿蒙原生用的是 ArkTS,格式化能力储备和生态成熟度都不如 Android/iOS,再写第四套TextFormatter逻辑,维护成本直接失控。我们当时算过一笔账:四个端各自维护格式化代码,每次格式规则调整的排期成本是 4 天,而统一到 JS 层后是 1 天。

1.3 React Native + 鸿蒙架构下,JS 层是收敛的天然位置

选择把格式化工具收敛到 JS 层,不是因为“React Native 本来就是这么写的”,而是因为这个位置有天然优势:不管最终运行在 iOS、Android 还是鸿蒙上,RN 的 JS 引擎层都能执行同一份代码。只要团队约定“所有金额和百分比必须走 formatCurrency / formatPercent 这两个函数”,三端行为一致性就由代码结构保证了,而不是靠 Code Review 时人工提醒。

同时要注意,React Native 在鸿蒙上通常跑在 ArkTS 运行时提供的 JS 引擎里(比如借助鸿蒙的兼容层方案),虽然底层引擎可能不同,但如果我们的格式化工具不依赖任何原生能力、不依赖Intl的不稳定行为,单纯用纯 JS/TypeScript 实现,就几乎不受宿主引擎差异影响。这一点在后面第 4 章会详细展开。

2. formatCurrency:把钱显示对,从理解需求边界开始

写 formatCurrency 之前,我建议先别急着写代码,而是把业务需求完整列一遍。货币格式化的完整要素远不止“四舍五入加逗号”这么简单。

2.1 先列清需求再写代码:货币格式化的完整要素

一个健壮的货币格式化函数,至少要回答这几个问题:

需求维度典型选项说明
货币符号¥、$、€、₩不同市场的默认符号不同
符号位置prefix 前置、suffix 后置$100.00vs100.00€
小数位数0、2、3、动态加密货币可能到 6 位
千分位开启、关闭部分场景(如图表轴)会关闭
负数展示-100.00、-¥100.00、(100.00)会计惯例常用括号
舍入方式half-up、floor、ceil金融场景强制要求
空值和异常兜底--、0.00、空串接口异常时必须可读

列完这张表你会发现,toFixed(2)只是其中一小块。金融应用中真正要命的不是“加不加逗号”,而是负数格式舍入方式,这两个维度直接关系到用户看到的盈亏数字是否正确。

2.2 第一版实现:基于浮点数的常规方案

第一版我们为了快速上线,写了一个基于 number 的常规实现。核心思路是:输入金额数字,定义好选项,然后做舍入、取绝对值、拆整数和小数、加千分位、拼接符号。

type RoundType = 'half-up' | 'floor' | 'ceil'; interface FormatCurrencyOptions { symbol?: string; // 货币符号,默认 '¥' decimalPlaces?: number; // 小数位,默认 2 thousandSeparator?: boolean; // 千分位,默认 true symbolPosition?: 'prefix' | 'suffix'; // 符号位置 negativeFormat?: 'sign' | 'parenthesis'; // 负数格式 roundType?: RoundType; // 舍入方式 emptyPlaceholder?: string; // 空值占位 } export function formatCurrency( amount: number | string | null | undefined, options: FormatCurrencyOptions = {} ): string { const { symbol = '¥', decimalPlaces = 2, thousandSeparator = true, symbolPosition = 'prefix', negativeFormat = 'sign', roundType = 'half-up', emptyPlaceholder = '--', } = options; if (amount === null || amount === undefined || amount === '') { return emptyPlaceholder; } const num = typeof amount === 'string' ? parseFloat(amount) : amount; if (isNaN(num) || !isFinite(num)) { return emptyPlaceholder; } const factor = Math.pow(10, decimalPlaces); let rounded: number; switch (roundType) { case 'floor': rounded = Math.floor(num * factor) / factor; break; case 'ceil': rounded = Math.ceil(num * factor) / factor; break; default: rounded = Math.round(Math.abs(num) * factor) / factor * Math.sign(num); break; } const negative = rounded < 0; const absValue = Math.abs(rounded); const fixedStr = absValue.toFixed(decimalPlaces); const [intPart, decimalPart] = fixedStr.split('.'); const formattedInt = thousandSeparator ? intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',') : intPart; const decimalStr = decimalPart ? `.${decimalPart}` : ''; if (negativeFormat === 'parenthesis' && negative) { return `(${symbol}${formattedInt}${decimalStr})`; } const sign = negative ? '-' : ''; if (symbolPosition === 'suffix') { return `${sign}${formattedInt}${decimalStr}${symbol}`; } return `${sign}${symbol}${formattedInt}${decimalStr}`; }

这版代码在绝大多数场景下都能用,但有一个金融场景绕不开的隐患:浮点误差。拿0.1 * 100来说,在 JS 里结果是10.000000000000002,如果恰好遇到这种中间值,舍入结果就可能偏移。这类问题不常出现,但金融应用不允许“偶发错误”。

2.3 精度进阶:整数分方案与浮点陷阱

金融行业有个约定俗成的做法:涉及金额的内部传输尽量用“分”为单位,用整数表示,从根上避开浮点误差。我们最终在项目里使用的也是这个方案:上游接口直接返回分(cents),格式化函数接收整数分作为主输入,内部全部用整数运算。

/** * 基于整数分格式化货币,避免浮点误差 * 例如:formatCurrencyFromCents(12345678) => "¥123,456.78" */ export function formatCurrencyFromCents( cents: number | string | null | undefined, options: FormatCurrencyOptions = {} ): string { const { symbol = '¥', decimalPlaces = 2, thousandSeparator = true, symbolPosition = 'prefix', negativeFormat = 'sign', roundType = 'half-up', emptyPlaceholder = '--', } = options; if (cents === null || cents === undefined || cents === '') { return emptyPlaceholder; } let centsNum = typeof cents === 'string' ? parseInt(cents, 10) : cents; if (isNaN(centsNum) || !isFinite(centsNum)) { return emptyPlaceholder; } const negative = centsNum < 0; const absCents = Math.abs(centsNum); let intPart: string; let decimalPart: string; if (decimalPlaces === 0) { const rounded = roundCents(absCents, 0, roundType); intPart = String(rounded); decimalPart = ''; } else { const scale = Math.pow(10, decimalPlaces - 2); let scaled: number; if (decimalPlaces < 2) { // 例如只需要 1 位小数,先把分转成角,再舍入 scaled = Math.floor(absCents / 10); if (roundType === 'half-up') { scaled = Math.round(absCents / 10); } else if (roundType === 'ceil') { scaled = Math.ceil(absCents / 10); } } else { // 常见场景:整数分直接拆成元和分 // 更多位小数时,用 scale 放大处理 scaled = roundCents(absCents, decimalPlaces, roundType); } intPart = String(Math.floor(scaled / Math.pow(10, decimalPlaces))); const rawDecimal = String(scaled % Math.pow(10, decimalPlaces)).padStart(decimalPlaces, '0'); decimalPart = rawDecimal; } const formattedInt = thousandSeparator ? intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',') : intPart; const decimalStr = decimalPart ? `.${decimalPart}` : ''; const body = symbolPosition === 'suffix' ? `${formattedInt}${decimalStr}${symbol}` : `${symbol}${formattedInt}${decimalStr}`; if (negativeFormat === 'parenthesis' && negative) { return `(${body})`; } return `${negative ? '-' : ''}${body}`; } function roundCents(absCents: number, decimalPlaces: number, roundType: RoundType): number { const scale = Math.pow(10, decimalPlaces - 2); const target = absCents / scale; switch (roundType) { case 'floor': return Math.floor(target); case 'ceil': return Math.ceil(target); default: return Math.round(target); } }

核心区别在于:整个格式化过程不出现浮点数乘除12345678分直接拆成123456元和78分,千分位也是打在整数字符串上,完全不依赖toFixed。这样就彻底规避了0.1 + 0.2这类经典问题。

但实际业务中并不是所有上游都返回分,有些接口返回的是“元”且是浮点数。这种场景我们不直接在格式化层修浮点,而是先提供一个yuanToCents转换函数,在数据进入业务层时就完成转换:

/** 元转分:在数据入口统一转换,后续所有金额展示都走整数分 */ export function yuanToCents(yuan: number | string): number { const num = typeof yuan === 'string' ? parseFloat(yuan) : yuan; if (isNaN(num) || !isFinite(num)) { return 0; } return Math.round((num + Number.EPSILON) * 100); }

这里用Number.EPSILON也是为了抵消部分浮点噪声。建议团队定一条硬性规范:数据层拿到金额后立刻转成整数分,业务层和展示层只认分。

3. formatPercent:比货币格式化更隐蔽的“单位刺客”

百分比格式化比货币格式化更容易踩坑,因为它的输入语义经常不统一。同样是利率0.126,有的接口直接传0.126让你自己乘 100,有的接口直接传12.6表示已经是百分比数值。如果不做统一约定,格式化函数输出就乱套。

3.1 百分比输入到底是 0.1234 还是 12.34,必须显式约定

我见过很多团队在这个问题上互相甩锅:业务组说“后端返回的就是 0.1234 嘛”,后端说“我们文档写的是百分比数值 12.34”,最后前端在展示层临时除以 100 或者乘以 100,一个端一个样子。

formatPercent 的第一个设计决策就是:输入统一按“原始比例”处理,也就是 0.1234 表示 12.34%。这样和国际化惯例一致,后端存的是什么单位不会影响前端展示。

但为了避免以后被“传 12.34 的接口”坑到,我提供了valueToPercent的扩展参数:如果你的上游字段稳定地传入已经放大 100 倍的值,可以显式声明inputScaled: true,让函数内部不再乘 100。这个参数一旦设置,代码里就必须写清楚,不能猜。

3.2 百分比格式化实现

interface FormatPercentOptions { decimalPlaces?: number; // 小数位,默认 2 thousandSeparator?: boolean; // 千分位,默认 true withSign?: boolean; // 是否显示正号,如 +3.45% allowNegative?: boolean; // 是否允许负数 inputScaled?: boolean; // 输入是否已是百分比数值(如 12.6 表示 12.6%) trimTrailingZeros?: boolean; // 是否去掉小数末尾的 0 emptyPlaceholder?: string; } export function formatPercent( value: number | string | null | undefined, options: FormatPercentOptions = {} ): string { const { decimalPlaces = 2, thousandSeparator = true, withSign = false, allowNegative = true, inputScaled = false, trimTrailingZeros = false, emptyPlaceholder = '--', } = options; if (value === null || value === undefined || value === '') { return emptyPlaceholder; } const num = typeof value === 'string' ? parseFloat(value) : value; if (isNaN(num) || !isFinite(num)) { return emptyPlaceholder; } const raw = inputScaled ? num : num * 100; const negative = raw < 0; const absRaw = Math.abs(raw); const factor = Math.pow(10, decimalPlaces); let rounded = Math.round((absRaw + Number.EPSILON) * factor) / factor; let [intPart, decimalPart] = rounded.toFixed(decimalPlaces).split('.'); decimalPart = decimalPart || ''; if (trimTrailingZeros && decimalPlaces > 0) { decimalPart = decimalPart.replace(/0+$/, ''); } const formattedInt = thousandSeparator ? intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',') : intPart; const decimalStr = decimalPart ? `.${decimalPart}` : ''; let sign = ''; if (negative) { if (!allowNegative) { return emptyPlaceholder; } sign = '-'; } else if (withSign) { sign = '+'; } return `${sign}${formattedInt}${decimalStr}%`; }

使用示例:

formatPercent(0.126) // "12.6%" formatPercent(0.126, { decimalPlaces: 2 }) // "12.60%" formatPercent(0.126, { decimalPlaces: 2, trimTrailingZeros: true }) // "12.6%" formatPercent(1234.5, { withSign: true }) // "+1,234.50%"(涨跌幅场景常用) formatPercent(-0.005, { decimalPlaces: 2, allowNegative: false }) // "--" formatPercent(12.34, { inputScaled: true }) // "12.34%"

3.3 与货币格式化共享底层逻辑的设计

百分比格式化和货币格式化看起来是两套函数,但它们的核心逻辑高度重叠:千分位格式化、整数部分拆分、符号处理、小数位补齐。如果各写各的,以后调整千分位规则或符号规则时就要改两处。

我建议抽一个内部公共函数formatNumberParts,专门处理“绝对值转千分位整数部分 + 小数部分 + 符号前缀”的通用逻辑,formatCurrency 和 formatPercent 都调用它。这样既减少了重复代码,也能保证两套输出的风格一致。

function formatAbsNumberParts( absValue: number, decimalPlaces: number, thousandSeparator: boolean, trimTrailingZeros: boolean ): string { const factor = Math.pow(10, decimalPlaces); const rounded = Math.round((absValue + Number.EPSILON) * factor) / factor; const [intPart, decimalPart] = rounded.toFixed(decimalPlaces).split('.'); const formattedInt = thousandSeparator ? intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',') : intPart; let decimalStr = decimalPart || ''; if (trimTrailingZeros && decimalPlaces > 0) { decimalStr = decimalStr.replace(/0+$/, ''); } return decimalStr ? `${formattedInt}.${decimalStr}` : formattedInt; }

这里的思路是:把“数字的展示玩法”(千分位、小数位、去零)和“语义玩法”(货币符号、百分比符号、正负号括号)分离。后续如果要统一改千分位分隔符为空格或者改成印度数字分组,只需要动一个函数。

4. 鸿蒙、iOS、Android 三端复用时的兼容性整治

工具函数写好了,真正让它成为“跨端核心资产”的关键一步是处理三端运行环境的差异。React Native 项目在鸿蒙端跑起来后,我们遇到了一堆在模拟器上根本暴露不出来、只有真机上才出现的兼容性问题。

4.1 Intl.NumberFormat 在不同 Runtime 上的表现差异

刚写第一版时,很多同事习惯性用Intl.NumberFormat来做千分位。在 iOS 的 JavaScriptCore 里表现正常,在 Android 的 Hermes 里基础用法也正常,但到了鸿蒙的 JS 运行时,某些版本对Intl.NumberFormat的支持不完整,特别是currency风格的货币符号映射,有的设备显示成CN¥,有的显示成¥,甚至有的直接返回不带符号的数字。

这个问题非常容易在联调阶段被漏掉,因为鸿蒙模拟器和开发者常用的高端真机可能表现正常,但用户的大众机型上就会翻车。我们的解决方案很简单:放弃依赖 Intl.NumberFormat,千分位和货币符号全部手动处理。这也意味着工具函数完全不去探测宿主环境支持什么,只依赖最基础的 JS 语法,稳定性大幅提升。

4.2 舍入规则与浮点误差的跨端一致性

另一类问题是舍入行为不一致。toFixed在不同 JS 引擎上对某些边界值的行为历史上就存在争议,比如(1.005).toFixed(2)在不同引擎上可能得到1.001.01。虽然现代引擎大多修复了,但鸿蒙端的新运行时是否完全对齐,我们没有十足的把握。

所以最终的 formatCurrencyFromCents 干脆绕开了toFixed,用整数运算直接算小数位;formatPercent 虽然还用toFixed,但统一乘了Number.EPSILON做补偿,并且在三端真机上跑了一轮断言用例。这里给的结论是:能绕开就绕开,绕不开就加补偿,但绝不能假设所有端行为一致。

4.3 字体渲染与 UX 细节:从“能用”到“好看”

格式化函数输出的字符串最终要渲染到界面上,不同端对货币符号的字体渲染也有差别。鸿蒙默认字体对¥的渲染和苹方对¥的渲染在宽度、基线位置上不完全一致。如果金额展示在表格或者卡片里,可能因为符号宽度差异导致数字不能对齐。

此外,负数括号格式(¥1,000.00)在金融场景常用,但在窄屏设备上括号容易和相邻元素重叠。我们后来加了两个 UI 层面的约定:金额类文本允许文本省略时不显示货币符号,只显示1,000.00;涨跌幅场景用颜色表示正负,同时保留+/-符号,方便色弱用户识别。这些细节不是格式化函数本身的问题,但如果没有统一的格式化层,这些 UX 约定根本无从谈起。

4.4 用测试矩阵守住三端一致性

跨端一致性的底线不是靠代码自证,而是靠测试矩阵。我们在 CI 里跑同一套 Jest 用例,覆盖 formatCurrency 和 formatPercent 的几十种输入输出断言;在真机测试阶段,专门做了一张三端对照表格,同一批输入在三端的截图输出必须逐字一致。

输入iOSAndroid鸿蒙期望结果
12345678(分)¥123,456.78¥123,456.78¥123,456.78¥123,456.78
-500(分,括号格式)(¥5.00)(¥5.00)(¥5.00)(¥5.00)
0.126(百分比)12.60%12.60%12.60%12.60%
null--------

这张表我会建议每个涉及跨端的项目都建一份。格式化的断言不通过,其他业务逻辑都不要测了,因为展示层已经错了。

5. 异常输入与边界场景:格式化函数也要有“防御装甲”

很多页面崩溃和数据显示异常,不是业务逻辑的问题,而是格式化函数收到了意料之外的输入。既然 formatCurrency 和 formatPercent 要作为公共资产在全项目铺开,它们就必须对异常输入有明确的防御策略。

5.1 常见异常输入与兜底策略

我把项目中实际遇到的异常输入归成几类,每类都明确处理方式:

异常类型示例兜底行为
空值null、undefined、空字符串返回--或配置的 emptyPlaceholder
非数字字符串"abc"返回空值占位符
NaN / InfinityNaNInfinity返回空值占位符
字符串数字"12345""12.5"按数字解析后格式化
超过安全整数范围9007199254740993项目内部约定按最大值截断或抛异常

这里最重要的原则是:格式化函数永远不抛异常。它处于展示链路的末端,一旦抛异常,轻则页面白屏,重则整个列表渲染失败。兜底返回--至少能让用户知道“这里应该有一个值,但现在没拿到数据”,这比直接崩溃更友好。

5.2 超大金额、超小金额与精度溢出

金融应用还会遇到余额宝收益这类超小数字,比如年化收益每日入账0.000123元。如果走 formatCurrencyFromCents,0.000123元转成整数分是0.0123分,四舍五入变成0.01分,展示成¥0.00其实没问题,因为确实没有满一分钱。但如果产品经理要求“显示到厘”,那就得在数据层提前把最小单位换成厘,而不是在格式化层硬凑。

超大数据方面,如果后端传的金额超过 JS 安全整数范围,parseInt的精度就会丢失。我们的方案是:接口层对超大金额统一用字符串传递,格式化层通过BigInt或者字符串拆分的方式处理。不过目前 99% 的金融场景不会到万亿级别,所以这个处理做成可选扩展,不做进主路径,避免把核心函数搞得太重。

5.3 推荐的单测用例清单

格式化工具是全项目的基石函数,单元测试的性价比极高。我强烈建议至少覆盖这些用例:

describe('formatCurrencyFromCents', () => { test('常规金额千分位和小数位', () => { expect(formatCurrencyFromCents(12345678)).toBe('¥123,456.78'); }); test('负数符号格式', () => { expect(formatCurrencyFromCents(-500)).toBe('-¥5.00'); }); test('负数括号格式', () => { expect(formatCurrencyFromCents(500, { negativeFormat: 'parenthesis', symbolPosition: 'suffix' })).toBe('($5.00)'); // 注意实际返回会带上符号,按实现断言 }); test('空值兜底', () => { expect(formatCurrencyFromCents(null)).toBe('--'); }); test('非法输入兜底', () => { expect(formatCurrencyFromCents('abc')).toBe('--'); }); }); describe('formatPercent', () => { test('常规比例转百分比', () => { expect(formatPercent(0.126)).toBe('12.60%'); }); test('千分位百分比', () => { expect(formatPercent(12345.678)).toBe('1,234,567.80%'); }); test('正号展示', () => { expect(formatPercent(0.0345, { withSign: true })).toBe('+3.45%'); }); test('负数被禁止时兜底', () => { expect(formatPercent(-0.01, { allowNegative: false })).toBe('--'); }); test('已放大输入', () => { expect(formatPercent(12.34, { inputScaled: true })).toBe('12.34%'); }); });

我特别想强调“负数被禁止时兜底为空值”这个设计。在金融场景里,利率或收益率出现了负数,往往意味着异常。与其展示一个让人困惑的-3.00%,不如用占位符触发后续的异常判断逻辑。当然,盈亏类场景负数必须展示,所以allowNegative默认是true,具体用哪个由业务层显式决定。

5.4 从“工具函数”到“资产”的团队落地经验

最后分享一点团队落地的经验。工具函数写完之后,我们做了一件事:把它从普通的 utils 目录提升到了独立的基础库里,并写了一份格式化规范文档,明确了以下几条团队红线:

  • 所有金额展示必须走 formatCurrency / formatCurrencyFromCents,禁止在业务代码里手写toFixed(2)
  • 所有百分比展示必须走 formatPercent,禁止手写(num * 100).toFixed(2) + '%'
  • 数据层拿到的金额如果单位是“元”,必须在数据转换层统一转成“分”,展示层只认分。
  • 新增格式化需求(比如新货币、新舍入方式)必须改工具库并在单测里补充用例,不允许在业务组件里临时拼字符串。

这几条红线刚开始执行时会有阻力,尤其是一些老手觉得“就一行代码,没必要封装”。但经历一次跨端金额对账事故后,团队就没人再质疑了。工具函数本身不复杂,真正有价值的是把它作为“资产”对待的制度约束。后续如果要把这套逻辑复用到 Flutter 或者其他跨端方案,核心的舍入规则和边界场景清单也可以平迁过去,这才是它作为“核心资产”的意义所在。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 20:16:05

【JAVA课程设计/毕业设计】 SpringBoot 框架下校园信息发布平台的设计与实现 基于 SpringBoot+ElementUI 的校园新闻更新系统的设计【附源码、数据库、万字文档】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/9/9 20:15:20

从虚拟偶像事件看软件测试如何防范诱导消费与极端消费

那天下午&#xff0c;测试群里有人甩了一条新闻链接。我没点开&#xff0c;光是标题就让我愣了几秒——虚拟偶像、线下见面、极端个案。群里安静了一会儿&#xff0c;然后有人问了一句让我记到现在的话&#xff1a;"这功能要是咱们测的&#xff0c;能拦住吗&#xff1f;&q…

作者头像 李华
网站建设 2026/9/9 20:15:04

2026年AI写作辅助软件推荐:9款实用AI工具全攻略

一、AI 全面赋能学术写作 人工智能技术正加速渗透至学术研究的各个环节&#xff0c;AI 写作工具在提升论文效率与质量方面展现出强大潜力。从选题策划、内容生成&#xff0c;到语言优化与查重检测&#xff0c;AI 已实现全流程支持。 本文将为您精选 9 款当前市场上实用性强且具…

作者头像 李华
网站建设 2026/9/9 20:14:38

React 列表页返回重复请求?从 useState 到 React Query 的缓存方案

前几天一个做移动端 H5 的同事跑过来问我&#xff1a;HomeList 列表页&#xff0c;从详情页返回时每次都要重新 loading&#xff0c;网络请求一遍遍发。他自己在组件内部用 useState 存了列表&#xff0c;但一离开页面状态就没了&#xff0c;回来又得重新拉一遍。这个场景太典型…

作者头像 李华