reuse PlatformMudPointWalletEntry for account

This commit is contained in:
2026-07-16 20:21:00 +08:00
parent c5999b54d3
commit 9b8af1210e
5 changed files with 46 additions and 144 deletions
+1
View File
@@ -3377,6 +3377,7 @@ name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.39.4"
+36 -94
View File
@@ -37,6 +37,9 @@ import {
User,
Zap,
} from 'lucide-react';
import {
PlatformMudPointWalletEntry,
} from '../../../packages/shared/src/components/PlatformMudPointWalletEntry';
import type {
AuthEntryResponse,
@@ -74,7 +77,7 @@ import {
} from '../../../packages/shared/src/contracts/gameCreationApp';
import type {
ProfileDashboardSummary,
ProfileWalletLedgerEntry,
ProfileMudPointBalance,
} from '../../../packages/shared/src/contracts/runtime';
import {
API_RESPONSE_ENVELOPE_HEADER,
@@ -83,7 +86,7 @@ import {
} from '../../../packages/shared/src/http';
import {
getClientProfileDashboard,
getClientProfileWalletLedger,
getClientProfileRechargeCenter,
} from './services/clientApi';
import HomeView, {
type HomeAgentMode,
@@ -3799,34 +3802,6 @@ const homeAgentModeLabels: Record<HomeAgentMode, string> = {
doc: '做方案',
};
function formatMudPoints(value: number | null | undefined) {
if (typeof value !== 'number' || !Number.isFinite(value)) {
return '--';
}
return Math.max(0, Math.floor(value)).toLocaleString('zh-CN');
}
function formatLedgerAmount(entry: ProfileWalletLedgerEntry) {
const amount = Math.trunc(entry.amountDelta);
return `${amount >= 0 ? '+' : ''}${amount}`;
}
function formatLocalDateTime(value: string | number | null | undefined) {
if (value === null || value === undefined || value === '') {
return '未知时间';
}
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return '未知时间';
}
return date.toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
}
function buildHomeConversationContent(
mode: HomeAgentMode,
prompt: string,
@@ -5380,11 +5355,12 @@ export function WorkspaceLauncher({
useState<PendingNonEmptyProject | null>(null);
const [profileDashboard, setProfileDashboard] =
useState<ProfileDashboardSummary | null>(null);
const [walletPanelOpen, setWalletPanelOpen] = useState(false);
const [walletLedger, setWalletLedger] = useState<ProfileWalletLedgerEntry[]>(
[],
);
const [walletLedgerStatus, setWalletLedgerStatus] = useState('尚未读取明细');
const [mudPointBalance, setMudPointBalance] =
useState<ProfileMudPointBalance | null>(null);
const [mudPointBalanceStatus, setMudPointBalanceStatus] = useState<
'idle' | 'loading' | 'ready' | 'error'
>('idle');
const [mudPointBalanceError, setMudPointBalanceError] = useState('');
const [launcherNotice, setLauncherNotice] = useState<{
title: string;
message: string;
@@ -5842,7 +5818,7 @@ export function WorkspaceLauncher({
return;
}
setProfileDashboard(null);
setWalletLedgerStatus(
setMudPointBalanceError(
error instanceof Error ? error.message : '泥点读取失败',
);
});
@@ -5851,13 +5827,6 @@ export function WorkspaceLauncher({
};
}, []);
useEffect(() => {
if (!walletPanelOpen || walletLedgerStatus !== '尚未读取明细') {
return;
}
void loadWalletLedger();
}, [walletPanelOpen, walletLedgerStatus]);
useEffect(() => {
if (launcherView !== 'agent-chat') {
return;
@@ -6194,24 +6163,29 @@ export function WorkspaceLauncher({
);
}
async function loadWalletLedger() {
setWalletLedgerStatus('正在读取明细');
async function loadMudPointBalance() {
if (mudPointBalanceStatus === 'loading') {
return;
}
setMudPointBalanceStatus('loading');
setMudPointBalanceError('');
try {
const response = await getClientProfileWalletLedger();
setWalletLedger(response.entries.slice(0, 5));
setWalletLedgerStatus(
response.entries.length > 0 ? '已读取明细' : '暂无泥点明细',
const center = await getClientProfileRechargeCenter();
setMudPointBalance(center.mudPointBalance ?? null);
setProfileDashboard((current) =>
current ? { ...current, walletBalance: center.walletBalance } : current,
);
setMudPointBalanceStatus('ready');
} catch (error) {
setWalletLedger([]);
setWalletLedgerStatus(
error instanceof Error ? error.message : '明细读取失败',
setMudPointBalance(null);
setMudPointBalanceStatus('error');
setMudPointBalanceError(
error instanceof Error ? error.message : '泥点明细读取失败',
);
}
}
function showLauncherNotice(title: string) {
setWalletPanelOpen(false);
setLauncherNotice({
title,
message: `${title}正在接入中,当前版本会先保留入口。`,
@@ -8533,47 +8507,15 @@ export function WorkspaceLauncher({
) : null}
<div className="launcher-account-bar" aria-label="账户资产">
<div className="launcher-wallet-group">
<div className="launcher-wallet-anchor">
<button
type="button"
aria-label="泥点余额"
onClick={() => {
setWalletPanelOpen((open) => !open);
}}
>
<Zap size={12} aria-hidden="true" />
{formatMudPoints(profileDashboard?.walletBalance)}
</button>
{walletPanelOpen ? (
<div className="launcher-wallet-popover" role="dialog">
<strong>泥点</strong>
<p>{formatMudPoints(profileDashboard?.walletBalance)} 泥点</p>
<div className="launcher-wallet-ledger">
{walletLedger.length > 0 ? (
walletLedger.map((entry) => (
<div key={entry.id}>
<span>{formatLedgerAmount(entry)}</span>
<small>{formatLocalDateTime(entry.createdAt)}</small>
</div>
))
) : (
<small>{walletLedgerStatus}</small>
)}
</div>
<button
type="button"
onClick={() => showLauncherNotice('使用详情')}
>
使用详情
</button>
</div>
) : null}
</div>
<button type="button" onClick={() => showLauncherNotice('升级')}>
升级
</button>
</div>
<PlatformMudPointWalletEntry
balance={profileDashboard?.walletBalance ?? null}
breakdown={mudPointBalance}
isLoading={mudPointBalanceStatus === 'loading'}
error={mudPointBalanceError || null}
onRequestDetails={() => void loadMudPointBalance()}
onRecharge={() => showLauncherNotice('充值')}
onOpenLedger={() => showLauncherNotice('使用详情')}
/>
</div>
{launcherView === 'home' ? (
@@ -2,7 +2,7 @@ import {
API_RESPONSE_ENVELOPE_HEADER,
API_RESPONSE_ENVELOPE_VERSION,
ProfileDashboardSummary,
ProfileWalletLedgerResponse,
ProfileRechargeCenterResponse,
unwrapApiResponse,
} from '../../../../packages/shared/src';
@@ -134,9 +134,9 @@ export function getClientProfileDashboard() {
);
}
export function getClientProfileWalletLedger() {
return requestClientApi<ProfileWalletLedgerResponse>(
'/api/profile/wallet-ledger',
export function getClientProfileRechargeCenter() {
return requestClientApi<ProfileRechargeCenterResponse>(
'/api/profile/recharge-center',
{ method: 'GET' },
'读取泥点明细失败',
);
@@ -1,7 +1,4 @@
/** @vitest-environment jsdom */
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import {
act,
cleanup,
@@ -1476,17 +1473,17 @@ describe('AI 游戏创作 App 界面边界', () => {
).toBeNull();
expect(
within(screen.getByLabelText('账户资产')).getByRole('button', {
name: '泥点余额',
name: /^泥点 /,
}),
).not.toBeNull();
expect(
within(screen.getByLabelText('账户资产')).getByRole('button', {
name: '升级',
name: '充值',
}),
).not.toBeNull();
fireEvent.click(
fireEvent.mouseEnter(
within(screen.getByLabelText('账户资产')).getByRole('button', {
name: '泥点余额',
name: /^泥点 /,
}),
);
expect(screen.queryByText('已读取账户')).toBeNull();
@@ -19866,45 +19863,6 @@ describe('AI 游戏创作 App 界面边界', () => {
});
});
it('keeps multiline chat evidence readable', () => {
const styles = readFileSync(
resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'),
'utf8',
);
expect(styles).toMatch(/\.message\s*\{[^}]*white-space:\s*pre-wrap/s);
expect(styles).toMatch(/\.message\s*\{[^}]*overflow-wrap:\s*anywhere/s);
expect(styles).toMatch(
/\.launcher-agent-chat-main\s*\{[^}]*grid-template-rows:[^;]*clamp\(260px,\s*40vh,\s*380px\)/s,
);
expect(styles).toMatch(
/\.launcher-agent-chat-messages\s*\{[^}]*grid-row:\s*5[^}]*height:\s*clamp\(260px,\s*40vh,\s*380px\)[^}]*overflow-y:\s*auto/s,
);
expect(styles).toMatch(
/\.launcher-agent-chat-composer\s*\{[^}]*grid-row:\s*6/s,
);
expect(styles).not.toMatch(
/\.launcher-agent-runtime-stack:empty\s*\{[^}]*display:\s*none/s,
);
});
it('keeps launcher page header actions styled after Tailwind preflight', () => {
const styles = readFileSync(
resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'),
'utf8',
);
const headerActionRule = styles.match(
/\.launcher-page \.launcher-project-list-actions button,[^{]*\{([^}]*)\}/s,
);
expect(headerActionRule?.[1]).toContain('padding: 0 12px;');
expect(headerActionRule?.[1]).toContain('border: 1px solid #d8dde5;');
expect(headerActionRule?.[1]).toContain('background: #fff;');
expect(styles).toMatch(
/\.launcher-page \.launcher-project-list-actions button:disabled\s*\{[^}]*cursor:\s*not-allowed;[^}]*opacity:\s*0\.55;/s,
);
});
it('shows developer panels only in dev mode', () => {
renderAppAt('/?dev');