重构个人设置与管理边界(05-30-profile-settings-boundary)
这个提交包含在:
@@ -53,14 +53,6 @@ function MobileActionLink({
|
||||
|
||||
export function MobileMePage() {
|
||||
const { me, permissions, refreshMe, logout } = useAuth();
|
||||
const canAccessAdmin = hasAnyPermission(permissions, [
|
||||
"*",
|
||||
"system.settings",
|
||||
"system.users",
|
||||
"masterdata.manage",
|
||||
"notification.channel.manage",
|
||||
"integration.manage",
|
||||
]);
|
||||
const canAccessAssets = hasAnyPermission(permissions, ["asset.view_own", "asset.view_all", "asset.manage", "asset.transfer", "asset.inventory"]);
|
||||
const canAccessProjects = hasAnyPermission(permissions, ["project.view_own", "project.view_all", "project.manage"]);
|
||||
const canAccessPurchase = hasAnyPermission(permissions, [...MOBILE_PURCHASE_PERMISSIONS]);
|
||||
@@ -115,17 +107,14 @@ export function MobileMePage() {
|
||||
|
||||
<SectionPanel title="账号与绑定">
|
||||
<div className="grid gap-2">
|
||||
<MobileActionLink to="/mobile/profile" label="个人设置" description="企业微信、账号与通知" icon="manage_accounts" />
|
||||
{me?.id ? (
|
||||
<MobileActionLink to="/mobile/profile" label="账号资料" description="员工号和联系方式" icon="badge" />
|
||||
) : null}
|
||||
<MobileActionLink to="/mobile/profile" label="个人设置" description="企业微信、账号安全与通知" icon="manage_accounts" />
|
||||
</div>
|
||||
</SectionPanel>
|
||||
|
||||
<SectionPanel title="更多入口" description="常用移动入口集中在这里。">
|
||||
<SectionPanel title="移动入口" description="只保留手机上常用的个人办事入口。">
|
||||
<div className="mobile-entry-grid mobile-me-entry-grid" data-testid="mobile-more-links">
|
||||
<MobileActionLink to="/mobile/notifications" label="通知中心" icon="notifications" />
|
||||
<MobileActionLink to="/mobile/leave" label="请假" icon="event_available" />
|
||||
<MobileActionLink to="/mobile/leave" label="请假记录" icon="event_available" />
|
||||
<MobileActionLink to="/mobile/reimbursements" label="报销" icon="request_quote" />
|
||||
{canAccessPurchase ? <MobileActionLink to="/mobile/purchase-requests" label="采购" icon="shopping_cart" /> : null}
|
||||
<MobileActionLink to="/mobile/approvals" label={canAccessApprovals ? "待办" : "进度"} icon="fact_check" />
|
||||
@@ -135,16 +124,6 @@ export function MobileMePage() {
|
||||
</div>
|
||||
</SectionPanel>
|
||||
|
||||
{canAccessAdmin ? (
|
||||
<SectionPanel title="管理入口">
|
||||
<div className="mobile-entry-grid mobile-me-entry-grid">
|
||||
<MobileActionLink to="/mobile/me" label="人员管理" icon="group" />
|
||||
<MobileActionLink to="/mobile/me" label="组织架构" icon="account_tree" />
|
||||
<MobileActionLink to="/mobile/me" label="系统设置" icon="settings" />
|
||||
</div>
|
||||
</SectionPanel>
|
||||
) : null}
|
||||
|
||||
<SectionPanel title="企业微信消息提醒">
|
||||
<label className="flex items-center gap-3 text-sm text-zinc-800">
|
||||
<input
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { Link, useSearchParams } from "react-router-dom";
|
||||
import { fetchWecomConfig, startWecomBind, unbindWecom, updateNotificationPreferences } from "../api/auth";
|
||||
import { isApiError } from "../api/types";
|
||||
import { useAuth } from "../auth/AuthContext";
|
||||
@@ -30,6 +30,25 @@ function statusTone(status?: string) {
|
||||
return "badge-neutral";
|
||||
}
|
||||
|
||||
function accountStatusLabel(status?: string) {
|
||||
if (status === "active") return "账号正常";
|
||||
if (status === "inactive") return "账号停用";
|
||||
return "账号待确认";
|
||||
}
|
||||
|
||||
function roleLabel(roleCode: string) {
|
||||
const labels: Record<string, string> = {
|
||||
admin: "管理员",
|
||||
ceo: "负责人",
|
||||
employee: "员工",
|
||||
finance: "财务",
|
||||
finance_mgr: "财务负责人",
|
||||
hr: "人事",
|
||||
manager: "审批人",
|
||||
};
|
||||
return labels[roleCode] ?? "其他角色";
|
||||
}
|
||||
|
||||
function resolveProfileError(error: unknown) {
|
||||
if (isApiError(error)) {
|
||||
return `${error.code}: ${error.message}`;
|
||||
@@ -107,13 +126,14 @@ export function MobileProfilePage() {
|
||||
const isBound = Boolean(binding.externalUserId);
|
||||
const wecomStatus = resolveWecomEntryStatus(wecomConfigQuery.data);
|
||||
const bindNotice = resolveWecomBindNotice(searchParams.get("wecomBind"));
|
||||
const roleLabels = (me?.roleCodes ?? []).map(roleLabel);
|
||||
|
||||
return (
|
||||
<PageScaffold className="pb-4">
|
||||
<div className="space-y-3" data-testid="mobile-profile-page">
|
||||
<PageHeader
|
||||
title="个人设置"
|
||||
meta={<span className={statusTone(me?.status)}>{me?.status === "active" ? "账号正常" : "账号待确认"}</span>}
|
||||
meta={<span className={statusTone(me?.status)}>{accountStatusLabel(me?.status)}</span>}
|
||||
/>
|
||||
|
||||
{bindNotice ? (
|
||||
@@ -144,6 +164,18 @@ export function MobileProfilePage() {
|
||||
</dl>
|
||||
</SectionPanel>
|
||||
|
||||
<SectionPanel title="密码与安全">
|
||||
<div className="mobile-list-card flex min-h-12 items-center justify-between gap-3 text-sm">
|
||||
<div>
|
||||
<div className="font-semibold text-zinc-900">账号密码</div>
|
||||
<div className="mt-0.5 text-xs leading-5 text-zinc-500">修改密码需要输入当前密码。</div>
|
||||
</div>
|
||||
<Button asChild type="button" variant="secondary">
|
||||
<Link to="/change-password">修改密码</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</SectionPanel>
|
||||
|
||||
<SectionPanel title="企业微信消息提醒">
|
||||
<label className="mobile-list-card flex min-h-12 items-center justify-between gap-3 text-sm text-zinc-800">
|
||||
<span>接收企业微信消息提醒</span>
|
||||
@@ -206,16 +238,32 @@ export function MobileProfilePage() {
|
||||
</div>
|
||||
</SectionPanel>
|
||||
|
||||
<SectionPanel title="账号状态">
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<span className={statusTone(me?.status)}>{me?.status === "active" ? "active" : me?.status ?? "unknown"}</span>
|
||||
{(me?.roleCodes ?? []).map((role) => (
|
||||
<span className="badge-neutral" key={role}>{role}</span>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-zinc-500">角色和权限由管理员统一维护。</p>
|
||||
</div>
|
||||
<SectionPanel title="组织与角色摘要">
|
||||
<details className="mobile-list-card text-sm">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-3">
|
||||
<span className="font-semibold text-zinc-900">查看组织和角色</span>
|
||||
<span className="text-xs text-zinc-500">展开</span>
|
||||
</summary>
|
||||
<dl className="mt-3 grid gap-2 text-sm">
|
||||
<div className="mobile-subcard">
|
||||
<dt className="text-xs text-zinc-500">账号状态</dt>
|
||||
<dd className="mt-1 font-medium text-zinc-900">{accountStatusLabel(me?.status)}</dd>
|
||||
</div>
|
||||
<div className="mobile-subcard">
|
||||
<dt className="text-xs text-zinc-500">组织归属</dt>
|
||||
<dd className="mt-1 font-medium text-zinc-900">{me?.departmentId ? "已关联组织" : "待管理员维护"}</dd>
|
||||
</div>
|
||||
<div className="mobile-subcard">
|
||||
<dt className="text-xs text-zinc-500">直属汇报关系</dt>
|
||||
<dd className="mt-1 font-medium text-zinc-900">{me?.directManagerId ? "已维护" : "待管理员维护"}</dd>
|
||||
</div>
|
||||
<div className="mobile-subcard">
|
||||
<dt className="text-xs text-zinc-500">角色</dt>
|
||||
<dd className="mt-1 font-medium text-zinc-900">{roleLabels.length ? roleLabels.join("、") : "员工"}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<p className="mt-3 text-xs leading-5 text-zinc-500">组织、角色和权限由管理员在管理入口统一维护。</p>
|
||||
</details>
|
||||
</SectionPanel>
|
||||
</div>
|
||||
</PageScaffold>
|
||||
|
||||
+16
-11
@@ -53,7 +53,7 @@ function roleLabel(roleCode: string) {
|
||||
hr: "人事",
|
||||
manager: "审批人",
|
||||
};
|
||||
return labels[roleCode] ?? roleCode;
|
||||
return labels[roleCode] ?? "其他角色";
|
||||
}
|
||||
|
||||
function resolveProfileError(error: unknown) {
|
||||
@@ -109,21 +109,14 @@ export function ProfilePage() {
|
||||
<PageHeader
|
||||
title="个人设置"
|
||||
eyebrow="账号与通知"
|
||||
subtitle="查看自己的账号信息,设置消息提醒和企业微信入口。员工档案由对应页面维护。"
|
||||
subtitle="只处理个人资料、安全、通知偏好和企业微信绑定。组织和权限维护统一在管理入口处理。"
|
||||
meta={<span className={accountStatusClassName(me?.status)}>{accountStatusLabel(me?.status)}</span>}
|
||||
/>
|
||||
|
||||
<div className="mx-auto max-w-5xl space-y-5">
|
||||
<SectionPanel
|
||||
title="账号信息"
|
||||
description="这些信息用于识别你的账号。需要维护更多员工档案时,进入完整档案页。"
|
||||
actions={
|
||||
me?.id ? (
|
||||
<Button asChild type="button" variant="secondary">
|
||||
<Link to={`/people/${me.id}`}>查看员工档案</Link>
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
title="个人资料"
|
||||
description="只展示本人基础信息。如需调整组织、岗位或角色,由管理员在管理入口维护。"
|
||||
>
|
||||
<dl className="grid gap-4 text-sm text-zinc-700 md:grid-cols-2">
|
||||
<div>
|
||||
@@ -145,6 +138,18 @@ export function ProfilePage() {
|
||||
</dl>
|
||||
</SectionPanel>
|
||||
|
||||
<SectionPanel title="密码与安全" description="修改登录密码,保持账号入口可控。">
|
||||
<div className="flex flex-col gap-3 text-sm text-zinc-600 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-zinc-900">账号密码</div>
|
||||
<p className="mt-1">修改密码需要输入当前密码;忘记密码时请从登录页找回。</p>
|
||||
</div>
|
||||
<Button asChild type="button" variant="secondary">
|
||||
<Link to="/change-password">修改密码</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</SectionPanel>
|
||||
|
||||
<SectionPanel title="通知设置" description="选择是否接收企业微信个人应用提醒。">
|
||||
<div className="space-y-3 text-sm text-zinc-600">
|
||||
<label className="flex items-center gap-3 text-zinc-800">
|
||||
|
||||
@@ -41,7 +41,10 @@ export function SystemSettingsPage() {
|
||||
|
||||
return (
|
||||
<ListWorkspace>
|
||||
<WorkspaceHeader title="管理" subtitle="低频配置集中到这里,侧边栏只保留一个管理入口。" />
|
||||
<WorkspaceHeader
|
||||
title="管理"
|
||||
subtitle="人员、组织、角色、审批规则、主数据、通知渠道、外部系统、功能开关和审计统一在这里处理。"
|
||||
/>
|
||||
<div className="finance-status-strip" data-testid="system-settings-summary">
|
||||
<div className="finance-metric-card">
|
||||
<div className="finance-metric-card__label">可用入口</div>
|
||||
|
||||
在新工单中引用
屏蔽一个用户