feat(portal): add light/dark theme sync and OA drive navigation
Memind CI / Test, build, and release guards (push) Successful in 4m31s

Unify chat and OA drive appearance with shared memind-theme storage, default light mode, and cross-frame sync so users can switch themes from chat or the drive shell.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-09-10 20:10:06 +08:00
parent 97a3e89ff7
commit 3e6b0c262a
10 changed files with 520 additions and 128 deletions
+18
View File
@@ -18,6 +18,7 @@ import { WechatBindPrompt } from './WechatBindPrompt';
import { WechatAccountButton } from './WechatAccountButton';
import { ChatHeaderMoreMenu } from './ChatHeaderMoreMenu';
import { NotificationCenter } from './NotificationCenter';
import { ThemeToggle } from './ThemeToggle';
import { GoalRunAwaitingBanner } from './GoalRunAwaitingBanner';
import { HealthChannelBanner } from './HealthChannelBanner';
import { HealthAlertsPanel } from './HealthAlertsPanel';
@@ -173,6 +174,7 @@ export function ChatView({
onGrantedSkillsUpdate,
onLogout,
onOpenSpace,
onOpenOaDrive,
onOpenPage,
onOpenAdmin,
onOpenFeedback,
@@ -184,6 +186,7 @@ export function ChatView({
onGrantedSkillsUpdate?: (skills: string[]) => void;
onLogout?: () => void;
onOpenSpace?: (target?: { categoryCode?: MindSpaceSaveCategory; pageId?: string }) => void;
onOpenOaDrive?: () => void;
onOpenPage?: (pageId: string) => void;
onOpenAdmin?: () => void;
onOpenFeedback?: () => void;
@@ -327,6 +330,9 @@ export function ChatView({
: pendingSessionTitle;
const moreMenuItems = [
...(onOpenOaDrive
? [{ id: 'oa-drive', label: 'OA 网盘', onClick: () => onOpenOaDrive() }]
: []),
...(onOpenAdmin
? [{ id: 'admin', label: '管理', onClick: () => onOpenAdmin() }]
: []),
@@ -427,6 +433,16 @@ export function ChatView({
管理
</button>
)}
{onOpenOaDrive && (
<button
type="button"
className="ghost-btn"
data-analytics-action="open_oa_drive"
onClick={() => onOpenOaDrive()}
>
OA 网盘
</button>
)}
{onOpenSpace && (
<button
ref={spaceButtonRef}
@@ -438,6 +454,7 @@ export function ChatView({
我的空间
</button>
)}
<ThemeToggle />
{user && <WechatAccountButton returnTo={window.location.pathname} />}
{onLogout && (
<button type="button" className="ghost-btn logout-btn" data-analytics-action="logout" onClick={onLogout}>
@@ -501,6 +518,7 @@ export function ChatView({
</svg>
</button>
)}
<ThemeToggle className="ghost-btn theme-toggle-btn theme-toggle-btn-mobile" />
<ChatHeaderMoreMenu
items={moreMenuItems}
returnTo={window.location.pathname}
+32
View File
@@ -0,0 +1,32 @@
import { useEffect, useState } from 'react';
import { getStoredTheme, toggleTheme, type MemindTheme } from '../utils/theme';
export function ThemeToggle({
className = 'ghost-btn theme-toggle-btn',
}: {
className?: string;
}) {
const [theme, setTheme] = useState<MemindTheme>(() => getStoredTheme());
useEffect(() => {
const onChange = (event: Event) => {
const detail = (event as CustomEvent<{ theme: MemindTheme }>).detail;
if (detail?.theme) setTheme(detail.theme);
};
window.addEventListener('memind-theme-change', onChange);
return () => window.removeEventListener('memind-theme-change', onChange);
}, []);
return (
<button
type="button"
className={className}
data-analytics-action="toggle_theme"
aria-label={theme === 'light' ? '切换深色模式' : '切换浅色模式'}
title={theme === 'light' ? '切换深色模式' : '切换浅色模式'}
onClick={() => setTheme(toggleTheme())}
>
{theme === 'light' ? '深色' : '浅色'}
</button>
);
}