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:
co-authored by
Claude Opus 4.8
parent
d41e487f08
commit
25bf3293df
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user