diff --git a/frontend/src/components/shared/AppPagination/index.css b/frontend/src/components/shared/AppPagination/index.css index 3a9f29f..fa9710d 100644 --- a/frontend/src/components/shared/AppPagination/index.css +++ b/frontend/src/components/shared/AppPagination/index.css @@ -15,3 +15,8 @@ .app-pagination-container .ant-pagination-total-text { margin-right: auto; } + +.app-pagination-container .ant-select-selection-search-input { + caret-color: transparent; + cursor: pointer; +} diff --git a/frontend/src/components/shared/AppPagination/index.tsx b/frontend/src/components/shared/AppPagination/index.tsx index 89ea642..1551321 100644 --- a/frontend/src/components/shared/AppPagination/index.tsx +++ b/frontend/src/components/shared/AppPagination/index.tsx @@ -9,18 +9,23 @@ export interface AppPaginationProps extends PaginationProps { export default function AppPagination(props: AppPaginationProps) { const { t } = useTranslation(); - const mergedClassName = ['app-global-pagination', props.className].filter(Boolean).join(' '); + const { className, showSizeChanger, ...restProps } = props; + const mergedClassName = ['app-global-pagination', className].filter(Boolean).join(' '); + const mergedShowSizeChanger = + showSizeChanger === undefined || showSizeChanger === true + ? { showSearch: false } + : showSizeChanger; return (
t('common.total', { total })} pageSizeOptions={['8','10', '20', '50', '100']} size="default" - {...props} + {...restProps} />
); diff --git a/frontend/src/components/shared/ListTable/ListTable.tsx b/frontend/src/components/shared/ListTable/ListTable.tsx index 1a765cf..954d7f1 100644 --- a/frontend/src/components/shared/ListTable/ListTable.tsx +++ b/frontend/src/components/shared/ListTable/ListTable.tsx @@ -35,7 +35,7 @@ function ListTable>({ onClearSelection, pagination = { pageSize: 10, - showSizeChanger: true, + showSizeChanger: { showSearch: false }, showQuickJumper: true, }, scroll = { x: 1200 }, @@ -68,9 +68,16 @@ function ListTable>({ const mergedPagination = pagination === false ? false - : { + : (() => { + const mergedShowSizeChanger = + pagination.showSizeChanger === undefined || pagination.showSizeChanger === true + ? { showSearch: false } + : pagination.showSizeChanger; + + return { ...pagination, className: ["app-global-pagination", pagination.className].filter(Boolean).join(" "), + showSizeChanger: mergedShowSizeChanger, showTotal: (total: number) => (
{isAllPagesSelected ? ( @@ -108,6 +115,7 @@ function ListTable>({
), }; + })(); const wrapperStyle = hasVerticalScroll ? ({ diff --git a/frontend/src/index.css b/frontend/src/index.css index aa8b316..0cf96a7 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -12,9 +12,11 @@ --app-bg-overlay-size: 36px 36px; --app-bg-card: rgba(255, 255, 255, 0.74); --app-text-main: #1f2937; + --app-text-secondary: #66758f; --app-border-color: rgba(103, 126, 189, 0.12); --app-shadow: 0 18px 40px rgba(100, 118, 171, 0.1); --app-bg-page: rgba(255, 255, 255, 0.18); + --app-bg-surface: rgba(255, 255, 255, 0.68); --app-bg-surface-soft: rgba(255, 255, 255, 0.56); --app-bg-surface-strong: rgba(255, 255, 255, 0.82); --app-text-muted: #66758f; @@ -29,9 +31,11 @@ linear-gradient(90deg, rgba(255, 255, 255, 0.24) 1px, transparent 1px); --app-bg-card: rgba(255, 255, 255, 0.82); --app-text-main: #111827; + --app-text-secondary: #5b6474; --app-border-color: rgba(148, 163, 184, 0.16); --app-shadow: 0 14px 32px rgba(15, 23, 42, 0.08); --app-bg-page: rgba(255, 255, 255, 0.16); + --app-bg-surface: rgba(255, 255, 255, 0.74); --app-bg-surface-soft: rgba(255, 255, 255, 0.62); --app-bg-surface-strong: rgba(255, 255, 255, 0.86); --app-text-muted: #5b6474; @@ -47,9 +51,11 @@ linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px); --app-bg-card: rgba(13, 23, 39, 0.62); --app-text-main: #e2e8f0; + --app-text-secondary: rgba(190, 206, 229, 0.74); --app-border-color: rgba(88, 151, 255, 0.18); --app-shadow: 0 18px 44px rgba(0, 0, 0, 0.34); --app-bg-page: rgba(5, 12, 24, 0.22); + --app-bg-surface: rgba(10, 21, 37, 0.78); --app-bg-surface-soft: rgba(10, 21, 37, 0.72); --app-bg-surface-strong: rgba(8, 17, 31, 0.88); --app-text-muted: rgba(190, 206, 229, 0.74); @@ -237,10 +243,14 @@ body::after { flex: 1; min-height: 0; min-width: 0; + display: flex; + flex-direction: column; overflow: hidden; } .app-page__table-wrap .ant-table-wrapper { + flex: 1; + min-height: 0; min-width: 0; } @@ -863,7 +873,6 @@ body::after { .ant-table-wrapper .ant-table-body { overflow-y: auto !important; - max-height: none !important; } .ant-table-wrapper .ant-table-pagination.ant-pagination.app-global-pagination, @@ -898,6 +907,12 @@ body::after { margin-inline-start: 12px; } +.app-global-pagination.ant-pagination .ant-select-selection-search-input, +.ant-table-wrapper .ant-table-pagination.ant-pagination.app-global-pagination .ant-select-selection-search-input { + caret-color: transparent; + cursor: pointer; +} + .meeting-share-popover .ant-popover-inner { padding: 0; border-radius: 16px; @@ -1140,7 +1155,7 @@ body::after { @keyframes ai-ring-expand { 0% { width: 40px; height: 40px; opacity: 0.8; border-width: 2px; } - 100% { width: 200px; height: 200px; opacity: 0; border-width: 0px; } + 100% { width: 200px; height: 200px; opacity: 0; border-width: 0; } } @keyframes ai-core-pulse { 0% { transform: scale(0.8); opacity: 0.3; } diff --git a/frontend/src/layouts/AppLayout.tsx b/frontend/src/layouts/AppLayout.tsx index 9ff1b0a..0af531c 100644 --- a/frontend/src/layouts/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout.tsx @@ -58,10 +58,38 @@ type PermissionMenuNode = SysPermission & { type CachedUserProfile = { displayName?: string; username?: string; avatarUrl?: string }; +type ActiveMenuMatch = { + key: string; + parentKeys: string[]; +}; + function getAvatarUrl(profile?: CachedUserProfile | null) { return profile?.avatarUrl?.trim() || ""; } +function getMenuKey(item: Pick) { + return `${item.path || item.code || "menu"}:${item.permId}`; +} + +function findActiveMenu(nodes: PermissionMenuNode[], path: string, parentKeys: string[] = []): ActiveMenuMatch | null { + for (const node of nodes) { + const key = getMenuKey(node); + + if (node.path === path) { + return { key, parentKeys }; + } + + if (node.children?.length) { + const found = findActiveMenu(node.children, path, [...parentKeys, key]); + if (found) { + return found; + } + } + } + + return null; +} + export default function AppLayout() { const { t, i18n } = useTranslation(); const [collapsed, setCollapsed] = useState(false); @@ -100,7 +128,7 @@ export default function AppLayout() { const location = useLocation(); const navigate = useNavigate(); const { logout } = useAuth(); - const { load: loadPermissions, can } = usePermission(); + const { load: loadPermissions } = usePermission(); const { layoutMode } = useThemeStore(); const fetchInitialData = useCallback(async () => { @@ -255,7 +283,7 @@ export default function AppLayout() { const toMenuItems = useCallback((nodes: PermissionMenuNode[]): MenuProps["items"] => nodes.map((item) => { - const key = item.path || item.code || String(item.permId); + const key = getMenuKey(item); const icon = resolveMenuIcon(item.icon); if (item.permType === "directory" || item.children?.length) { @@ -274,39 +302,18 @@ export default function AppLayout() { }; }), []); - const menuItems = useMemo(() => toMenuItems(buildMenuTree(menus)), [buildMenuTree, menus, toMenuItems]); + const menuTree = useMemo(() => buildMenuTree(menus), [buildMenuTree, menus]); + const menuItems = useMemo(() => toMenuItems(menuTree), [menuTree, toMenuItems]); + const activeMenu = useMemo(() => findActiveMenu(menuTree, location.pathname), [location.pathname, menuTree]); + const selectedMenuKeys = activeMenu ? [activeMenu.key] : []; useEffect(() => { - if (!menus.length) { + if (!activeMenu?.parentKeys.length) { return; } - const findParentKeys = (nodes: NonNullable, path: string, parents: string[] = []): string[] | null => { - for (const node of nodes) { - if (!node || typeof node !== "object" || !("key" in node)) { - continue; - } - - if (String(node.key) === path) { - return parents; - } - - if ("children" in node && node.children) { - const found = findParentKeys(node.children, path, [...parents, String(node.key)]); - if (found) { - return found; - } - } - } - - return null; - }; - - const keys = findParentKeys(menuItems || [], location.pathname); - if (keys?.length) { - setOpenKeys((prev) => Array.from(new Set([...prev, ...keys]))); - } - }, [location.pathname, menuItems, menus.length]); + setOpenKeys((prev) => Array.from(new Set([...prev, ...activeMenu.parentKeys]))); + }, [activeMenu]); const userMenuItems: MenuProps["items"] = useMemo(() => { const items: NonNullable = [ @@ -418,7 +425,7 @@ export default function AppLayout() {
{ ); }; +const DragHandle = () => { + const { setActivatorNodeRef, listeners } = React.useContext(DragContext); + + return ( + + ); +}; + const menuIconOptions = Object.keys(AntIcons) .filter((key) => /(?:Outlined|Filled|TwoTone)$/.test(key)) .sort((left, right) => left.localeCompare(right)); @@ -331,16 +343,7 @@ export default function Permissions() { key: 'sortHandle', width: 40, align: 'center' as const, - render: () => { - const { setActivatorNodeRef, listeners } = React.useContext(DragContext); - return ( - - ); - }, + render: () => , }, { title: t("permissions.permName"), diff --git a/frontend/src/pages/business/AiModels.tsx b/frontend/src/pages/business/AiModels.tsx index ddc7a5c..1c2c180 100644 --- a/frontend/src/pages/business/AiModels.tsx +++ b/frontend/src/pages/business/AiModels.tsx @@ -444,7 +444,7 @@ const AiModels: React.FC = () => { columns={columns} dataSource={data} loading={loading} - scroll={{ x: "max-content" }} + scroll={{ x: "max-content", y: "100%" }} pagination={false} />
@@ -559,9 +559,7 @@ const AiModels: React.FC = () => { rules={activeType === "LLM" ? [{ required: true, message: "请输入或选择模型名称" }] : []} > { if (isLocalProvider && remoteModels.length === 0) { void handleFetchRemote(); @@ -573,7 +571,7 @@ const AiModels: React.FC = () => { String(option?.value || "").toLowerCase().includes(inputValue.toLowerCase()) } > - +