feat: 全局分页参数:滚动条修复,前端其他异常警告修复
parent
b34efa5eb0
commit
5ff2ddc388
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="app-pagination-container">
|
||||
<Pagination
|
||||
className={mergedClassName}
|
||||
showSizeChanger
|
||||
showSizeChanger={mergedShowSizeChanger}
|
||||
showQuickJumper
|
||||
showTotal={(total) => t('common.total', { total })}
|
||||
pageSizeOptions={['8','10', '20', '50', '100']}
|
||||
size="default"
|
||||
{...props}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ function ListTable<T extends Record<string, any>>({
|
|||
onClearSelection,
|
||||
pagination = {
|
||||
pageSize: 10,
|
||||
showSizeChanger: true,
|
||||
showSizeChanger: { showSearch: false },
|
||||
showQuickJumper: true,
|
||||
},
|
||||
scroll = { x: 1200 },
|
||||
|
|
@ -68,9 +68,16 @@ function ListTable<T extends Record<string, any>>({
|
|||
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) => (
|
||||
<div className="table-selection-info">
|
||||
{isAllPagesSelected ? (
|
||||
|
|
@ -108,6 +115,7 @@ function ListTable<T extends Record<string, any>>({
|
|||
</div>
|
||||
),
|
||||
};
|
||||
})();
|
||||
|
||||
const wrapperStyle = hasVerticalScroll
|
||||
? ({
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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<SysPermission, "permId" | "path" | "code">) {
|
||||
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<MenuProps["items"]>, 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<MenuProps["items"]> = [
|
||||
|
|
@ -418,7 +425,7 @@ export default function AppLayout() {
|
|||
<div style={{ flex: 1, overflowY: "auto", overflowX: "hidden" }}>
|
||||
<Menu
|
||||
mode="inline"
|
||||
selectedKeys={[location.pathname]}
|
||||
selectedKeys={selectedMenuKeys}
|
||||
openKeys={openKeys}
|
||||
onOpenChange={setOpenKeys}
|
||||
items={menuItems}
|
||||
|
|
@ -458,7 +465,7 @@ export default function AppLayout() {
|
|||
<div style={{ flex: 1, minWidth: 0, padding: "0 24px" }}>
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
selectedKeys={[location.pathname]}
|
||||
selectedKeys={selectedMenuKeys}
|
||||
items={menuItems}
|
||||
style={{
|
||||
borderBottom: 0,
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@
|
|||
.ant-table-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
max-height: none !important;
|
||||
overflow-y: auto !important;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,18 @@ const DraggableRow = ({ children, ...props }: RowProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
const DragHandle = () => {
|
||||
const { setActivatorNodeRef, listeners } = React.useContext(DragContext);
|
||||
|
||||
return (
|
||||
<HolderOutlined
|
||||
ref={setActivatorNodeRef}
|
||||
style={{ touchAction: 'none', cursor: 'move', color: '#999' }}
|
||||
{...listeners}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<HolderOutlined
|
||||
ref={setActivatorNodeRef}
|
||||
style={{ touchAction: 'none', cursor: 'move', color: '#999' }}
|
||||
{...listeners}
|
||||
/>
|
||||
);
|
||||
},
|
||||
render: () => <DragHandle />,
|
||||
},
|
||||
{
|
||||
title: t("permissions.permName"),
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -559,9 +559,7 @@ const AiModels: React.FC = () => {
|
|||
rules={activeType === "LLM" ? [{ required: true, message: "请输入或选择模型名称" }] : []}
|
||||
>
|
||||
<AutoComplete
|
||||
allowClear
|
||||
style={{ width: "calc(100% - 100px)" }}
|
||||
placeholder="可选择或自定义输入模型名称"
|
||||
onFocus={() => {
|
||||
if (isLocalProvider && remoteModels.length === 0) {
|
||||
void handleFetchRemote();
|
||||
|
|
@ -573,7 +571,7 @@ const AiModels: React.FC = () => {
|
|||
String(option?.value || "").toLowerCase().includes(inputValue.toLowerCase())
|
||||
}
|
||||
>
|
||||
<Input />
|
||||
<Input allowClear placeholder="可选择或自定义输入模型名称" />
|
||||
</AutoComplete>
|
||||
</Form.Item>
|
||||
<Button icon={<SyncOutlined spin={fetchLoading} />} onClick={handleFetchRemote} style={{ width: 100 }}>
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@ export default function ClientManagement() {
|
|||
>
|
||||
<Row gutter={24} style={{ marginBottom: 24 }}>
|
||||
<Col span={6}>
|
||||
<Card bordered={false} style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<Card variant="borderless" style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<span style={{ color: 'var(--app-text-secondary)', fontSize: 13, fontWeight: 500 }}>发布总数</span>
|
||||
|
|
@ -431,7 +431,7 @@ export default function ClientManagement() {
|
|||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card bordered={false} style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<Card variant="borderless" style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<span style={{ color: 'var(--app-text-secondary)', fontSize: 13, fontWeight: 500 }}>已启用</span>
|
||||
|
|
@ -444,7 +444,7 @@ export default function ClientManagement() {
|
|||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card bordered={false} style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<Card variant="borderless" style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<span style={{ color: 'var(--app-text-secondary)', fontSize: 13, fontWeight: 500 }}>最新版本</span>
|
||||
|
|
@ -457,7 +457,7 @@ export default function ClientManagement() {
|
|||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card bordered={false} style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<Card variant="borderless" style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<span style={{ color: 'var(--app-text-secondary)', fontSize: 13, fontWeight: 500 }}>平台分组</span>
|
||||
|
|
@ -497,7 +497,7 @@ export default function ClientManagement() {
|
|||
dataSource={pagedRecords}
|
||||
loading={loading || groupLoading || platformLoading}
|
||||
locale={platformGroups.length === 0 ? { emptyText: <Empty description="未配置 client_platform 字典项" /> } : undefined}
|
||||
scroll={{x: 1100}}
|
||||
scroll={{ x: 1100, y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ export default function ExternalAppManagement() {
|
|||
>
|
||||
<Row gutter={24} style={{ marginBottom: 24 }}>
|
||||
<Col span={6}>
|
||||
<Card bordered={false} style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<Card variant="borderless" style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<span style={{ color: 'var(--app-text-secondary)', fontSize: 13, fontWeight: 500 }}>应用总数</span>
|
||||
|
|
@ -316,7 +316,7 @@ export default function ExternalAppManagement() {
|
|||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card bordered={false} style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<Card variant="borderless" style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<span style={{ color: 'var(--app-text-secondary)', fontSize: 13, fontWeight: 500 }}>原生应用</span>
|
||||
|
|
@ -329,7 +329,7 @@ export default function ExternalAppManagement() {
|
|||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card bordered={false} style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<Card variant="borderless" style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<span style={{ color: 'var(--app-text-secondary)', fontSize: 13, fontWeight: 500 }}>Web 应用</span>
|
||||
|
|
@ -342,7 +342,7 @@ export default function ExternalAppManagement() {
|
|||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card bordered={false} style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<Card variant="borderless" style={{ borderRadius: 16, border: '1px solid var(--app-border-color)', background: 'var(--app-bg-surface-strong)', backdropFilter: 'blur(12px)', boxShadow: '0 4px 20px rgba(0,0,0,0.02)' }} styles={{ body: { padding: '20px 24px' } }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<span style={{ color: 'var(--app-text-secondary)', fontSize: 13, fontWeight: 500 }}>已启用</span>
|
||||
|
|
@ -368,7 +368,7 @@ export default function ExternalAppManagement() {
|
|||
|
||||
<Card className="app-page__content-card flex-1 flex flex-col overflow-hidden" styles={{ body: { padding: 0, flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" } }}>
|
||||
<div className="app-page__table-wrap" style={{ padding: "0 24px", overflow: "auto" }}>
|
||||
<Table rowKey="id" columns={columns} dataSource={pagedRecords} loading={loading} scroll={{ x: "max-content" }} pagination={false} />
|
||||
<Table rowKey="id" columns={columns} dataSource={pagedRecords} loading={loading} scroll={{ x: "max-content", y: "100%" }} pagination={false} />
|
||||
</div>
|
||||
<AppPagination
|
||||
current={page}
|
||||
|
|
|
|||
|
|
@ -541,7 +541,7 @@ const HotWords: React.FC = () => {
|
|||
dataSource={data}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
scroll={{ x: "max-content" }}
|
||||
scroll={{ x: "max-content", y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ export default function LicenseManagement() {
|
|||
>
|
||||
<Row gutter={24} style={{ marginBottom: 24 }}>
|
||||
<Col span={6}>
|
||||
<Card bordered={false} style={{ borderRadius: 16 }}>
|
||||
<Card variant="borderless" style={{ borderRadius: 16 }}>
|
||||
<Space size={16}>
|
||||
<KeyOutlined style={{ color: "#1677ff", fontSize: 24 }} />
|
||||
<div>
|
||||
|
|
@ -182,7 +182,7 @@ export default function LicenseManagement() {
|
|||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card bordered={false} style={{ borderRadius: 16 }}>
|
||||
<Card variant="borderless" style={{ borderRadius: 16 }}>
|
||||
<Space size={16}>
|
||||
<LinkOutlined style={{ color: "#52c41a", fontSize: 24 }} />
|
||||
<div>
|
||||
|
|
@ -193,7 +193,7 @@ export default function LicenseManagement() {
|
|||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card bordered={false} style={{ borderRadius: 16 }}>
|
||||
<Card variant="borderless" style={{ borderRadius: 16 }}>
|
||||
<Space size={16}>
|
||||
<CheckCircleOutlined style={{ color: "#722ed1", fontSize: 24 }} />
|
||||
<div>
|
||||
|
|
@ -204,7 +204,7 @@ export default function LicenseManagement() {
|
|||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card bordered={false} style={{ borderRadius: 16 }}>
|
||||
<Card variant="borderless" style={{ borderRadius: 16 }}>
|
||||
<Space size={16}>
|
||||
<ClockCircleOutlined style={{ color: "#fa8c16", fontSize: 24 }} />
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -2,12 +2,33 @@
|
|||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// 强制覆盖全局可能存在的 max-height: none !important 限制
|
||||
// 确保 antd table 的 scroll.y 能够生效
|
||||
.ant-table-wrapper .ant-table-body {
|
||||
max-height: inherit !important;
|
||||
}
|
||||
.dictionaries-page > .ant-row {
|
||||
flex: 1 1 0;
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.dictionaries-page > .ant-row > .ant-col {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dictionaries-page > .ant-row > .ant-col > .app-page__panel-card {
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dictionaries-page > .ant-row > .ant-col > .app-page__panel-card > .ant-card-body {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.dictionaries-header {
|
||||
|
|
@ -86,6 +107,89 @@
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
.dict-type-table-wrap,
|
||||
.dict-item-table-wrap {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dictionaries-page .dict-type-table-wrap .ant-table-wrapper,
|
||||
.dictionaries-page .dict-type-table-wrap .ant-spin-container,
|
||||
.dictionaries-page .dict-type-table-wrap .ant-table,
|
||||
.dictionaries-page .dict-type-table-wrap .ant-table-container,
|
||||
.dictionaries-page .dict-item-table-wrap .ant-table-wrapper,
|
||||
.dictionaries-page .dict-item-table-wrap .ant-spin-container,
|
||||
.dictionaries-page .dict-item-table-wrap .ant-table,
|
||||
.dictionaries-page .dict-item-table-wrap .ant-table-container {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.dictionaries-page .dict-type-table-wrap .ant-table-wrapper,
|
||||
.dictionaries-page .dict-item-table-wrap .ant-table-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dictionaries-page .dict-type-table-wrap .ant-spin-nested-loading,
|
||||
.dictionaries-page .dict-item-table-wrap .ant-spin-nested-loading {
|
||||
flex: 1 1 0;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dictionaries-page .dict-type-table-wrap .ant-spin-container,
|
||||
.dictionaries-page .dict-type-table-wrap .ant-table,
|
||||
.dictionaries-page .dict-type-table-wrap .ant-table-container,
|
||||
.dictionaries-page .dict-item-table-wrap .ant-spin-container,
|
||||
.dictionaries-page .dict-item-table-wrap .ant-table,
|
||||
.dictionaries-page .dict-item-table-wrap .ant-table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dictionaries-page .dict-type-table-wrap .ant-table-body,
|
||||
.dictionaries-page .dict-item-table-wrap .ant-table-body {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.dictionaries-page .dict-type-pagination {
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
padding: 10px 4px 0;
|
||||
column-gap: 6px;
|
||||
row-gap: 8px;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.dictionaries-page .dict-type-pagination .ant-pagination-simple-pager {
|
||||
margin-inline-end: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.dictionaries-page .dict-type-pagination .ant-pagination-simple-pager input {
|
||||
margin-inline-end: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dictionaries-page .dict-type-pagination .ant-pagination-simple-pager .ant-pagination-slash {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dictionaries-page .dict-type-pagination .ant-pagination-total-text {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.dictionaries-page .dict-type-pagination .ant-pagination-options {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
.tabular-nums {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
|
@ -99,9 +203,3 @@
|
|||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
.ant-table-wrapper .ant-spin-container {
|
||||
height: calc(100vh - 410px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ export default function Dictionaries() {
|
|||
<Button onClick={handleTypeReset}>{t("common.reset")}</Button>
|
||||
</Space.Compact>
|
||||
</div>
|
||||
<div className="flex-1 min-h-0">
|
||||
<div className="dict-type-table-wrap">
|
||||
<Table
|
||||
rowKey="dictTypeId"
|
||||
loading={loadingTypes}
|
||||
|
|
@ -202,11 +202,13 @@ export default function Dictionaries() {
|
|||
...getStandardPagination(typeTotal, typeParams.current, typeParams.size, (page, size) => setTypeParams({ ...typeParams, current: page, size })),
|
||||
simple: true,
|
||||
size: "small",
|
||||
position: ["bottomCenter"]
|
||||
showQuickJumper: false,
|
||||
position: ["bottomCenter"],
|
||||
className: "app-global-pagination dict-type-pagination"
|
||||
}}
|
||||
size="small"
|
||||
showHeader={false}
|
||||
scroll={{ y: "calc(100vh - 480px)" }}
|
||||
scroll={{ y: "100%" }}
|
||||
onRow={(record) => ({ onClick: () => setSelectedType(record), className: `cursor-pointer dict-type-row ${selectedType?.dictTypeId === record.dictTypeId ? "dict-type-row-selected" : ""}` })}
|
||||
columns={[
|
||||
{
|
||||
|
|
@ -232,7 +234,7 @@ export default function Dictionaries() {
|
|||
<Col span={16} className="h-full flex flex-col overflow-hidden">
|
||||
<Card title={<Space><ProfileOutlined aria-hidden="true" /><span>{t("dicts.dictItem")}{selectedType ? ` - ${selectedType.typeName}` : ""}</span></Space>} className="app-page__panel-card flex-1 flex flex-col overflow-hidden" styles={{ body: { padding: 0, flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" } }} extra={can("sys_dict:item:create") && <Button type="primary" size="small" icon={<PlusOutlined aria-hidden="true" />} onClick={handleAddItem} disabled={!selectedType}>{t("common.create")}</Button>}>
|
||||
{selectedType ? (
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<div className="dict-item-table-wrap">
|
||||
<Table
|
||||
rowKey="dictItemId"
|
||||
loading={loadingItems}
|
||||
|
|
|
|||
|
|
@ -12,19 +12,23 @@ export const getStandardPagination = (
|
|||
overrides: TablePaginationConfig = {}
|
||||
): TablePaginationConfig => {
|
||||
const mergedClassName = ['app-global-pagination', overrides.className].filter(Boolean).join(' ');
|
||||
const mergedShowSizeChanger =
|
||||
overrides.showSizeChanger === undefined || overrides.showSizeChanger === true
|
||||
? { showSearch: false }
|
||||
: overrides.showSizeChanger;
|
||||
|
||||
return {
|
||||
total,
|
||||
current,
|
||||
pageSize,
|
||||
onChange,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (totalCount) => i18n.t('common.total', { total: totalCount }),
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
size: 'default',
|
||||
position: ['bottomRight'],
|
||||
...overrides,
|
||||
showSizeChanger: mergedShowSizeChanger,
|
||||
className: mergedClassName
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue