首页原来是居中窄栏,点进工作区又变全屏,进出像换了个网站。现在把首页也做成 全宽 + 左侧栏(SideRail 抽出来给首页和账号页共用),工作区卡片改成网格, 新建工作区是网格里的虚线卡片——宽度不再跳。 账号页 /account:基本信息(头像首字母、昵称、只读手机号、加入时间)、 登录密码(设置/修改 + 确认输入)、退出登录。工作区侧栏底部的用户块点进去就是它。 e2e account.mjs 14 项全绿,其中一条断言进工作区后侧栏宽度和首页差值 < 1px。
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
Inbox,
|
||||
Logo,
|
||||
Logout,
|
||||
Settings,
|
||||
TicketIcon,
|
||||
Users,
|
||||
X,
|
||||
@@ -209,13 +210,26 @@ export function AppShell({
|
||||
|
||||
<div className="border-t border-line-200 px-3 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="grid h-7 w-7 place-items-center rounded-full bg-ink-200 text-2xs font-semibold text-ink-700">
|
||||
<Link
|
||||
to="/account"
|
||||
title="账号设置"
|
||||
className="grid h-7 w-7 shrink-0 place-items-center rounded-full bg-ink-200 text-2xs font-semibold text-ink-700 transition hover:bg-ink-300"
|
||||
>
|
||||
{initial(user?.nickname ?? "我")}
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-xs font-medium text-ink-800">{user?.nickname}</div>
|
||||
</Link>
|
||||
<Link to="/account" className="group min-w-0 flex-1">
|
||||
<div className="truncate text-xs font-medium text-ink-800 group-hover:text-primary-700">
|
||||
{user?.nickname}
|
||||
</div>
|
||||
<div className="truncate text-2xs text-ink-400">{phoneMasked}</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
to="/account"
|
||||
title="账号设置"
|
||||
className="rounded-md p-1.5 text-ink-400 transition hover:bg-ink-100 hover:text-ink-700"
|
||||
>
|
||||
<Settings size={15} />
|
||||
</Link>
|
||||
<button
|
||||
onClick={signOut}
|
||||
title="退出登录"
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { useAuth } from "../App";
|
||||
import { Logout, Logo, Settings } from "./icons";
|
||||
|
||||
/**
|
||||
* 工作区里侧栏长什么样,工作区外面(工作区列表、账号设置)就长什么样。
|
||||
*
|
||||
* 之前首页是居中窄栏、进了工作区又是全宽,宽度跳变一次、导航位置换一次,
|
||||
* 像是两个产品。这里把外壳收成一处,首页与账号页复用它。
|
||||
*/
|
||||
export function SideRail({ activeId }: { activeId?: string | null }) {
|
||||
const { user, workspaces, signOut } = useAuth();
|
||||
const masked = user?.phone ? user.phone.replace(/^(\d{3})\d{4}(\d{4})$/, "$1 **** $2") : "";
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 桌面:左侧栏 */}
|
||||
<aside className="sticky top-0 z-20 hidden h-screen w-60 shrink-0 flex-col border-r border-line-200 bg-paper-50 lg:flex xl:w-[264px]">
|
||||
<div className="px-5 pb-4 pt-5">
|
||||
<Link to="/" className="flex items-center gap-2.5">
|
||||
<Logo size={30} />
|
||||
<div className="leading-tight">
|
||||
<div className="text-sm font-semibold tracking-tight text-ink-900">达道Task</div>
|
||||
<div className="text-2xs text-ink-400">任务 · 工单 · 知识</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="mt-1 px-3">
|
||||
<div className="px-2 pb-1 text-2xs font-medium text-ink-400">我的工作区</div>
|
||||
</div>
|
||||
|
||||
<nav className="mt-1 flex flex-1 flex-col gap-0.5 overflow-y-auto px-3">
|
||||
{workspaces.map((w) => {
|
||||
const isActive = w.id === activeId;
|
||||
return (
|
||||
<Link
|
||||
key={w.id}
|
||||
to={`/w/${w.id}`}
|
||||
className={`flex items-center gap-2 rounded-lg px-2 py-1.5 text-xs transition ${
|
||||
isActive
|
||||
? "bg-paper-200/70 font-medium text-ink-900"
|
||||
: "text-ink-600 hover:bg-paper-100"
|
||||
}`}
|
||||
>
|
||||
<span className="grid h-5 w-5 shrink-0 place-items-center rounded bg-line-100 text-2xs font-semibold text-ink-600">
|
||||
{w.name.trim().slice(0, 1).toUpperCase()}
|
||||
</span>
|
||||
<span className="truncate">{w.name}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
{workspaces.length === 0 ? (
|
||||
<p className="px-2 py-1.5 text-2xs leading-relaxed text-ink-400">
|
||||
还没有工作区,右边建一个就能开始。
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div className="my-1 h-px bg-line-100" />
|
||||
<Link
|
||||
to="/account"
|
||||
className="flex items-center gap-2 rounded-lg px-2 py-1.5 text-xs text-ink-500 transition hover:bg-paper-100 hover:text-ink-800"
|
||||
>
|
||||
<Settings size={14} className="text-ink-400" />
|
||||
账号设置
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
<div className="border-t border-line-200 px-3 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="grid h-7 w-7 shrink-0 place-items-center rounded-full bg-ink-200 text-2xs font-semibold text-ink-700">
|
||||
{(user?.nickname ?? "我").trim().slice(0, 1).toUpperCase()}
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-xs font-medium text-ink-800">{user?.nickname}</div>
|
||||
<div className="truncate text-2xs text-ink-400">{masked}</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={signOut}
|
||||
title="退出登录"
|
||||
className="rounded-md p-1.5 text-ink-400 transition hover:bg-ink-100 hover:text-ink-700"
|
||||
>
|
||||
<Logout size={15} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* 移动端:顶条 */}
|
||||
<header className="sticky top-0 z-20 border-b border-line-200 bg-paper-50/95 backdrop-blur lg:hidden">
|
||||
<div className="flex items-center justify-between px-4 py-2.5">
|
||||
<Link to="/" className="flex items-center gap-2">
|
||||
<Logo size={24} />
|
||||
<span className="text-sm font-semibold text-ink-900">达道Task</span>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1">
|
||||
<Link
|
||||
to="/account"
|
||||
className="rounded-md p-1.5 text-ink-400 hover:bg-ink-100"
|
||||
aria-label="账号设置"
|
||||
>
|
||||
<Settings size={16} />
|
||||
</Link>
|
||||
<button
|
||||
onClick={signOut}
|
||||
className="rounded-md p-1.5 text-ink-400 hover:bg-ink-100"
|
||||
aria-label="退出登录"
|
||||
>
|
||||
<Logout size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -220,6 +220,20 @@ export const Logout = (p: IconProps) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Settings = (p: IconProps) => (
|
||||
<svg {...base(p)}>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M12 3.5v2M12 18.5v2M20.5 12h-2M5.5 12h-2M17.9 6.1l-1.4 1.4M7.5 16.5l-1.4 1.4M17.9 17.9l-1.4-1.4M7.5 7.5 6.1 6.1" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const User = (p: IconProps) => (
|
||||
<svg {...base(p)}>
|
||||
<circle cx="12" cy="8.5" r="3.5" />
|
||||
<path d="M5 20c0-3.3 3.1-5.5 7-5.5s7 2.2 7 5.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Flag = (p: IconProps) => (
|
||||
<svg {...base(p)}>
|
||||
<path d="M6 21V4.5M6 5h10.5l-1.7 3.6L16.5 12H6" />
|
||||
|
||||
Reference in New Issue
Block a user