Files
DadaoTask/web/src/App.tsx
T
Steven d34a6ac69e
CI / backend (push) Canceled after 0s
feat(knowledge): 阅读改成独立整页,抽屉只用来编辑
知识原来一律在抽屉里打开,正文贴着屏幕右半边,长文读十几屏眼睛一直在右边。
阅读是「坐下来读一会儿」,编辑是「改两句就走」,两件事不该共用一个容器。

- 新增阅读页 /w/:wsId/knowledge/:itemId:正文列限到 40rem 居中,28px 标题,
  更新/发布/切片行,标签与来源,动作行放编辑与发布/归档/恢复
- 列表页与搜索结果改成跳阅读页,不再弹抽屉
- KnowledgeDrawer 加 initialEditing:进来就是编辑器,保存或取消直接关
- 文案常量搬到 lib/knowledge.ts,列表页与阅读页共用一份

顺带修三个 e2e 脚本:新建后进阅读页没有「关闭」按钮可点、侧栏色点按钮让树
顺序断言多出空串、翻页脚本还在用已关闭的短信登录(改密码登录)。
2026-11-03 12:12:51 +08:00

114 lines
4.0 KiB
TypeScript

import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
import { Navigate, Route, Routes, useNavigate } from "react-router-dom";
import { ToastProvider } from "./components/ui";
import { UpdatePrompt } from "./components/UpdatePrompt";
import { api, authExpired, session } from "./lib/client";
import type { User, Workspace } from "./lib/types";
import { LoginPage } from "./pages/Login";
import { RegisterPage } from "./pages/Register";
import { HomePage } from "./pages/Home";
import { WorkspacePage } from "./pages/Workspace";
import { TasksPage } from "./pages/Tasks";
import { BoardPage } from "./pages/Board";
import { TicketsPage } from "./pages/Tickets";
import { KnowledgePage } from "./pages/Knowledge";
import { KnowledgeDocPage } from "./pages/KnowledgeDoc";
import { NotificationsPage } from "./pages/Notifications";
import { JoinPage } from "./pages/Join";
import { AccountPage } from "./pages/Account";
import { SettingsPage } from "./pages/Settings";
interface AuthState {
user: User | null;
workspaces: Workspace[];
loading: boolean;
reload: () => Promise<void>;
signOut: () => void;
}
const AuthContext = createContext<AuthState | null>(null);
export function useAuth(): AuthState {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth 必须在 AuthProvider 内使用");
return ctx;
}
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
const reload = useCallback(async () => {
if (!session.access) {
setUser(null);
setWorkspaces([]);
setLoading(false);
return;
}
try {
const me = await api.get<{ user: User; workspaces: Workspace[] }>("/auth/me");
setUser(me.user);
setWorkspaces(me.workspaces);
} catch {
setUser(null);
setWorkspaces([]);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
void reload();
const onExpired = () => {
setUser(null);
setWorkspaces([]);
navigate("/login", { replace: true });
};
authExpired.addEventListener("expired", onExpired);
return () => authExpired.removeEventListener("expired", onExpired);
}, [reload, navigate]);
const signOut = useCallback(() => {
session.clear();
setUser(null);
setWorkspaces([]);
navigate("/login", { replace: true });
}, [navigate]);
const value = useMemo<AuthState>(
() => ({ user, workspaces, loading, reload, signOut }),
[user, workspaces, loading, reload, signOut],
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function App() {
return (
<ToastProvider>
<AuthProvider>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/" element={<HomePage />} />
<Route path="/account" element={<AccountPage />} />
<Route path="/settings" element={<SettingsPage />} />
<Route path="/w/:wsId" element={<TasksPage />} />
<Route path="/w/:wsId/board" element={<BoardPage />} />
<Route path="/w/:wsId/tickets" element={<TicketsPage />} />
<Route path="/w/:wsId/knowledge" element={<KnowledgePage />} />
{/* 阅读是整页:抽屉只用来编辑,长文在抽屉里读会一直偏在屏幕右侧 */}
<Route path="/w/:wsId/knowledge/:itemId" element={<KnowledgeDocPage />} />
<Route path="/w/:wsId/notifications" element={<NotificationsPage />} />
<Route path="/w/:wsId/members" element={<WorkspacePage />} />
<Route path="/join/:wsId/:code" element={<JoinPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
<UpdatePrompt />
</AuthProvider>
</ToastProvider>
);
}