R109/A1,A5 优化移动端底部导航和常用页面

这个提交包含在:
2026-05-08 01:04:34 +08:00
父节点 ee4aebd237
当前提交 06c9ab1be7
共修改 3 个文件,包含 34 行新增和 34 行删除
@@ -33,8 +33,8 @@ export function MobileBottomNav() {
const pendingCount = pendingCountQuery.data ?? 0; const pendingCount = pendingCountQuery.data ?? 0;
return ( return (
<nav className="z-20 shrink-0 border-t border-zinc-200 bg-white/95 px-2 pb-[max(0.5rem,env(safe-area-inset-bottom))] pt-2 shadow-[0_-8px_24px_rgba(15,23,42,0.08)] backdrop-blur md:hidden"> <nav className="z-20 shrink-0 border-t border-zinc-200 bg-white/95 px-2 pb-[max(0.25rem,env(safe-area-inset-bottom))] pt-1.5 shadow-[0_-6px_18px_rgba(15,23,42,0.06)] backdrop-blur md:hidden">
<div className="grid grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-1">
{MOBILE_NAV_ITEMS.map((item) => { {MOBILE_NAV_ITEMS.map((item) => {
const disabled = item.anyOf ? !hasAnyPermission(permissions, [...item.anyOf]) : false; const disabled = item.anyOf ? !hasAnyPermission(permissions, [...item.anyOf]) : false;
const badge = item.key === "approvals" && pendingCount > 0 ? resolveBadgeLabel(pendingCount) : null; const badge = item.key === "approvals" && pendingCount > 0 ? resolveBadgeLabel(pendingCount) : null;
@@ -45,7 +45,7 @@ export function MobileBottomNav() {
key={item.key} key={item.key}
type="button" type="button"
data-testid={`mobile-nav-${item.key}`} data-testid={`mobile-nav-${item.key}`}
className="touch-target flex flex-col items-center justify-center gap-1 rounded-2xl px-2 py-2 text-xs font-medium text-zinc-400" className="touch-target flex h-12 min-h-0 flex-col items-center justify-center gap-0.5 rounded-xl px-1 py-1 text-[11px] font-medium leading-none text-zinc-400"
disabled disabled
aria-disabled="true" aria-disabled="true"
title={`${item.label}不可用`} title={`${item.label}不可用`}
@@ -59,14 +59,14 @@ export function MobileBottomNav() {
return ( return (
<NavLink <NavLink
key={item.key} key={item.key}
end end={item.to === "/mobile"}
to={item.to} to={item.to}
data-testid={`mobile-nav-${item.key}`} data-testid={`mobile-nav-${item.key}`}
className={({ isActive }) => className={({ isActive }) =>
cn( cn(
"touch-target flex flex-col items-center justify-center gap-1 rounded-2xl px-2 py-2 text-xs font-medium transition-colors", "touch-target flex h-12 min-h-0 flex-col items-center justify-center gap-0.5 rounded-xl px-1 py-1 text-[11px] font-medium leading-none transition-colors",
isActive isActive
? "bg-primary-50 text-primary ring-1 ring-primary-100" ? "bg-primary text-white shadow-sm"
: "text-zinc-500 hover:bg-zinc-50 hover:text-zinc-900", : "text-zinc-500 hover:bg-zinc-50 hover:text-zinc-900",
) )
} }
@@ -74,12 +74,12 @@ export function MobileBottomNav() {
{({ isActive }) => ( {({ isActive }) => (
<> <>
<span className="relative"> <span className="relative">
<span className="material-symbols-outlined text-[20px]">{item.icon}</span> <span className="material-symbols-outlined text-[19px] leading-none">{item.icon}</span>
{badge ? ( {badge ? (
<span <span
className={cn( className={cn(
"absolute -right-3 -top-2 min-w-5 rounded-full px-1.5 py-0.5 text-[10px] font-semibold leading-none", "absolute -right-3 -top-2 min-w-5 rounded-full px-1.5 py-0.5 text-[10px] font-semibold leading-none",
isActive ? "bg-primary text-white" : "bg-red-500 text-white", isActive ? "bg-white text-primary" : "bg-red-500 text-white",
)} )}
> >
{badge} {badge}
+7 -7
查看文件
@@ -248,10 +248,10 @@ export function MobileLeavePage() {
}; };
return ( return (
<PageScaffold className="space-y-4"> <PageScaffold className="space-y-3 pb-4">
<PageHeader title="请假" /> <PageHeader title="请假" />
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-leave-summary"> <section className="rounded-lg border border-zinc-200 bg-white p-3 shadow-sm" data-testid="mobile-leave-summary">
<div className="grid grid-cols-3 gap-3 text-sm"> <div className="grid grid-cols-3 gap-3 text-sm">
<div> <div>
<div className="text-xs text-zinc-500">可用</div> <div className="text-xs text-zinc-500">可用</div>
@@ -268,7 +268,7 @@ export function MobileLeavePage() {
</div> </div>
</section> </section>
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-leave-form"> <section className="rounded-lg border border-zinc-200 bg-white p-3 shadow-sm" data-testid="mobile-leave-form">
<div className="mb-4 flex items-center justify-between gap-3"> <div className="mb-4 flex items-center justify-between gap-3">
<h3 className="text-base font-semibold text-zinc-900">{editingItem ? "编辑请假" : "新建请假"}</h3> <h3 className="text-base font-semibold text-zinc-900">{editingItem ? "编辑请假" : "新建请假"}</h3>
{editingItem ? ( {editingItem ? (
@@ -278,7 +278,7 @@ export function MobileLeavePage() {
) : null} ) : null}
</div> </div>
<div className="space-y-4"> <div className="space-y-3">
<label className="block space-y-2 text-sm text-zinc-700"> <label className="block space-y-2 text-sm text-zinc-700">
<span>假期类型</span> <span>假期类型</span>
<select className="select-std" value={leaveTypeId} onChange={(event) => setLeaveTypeId(event.target.value)}> <select className="select-std" value={leaveTypeId} onChange={(event) => setLeaveTypeId(event.target.value)}>
@@ -353,7 +353,7 @@ export function MobileLeavePage() {
</label> </label>
<Button <Button
className="w-full" className="min-h-12 w-full"
type="button" type="button"
disabled={!canCreateLeave || isSaving || !leaveTypeId || !startDate || !endDate} disabled={!canCreateLeave || isSaving || !leaveTypeId || !startDate || !endDate}
onClick={() => { onClick={() => {
@@ -370,7 +370,7 @@ export function MobileLeavePage() {
</div> </div>
</section> </section>
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-leave-balances"> <section className="rounded-lg border border-zinc-200 bg-white p-3 shadow-sm" data-testid="mobile-leave-balances">
<h3 className="text-base font-semibold text-zinc-900">额度</h3> <h3 className="text-base font-semibold text-zinc-900">额度</h3>
<div className="mt-3 space-y-2"> <div className="mt-3 space-y-2">
{balancesQuery.isLoading ? <div className="text-sm text-zinc-500">加载中...</div> : null} {balancesQuery.isLoading ? <div className="text-sm text-zinc-500">加载中...</div> : null}
@@ -400,7 +400,7 @@ export function MobileLeavePage() {
</div> </div>
</section> </section>
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-leave-records"> <section className="rounded-lg border border-zinc-200 bg-white p-3 shadow-sm" data-testid="mobile-leave-records">
<h3 className="text-base font-semibold text-zinc-900">请假记录</h3> <h3 className="text-base font-semibold text-zinc-900">请假记录</h3>
<Tabs value={statusFilter} onValueChange={(value) => setStatusFilter(value as LeaveStatusFilter)}> <Tabs value={statusFilter} onValueChange={(value) => setStatusFilter(value as LeaveStatusFilter)}>
<TabsList className="mt-3 grid h-auto w-full grid-cols-4"> <TabsList className="mt-3 grid h-auto w-full grid-cols-4">
+19 -19
查看文件
@@ -31,20 +31,20 @@ function MobileActionLink({
}: { }: {
to: string; to: string;
label: string; label: string;
description: string; description?: string;
icon: string; icon: string;
}) { }) {
return ( return (
<Link <Link
to={to} to={to}
className="flex min-h-16 items-center gap-3 rounded-2xl border border-zinc-200 bg-white px-4 py-3 text-left shadow-sm transition-colors hover:bg-zinc-50" className="flex min-h-12 items-center gap-3 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-left shadow-sm transition-colors hover:bg-zinc-50"
> >
<span className="material-symbols-outlined flex size-10 shrink-0 items-center justify-center rounded-xl bg-zinc-100 text-[20px] text-zinc-700"> <span className="material-symbols-outlined flex size-8 shrink-0 items-center justify-center rounded-lg bg-zinc-100 text-[18px] text-zinc-700">
{icon} {icon}
</span> </span>
<span className="min-w-0"> <span className="min-w-0">
<span className="block text-sm font-semibold text-zinc-900">{label}</span> <span className="block text-sm font-semibold text-zinc-900">{label}</span>
<span className="mt-0.5 block text-xs leading-5 text-zinc-500">{description}</span> {description ? <span className="mt-0.5 block text-xs leading-5 text-zinc-500">{description}</span> : null}
</span> </span>
</Link> </Link>
); );
@@ -78,12 +78,12 @@ export function MobileMePage() {
const departmentName = profile?.department?.name ?? (me?.departmentId ? "已关联组织" : "待管理员维护"); const departmentName = profile?.department?.name ?? (me?.departmentId ? "已关联组织" : "待管理员维护");
return ( return (
<PageScaffold className="space-y-4"> <PageScaffold className="space-y-3 pb-4">
<PageHeader title="我的" /> <PageHeader title="我的" />
<SectionPanel title="个人信息"> <SectionPanel title="个人信息">
<div className="flex items-center gap-4"> <div className="flex items-center gap-3">
<EmployeeAvatar name={me?.name ?? "员工"} src={profile?.avatarUrl} size="lg" /> <EmployeeAvatar name={me?.name ?? "员工"} src={profile?.avatarUrl} size="md" />
<div className="min-w-0 space-y-1"> <div className="min-w-0 space-y-1">
<div className="text-lg font-semibold text-zinc-900">{me?.name ?? "-"}</div> <div className="text-lg font-semibold text-zinc-900">{me?.name ?? "-"}</div>
<div className="text-sm text-zinc-500">{departmentName}</div> <div className="text-sm text-zinc-500">{departmentName}</div>
@@ -100,7 +100,7 @@ export function MobileMePage() {
</SectionPanel> </SectionPanel>
<SectionPanel title="账号与绑定"> <SectionPanel title="账号与绑定">
<div className="grid gap-3"> <div className="grid gap-2">
<MobileActionLink to="/settings/profile" label="个人设置" description="企业微信、账号与通知" icon="manage_accounts" /> <MobileActionLink to="/settings/profile" label="个人设置" description="企业微信、账号与通知" icon="manage_accounts" />
{me?.id ? ( {me?.id ? (
<MobileActionLink to={`/people/${me.id}`} label="员工档案" description="办公地点和紧急联系人" icon="badge" /> <MobileActionLink to={`/people/${me.id}`} label="员工档案" description="办公地点和紧急联系人" icon="badge" />
@@ -109,22 +109,22 @@ export function MobileMePage() {
</SectionPanel> </SectionPanel>
<SectionPanel title="更多入口"> <SectionPanel title="更多入口">
<div className="grid gap-3" data-testid="mobile-more-links"> <div className="grid grid-cols-2 gap-2" data-testid="mobile-more-links">
<MobileActionLink to="/notifications" label="通知中心" description="查看全部站内通知" icon="notifications" /> <MobileActionLink to="/notifications" label="通知中心" icon="notifications" />
<MobileActionLink to="/mobile/leave" label="请假" description="申请和记录" icon="event_available" /> <MobileActionLink to="/mobile/leave" label="请假" icon="event_available" />
<MobileActionLink to="/mobile/reimbursements" label="报销" description="草稿和进度" icon="request_quote" /> <MobileActionLink to="/mobile/reimbursements" label="报销" icon="request_quote" />
{canAccessApprovals ? <MobileActionLink to="/mobile/approvals" label="审批" description="待办处理" icon="fact_check" /> : null} {canAccessApprovals ? <MobileActionLink to="/mobile/approvals" label="审批" icon="fact_check" /> : null}
{canAccessAssets ? <MobileActionLink to="/assets" label="资产" description="台账和盘点" icon="inventory_2" /> : null} {canAccessAssets ? <MobileActionLink to="/assets" label="资产" icon="inventory_2" /> : null}
{canAccessProjects ? <MobileActionLink to="/projects" label="项目" description="项目和里程碑" icon="workspaces" /> : null} {canAccessProjects ? <MobileActionLink to="/projects" label="项目" icon="workspaces" /> : null}
</div> </div>
</SectionPanel> </SectionPanel>
{canAccessAdmin ? ( {canAccessAdmin ? (
<SectionPanel title="管理入口"> <SectionPanel title="管理入口">
<div className="grid gap-3"> <div className="grid grid-cols-2 gap-2">
<MobileActionLink to="/people" label="人员管理" description="用户、角色和员工资料" icon="group" /> <MobileActionLink to="/people" label="人员管理" icon="group" />
<MobileActionLink to="/organization" label="组织架构" description="部门和汇报关系" icon="account_tree" /> <MobileActionLink to="/organization" label="组织架构" icon="account_tree" />
<MobileActionLink to="/system/settings" label="系统设置" description="工作区和系统参数" icon="settings" /> <MobileActionLink to="/system/settings" label="系统设置" icon="settings" />
</div> </div>
</SectionPanel> </SectionPanel>
) : null} ) : null}