feat(studio): 个人中心渲染真实购买/授权记录(account 深页活体切片)
承 account facade 活体订正(单体内 /account/{purchases,licenses,publish-records} 经 market 跨模块
MarketAccountProjectionProvider 本就可用),把已确认可用的后端接进 UI:
- PersonalCenter 新增「我的购买 / 我的授权」两区,渲染 market→account 投影真实记录;
- useAccount 加 useAccountPurchases / useAccountLicenses(GET /account/{purchases,licenses})。
- live-read.spec.ts 加第 5 例(chromium, MSW off, 真连 48080):/account 渲染真实资产「活体市场资产·测试」。
验证:整套 e2e 12/12、vitest 47/47、tsc 绿。进度总账 §五C 个人中心就地更新。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4433c96d7d
commit
c7c65a97f2
@ -125,7 +125,7 @@
|
||||
- ✅ **content/market/account/ai-agent 读旅程(rendered UI 活体证,2026-06-14)**:`live-read.spec.ts` 4/4 绿(MSW off,chromium:作品列表/市场浏览/个人中心/智能体列表均渲染真实后端数据)——证 tenant-id 修复后 studio 直连活体单体跨 BC 取数、真实数据形态与页面渲染契约对齐。
|
||||
- 🔧 知识库工作台:**✅ 确认草稿(先审后入)rendered-UI 活体证(2026-06-14)**——`KnowledgeDraftPanel`(/knowledge/:workId)列待确认草稿+「确认入库」→ 真 confirm 物化 Canonical 实体(`knowledge-confirm.spec.ts` chromium MSW-off 绿,DB 证 draft confirmed+entity_id);后端补 `SummaryRespVO` 暴露 confirm 并发/源令牌。**仍 ⬜**:bindings/publish/graph UI;**遗留 bug**:confirm 对已存在同名实体抛 500(应干净 conflict,已 flag task_497da70f)。
|
||||
- 🔧 市场生产侧:**✅ 发布草稿 + 提交审核全链(创作者飞轮)rendered-UI 活体证(2026-06-15)**——`MarketPublish`(/market/publish):①「保存发布草稿」(`POST /marketplace/publish-drafts`)→`my-publish-records` 真实回读;②「提交审核」做 save→运行检查(`POST .../checks`)→检查通过提交申请(`POST /publish-requests`)全链(licenseType+权利声明为检查硬门槛)。`market-publish.spec.ts` chromium MSW-off **3/3 绿**(草稿正 / 缺标题负 / 提交全链),DB 证:`muse_market_publish_draft`=draft、`muse_market_publish_request`=submitted、`muse_market_review_event`=submitted 落库;草稿测幂等自包含、全链测唯一名可重复跑。MarketBrowse 加「我要发布资产」入口。**附带修真实潜伏后端 bug**:publish draft/check/request/review_event 的 (tenant_id,command_id) 为**部分**唯一索引(V15 `WHERE command_id IS NOT NULL`),mapper insertIgnore 却发无谓词 `ON CONFLICT (tenant_id,command_id)`→PG 无法用部分索引作仲裁器→save/check/submit 500(链路从未活体跑过故潜伏);修=部分→完整唯一索引对齐 command/purchase 约定(`V22__fix_market_publish_command_unique_index.sql` 4 表 + muse_slice_live 已应用,零 Java 改动/零重启)。**仍 ⬜**:上架(审核通过后)/申诉 UI;**遗留**:handoff/appeal/appeal_event/appeal_material/authorization_summary 等 market 表同为部分 command 索引、存同类潜伏 ON CONFLICT 不一致,待其流程活体时收口。
|
||||
- ⬜ 个人中心:21/33 端无 UI(security-events/exports/downloads/licenses/purchases/new-api-binding)。
|
||||
- 🔧 个人中心:**✅ profile/用量/权益 + 我的购买/我的授权(rendered-UI 活体证,2026-06-15)**——PersonalCenter 新增「我的购买/我的授权」渲染 market→account 投影真实记录(`useAccountPurchases`/`useAccountLicenses`→`/account/{purchases,licenses}`;`live-read.spec.ts` 第 5 例 chromium MSW-off 绿,断言真实资产「活体市场资产·测试」渲染——证后端 account 读端在单体可用即被 UI 消费,见 §五A 活体订正)。**仍 ⬜**:security-events / exports / downloads / new-api-binding 深页(其中 exports/downloads/new-api 后端为**正确 fail-closed 外部依赖**,非接线缺口,见 §五A)。
|
||||
- 收口口径:每切片"关 MSW→真连活体单体→playwright e2e 绿"才算完成(反假绿)。
|
||||
|
||||
### D. gateway 接线(B4,**判定:单体部署范围外**)
|
||||
|
||||
@ -42,4 +42,11 @@ test.describe('活体只读旅程(真实后端,MSW off)', () => {
|
||||
await page.goto('/agents');
|
||||
await expect(page.getByText('活体测试智能体').first()).toBeVisible({ timeout: 15_000 });
|
||||
});
|
||||
|
||||
test('个人中心渲染真实购买/授权记录(account 深页)', async ({ page }) => {
|
||||
await seedToken(page);
|
||||
await page.goto('/account');
|
||||
// 个人中心「我的购买 / 我的授权」渲染 market→account 投影的真实资产(后端经 market 跨模块 Facade 提供,curl 已证)。
|
||||
await expect(page.getByText('活体市场资产·测试').first()).toBeVisible({ timeout: 15_000 });
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
import { useState } from 'react';
|
||||
import { useAccountEntitlements, useAccountProfile, useAccountUsage, useUpdateAccountProfile } from '../hooks/useAccount';
|
||||
import {
|
||||
useAccountEntitlements,
|
||||
useAccountLicenses,
|
||||
useAccountProfile,
|
||||
useAccountPurchases,
|
||||
useAccountUsage,
|
||||
useUpdateAccountProfile,
|
||||
} from '../hooks/useAccount';
|
||||
import UsageStats from './UsageStats';
|
||||
|
||||
/** 个人中心首页,聚合资料、用量、权益和账户状态。 */
|
||||
@ -7,6 +14,8 @@ export default function PersonalCenter() {
|
||||
const { data: profile, isLoading: isProfileLoading } = useAccountProfile();
|
||||
const { data: entitlements } = useAccountEntitlements();
|
||||
const { data: usage } = useAccountUsage('month');
|
||||
const { data: purchases } = useAccountPurchases();
|
||||
const { data: licenses } = useAccountLicenses();
|
||||
const updateProfile = useUpdateAccountProfile();
|
||||
const [nickname, setNickname] = useState('');
|
||||
const [publicPenName, setPublicPenName] = useState('');
|
||||
@ -87,6 +96,62 @@ export default function PersonalCenter() {
|
||||
<div className="mt-6">
|
||||
<UsageStats usage={usage} entitlements={entitlements} />
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
<section className="rounded-3xl border border-slate-100 bg-white p-6 shadow-sm">
|
||||
<h2 className="text-base font-bold text-slate-800">我的购买</h2>
|
||||
{(purchases?.list?.length ?? 0) === 0 ? (
|
||||
<p className="mt-3 text-sm text-slate-400">暂无购买记录</p>
|
||||
) : (
|
||||
<ul className="mt-3 divide-y divide-slate-50">
|
||||
{purchases?.list?.map((rec) => (
|
||||
<li key={rec.recordId} className="flex items-center justify-between gap-3 py-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="rounded-md bg-slate-100 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">
|
||||
{rec.assetType}
|
||||
</span>
|
||||
<strong className="truncate text-sm font-semibold text-slate-800">{rec.assetName}</strong>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-slate-400">{rec.authorizationResultSummary || rec.status}</div>
|
||||
</div>
|
||||
<span className="shrink-0 rounded-full bg-emerald-50 px-2.5 py-1 text-[11px] font-bold text-emerald-700">
|
||||
{rec.status}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="rounded-3xl border border-slate-100 bg-white p-6 shadow-sm">
|
||||
<h2 className="text-base font-bold text-slate-800">我的授权</h2>
|
||||
{(licenses?.list?.length ?? 0) === 0 ? (
|
||||
<p className="mt-3 text-sm text-slate-400">暂无授权记录</p>
|
||||
) : (
|
||||
<ul className="mt-3 divide-y divide-slate-50">
|
||||
{licenses?.list?.map((rec) => (
|
||||
<li key={rec.licenseId} className="flex items-center justify-between gap-3 py-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="rounded-md bg-slate-100 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">
|
||||
{rec.assetType}
|
||||
</span>
|
||||
<strong className="truncate text-sm font-semibold text-slate-800">{rec.assetName}</strong>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-slate-400">
|
||||
{rec.installSummary || rec.licenseStatus}{rec.publisherName ? ` · ${rec.publisherName}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<span className="shrink-0 rounded-full bg-indigo-50 px-2.5 py-1 text-[11px] font-bold text-indigo-700">
|
||||
{rec.licenseStatus}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -46,3 +46,52 @@ export function useAccountUsage(period: UsagePeriod = 'month') {
|
||||
staleTime: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
/** 账户购买记录条目(对齐后端 AppPurchaseRecordRespVO 关键字段)。 */
|
||||
export interface AccountPurchaseRecord {
|
||||
recordId: string;
|
||||
assetType: string;
|
||||
assetName: string;
|
||||
status: string;
|
||||
amount: number | null;
|
||||
purchaseType?: string;
|
||||
authorizationResultSummary?: string;
|
||||
createdAt?: number;
|
||||
}
|
||||
|
||||
/** 账户授权记录条目(对齐后端 AppLicenseRecordRespVO 关键字段)。 */
|
||||
export interface AccountLicenseRecord {
|
||||
licenseId: string;
|
||||
assetType: string;
|
||||
assetName: string;
|
||||
licenseStatus: string;
|
||||
publisherName?: string;
|
||||
version?: string;
|
||||
installSummary?: string;
|
||||
bindingSummary?: string;
|
||||
}
|
||||
|
||||
interface AccountRecordsPage<T> {
|
||||
total: number;
|
||||
pageNo: number;
|
||||
pageSize: number;
|
||||
list: T[];
|
||||
}
|
||||
|
||||
/** 获取我的市场购买记录(market→account 投影,由 market 跨模块 Facade 提供)。 */
|
||||
export function useAccountPurchases() {
|
||||
return useQuery<AccountRecordsPage<AccountPurchaseRecord>, Error>({
|
||||
queryKey: ['accountPurchases'],
|
||||
queryFn: () => api.get<AccountRecordsPage<AccountPurchaseRecord>>('/account/purchases?pageNo=1&pageSize=50'),
|
||||
staleTime: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取我的资产授权记录。 */
|
||||
export function useAccountLicenses() {
|
||||
return useQuery<AccountRecordsPage<AccountLicenseRecord>, Error>({
|
||||
queryKey: ['accountLicenses'],
|
||||
queryFn: () => api.get<AccountRecordsPage<AccountLicenseRecord>>('/account/licenses?pageNo=1&pageSize=50'),
|
||||
staleTime: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user