diff --git a/assets/css/routes/user-center/credits.css b/assets/css/routes/user-center/credits.css index 081507db..e8b3ce12 100644 --- a/assets/css/routes/user-center/credits.css +++ b/assets/css/routes/user-center/credits.css @@ -15,7 +15,7 @@ /* Dual asset row — layout only (no outer mega-card). Desktop: 1 row; mobile: 2 rows. */ .uc-assets-row { display: grid; - grid-template-columns: 1fr; + grid-template-columns: minmax(0, 1fr); gap: 12px; margin-bottom: 10px; background: transparent; @@ -65,10 +65,15 @@ grid-column: auto; grid-row: auto; } + .uc-asset-card__body { + flex-direction: column; + align-items: flex-start; + } } /* One surface per asset (wallet / points) — never nest inside another card shell */ .uc-asset-card { + min-width: 0; display: flex; flex-direction: column; background: var(--main-bg-color, var(--surface-1, #fff)); @@ -78,6 +83,11 @@ padding: 16px 20px; margin-bottom: 0; } +/* Amount keeps its row; actions wrap below as a whole when space runs out. */ +.uc-asset-card__body { + flex-wrap: wrap; + row-gap: 10px; +} /* Card-pass under a col must not look like a second stacked card */ .uc-assets-col > .uc-card-pass { margin: 0; @@ -95,6 +105,12 @@ color: var(--main-color, #4e5358); } .uc-asset-card__amount { + flex: none; + max-width: 100%; + white-space: nowrap; + font-variant-numeric: tabular-nums; + overflow: hidden; + text-overflow: ellipsis; font-size: 1.75rem; line-height: 1.2; } diff --git a/assets/js/credits.js b/assets/js/credits.js index 6f259bda..9e3b5406 100644 --- a/assets/js/credits.js +++ b/assets/js/credits.js @@ -62,9 +62,10 @@ var points = data.credit_balance !== undefined ? data.credit_balance : data.points_balance; if (wallet !== undefined && wallet !== null && wallet !== '') { var walletNumber = Number(wallet); - var walletText = Number.isFinite(walletNumber) ? walletNumber.toFixed(2) : String(wallet); + var walletText = Number.isFinite(walletNumber) ? walletNumber.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : String(wallet); document.querySelectorAll('[data-aksrt-wallet-value]').forEach(function (element) { element.textContent = walletText; + element.title = walletText; }); } if (points !== undefined && points !== null && points !== '') { @@ -72,6 +73,7 @@ var pointsText = Number.isFinite(pointsNumber) ? pointsNumber.toLocaleString() : String(points); document.querySelectorAll('[data-aksrt-points-value]').forEach(function (element) { element.textContent = pointsText; + element.title = pointsText; }); } } diff --git a/assets/js/signin.js b/assets/js/signin.js index 3298f287..19e52b87 100644 --- a/assets/js/signin.js +++ b/assets/js/signin.js @@ -117,6 +117,7 @@ notyf(result.json.message || '签到成功', 'success', 5, 'aksrt-balance-signin-toast'); document.querySelectorAll('[data-credits-value]').forEach(function (element) { element.textContent = Number(result.json.balance || 0).toLocaleString(); + element.title = element.textContent; }); setButtonState('done'); refresh(); diff --git a/inc/helpers/user-center-renderers/credits-balance.php b/inc/helpers/user-center-renderers/credits-balance.php index 258bc267..0acef0dd 100644 --- a/inc/helpers/user-center-renderers/credits-balance.php +++ b/inc/helpers/user-center-renderers/credits-balance.php @@ -46,7 +46,7 @@ $currency = (string) ($currency_name ?? __('积分', 'aksrt'));
- +
diff --git a/inc/helpers/user-center-renderers/credits-wallet.php b/inc/helpers/user-center-renderers/credits-wallet.php index fca1dbca..d83c7424 100644 --- a/inc/helpers/user-center-renderers/credits-wallet.php +++ b/inc/helpers/user-center-renderers/credits-wallet.php @@ -78,7 +78,7 @@ $mark = (string) ($charge_view['currency_mark'] ?? '元');
- +
diff --git a/src/Ledger/AssetAmount.php b/src/Ledger/AssetAmount.php index a7777417..f13e4a2d 100644 --- a/src/Ledger/AssetAmount.php +++ b/src/Ledger/AssetAmount.php @@ -83,6 +83,8 @@ final class AssetAmount { if ($this->asset === AssetType::POINTS) { return number_format_i18n($this->minor); } - return $this->decimal(); + global $wp_locale; + $decimal_point = isset($wp_locale->number_format['decimal_point']) ? $wp_locale->number_format['decimal_point'] : '.'; + return number_format_i18n(intdiv($this->minor, 100)) . $decimal_point . sprintf('%02d', $this->minor % 100); } } diff --git a/tests/asset-ledger/AssetAmountTest.php b/tests/asset-ledger/AssetAmountTest.php index b991dd56..3eae3c53 100644 --- a/tests/asset-ledger/AssetAmountTest.php +++ b/tests/asset-ledger/AssetAmountTest.php @@ -73,4 +73,12 @@ final class AssetAmountTest extends TestCase { $this->expectException(InvalidArgumentException::class); AssetAmount::percentage(100, 101); } + + public function test_formatted_adds_thousands_separators_to_balance(): void { + self::assertSame('0.00', AssetAmount::balance('0')->formatted()); + self::assertSame('12.34', AssetAmount::balance('12.34')->formatted()); + self::assertSame('564,000.00', AssetAmount::balance('564000')->formatted()); + self::assertSame('1,234,567.89', AssetAmount::balance('1234567.89')->formatted()); + self::assertSame('37', AssetAmount::points(37)->formatted()); + } } diff --git a/tests/asset-ledger/bootstrap.php b/tests/asset-ledger/bootstrap.php index 9a5f23f2..afe95d48 100644 --- a/tests/asset-ledger/bootstrap.php +++ b/tests/asset-ledger/bootstrap.php @@ -56,6 +56,11 @@ if (!function_exists('wp_is_uuid')) { return $version === null || strtolower($matches[1]) === strtolower((string) $version); } } +if (!function_exists('number_format_i18n')) { + function number_format_i18n($number): string { + return number_format((float) $number); + } +} if (!class_exists('WP_Error')) { class WP_Error { public function __construct(private string $code, private string $message = '', private $data = null) {} diff --git a/tests/widget-system/user-assets-layout-contract.test.cjs b/tests/widget-system/user-assets-layout-contract.test.cjs index 901b2c7a..3cf15d72 100644 --- a/tests/widget-system/user-assets-layout-contract.test.cjs +++ b/tests/widget-system/user-assets-layout-contract.test.cjs @@ -38,6 +38,26 @@ test('dual asset layout returns to a single-column flex flow on mobile', () => { assert.match(mobile[1], /\.uc-assets-row--dual\s*>\s*\.uc-assets-col\s*\{[\s\S]*?display:\s*flex;/) assert.match(mobile[1], /grid-column:\s*auto;/) assert.match(mobile[1], /grid-row:\s*auto;/) + assert.match(mobile[1], /\.uc-asset-card__body\s*\{[\s\S]*?flex-direction:\s*column;/) +}) + +test('asset card amounts stay on one line and actions wrap below as a whole', () => { + const css = read('assets/css/routes/user-center/credits.css') + + assert.match(css, /\.uc-assets-row\s*\{[\s\S]*?grid-template-columns:\s*minmax\(0,\s*1fr\);/) + assert.match(css, /\.uc-asset-card\s*\{[\s\S]*?min-width:\s*0;/) + assert.match(css, /\.uc-asset-card__body\s*\{[\s\S]*?flex-wrap:\s*wrap;/) + assert.match(css, /\.uc-asset-card__amount\s*\{[\s\S]*?white-space:\s*nowrap;[\s\S]*?font-variant-numeric:\s*tabular-nums;[\s\S]*?text-overflow:\s*ellipsis;/) +}) + +test('asset card amounts expose the full value via title tooltips', () => { + const wallet = read('inc/helpers/user-center-renderers/credits-wallet.php') + const balance = read('inc/helpers/user-center-renderers/credits-balance.php') + const amount = read('src/Ledger/AssetAmount.php') + + assert.match(wallet, /data-aksrt-wallet-value[^>]*title="/) + assert.match(balance, /data-aksrt-points-value[^>]*data-credits-value[^>]*title="/) + assert.match(amount, /number_format_i18n\(intdiv\(\$this->minor, 100\)\)/) }) test('removed asset card selectors do not return in loaded user-center styles', () => {