feat: 去掉角色权限点与菜单权限码配置

This commit is contained in:
chengkai3
2026-05-01 19:29:51 +08:00
parent 4750e695da
commit afe1fd7fe0
5 changed files with 44 additions and 72 deletions
+6
View File
@@ -1018,3 +1018,9 @@
- 总数以接口返回 `total` 为准。 - 总数以接口返回 `total` 为准。
- 筛选条件(关键词/塔型/风险等级)或线路切换时,分页需自动回到第 1 页,避免落在无数据页。 - 筛选条件(关键词/塔型/风险等级)或线路切换时,分页需自动回到第 1 页,避免落在无数据页。
- 地图视图保留大页查询(当前 500 条)用于展示线路点位,不与表格分页参数共用同一页码。 - 地图视图保留大页查询(当前 500 条)用于展示线路点位,不与表格分页参数共用同一页码。
## 角色/菜单配置口径(2026-05-01
- 角色管理页面(`/admin/roles`)当前仅提供角色基础信息与“可见菜单”配置,不再提供权限点(`permission_codes`)配置入口。
- 菜单管理页面(`/admin/menus`)当前不再提供菜单权限码(`permission_code`)配置入口与列表展示。
- 后端权限字段与接口兼容能力保留,作为历史数据与鉴权映射兜底;前端交互层默认不暴露该配置。
+31
View File
@@ -419,3 +419,34 @@
- 风险与影响: - 风险与影响:
- `.img/.tif` 回填依赖 `rasterio`(及底层 GDAL 运行时),部署环境需确保镜像能成功安装该依赖。 - `.img/.tif` 回填依赖 `rasterio`(及底层 GDAL 运行时),部署环境需确保镜像能成功安装该依赖。
- 栅格 bbox 直接来自源栅格 CRS;非经纬度坐标系场景会返回告警,便于识别与后续治理。 - 栅格 bbox 直接来自源栅格 CRS;非经纬度坐标系场景会返回告警,便于识别与后续治理。
## Work Log - 去掉角色权限点与菜单权限码配置(2026-05-01)
- 背景:
- Issue `FL-139` 要求“去掉角色管理的权限点配置,菜单的权限码配置”。
- 本次改动(最小闭环):
- 角色管理页面:`web/src/app/admin/roles/page.tsx`
- 移除权限点配置入口与相关请求链路:
- 移除 `permissions` 状态与 `/api/v1/admin/permissions` 加载请求。
- 新建/编辑角色表单移除 `permission_codes` 字段,仅保留 `code/name/menu_ids`
- 角色更新请求不再提交 `permission_codes`
- 列表展示移除“权限”列,仅展示“角色编码/角色名称/菜单/操作”。
- 搜索口径同步调整为“角色编码、名称、菜单”。
- 菜单管理页面:`web/src/app/admin/menus/page.tsx`
- 移除菜单权限码配置入口:
- 菜单表单移除 `permission_code` 字段。
- 新建/编辑菜单提交 payload 不再包含 `permission_code`
- 列表展示移除“权限码”列。
- 搜索口径移除权限码匹配,关键词仅匹配“编码/名称/路径”。
- 后台首页文案:`web/src/app/admin/page.tsx`
- 角色管理说明改为“配置角色并分配菜单可见范围”。
- 菜单管理说明改为“维护后台导航结构、菜单层级与展示状态”。
- 验证(遵循任务约束,未执行编译检查):
- `git diff` 已确认改动仅涉及上述三个前端文件。
- 代码扫描确认上述页面不再包含 `permission_codes` / `permission_code` 配置与展示逻辑。
- 风险与影响:
- 影响范围限定在前端管理页交互层;后端接口仍保持兼容,可继续返回权限相关字段但前端不再暴露配置入口。
- 若后续需彻底下线该能力(含后端字段/持久化),需单独评估接口契约与历史数据兼容。
+2 -18
View File
@@ -48,7 +48,6 @@ type MenuFormValues = {
visible: boolean; visible: boolean;
cacheable: boolean; cacheable: boolean;
component?: string; component?: string;
permission_code?: string;
}; };
const SORT_OPTIONS: Array<{ value: SortKey; label: string }> = [ const SORT_OPTIONS: Array<{ value: SortKey; label: string }> = [
@@ -108,7 +107,6 @@ const DEFAULT_FORM_VALUES: MenuFormValues = {
visible: true, visible: true,
cacheable: false, cacheable: false,
component: "", component: "",
permission_code: "",
}; };
function compareMenuIds(a: string, b: string): number { function compareMenuIds(a: string, b: string): number {
@@ -166,7 +164,7 @@ export default function AdminMenusPage() {
if (!query) { if (!query) {
return true; return true;
} }
const haystack = [menu.code, menu.name, menu.path ?? "", menu.permission_code ?? ""] const haystack = [menu.code, menu.name, menu.path ?? ""]
.join(" ") .join(" ")
.toLowerCase(); .toLowerCase();
return haystack.includes(query); return haystack.includes(query);
@@ -250,7 +248,6 @@ export default function AdminMenusPage() {
visible: menu.visible, visible: menu.visible,
cacheable: menu.cacheable, cacheable: menu.cacheable,
component: menu.component ?? "", component: menu.component ?? "",
permission_code: menu.permission_code ?? "",
}); });
setDialogOpen(true); setDialogOpen(true);
}, [form]); }, [form]);
@@ -273,7 +270,6 @@ export default function AdminMenusPage() {
visible: values.visible, visible: values.visible,
cacheable: values.cacheable, cacheable: values.cacheable,
component: values.component?.trim() ? values.component.trim() : null, component: values.component?.trim() ? values.component.trim() : null,
permission_code: values.permission_code?.trim() ? values.permission_code.trim() : null,
}; };
const response = editingMenuId const response = editingMenuId
@@ -362,12 +358,6 @@ export default function AdminMenusPage() {
width: 220, width: 220,
render: (value: string | null) => value || "-", render: (value: string | null) => value || "-",
}, },
{
title: "权限码",
dataIndex: "permission_code",
width: 180,
render: (value: string | null) => value || "-",
},
{ {
title: "父菜单", title: "父菜单",
dataIndex: "parent_id", dataIndex: "parent_id",
@@ -488,7 +478,7 @@ export default function AdminMenusPage() {
allowClear allowClear
value={keyword} value={keyword}
onChange={(event) => setKeyword(event.currentTarget.value)} onChange={(event) => setKeyword(event.currentTarget.value)}
placeholder="按编码/名称/路径/权限筛选" placeholder="按编码/名称/路径筛选"
/> />
</Form.Item> </Form.Item>
@@ -634,12 +624,6 @@ export default function AdminMenusPage() {
<Input placeholder="app/admin/users/page" /> <Input placeholder="app/admin/users/page" />
</Form.Item> </Form.Item>
</Col> </Col>
<Col xs={24} md={12}>
<Form.Item label="权限码" name="permission_code">
<Input placeholder="menu.read" />
</Form.Item>
</Col>
<Col xs={24} md={12}> <Col xs={24} md={12}>
<Form.Item name="visible" valuePropName="checked"> <Form.Item name="visible" valuePropName="checked">
<Checkbox></Checkbox> <Checkbox></Checkbox>
+2 -2
View File
@@ -43,7 +43,7 @@ const CARDS: DashboardCard[] = [
{ {
href: "/roles", href: "/roles",
title: "角色管理", title: "角色管理",
description: "配置角色、绑定权限点、分配菜单可见范围。", description: "配置角色分配菜单可见范围。",
category: "权限", category: "权限",
icon: <SafetyCertificateOutlined />, icon: <SafetyCertificateOutlined />,
visible: (hasPermission) => hasPermission("role.read") || hasPermission("role.manage"), visible: (hasPermission) => hasPermission("role.read") || hasPermission("role.manage"),
@@ -51,7 +51,7 @@ const CARDS: DashboardCard[] = [
{ {
href: "/menus", href: "/menus",
title: "菜单管理", title: "菜单管理",
description: "维护后台导航、菜单层级和菜单对应权限。", description: "维护后台导航结构、菜单层级与展示状态。",
category: "权限", category: "权限",
icon: <AppstoreOutlined />, icon: <AppstoreOutlined />,
visible: (hasPermission) => hasPermission("menu.read") || hasPermission("menu.manage"), visible: (hasPermission) => hasPermission("menu.read") || hasPermission("menu.manage"),
+3 -52
View File
@@ -27,25 +27,22 @@ import type { ComponentType } from "react";
import { useAuth } from "@/components/auth-provider"; import { useAuth } from "@/components/auth-provider";
import { useTopicSubscription } from "@/hooks/use-topic-subscription"; import { useTopicSubscription } from "@/hooks/use-topic-subscription";
import { readApiError } from "@/lib/api"; import { readApiError } from "@/lib/api";
import type { MenuItem, PermissionItem, RoleItem, RoleListResponse } from "@/types/auth"; import type { MenuItem, RoleItem, RoleListResponse } from "@/types/auth";
const AntCard = Card as unknown as ComponentType<CardProps>; const AntCard = Card as unknown as ComponentType<CardProps>;
const AntResult = Result as unknown as ComponentType<ResultProps>; const AntResult = Result as unknown as ComponentType<ResultProps>;
type PermissionResponse = { items: PermissionItem[] };
type MenuListResponse = { items: MenuItem[]; total: number }; type MenuListResponse = { items: MenuItem[]; total: number };
type RoleFormValues = { type RoleFormValues = {
code: string; code: string;
name: string; name: string;
permission_codes: string[];
menu_ids: string[]; menu_ids: string[];
}; };
const EMPTY_FORM: RoleFormValues = { const EMPTY_FORM: RoleFormValues = {
code: "", code: "",
name: "", name: "",
permission_codes: [],
menu_ids: [], menu_ids: [],
}; };
@@ -55,7 +52,6 @@ export default function AdminRolesPage() {
const [form] = Form.useForm<RoleFormValues>(); const [form] = Form.useForm<RoleFormValues>();
const [roles, setRoles] = useState<RoleItem[]>([]); const [roles, setRoles] = useState<RoleItem[]>([]);
const [searchKeyword, setSearchKeyword] = useState(""); const [searchKeyword, setSearchKeyword] = useState("");
const [permissions, setPermissions] = useState<PermissionItem[]>([]);
const [menus, setMenus] = useState<MenuItem[]>([]); const [menus, setMenus] = useState<MenuItem[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
@@ -66,15 +62,6 @@ export default function AdminRolesPage() {
const canRead = hasPermission("role.read") || hasPermission("role.manage"); const canRead = hasPermission("role.read") || hasPermission("role.manage");
const canManage = hasPermission("role.manage"); const canManage = hasPermission("role.manage");
const permissionOptions = useMemo(
() =>
permissions.map((permission) => ({
value: permission.code,
label: `${permission.name || permission.code} (${permission.code})`,
})),
[permissions],
);
const menuOptions = useMemo( const menuOptions = useMemo(
() => menus.map((menu) => ({ value: menu.id, label: `${menu.name} (${menu.code})` })), () => menus.map((menu) => ({ value: menu.id, label: `${menu.name} (${menu.code})` })),
[menus], [menus],
@@ -97,7 +84,6 @@ export default function AdminRolesPage() {
const haystack = [ const haystack = [
role.code, role.code,
role.name, role.name,
role.permission_codes.join(" "),
menuNames, menuNames,
] ]
.join(" ") .join(" ")
@@ -115,28 +101,22 @@ export default function AdminRolesPage() {
setLoading(true); setLoading(true);
setError(""); setError("");
try { try {
const [roleRes, permissionRes, menuRes] = await Promise.all([ const [roleRes, menuRes] = await Promise.all([
fetchWithAuth("/api/v1/admin/roles"), fetchWithAuth("/api/v1/admin/roles"),
fetchWithAuth("/api/v1/admin/permissions"),
fetchWithAuth("/api/v1/admin/menus"), fetchWithAuth("/api/v1/admin/menus"),
]); ]);
if (!roleRes.ok) { if (!roleRes.ok) {
throw new Error(await readApiError(roleRes)); throw new Error(await readApiError(roleRes));
} }
if (!permissionRes.ok) {
throw new Error(await readApiError(permissionRes));
}
if (!menuRes.ok) { if (!menuRes.ok) {
throw new Error(await readApiError(menuRes)); throw new Error(await readApiError(menuRes));
} }
const rolePayload = (await roleRes.json()) as RoleListResponse; const rolePayload = (await roleRes.json()) as RoleListResponse;
const permissionPayload = (await permissionRes.json()) as PermissionResponse;
const menuPayload = (await menuRes.json()) as MenuListResponse; const menuPayload = (await menuRes.json()) as MenuListResponse;
setRoles(rolePayload.items); setRoles(rolePayload.items);
setPermissions(permissionPayload.items);
setMenus(menuPayload.items); setMenus(menuPayload.items);
} catch (candidate) { } catch (candidate) {
setError(candidate instanceof Error ? candidate.message : "角色数据加载失败"); setError(candidate instanceof Error ? candidate.message : "角色数据加载失败");
@@ -183,7 +163,6 @@ export default function AdminRolesPage() {
form.setFieldsValue({ form.setFieldsValue({
code: role.code, code: role.code,
name: role.name, name: role.name,
permission_codes: role.permission_codes,
menu_ids: role.menu_ids, menu_ids: role.menu_ids,
}); });
setDialogOpen(true); setDialogOpen(true);
@@ -198,7 +177,6 @@ export default function AdminRolesPage() {
const payload: RoleFormValues = { const payload: RoleFormValues = {
code: values.code.trim(), code: values.code.trim(),
name: values.name.trim(), name: values.name.trim(),
permission_codes: values.permission_codes ?? [],
menu_ids: values.menu_ids ?? [], menu_ids: values.menu_ids ?? [],
}; };
@@ -208,7 +186,6 @@ export default function AdminRolesPage() {
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({
name: payload.name, name: payload.name,
permission_codes: payload.permission_codes,
menu_ids: payload.menu_ids, menu_ids: payload.menu_ids,
}), }),
}) })
@@ -282,22 +259,6 @@ export default function AdminRolesPage() {
dataIndex: "name", dataIndex: "name",
width: 180, width: 180,
}, },
{
title: "权限",
dataIndex: "permission_codes",
render: (value: string[]) => {
if (value.length === 0) {
return <Typography.Text type="secondary"></Typography.Text>;
}
return (
<Space wrap size={[4, 4]}>
{value.map((permissionCode) => (
<Tag key={permissionCode}>{permissionCode}</Tag>
))}
</Space>
);
},
},
{ {
title: "菜单", title: "菜单",
dataIndex: "menu_ids", dataIndex: "menu_ids",
@@ -414,7 +375,7 @@ export default function AdminRolesPage() {
<Space align="center" wrap> <Space align="center" wrap>
<Input.Search <Input.Search
allowClear allowClear
placeholder="搜索角色编码、名称、权限或菜单" placeholder="搜索角色编码、名称或菜单"
style={{ width: 360, maxWidth: "100%" }} style={{ width: 360, maxWidth: "100%" }}
value={searchKeyword} value={searchKeyword}
onChange={(event) => setSearchKeyword(event.currentTarget.value)} onChange={(event) => setSearchKeyword(event.currentTarget.value)}
@@ -482,16 +443,6 @@ export default function AdminRolesPage() {
<Input placeholder="运营管理员" /> <Input placeholder="运营管理员" />
</Form.Item> </Form.Item>
<Form.Item label="权限点" name="permission_codes">
<Select
allowClear
mode="multiple"
optionFilterProp="label"
options={permissionOptions}
placeholder="请选择权限点"
/>
</Form.Item>
<Form.Item label="可见菜单" name="menu_ids"> <Form.Item label="可见菜单" name="menu_ids">
<Select <Select
allowClear allowClear