fix(frontend): route every user-center points label through the custom currency name

后台「积分配置 → 自定义货币名称」改名后,用户中心仍有大量硬编码
「积分」:做任务赚积分 / 购买积分 / 签到领积分、积分记录 / 积分任务、
每日可免费获取 XX 积分、我的积分、积分+1、创作中心积分收益等。

get_currency_name() 早已存在且可用,问题只是这些文案没有走它。
本PR 全部替换,无双路、无迁移:

服务端渲染(用户中心 renderers)
- credits-balance.php:购买/做任务赚/签到领 + 整个购买弹窗
- profile-credits.php:积分记录 / 积分任务 页签
- credits-points-tasks.php:每日上限三态与空态
- sidebar-creator.php / profile-creator.php:积分收益
- transfer-modal.php:积分转账提示
- Signin.php:签到详情弹窗(我的积分 / 奖励 tooltip / 积分+N)
- VipServiceModal.php:我的积分 / 积分兑换会员
- download-permission-card.php:购买价格

客户端渲染(JS 无法调用 PHP,注入 currencyName 到 aksrtData)
- signin.js:签到状态行「已签到 N 天 +1」
- credits.js:转账标题/金额标签 + 购买提示 toast
- cms-download.js:积分不足视图、我的积分 入口、余额不足文案
- pay-vip.js:积分兑换会员入口(优先 catalog.currency_name)
- order-center.js:订单行单位回退

契约测试同步改为断言模板构造而非死字符串,并新增两例:
改名后跟随、未注入时回退「积分」。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Opus 5 committed 2026-11-01 15:09:49 +08:00
1 parent 1c95f94bca
commit 5c6b422866
19 files changed
+170 -59

No files matched your search

