抽取钱包/充值入口组件, 用来在客户端里复用 #77

Merged
kdletters merged 16 commits from extract-wallet-entry-to-shared into master 2026-07-14 21:03:16 +08:00
15 changed files with 25 additions and 19 deletions
@@ -45,7 +45,7 @@ server-rs + Axum + SpacetimeDB
- `spacetime-client`:后端访问 SpacetimeDB 的 typed facade。
- `module-*`:纯领域模型、命令、应用规则、领域事件和领域错误。
- `platform-*`:OSS、LLM、认证、语音等外部平台能力。
- `shared-contracts` / `packages/shared`:前后端 DTO公开契约。
- `shared-contracts` / `packages/shared`:前后端 DTO公开契约和跨页面复用的无业务真相 TypeScript 代码;`packages/shared` 可承载共享 UI 组件与纯工具,但不承载领域规则、后端副作用或正式状态
- 前端:表现、交互、临时 UI 状态和后端结果渲染。
明确废弃:旧 `server-node`、Express、PostgreSQL、Go 服务端、`maincloud`、人工 `spacetime --root-dir` 口径,以及前端承接正式业务真相的路线。
@@ -47,6 +47,7 @@
- 新增 Markdown 文档时,文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`;只在任务需要时重命名历史文档,避免无关大 diff。
- 涉及中文文本时注意 UTF-8 编码和乱码排查。
- 涉及后端时遵循 DDD 分层,不把业务真相下沉到前端或临时兼容层。
- `packages/shared` 用于前后端 DTO、公开契约及跨页面复用的无业务真相 UI 组件和纯工具;不得把领域规则、后端副作用或正式状态放入其中。
- `maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求均视为历史残留,禁止新增、运行或引用;API smoke 统一使用 `npm run dev:api-server``/healthz`
- 涉及 SpacetimeDB 表结构、发布或迁移时,先看 `SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md``SPACETIMEDB_TABLE_CATALOG.md`
- 涉及生产发布、服务器配置、Jenkins Job 重建或回滚时,先看 `PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
@@ -66,7 +66,7 @@ RAG 默认不安装运行时依赖,也不把 LanceDB、Transformers.js 或本
- 领域规则沉到 `module-*`SpacetimeDB 表、reducer、procedure、事务 adapter 和 row mapper 留在 `spacetime-module`
- 后端访问 SpacetimeDB 统一经 `spacetime-client` facade。
- HTTP / SSE / BFF 和外部副作用编排留在 `api-server`;OSS、LLM、认证、语音等外部平台能力留在 `platform-*`
- 前后端 DTO公开契约留在 `shared-contracts` / `packages/shared`
- 前后端 DTO公开契约和跨页面复用的无业务真相 TypeScript 代码留在 `shared-contracts` / `packages/shared`;共享 UI 组件与纯工具可留在 `packages/shared`,但领域规则、后端副作用和正式状态不得下沉
- 契约、路由、DTO 去留和 breaking change 以当前后端架构文档、`api-server/src/app.rs``shared-contracts``packages/shared` 为准。
后端修改后按当前 DDD 文档执行验收。涉及 API smoke 时,使用 `npm run dev:api-server` 重新拉起后端并检查 `/healthz`;不要使用旧 `maincloud` 启动口径。
@@ -34,7 +34,7 @@ SpacetimeDB 版本口径:当前 Rust crate `spacetimedb`、`spacetimedb-sdk`
3. SpacetimeDB 表结构、reducer、procedure 和持久化 row shape 留在 `spacetime-module`
4. 后端访问 SpacetimeDB 必须经 `spacetime-client` facade。
5. HTTP 鉴权、BFF 聚合、SSE、外部模型编排、OSS 上传和第三方回调在 `api-server`
6. 前端共享 DTO 通过 `shared-contracts``packages/shared` 对齐,不在页面内重新发明旧接口。
6. 前端共享 DTO 通过 `shared-contracts``packages/shared` 对齐,不在页面内重新发明旧接口`packages/shared` 还可复用无业务真相的 UI 组件和纯工具,领域规则、后端副作用与正式状态仍留在各自分层
7. 微信能力按两层收口:`server-rs/crates/platform-wechat` 承载微信协议 client、订阅消息 `stable_token` / `subscribeMessage.send`、微信支付 V3 / 虚拟支付消息推送的 HTTP header、签名、验签、解密、mock 响应和协议 payload 解析;`server-rs/crates/api-server/src/wechat.rs``wechat/*` 承载 Axum handler、AppConfig 到平台配置的映射、Genarrative 用户 / 订单 / 钱包 / SSE / 错误 envelope 编排。`platform-auth` 当前仍承载微信 OAuth / 小程序登录 provider 协议,`api-server::wechat::provider` 只作为组合根 adapter,不在业务 handler 内散落 provider 构造。
验证:
@@ -81,7 +81,7 @@ server-rs + Axum + SpacetimeDB
- `spacetime-client`:后端访问 SpacetimeDB 的 typed facade。
- `module-*`:纯领域模型、命令、应用规则、领域事件和领域错误。
- `platform-*`:OSS、LLM、认证、语音等外部平台能力。
- `shared-contracts` / `packages/shared`:前后端 DTO公开契约。
- `shared-contracts` / `packages/shared`:前后端 DTO公开契约和跨页面复用的无业务真相 TypeScript 代码;`packages/shared` 可承载共享 UI 组件与纯工具,但不承载领域规则、后端副作用或正式状态
- 前端:表现、交互、临时 UI 状态和后端结果渲染。
明确废弃:
@@ -4,8 +4,8 @@ import { act, fireEvent, render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect, test, vi } from 'vitest';
import { PlatformMudPointWalletEntry } from './PlatformMudPointWalletEntry';
import { formatMudPointCount } from './platformMudPointWalletModel';
import { formatMudPointCount } from '../utils/format';
import { PlatformMudPointWalletEntry } from './PlatformMudPointWalletEntry.tsx';
const breakdown = {
totalPoints: 207,
@@ -8,10 +8,9 @@ import {
useState,
} from 'react';
import type { ProfileMudPointBalance } from '../../../packages/shared/src/contracts/runtime';
import { formatMudPointCount } from './platformMudPointWalletModel';
const MUD_POINT_ICON_SRC = '/creation-home/topbar-wallet.png';
import type { ProfileMudPointBalance } from '../contracts/runtime';
import MUD_POINT_ICON_SRC from '../icons/topbar-wallet.png';
import { formatMudPointCount } from '../utils/format';
export type PlatformMudPointWalletEntryProps = {
balance: number | null;
Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

@@ -108,9 +108,6 @@ describe('ImageCanvasTopbarView', () => {
});
expect(walletChip.textContent).toBe('泥点 1.2万');
expect(walletChip.querySelector('img')?.getAttribute('src')).toBe(
'/creation-home/topbar-wallet.png',
);
await user.hover(walletChip);
const details = screen.getByRole('dialog', { name: '泥点账户详情' });
@@ -7,8 +7,9 @@ import {
X,
} from 'lucide-react';
import { PlatformMudPointWalletEntry } from '@/packages/shared/src/components/PlatformMudPointWalletEntry.tsx';
import type { ProfileMudPointBalance } from '../../../packages/shared/src/contracts/runtime';
import { PlatformMudPointWalletEntry } from '../common/PlatformMudPointWalletEntry';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformTextField } from '../common/PlatformTextField';
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
@@ -1,6 +1,8 @@
import QRCode from 'qrcode';
import { useCallback, useEffect, useState } from 'react';
import { formatMudPointCount } from '@/packages/shared/src/utils/format.ts';
import type {
ProfileRechargeCenterResponse,
ProfileRechargeProduct,
@@ -8,7 +10,6 @@ import type {
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformAsyncStatePanel } from '../common/PlatformAsyncStatePanel';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { formatMudPointCount } from '../common/platformMudPointWalletModel';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import { PlatformProfileSkeletonList } from '../common/PlatformProfileSkeletonList';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
@@ -4338,9 +4338,6 @@ test('logged in create tab shows real wallet balance beside the brand', () => {
expect(walletEntry?.className).toContain(
'platform-mobile-create-wallet-chip',
);
expect(walletButton.querySelector('img')?.getAttribute('src')).toBe(
'/creation-home/topbar-wallet.png',
);
expect(
within(topbar as HTMLElement).getByRole('button', { name: '充值' }),
).toBeTruthy();
@@ -40,6 +40,8 @@ import {
useState,
} from 'react';
import { PlatformMudPointWalletEntry } from '@/packages/shared/src/components/PlatformMudPointWalletEntry.tsx';
import profileClockImage from '../../../media/profile/_Image (1).png';
import profileGamepadImage from '../../../media/profile/_Image (2).png';
import profileStillLifeImage from '../../../media/profile/_Image (3).png';
@@ -89,7 +91,6 @@ import { PlatformAsyncStatePanel } from '../common/PlatformAsyncStatePanel';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformFilterToolbar } from '../common/PlatformFilterToolbar';
import { PlatformIconButton } from '../common/PlatformIconButton';
import { PlatformMudPointWalletEntry } from '../common/PlatformMudPointWalletEntry';
import { PlatformNavigableListItem } from '../common/PlatformNavigableListItem';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import {
+9
View File
@@ -1,6 +1,14 @@
import path from 'node:path';
import {defineConfig} from 'vitest/config';
export default defineConfig({
resolve: {
alias: {
// Keep shared components' application-root imports resolvable in tests.
'@': path.resolve(__dirname, '.'),
},
},
test: {
environment: 'node',
globals: true,
@@ -12,6 +20,7 @@ export default defineConfig({
'miniprogram/**/*.test.js',
'scripts/**/*.test.ts',
'packages/shared/src/**/*.test.ts',
'packages/shared/src/**/*.test.tsx',
],
},
});