feat(core/frontend): wire P0 team mgmt / recharge / notifications

- App.tsx: load notifications, expose team/recharge/notification handlers, real unread bell count (was hardcoded 12)
- team.tsx: create/edit/reset-password/remove member + team recharge modals
- account.tsx: recharge via wechat/alipay buttons + custom amount
- messages.tsx: real notification inbox, mark-read on select, mark-all-read
- verified: tsc --noEmit clean; e2e create->update->reset->recharge->mark-read->delete all green

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-05 12:48:51 +08:00
co-authored by Claude Opus 4.8
parent d41e487f08
commit 25bf3293df
6 changed files with 407 additions and 38 deletions
+61 -6
View File
@@ -7,6 +7,7 @@ import type {
BillingSummary,
Ledger,
ModelConfig,
Notification,
Product,
Project,
Team,
@@ -75,6 +76,8 @@ export function App() {
const [aiTasks, setAiTasks] = useState<AITask[]>([]);
const [billing, setBilling] = useState<BillingSummary | null>(null);
const [ledgers, setLedgers] = useState<Ledger[]>([]);
const [notifications, setNotifications] = useState<Notification[]>([]);
const [unreadCount, setUnreadCount] = useState(0);
const [projectDetail, setProjectDetail] = useState<Project | null>(null);
const [activeProductId, setActiveProductId] = useState(route.productId || "");
@@ -92,7 +95,7 @@ export function App() {
);
const loadData = useCallback(async () => {
const [productData, projectData, assetData, billingData, ledgerData, memberData, modelData, taskData] =
const [productData, projectData, assetData, billingData, ledgerData, memberData, modelData, taskData, notificationData] =
await Promise.all([
api.products(),
api.projects(),
@@ -101,7 +104,8 @@ export function App() {
api.ledgers().catch(() => []),
api.teamMembers().catch(() => []),
api.modelConfigs().catch(() => null),
api.aiTasks().catch(() => null)
api.aiTasks().catch(() => null),
api.listNotifications().catch(() => null)
]);
setProducts(productData.results);
setProjects(projectData.results);
@@ -111,10 +115,22 @@ export function App() {
setAiTasks(taskData?.results || []);
if (billingData) setBilling(billingData);
setLedgers(ledgerData);
if (notificationData) {
setNotifications(notificationData.results);
setUnreadCount(notificationData.unread_count);
}
setActiveProjectId((current) => current || projectData.results[0]?.id || "");
setActiveProductId((current) => current || productData.results[0]?.id || "");
}, []);
const reloadNotifications = useCallback(async () => {
const data = await api.listNotifications().catch(() => null);
if (data) {
setNotifications(data.results);
setUnreadCount(data.unread_count);
}
}, []);
// Boot: validate token, hydrate identity + data.
useEffect(() => {
if (!getToken()) {
@@ -210,6 +226,16 @@ export function App() {
}
}
async function markNotificationRead(id: string) {
await api.markNotificationRead(id).catch(() => undefined);
await reloadNotifications();
}
async function markAllNotificationsRead() {
await api.markAllNotificationsRead().catch(() => undefined);
await reloadNotifications();
}
function onAuthed(payload: { token: string; user: User; team: Team }) {
setToken(payload.token);
setUser(payload.user);
@@ -341,11 +367,40 @@ export function App() {
case "library":
return <LibraryPage assets={assets} onUpload={(formData) => action(() => api.uploadAsset(formData), "资产已上传")} />;
case "account":
return <AccountPage billing={billing} ledgers={ledgers} projects={projects} teamMembers={teamMembers} />;
return (
<AccountPage
billing={billing}
ledgers={ledgers}
projects={projects}
teamMembers={teamMembers}
onRecharge={(amount, bonus) => action(() => api.recharge({ amount, bonus }), "充值成功")}
/>
);
case "team":
return <TeamPage team={currentTeam} user={currentUser} members={teamMembers} billing={billing} navigate={navigate} />;
return (
<TeamPage
team={currentTeam}
user={currentUser}
members={teamMembers}
billing={billing}
navigate={navigate}
onCreateMember={(payload) => action(() => api.createTeamMember(payload), "成员账户已创建")}
onUpdateMember={(id, payload) => action(() => api.updateTeamMember(id, payload), "成员已更新")}
onRemoveMember={(id) => action(() => api.removeTeamMember(id), "成员已移除")}
onResetPassword={(id, password) => action(() => api.resetMemberPassword(id, password), "密码已重置")}
onRecharge={(amount, bonus) => action(() => api.recharge({ amount, bonus }), "充值成功")}
/>
);
case "messages":
return <MessagesPage navigate={navigate} />;
return (
<MessagesPage
notifications={notifications}
unreadCount={unreadCount}
onMarkRead={markNotificationRead}
onMarkAllRead={markAllNotificationsRead}
navigate={navigate}
/>
);
case "assetFactory":
return <AssetFactoryPage navigate={navigate} aiTasks={aiTasks} />;
case "imageOptimize":
@@ -444,7 +499,7 @@ export function App() {
</span>
<button className="icon-btn" type="button" onClick={() => navigate("messages")} title="消息中心">
<IconKitSvg name="bell" />
<span className="count-noti">12</span>
{unreadCount > 0 && <span className="count-noti">{unreadCount}</span>}
</button>
<div className="topbar-avatar" onDoubleClick={logout} title="账户(双击退出)">
<span>{avatarChar}</span>