@@ -71,7 +71,9 @@ test('购买积分弹窗:Zibll points-pay modal 结构 + 卡密渠道', () =>
assert.match(creditsRenderer, /data-aksrt-points-pay-form/);
assert.match(creditsRenderer, /data-aksrt-points-pay-open/);
assert.doesNotMatch(creditsRenderer, /data-aksrt-points-exchange-(open|modal|form|amount|preview|close)/);
assert.match(creditsRenderer, /请选择需购买的积分|请输入需购买的积分数额/);
// 商品/自定义提示语跟随自定义货币名称(后台「积分配置 → 自定义货币名称」)。
assert.match(creditsRenderer, /请选择需购买的%s', 'aksrt'\), esc_html\(\$currency\)/);
assert.match(creditsRenderer, /请输入需购买的%s数额', 'aksrt'\), esc_html\(\$currency\)/);
assert.match(creditsRenderer, /name="points_product"/);
assert.match(creditsRenderer, /name="points_custom"/);
assert.match(creditsRenderer, /name="payment_method"/);
+31 -4
View File
@@ -64,7 +64,7 @@ function deferred() {
return { promise, resolve, reject };
}
function setup({ buttons = [makeButton('btn uc-header__signin-btn', '签到')], captchaSignin = false, today = Promise.resolve({ ok: true, status: 200, json: { enabled: true, signed: false, streak: 1, reward_if_signin: 2 } }), signin = Promise.resolve({ ok: true, status: 200, json: { message: '签到成功,积分 +2', balance: 12 } }), captchaRun } = {}) {
function setup({ buttons = [makeButton('btn uc-header__signin-btn', '签到')], captchaSignin = false, currencyName = '', today = Promise.resolve({ ok: true, status: 200, json: { enabled: true, signed: false, streak: 1, reward_if_signin: 2 } }), signin = Promise.resolve({ ok: true, status: 200, json: { message: '签到成功,积分 +2', balance: 12 } }), captchaRun } = {}) {
const calls = [];
const notices = [];
const balance = { textContent: '10', title: '10' };
@@ -84,7 +84,7 @@ function setup({ buttons = [makeButton('btn uc-header__signin-btn', '签到')],
};
const jq = { off() { return this; }, on(event, callback) { listeners[event] = callback; return this; } };
const window = {
aksrtData: { captchaSignin, signinDetailsUrl: '/wp-json/aksrt/v1/signin/details' },
aksrtData: { captchaSignin, signinDetailsUrl: '/wp-json/aksrt/v1/signin/details', currencyName },
AksrtModalRuntime: { open: (...args) => modals.push(args), openRemote: (...args) => modals.push(args) },
AksrtRequest: { request(endpoint, options) { calls.push({ endpoint, options }); return endpoint === '/signin/today' ? today : signin; } },
};
@@ -99,6 +99,32 @@ function setup({ buttons = [makeButton('btn uc-header__signin-btn', '签到')],
const settle = () => new Promise((resolve) => setImmediate(resolve));
test('status line follows the custom currency name instead of hardcoding 积分', async () => {
// 后台「积分配置 → 自定义货币名称」改名后,签到状态行必须跟着变,
// 否则这里仍显示「积分」。
const button = makeButton('btn', '签到');
const success = deferred();
const h = setup({ buttons: [button], currencyName: '金币', signin: success.promise });
await settle();
button.click();
await settle();
success.resolve({ ok: true, status: 200, json: { message: '签到成功', balance: 12, streak: 3, reward: 2 } });
await settle();
assert.equal(h.status.innerHTML, '已签到 <strong>3</strong> 天 +2 金币');
});
test('status line falls back to 积分 when no custom currency name is injected', async () => {
const button = makeButton('btn', '签到');
const success = deferred();
const h = setup({ buttons: [button], signin: success.promise });
await settle();
button.click();
await settle();
success.resolve({ ok: true, status: 200, json: { message: '签到成功', balance: 12, streak: 3, reward: 2 } });
await settle();
assert.equal(h.status.innerHTML, '已签到 <strong>3</strong> 天 +2 积分');
});
test('uses reference loading, success toast, and replaces all entry points with detail links', async () => {
const first = makeButton('img-badge jb-yellow', '签到', '<i class="fa far fa-calendar-check ml3 mr6"></i>');
const second = makeButton('btn uc-header__signin-btn', '签到', '<i class="fa fa-calendar-check"></i>', '今日已签到');
@@ -136,7 +162,8 @@ test('uses reference loading, success toast, and replaces all entry points with
assert.equal(h.calls.filter((call) => call.endpoint === '/signin/today').length, 1, 'success does not issue another status request');
assert.equal(h.modals.length, 0, 'success does not open a modal');
assert.equal(h.balance.textContent, '12');
assert.equal(h.status.innerHTML, '已签到 <strong>2</strong> 天 +2');
// 状态行在奖励数值后追加自定义货币名称(未注入时回退「积分」)。
assert.equal(h.status.innerHTML, '已签到 <strong>2</strong> 天 +2 积分');
});
test('keeps the profile done style on its replacement link', async () => {
@@ -228,7 +255,7 @@ test('ignores an old today response after successful sign-in', async () => {
await settle();
assert.ok(button.replaced, 'success replaced the action with the details link');
assert.match(button.replaced.innerHTML, /已签到/);
assert.equal(h.status.innerHTML, '已签到 <strong>3</strong> 天 +2');
assert.equal(h.status.innerHTML, '已签到 <strong>3</strong> 天 +2 积分');
});
test('leaves a disabled sign-in feature disabled without turning it into a signed state', async () => {
@@ -78,7 +78,12 @@ test('其余侧栏部件按参考主题选型替换为 sprite 图标', () => {
assert.match(creator, /class="mb10-sm zib-widget padding-10 flex1 pointer"/);
assert.match(creator, /flex ab jsb col-ml6/);
assert.match(creator, /<i class="ml6 fa fas fa-angle-right em12"/);
for (const label of ['创作中心', '推广中心', '现金收益', '积分收益', '比例', '待提现', '累计佣金']) {
assert.match(creator, /'创作中心'/);
assert.match(creator, /'推广中心'/);
assert.match(creator, /'现金收益'/);
// 收益单位名跟随自定义货币名称,不再硬编码「积分收益」。
assert.match(creator, /sprintf\(__\('%s收益', 'aksrt'\), \$currency_name\)/);
for (const label of ['比例', '待提现', '累计佣金']) {
assert.ok(creator.includes(`'${label}'`), `creator card missing label: ${label}`);
}
// 创作中心不再统计商品数与今日收入。
@@ -46,16 +46,18 @@ test('弹窗为参考主题 refresh_modal 同构静态壳并带 data 钩子', ()
assert.match(modal, /data-aksrt-vip-points-form/);
});
test('弹窗内容保持参考主题结构:标题/我的积分/卡片选择/立即兑换', () => {
test('弹窗内容保持参考主题结构:标题/我的{货币名}/卡片选择/立即兑换', () => {
// 头部:.mb10.touch + modal-title.flex.ac + vip_1 图标。
assert.match(modal, /'<div class="mb10 touch">'/);
assert.match(modal, /class="modal-title flex ac"/);
assert.match(modal, /#icon-vip_1/);
// 我的积分盒(点回用户中心积分页)。
// 我的{货币名}盒(点回用户中心积分页)。货币名走自定义名称,不硬编码。
assert.match(modal, /muted-box padding-h10 mb20/);
assert.match(modal, /我的积分/);
assert.match(modal, /sprintf\(__\('我的%s', 'aksrt'\), \$currency\)/);
assert.match(modal, /#icon-points/);
assert.match(modal, /aksrt_get_user_center_tab_url\('credits'\)/);
// 标题同样跟随货币名。
assert.match(modal, /sprintf\(__\('%s兑换会员', 'aksrt'\), \$currency\)/);
// 卡片列表 + 参考主题 data-for/data-value 选中模型。
assert.match(modal, /请选择需兑换的会员/);
assert.match(modal, /flex hh exchange-card-box/);
@@ -81,7 +83,7 @@ test('弹窗壳随 wp_footer 全局输出且不进入 catalog 载荷', () => {
assert.doesNotMatch(modal, /'vip_url'/);
});
test('我的积分盒恢复参考主题 muted-box 的背景、圆角与内边距', () => {
test('我的{货币名}盒恢复参考主题 muted-box 的背景、圆角与内边距', () => {
assert.match(vipCss, /\.modal\[data-aksrt-vip-points-modal\]\s+\.muted-box\.padding-h10\s*\{\s*background-color:\s*var\(--this-bg,\s*var\(--muted-border-color\)\);\s*border-radius:\s*var\(--main-radius\);\s*padding:\s*10px 15px;\s*\}/);
assert.match(vipCss, /\.modal\[data-aksrt-vip-points-modal\]\s+\.muted-box\.padding-10,\s*\.modal\[data-aksrt-vip-points-modal\]\s+\.aksrt-vip-points-desc\s*\{\s*background-color:\s*var\(--this-bg,\s*var\(--muted-border-color\)\);\s*border-radius:\s*var\(--main-radius\);\s*padding:\s*10px;\s*\}/);
});