From 3f31ec0eb1e4779f4e98ae1256c5c217cacbe7d7 Mon Sep 17 00:00:00 2001 From: chenhao Date: Fri, 27 Feb 2026 15:07:03 +0800 Subject: [PATCH] =?UTF-8?q?refactor(layout):=20=E9=87=8D=E6=9E=84=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=B8=83=E5=B1=80=E5=92=8C=E6=9D=83=E9=99=90=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新AppLayout支持目录类型的菜单结构 - 实现菜单展开状态基于当前路径的自动管理 - 添加目录类型权限的过滤和渲染逻辑 - 优化页面布局的flexbox结构和滚动处理 - 修改权限管理页面支持目录、菜单、按钮三级结构 - 更新字典类型API支持分页查询参数 - 调整多个页面的表格布局和滚动配置 - 添加标准分页工具函数并统一使用 - 更新租户管理页面为卡片列表展示方式 --- .../controller/DictTypeController.java | 21 +- .../imeeting/entity/SysPlatformConfig.java | 6 +- frontend/design/AGENTS.md | 1 + frontend/src/api/dict.ts | 6 +- .../shared/ListActionBar/ListActionBar.jsx | 2 +- .../components/shared/ListTable/ListTable.css | 7 - .../components/shared/ListTable/ListTable.tsx | 11 +- frontend/src/layouts/AppLayout.tsx | 49 +++- frontend/src/pages/Devices.tsx | 15 +- frontend/src/pages/Dictionaries.tsx | 81 +++++-- frontend/src/pages/Logs.tsx | 48 ++-- frontend/src/pages/Orgs.tsx | 7 +- frontend/src/pages/Permissions.tsx | 114 ++++++---- frontend/src/pages/SysParams.tsx | 18 +- frontend/src/pages/Tenants.tsx | 215 ++++++++++-------- frontend/src/pages/Users.tsx | 43 ++-- frontend/src/types/index.ts | 1 + 17 files changed, 391 insertions(+), 254 deletions(-) diff --git a/backend/src/main/java/com/imeeting/controller/DictTypeController.java b/backend/src/main/java/com/imeeting/controller/DictTypeController.java index fbf64f6..cc26c5d 100644 --- a/backend/src/main/java/com/imeeting/controller/DictTypeController.java +++ b/backend/src/main/java/com/imeeting/controller/DictTypeController.java @@ -1,13 +1,13 @@ package com.imeeting.controller; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.imeeting.common.ApiResponse; import com.imeeting.entity.SysDictType; import com.imeeting.service.SysDictTypeService; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequestMapping("/api/dict-types") public class DictTypeController { @@ -19,8 +19,21 @@ public class DictTypeController { @GetMapping @PreAuthorize("@ss.hasPermi('sys_dict:list')") - public ApiResponse> list() { - return ApiResponse.ok(sysDictTypeService.list()); + public ApiResponse> list( + @RequestParam(defaultValue = "1") Integer current, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(required = false) String typeCode, + @RequestParam(required = false) String typeName) { + Page page = new Page<>(current, size); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (typeCode != null && !typeCode.isEmpty()) { + queryWrapper.like(SysDictType::getTypeCode, typeCode); + } + if (typeName != null && !typeName.isEmpty()) { + queryWrapper.like(SysDictType::getTypeName, typeName); + } + queryWrapper.orderByAsc(SysDictType::getTypeCode); + return ApiResponse.ok(sysDictTypeService.page(page, queryWrapper)); } @GetMapping("/{id}") diff --git a/backend/src/main/java/com/imeeting/entity/SysPlatformConfig.java b/backend/src/main/java/com/imeeting/entity/SysPlatformConfig.java index 0759820..4eaaad3 100644 --- a/backend/src/main/java/com/imeeting/entity/SysPlatformConfig.java +++ b/backend/src/main/java/com/imeeting/entity/SysPlatformConfig.java @@ -7,9 +7,9 @@ import lombok.Data; import lombok.EqualsAndHashCode; @Data -@EqualsAndHashCode(callSuper = true) + @TableName("sys_platform_config") -public class SysPlatformConfig extends BaseEntity { +public class SysPlatformConfig { @TableId private Long id; private String projectName; @@ -20,6 +20,4 @@ public class SysPlatformConfig extends BaseEntity { private String copyrightInfo; private String systemDescription; - @TableField(exist = false) - private Long tenantId; } diff --git a/frontend/design/AGENTS.md b/frontend/design/AGENTS.md index ccfca13..cc9f168 100644 --- a/frontend/design/AGENTS.md +++ b/frontend/design/AGENTS.md @@ -50,6 +50,7 @@ src > 阅读对应的后端controller了解接口 ### 核心原则 +* 优先使用frontend/src/components/shared中的组件 如果是typeScript 需要修改为typeScript * 清晰的意图胜于技巧性的实现 * 组件简单直观优于过度抽象 * 奥卡姆剃刀:不必要的复杂度一律删除 diff --git a/frontend/src/api/dict.ts b/frontend/src/api/dict.ts index 4dc5bfb..7da3575 100644 --- a/frontend/src/api/dict.ts +++ b/frontend/src/api/dict.ts @@ -2,9 +2,9 @@ import http from "./http"; import { SysDictType, SysDictItem } from "../types"; // Dictionary Type APIs -export async function fetchDictTypes() { - const resp = await http.get("/api/dict-types"); - return resp.data.data as SysDictType[]; +export async function fetchDictTypes(params?: { current?: number; size?: number; typeCode?: string; typeName?: string }) { + const resp = await http.get("/api/dict-types", { params }); + return resp.data.data; } export async function createDictType(data: Partial) { diff --git a/frontend/src/components/shared/ListActionBar/ListActionBar.jsx b/frontend/src/components/shared/ListActionBar/ListActionBar.jsx index f1b1c44..7dff36d 100644 --- a/frontend/src/components/shared/ListActionBar/ListActionBar.jsx +++ b/frontend/src/components/shared/ListActionBar/ListActionBar.jsx @@ -1,4 +1,4 @@ -import { Button, Input, Space, Popover } from 'antd' + import { Button, Input, Space, Popover } from 'antd' import { ReloadOutlined, FilterOutlined } from '@ant-design/icons' import './ListActionBar.css' diff --git a/frontend/src/components/shared/ListTable/ListTable.css b/frontend/src/components/shared/ListTable/ListTable.css index 0e6cd31..1dcd522 100644 --- a/frontend/src/components/shared/ListTable/ListTable.css +++ b/frontend/src/components/shared/ListTable/ListTable.css @@ -1,13 +1,6 @@ /* 列表表格容器 */ .list-table-container { - background: var(--card-bg); - border-radius: 8px; - padding: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); - height: 626px; - overflow-y: auto; width: 100%; - border: 1px solid var(--border-color); } /* 行选中样式 */ diff --git a/frontend/src/components/shared/ListTable/ListTable.tsx b/frontend/src/components/shared/ListTable/ListTable.tsx index a92a9be..fa57b3b 100644 --- a/frontend/src/components/shared/ListTable/ListTable.tsx +++ b/frontend/src/components/shared/ListTable/ListTable.tsx @@ -1,6 +1,8 @@ +import React from "react"; import { Table } from "antd"; import type { TablePaginationConfig, TableProps } from "antd"; import "./ListTable.css"; +import i18n from "../../../i18n"; export type ListTableProps> = { columns: TableProps["columns"]; @@ -13,11 +15,12 @@ export type ListTableProps> = { onSelectAllPages?: () => void; onClearSelection?: () => void; pagination?: TablePaginationConfig | false; - scroll?: { x?: number | true | string }; + scroll?: { x?: number | true | string; y?: number | string }; onRowClick?: (record: T) => void; selectedRow?: T | null; loading?: boolean; className?: string; + onChange?: TableProps["onChange"]; }; function ListTable>({ @@ -40,6 +43,7 @@ function ListTable>({ selectedRow, loading = false, className = "", + onChange, }: ListTableProps) { const rowSelection: TableProps["rowSelection"] = onSelectionChange ? { @@ -88,7 +92,9 @@ function ListTable>({ )} ) : ( - 已选择 0 项 + + {i18n.t("common.total", { total: totalCount || total })} + )} ), @@ -105,6 +111,7 @@ function ListTable>({ pagination={mergedPagination} scroll={scroll} loading={loading} + onChange={onChange} onRow={(record) => ({ onClick: () => onRowClick?.(record), className: selectedRow?.[rowKey] === record[rowKey] ? "row-selected" : "", diff --git a/frontend/src/layouts/AppLayout.tsx b/frontend/src/layouts/AppLayout.tsx index 3619a34..da110c0 100644 --- a/frontend/src/layouts/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout.tsx @@ -77,7 +77,7 @@ export default function AppLayout() { // Filter visible menus and sort them const filtered = data - .filter(p => p.permType === 'menu' && p.isVisible === 1 && p.status === 1) + .filter(p => (p.permType === 'menu' || p.permType === 'directory') && p.isVisible === 1 && p.status === 1) .sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0)); setMenus(filtered); } catch (e) { @@ -144,14 +144,17 @@ export default function AppLayout() { nodes.map((m) => { const key = m.path || m.code || String(m.permId); const icon = m.icon ? (iconMap[m.icon] || ) : ; - if (m.children && m.children.length > 0) { + + // Directory type or item with children should not have a link if it's a directory + if (m.permType === 'directory' || (m.children && m.children.length > 0)) { return { key, icon, label: m.name, - children: toMenuItems(m.children), + children: m.children && m.children.length > 0 ? toMenuItems(m.children) : undefined, }; } + return { key, icon, @@ -161,6 +164,28 @@ export default function AppLayout() { const menuItems = useMemo(() => toMenuItems(buildMenuTree(menus)), [menus, buildMenuTree, toMenuItems]); + // Calculate open keys based on current path + const [openKeys, setOpenKeys] = useState([]); + + useEffect(() => { + if (menus.length > 0) { + const findParentKeys = (nodes: any[], path: string, parents: string[] = []): string[] | null => { + for (const node of nodes) { + if (node.key === path) return parents; + if (node.children) { + const found = findParentKeys(node.children, path, [...parents, node.key]); + if (found) return found; + } + } + return null; + }; + const keys = findParentKeys(menuItems, location.pathname); + if (keys) { + setOpenKeys(prev => Array.from(new Set([...prev, ...keys]))); + } + } + }, [location.pathname, menuItems, menus]); + const userMenuItems: MenuProps["items"] = useMemo(() => { const items: any[] = [ { @@ -241,11 +266,13 @@ export default function AppLayout() { - +