抽取钱包/充值入口组件, 用来在客户端里复用 #77
@@ -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 状态和后端结果渲染。
|
||||
|
||||
明确废弃:
|
||||
|
||||
+2
-2
@@ -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,
|
||||
+3
-4
@@ -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 {
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user