feat(ui): 统一全局分页组件并优化布局样式
- 新增 AppPagination 组件,统一分页样式与行为 - 将多个页面的自定义分页替换为标准分页组件 - 修复布局容器高度计算问题,确保内容区域正确滚动 - 调整表格容器样式,支持响应式布局和水平滚动 - 优化国际化配置,支持 Ant Design 组件多语言 - 统一分页边距和边框样式,提升视觉一致性dev_na
parent
6d46998abe
commit
2b30744d2e
|
|
@ -1,13 +1,19 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { ConfigProvider, theme, App as AntdApp } from "antd";
|
import { ConfigProvider, theme, App as AntdApp } from "antd";
|
||||||
|
import zhCN from "antd/locale/zh_CN";
|
||||||
|
import enUS from "antd/locale/en_US";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import AppRoutes from "./routes";
|
import AppRoutes from "./routes";
|
||||||
import { getOpenPlatformConfig } from "./api";
|
import { getOpenPlatformConfig } from "./api";
|
||||||
import {useThemeStore} from "./store/themeStore";
|
import { useThemeStore } from "./store/themeStore";
|
||||||
import type { SysPlatformConfig } from "./types";
|
import type { SysPlatformConfig } from "./types";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [config, setConfig] = useState<SysPlatformConfig | null>(null);
|
const [config, setConfig] = useState<SysPlatformConfig | null>(null);
|
||||||
const { colorPrimary, themeMode, initTheme } = useThemeStore();
|
const { colorPrimary, themeMode, initTheme } = useThemeStore();
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
const antdLocale = useMemo(() => (i18n.language === "en-US" ? enUS : zhCN), [i18n.language]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initTheme();
|
initTheme();
|
||||||
const fetchConfig = async () => {
|
const fetchConfig = async () => {
|
||||||
|
|
@ -37,6 +43,7 @@ export default function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigProvider
|
<ConfigProvider
|
||||||
|
locale={antdLocale}
|
||||||
theme={{
|
theme={{
|
||||||
algorithm: themeMode === 'tech' ? theme.darkAlgorithm : theme.defaultAlgorithm,
|
algorithm: themeMode === 'tech' ? theme.darkAlgorithm : theme.defaultAlgorithm,
|
||||||
token: {
|
token: {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
.app-pagination-container {
|
||||||
|
margin-top: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-pagination-container .ant-pagination {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-pagination-container .ant-pagination-total-text {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Pagination, PaginationProps } from 'antd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
export interface AppPaginationProps extends PaginationProps {
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AppPagination(props: AppPaginationProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const mergedClassName = ['app-global-pagination', props.className].filter(Boolean).join(' ');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="app-pagination-container">
|
||||||
|
<Pagination
|
||||||
|
className={mergedClassName}
|
||||||
|
showSizeChanger
|
||||||
|
showQuickJumper
|
||||||
|
showTotal={(total) => t('common.total', { total })}
|
||||||
|
pageSizeOptions={['10', '20', '50', '100']}
|
||||||
|
size="default"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -62,6 +62,7 @@ function ListTable<T extends Record<string, any>>({
|
||||||
? false
|
? false
|
||||||
: {
|
: {
|
||||||
...pagination,
|
...pagination,
|
||||||
|
className: ["app-global-pagination", pagination.className].filter(Boolean).join(" "),
|
||||||
showTotal: (total: number) => (
|
showTotal: (total: number) => (
|
||||||
<div className="table-selection-info">
|
<div className="table-selection-info">
|
||||||
{isAllPagesSelected ? (
|
{isAllPagesSelected ? (
|
||||||
|
|
|
||||||
|
|
@ -182,10 +182,11 @@ body::after {
|
||||||
|
|
||||||
.app-page {
|
.app-page {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-page--contained {
|
.app-page--contained {
|
||||||
|
|
@ -210,12 +211,37 @@ body::after {
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-page__content-card {
|
.app-page__content-card {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-page__panel-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-page__content-card .ant-card-body,
|
.app-page__content-card .ant-card-body,
|
||||||
.app-page__panel-card .ant-card-body {
|
.app-page__panel-card .ant-card-body {
|
||||||
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-page__table-wrap {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-page__table-wrap .ant-table-wrapper {
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-page__toolbar {
|
.app-page__toolbar {
|
||||||
|
|
@ -796,3 +822,53 @@ body::after {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Global Pagination Style */
|
||||||
|
.ant-table-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.ant-table-wrapper .ant-spin-nested-loading {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.ant-table-wrapper .ant-spin-container {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-wrapper .ant-table {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-wrapper .ant-table-pagination.ant-pagination.app-global-pagination,
|
||||||
|
.app-global-pagination.ant-pagination {
|
||||||
|
margin: auto 0 0 0 !important;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-top: 1px solid var(--app-border-color);
|
||||||
|
background: var(--app-bg-card);
|
||||||
|
border-radius: 0 0 16px 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-wrapper .ant-table-pagination.ant-pagination.app-global-pagination .ant-pagination-total-text,
|
||||||
|
.app-global-pagination.ant-pagination .ant-pagination-total-text {
|
||||||
|
margin-right: auto;
|
||||||
|
color: var(--app-text-muted);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-global-pagination.ant-pagination .ant-pagination-options,
|
||||||
|
.ant-table-wrapper .ant-table-pagination.ant-pagination.app-global-pagination .ant-pagination-options {
|
||||||
|
margin-inline-start: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-global-pagination.ant-pagination .ant-pagination-options-quick-jumper,
|
||||||
|
.ant-table-wrapper .ant-table-pagination.ant-pagination.app-global-pagination .ant-pagination-options-quick-jumper {
|
||||||
|
margin-inline-start: 12px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -396,7 +396,7 @@ export default function AppLayout() {
|
||||||
</Sider>
|
</Sider>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Layout style={{ flex: 1, minWidth: 0 }}>
|
<Layout style={{ flex: 1, minWidth: 0, minHeight: 0 }}>
|
||||||
<Header
|
<Header
|
||||||
style={{
|
style={{
|
||||||
background: "var(--app-bg-card)",
|
background: "var(--app-bg-card)",
|
||||||
|
|
@ -446,6 +446,8 @@ export default function AppLayout() {
|
||||||
|
|
||||||
<Content
|
<Content
|
||||||
style={{
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
minHeight: 0,
|
||||||
margin: "24px 24px 12px",
|
margin: "24px 24px 12px",
|
||||||
padding: location.pathname === "/" || location.pathname === "/home" ? 0 : 24,
|
padding: location.pathname === "/" || location.pathname === "/home" ? 0 : 24,
|
||||||
background: "var(--app-bg-card)",
|
background: "var(--app-bg-card)",
|
||||||
|
|
@ -495,4 +497,3 @@ export default function AppLayout() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
"refresh": "刷新",
|
"refresh": "刷新",
|
||||||
"success": "操作成功",
|
"success": "操作成功",
|
||||||
"error": "操作失败",
|
"error": "操作失败",
|
||||||
"total": "共 {{total}} 条数据",
|
"total": "共{{total}}条数据",
|
||||||
"loading": "加载中...",
|
"loading": "加载中...",
|
||||||
"view": "查看"
|
"view": "查看"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -207,11 +207,10 @@
|
||||||
|
|
||||||
.role-list-pagination {
|
.role-list-pagination {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding-top: 16px;
|
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
border-top: 1px solid #f1f5f9;
|
margin-left: -16px;
|
||||||
display: flex;
|
margin-right: -16px;
|
||||||
justify-content: flex-end;
|
margin-bottom: -16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Detail Card Adjustments */
|
/* Detail Card Adjustments */
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Avatar, Button, Card, Col, Drawer, Empty, Form, Input, List, Pagination, Radio, Modal, Popconfirm, Select, Space, Table, Tabs, Tag, Tooltip, Tree, Typography, Row, App } from 'antd';
|
import { Avatar, Button, Card, Col, Drawer, Empty, Form, Input, List, Radio, Modal, Popconfirm, Select, Space, Table, Tabs, Tag, Tooltip, Tree, Typography, Row, App } from 'antd';
|
||||||
import type { DataNode } from "antd/es/tree";
|
import type { DataNode } from "antd/es/tree";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
|
|
@ -35,7 +35,9 @@ import {
|
||||||
} from "@/api";
|
} from "@/api";
|
||||||
import { useDict } from "@/hooks/useDict";
|
import { useDict } from "@/hooks/useDict";
|
||||||
import { usePermission } from "@/hooks/usePermission";
|
import { usePermission } from "@/hooks/usePermission";
|
||||||
|
import AppPagination from "@/components/shared/AppPagination";
|
||||||
import PageHeader from "@/components/shared/PageHeader";
|
import PageHeader from "@/components/shared/PageHeader";
|
||||||
|
import { getStandardPagination } from "@/utils/pagination";
|
||||||
import type { RoleDataScope, SysOrg, SysPermission, SysRole, SysTenant, SysUser } from "@/types";
|
import type { RoleDataScope, SysOrg, SysPermission, SysRole, SysTenant, SysUser } from "@/types";
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
|
|
||||||
|
|
@ -492,14 +494,11 @@ export default function Roles() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="role-list-pagination">
|
<div className="role-list-pagination">
|
||||||
<Pagination
|
<AppPagination
|
||||||
current={rolePage.current}
|
current={rolePage.current}
|
||||||
pageSize={rolePage.size}
|
pageSize={rolePage.size}
|
||||||
total={rolePage.total}
|
total={rolePage.total}
|
||||||
size="small"
|
|
||||||
onChange={handleRolePageChange}
|
onChange={handleRolePageChange}
|
||||||
showSizeChanger
|
|
||||||
pageSizeOptions={["10", "20", "50"]}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -589,7 +588,7 @@ export default function Roles() {
|
||||||
size="small"
|
size="small"
|
||||||
loading={loadingUsers}
|
loading={loadingUsers}
|
||||||
dataSource={roleUsers}
|
dataSource={roleUsers}
|
||||||
pagination={{ pageSize: 10, size: "small" }}
|
pagination={getStandardPagination(roleUsers.length, 1, 10)}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: "用户信息",
|
title: "用户信息",
|
||||||
|
|
@ -634,7 +633,7 @@ export default function Roles() {
|
||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<Input placeholder="搜索用户名或显示名称" prefix={<SearchOutlined />} value={userSearchText} onChange={(event) => setUserSearchText(event.target.value)} allowClear />
|
<Input placeholder="搜索用户名或显示名称" prefix={<SearchOutlined />} value={userSearchText} onChange={(event) => setUserSearchText(event.target.value)} allowClear />
|
||||||
</div>
|
</div>
|
||||||
<Table rowKey="userId" size="small" dataSource={filteredModalUsers} pagination={{ pageSize: 6 }} rowSelection={{ selectedRowKeys: selectedUserKeys, onChange: (keys) => setSelectedUserKeys(keys as number[]) }} columns={[{ title: "显示名称", dataIndex: "displayName" }, { title: "用户名", dataIndex: "username" }, { title: "手机号", dataIndex: "phone" }]} />
|
<Table rowKey="userId" size="small" dataSource={filteredModalUsers} pagination={getStandardPagination(filteredModalUsers.length, 1, 6)} rowSelection={{ selectedRowKeys: selectedUserKeys, onChange: (keys) => setSelectedUserKeys(keys as number[]) }} columns={[{ title: "显示名称", dataIndex: "displayName" }, { title: "用户名", dataIndex: "username" }, { title: "手机号", dataIndex: "phone" }]} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Drawer title={editing ? "编辑角色" : "新增角色"} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={420} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>{"取消"}</Button><Button type="primary" loading={saving} onClick={() => void submitBasic()}>{"保存"}</Button></div>}>
|
<Drawer title={editing ? "编辑角色" : "新增角色"} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={420} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>{"取消"}</Button><Button type="primary" loading={saving} onClick={() => void submitBasic()}>{"保存"}</Button></div>}>
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ApartmentOutlined, DeleteOutlined, EditOutlined, MinusCircleOutlined, PlusOutlined, SearchOutlined, ShopOutlined, UploadOutlined, UserOutlined } from "@ant-design/icons";
|
import { ApartmentOutlined, DeleteOutlined, EditOutlined, MinusCircleOutlined, PlusOutlined, SearchOutlined, ShopOutlined, UploadOutlined, UserOutlined } from "@ant-design/icons";
|
||||||
import { createUser, deleteUser, getUserDetail, listOrgs, listRoles, listTenants, listUserRoles, listUsers, saveUserRoles, updateUser, uploadPlatformAsset } from "@/api";
|
import { createUser, deleteUser, getUserDetail, listOrgs, listRoles, listTenants, listUserRoles, listUsers, saveUserRoles, updateUser, uploadPlatformAsset } from "@/api";
|
||||||
|
import AppPagination from "@/components/shared/AppPagination";
|
||||||
import { useDict } from "@/hooks/useDict";
|
import { useDict } from "@/hooks/useDict";
|
||||||
import { usePermission } from "@/hooks/usePermission";
|
import { usePermission } from "@/hooks/usePermission";
|
||||||
import PageHeader from "@/components/shared/PageHeader";
|
import PageHeader from "@/components/shared/PageHeader";
|
||||||
import { getStandardPagination } from "@/utils/pagination";
|
|
||||||
import type { SysOrg, SysRole, SysTenant, SysUser } from "@/types";
|
import type { SysOrg, SysRole, SysTenant, SysUser } from "@/types";
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
|
|
||||||
|
|
@ -399,9 +399,10 @@ export default function Users() {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<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" } }}>
|
<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="flex-1 min-h-0 h-full">
|
<div className="app-page__table-wrap" style={{ flex: 1, minHeight: 0, overflow: "auto", padding: "0 24px" }}>
|
||||||
<Table rowKey="userId" columns={columns} dataSource={filteredData} loading={loading} size="middle" scroll={{ y: "calc(100vh - 420px)" }} pagination={getStandardPagination(filteredData.length, current, pageSize, (page, size) => { setCurrent(page); setPageSize(size); })} />
|
<Table rowKey="userId" columns={columns} dataSource={filteredData} loading={loading} size="middle" scroll={{ x: "max-content" }} pagination={false} />
|
||||||
</div>
|
</div>
|
||||||
|
<AppPagination current={current} pageSize={pageSize} total={filteredData.length} onChange={(page, size) => { setCurrent(page); setPageSize(size); }} />
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Drawer title={<div className="user-drawer-title"><UserOutlined className="mr-2" aria-hidden="true" />{editing ? t("users.drawerTitleEdit") : t("users.drawerTitleCreate")}</div>} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={520} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>{t("common.cancel")}</Button><Button type="primary" loading={saving} onClick={submit}>{t("common.save")}</Button></div>}>
|
<Drawer title={<div className="user-drawer-title"><UserOutlined className="mr-2" aria-hidden="true" />{editing ? t("users.drawerTitleEdit") : t("users.drawerTitleCreate")}</div>} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={520} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>{t("common.cancel")}</Button><Button type="primary" loading={saving} onClick={submit}>{t("common.save")}</Button></div>}>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||||
import { ClusterOutlined, KeyOutlined, SafetyCertificateOutlined, SaveOutlined, SearchOutlined } from "@ant-design/icons";
|
import { ClusterOutlined, KeyOutlined, SafetyCertificateOutlined, SaveOutlined, SearchOutlined } from "@ant-design/icons";
|
||||||
import { listPermissions, listRolePermissions, listRoles, saveRolePermissions } from "@/api";
|
import { listPermissions, listRolePermissions, listRoles, saveRolePermissions } from "@/api";
|
||||||
import PageHeader from "@/components/shared/PageHeader";
|
import PageHeader from "@/components/shared/PageHeader";
|
||||||
|
import { getStandardPagination } from "@/utils/pagination";
|
||||||
import type { SysPermission, SysRole } from "@/types";
|
import type { SysPermission, SysRole } from "@/types";
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
@ -181,7 +182,7 @@ export default function RolePermissionBinding() {
|
||||||
|
|
||||||
<Row gutter={24} className="app-page__split" style={{ height: "calc(100vh - 180px)" }}>
|
<Row gutter={24} className="app-page__split" style={{ height: "calc(100vh - 180px)" }}>
|
||||||
<Col xs={24} lg={10} style={{ height: "100%" }}>
|
<Col xs={24} lg={10} style={{ height: "100%" }}>
|
||||||
<Card title={<Space><SafetyCertificateOutlined aria-hidden="true" /><span>{t("rolePerm.roleList")}</span></Space>} className="app-page__panel-card full-height-card">
|
<Card title={<Space><SafetyCertificateOutlined aria-hidden="true" /><span>{t("rolePerm.roleList")}</span></Space>} className="app-page__panel-card full-height-card" styles={{ body: { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden" } }}>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("rolePerm.searchRole")}
|
placeholder={t("rolePerm.searchRole")}
|
||||||
|
|
@ -192,12 +193,13 @@ export default function RolePermissionBinding() {
|
||||||
aria-label={t("rolePerm.searchRole")}
|
aria-label={t("rolePerm.searchRole")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ height: "calc(100% - 60px)", overflowY: "auto" }}>
|
<div style={{ flex: 1, minHeight: 0 }}>
|
||||||
<Table
|
<Table
|
||||||
rowKey="roleId"
|
rowKey="roleId"
|
||||||
size="middle"
|
size="middle"
|
||||||
loading={loadingRoles}
|
loading={loadingRoles}
|
||||||
dataSource={filteredRoles}
|
dataSource={filteredRoles}
|
||||||
|
scroll={{ y: "calc(100vh - 370px)" }}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
type: "radio",
|
type: "radio",
|
||||||
selectedRowKeys: selectedRoleId ? [selectedRoleId] : [],
|
selectedRowKeys: selectedRoleId ? [selectedRoleId] : [],
|
||||||
|
|
@ -207,7 +209,7 @@ export default function RolePermissionBinding() {
|
||||||
onClick: () => setSelectedRoleId(record.roleId),
|
onClick: () => setSelectedRoleId(record.roleId),
|
||||||
className: "cursor-pointer"
|
className: "cursor-pointer"
|
||||||
})}
|
})}
|
||||||
pagination={{ pageSize: 10, showTotal: (total) => t("common.total", { total }) }}
|
pagination={getStandardPagination(filteredRoles.length, 1, 10)}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: t("roles.roleName"),
|
title: t("roles.roleName"),
|
||||||
|
|
@ -235,6 +237,7 @@ export default function RolePermissionBinding() {
|
||||||
<Card
|
<Card
|
||||||
title={<Space><KeyOutlined aria-hidden="true" /><span>{t("rolePerm.permConfig")}</span></Space>}
|
title={<Space><KeyOutlined aria-hidden="true" /><span>{t("rolePerm.permConfig")}</span></Space>}
|
||||||
className="app-page__panel-card full-height-card"
|
className="app-page__panel-card full-height-card"
|
||||||
|
styles={{ body: { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden" } }}
|
||||||
extra={selectedRole ? <Tag color="blue">{t("rolePerm.currentRole")}: {selectedRole.roleName}</Tag> : null}
|
extra={selectedRole ? <Tag color="blue">{t("rolePerm.currentRole")}: {selectedRole.roleName}</Tag> : null}
|
||||||
>
|
>
|
||||||
{selectedRoleId ? (
|
{selectedRoleId ? (
|
||||||
|
|
@ -267,4 +270,3 @@ export default function RolePermissionBinding() {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { listRoles, listUserRoles, listUsers, saveUserRoles } from "@/api";
|
import { listRoles, listUserRoles, listUsers, saveUserRoles } from "@/api";
|
||||||
import { SaveOutlined, SearchOutlined, TeamOutlined, UserOutlined } from "@ant-design/icons";
|
import { SaveOutlined, SearchOutlined, TeamOutlined, UserOutlined } from "@ant-design/icons";
|
||||||
|
import { getStandardPagination } from "@/utils/pagination";
|
||||||
import type { SysRole, SysUser } from "@/types";
|
import type { SysRole, SysUser } from "@/types";
|
||||||
import PageHeader from "@/components/shared/PageHeader";
|
import PageHeader from "@/components/shared/PageHeader";
|
||||||
|
|
||||||
|
|
@ -101,7 +102,7 @@ export default function UserRoleBinding() {
|
||||||
|
|
||||||
<Row gutter={24} className="app-page__split" style={{ height: "calc(100vh - 180px)" }}>
|
<Row gutter={24} className="app-page__split" style={{ height: "calc(100vh - 180px)" }}>
|
||||||
<Col xs={24} lg={12} style={{ height: "100%" }}>
|
<Col xs={24} lg={12} style={{ height: "100%" }}>
|
||||||
<Card title={<Space><UserOutlined aria-hidden="true" /><span>{t("userRole.userList")}</span></Space>} className="app-page__panel-card full-height-card">
|
<Card title={<Space><UserOutlined aria-hidden="true" /><span>{t("userRole.userList")}</span></Space>} className="app-page__panel-card full-height-card" styles={{ body: { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden" } }}>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("userRole.searchUser")}
|
placeholder={t("userRole.searchUser")}
|
||||||
|
|
@ -112,12 +113,13 @@ export default function UserRoleBinding() {
|
||||||
aria-label={t("userRole.searchUser")}
|
aria-label={t("userRole.searchUser")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ height: "calc(100% - 60px)", overflowY: "auto" }}>
|
<div style={{ flex: 1, minHeight: 0 }}>
|
||||||
<Table
|
<Table
|
||||||
rowKey="userId"
|
rowKey="userId"
|
||||||
size="middle"
|
size="middle"
|
||||||
loading={loadingUsers}
|
loading={loadingUsers}
|
||||||
dataSource={filteredUsers}
|
dataSource={filteredUsers}
|
||||||
|
scroll={{ y: "calc(100vh - 370px)" }}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
type: "radio",
|
type: "radio",
|
||||||
selectedRowKeys: selectedUserId ? [selectedUserId] : [],
|
selectedRowKeys: selectedUserId ? [selectedUserId] : [],
|
||||||
|
|
@ -127,7 +129,7 @@ export default function UserRoleBinding() {
|
||||||
onClick: () => setSelectedUserId(record.userId),
|
onClick: () => setSelectedUserId(record.userId),
|
||||||
className: "cursor-pointer"
|
className: "cursor-pointer"
|
||||||
})}
|
})}
|
||||||
pagination={{ pageSize: 10, showTotal: (total) => t("common.total", { total }) }}
|
pagination={getStandardPagination(filteredUsers.length, 1, 10)}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: t("users.userInfo"),
|
title: t("users.userInfo"),
|
||||||
|
|
@ -155,10 +157,11 @@ export default function UserRoleBinding() {
|
||||||
<Card
|
<Card
|
||||||
title={<Space><TeamOutlined aria-hidden="true" /><span>{t("userRole.grantRoles")}</span></Space>}
|
title={<Space><TeamOutlined aria-hidden="true" /><span>{t("userRole.grantRoles")}</span></Space>}
|
||||||
className="app-page__panel-card full-height-card"
|
className="app-page__panel-card full-height-card"
|
||||||
|
styles={{ body: { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden" } }}
|
||||||
extra={selectedUser ? <Tag color="blue">{t("userRole.editing")}: {selectedUser.displayName}</Tag> : null}
|
extra={selectedUser ? <Tag color="blue">{t("userRole.editing")}: {selectedUser.displayName}</Tag> : null}
|
||||||
>
|
>
|
||||||
{selectedUserId ? (
|
{selectedUserId ? (
|
||||||
<div style={{ padding: "8px 0", height: "100%", overflowY: "auto" }}>
|
<div style={{ padding: "8px 0", flex: 1, minHeight: 0, overflowY: "auto" }}>
|
||||||
<Checkbox.Group style={{ width: "100%" }} value={checkedRoleIds} onChange={(values) => setCheckedRoleIds(values as number[])} disabled={loadingRoles}>
|
<Checkbox.Group style={{ width: "100%" }} value={checkedRoleIds} onChange={(values) => setCheckedRoleIds(values as number[])} disabled={loadingRoles}>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
{roles.map((role) => (
|
{roles.map((role) => (
|
||||||
|
|
@ -188,4 +191,4 @@ export default function UserRoleBinding() {
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import {
|
||||||
testLocalModelConnectivity,
|
testLocalModelConnectivity,
|
||||||
updateAiModel,
|
updateAiModel,
|
||||||
} from "../../api/business/aimodel";
|
} from "../../api/business/aimodel";
|
||||||
|
import AppPagination from "../../components/shared/AppPagination";
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
@ -362,9 +363,11 @@ const AiModels: React.FC = () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 24 }}>
|
<div className="app-page">
|
||||||
<Card
|
<Card
|
||||||
|
className="app-page__content-card flex-1 flex flex-col overflow-hidden"
|
||||||
title="AI 模型配置"
|
title="AI 模型配置"
|
||||||
|
styles={{ body: { padding: 0, flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" } }}
|
||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
<Input
|
<Input
|
||||||
|
|
@ -380,7 +383,8 @@ const AiModels: React.FC = () => {
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Tabs
|
<div style={{ padding: "16px 24px 0", flexShrink: 0 }}>
|
||||||
|
<Tabs
|
||||||
activeKey={activeType}
|
activeKey={activeType}
|
||||||
onChange={(key) => {
|
onChange={(key) => {
|
||||||
setActiveType(key as ModelType);
|
setActiveType(key as ModelType);
|
||||||
|
|
@ -390,21 +394,25 @@ const AiModels: React.FC = () => {
|
||||||
{ key: "ASR", label: "ASR 模型" },
|
{ key: "ASR", label: "ASR 模型" },
|
||||||
{ key: "LLM", label: "LLM 模型" },
|
{ key: "LLM", label: "LLM 模型" },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<Table
|
<div className="app-page__table-wrap" style={{ flex: 1, minHeight: 0, overflow: "auto", padding: "0 24px" }}>
|
||||||
rowKey="id"
|
<Table
|
||||||
columns={columns}
|
rowKey="id"
|
||||||
dataSource={data}
|
columns={columns}
|
||||||
loading={loading}
|
dataSource={data}
|
||||||
pagination={{
|
loading={loading}
|
||||||
current,
|
scroll={{ x: "max-content" }}
|
||||||
pageSize: size,
|
pagination={false}
|
||||||
total,
|
/>
|
||||||
onChange: (page, pageSize) => {
|
</div>
|
||||||
setCurrent(page);
|
<AppPagination
|
||||||
setSize(pageSize);
|
current={current}
|
||||||
},
|
pageSize={size}
|
||||||
|
total={total}
|
||||||
|
onChange={(page, pageSize) => {
|
||||||
|
setCurrent(page);
|
||||||
|
setSize(pageSize);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
|
|
@ -410,7 +410,17 @@ export default function ClientManagement() {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<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" } }}>
|
<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" } }}>
|
||||||
<Table rowKey="id" columns={columns} dataSource={pagedRecords} loading={loading || groupLoading || platformLoading} locale={platformGroups.length === 0 ? { emptyText: <Empty description="未配置 client_platform 字典项" /> } : undefined} scroll={{ x: 960, y: "calc(100vh - 360px)" }} pagination={getStandardPagination(filteredRecords.length, page, pageSize, (nextPage, nextSize) => { setPage(nextPage); setPageSize(nextSize); })} />
|
<div className="app-page__table-wrap">
|
||||||
|
<Table
|
||||||
|
rowKey="id"
|
||||||
|
columns={columns}
|
||||||
|
dataSource={pagedRecords}
|
||||||
|
loading={loading || groupLoading || platformLoading}
|
||||||
|
locale={platformGroups.length === 0 ? { emptyText: <Empty description="未配置 client_platform 字典项" /> } : undefined}
|
||||||
|
scroll={{ x: "max-content", y: "calc(100vh - 360px)" }}
|
||||||
|
pagination={getStandardPagination(filteredRecords.length, page, pageSize, (nextPage: number, nextSize: number) => { setPage(nextPage); setPageSize(nextSize); })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Drawer title={editing ? "编辑客户端版本" : "新增客户端版本"} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={680} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>取消</Button><Button type="primary" icon={<UploadOutlined />} loading={saving} onClick={() => void handleSubmit()}>保存</Button></div>}>
|
<Drawer title={editing ? "编辑客户端版本" : "新增客户端版本"} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={680} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>取消</Button><Button type="primary" icon={<UploadOutlined />} loading={saving} onClick={() => void handleSubmit()}>保存</Button></div>}>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { App, Avatar, Button, Card, Col, Drawer, Form, Input, InputNumber, Popconfirm, Row, Select, Space, Switch, Table, Tag, Typography, Upload } from "antd";
|
import { App, Avatar, Button, Card, Col, Drawer, Form, Input, InputNumber, Popconfirm, Row, Select, Space, Switch, Table, Tag, Typography, Upload } from "antd";
|
||||||
import type { ColumnsType } from "antd/es/table";
|
import type { ColumnsType } from "antd/es/table";
|
||||||
import { AppstoreOutlined, DeleteOutlined, EditOutlined, GlobalOutlined, LinkOutlined, PictureOutlined, PlusOutlined, ReloadOutlined, RobotOutlined, SaveOutlined, SearchOutlined, UploadOutlined } from "@ant-design/icons";
|
import { AppstoreOutlined, DeleteOutlined, EditOutlined, GlobalOutlined, LinkOutlined, PictureOutlined, PlusOutlined, ReloadOutlined, RobotOutlined, SaveOutlined, SearchOutlined, UploadOutlined } from "@ant-design/icons";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
@ -31,7 +31,7 @@ const STATUS_OPTIONS = [
|
||||||
{ label: "已停用", value: "disabled" },
|
{ label: "已停用", value: "disabled" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
function normalizeAppInfo(value: ExternalAppVO["appInfo"]): ExternalAppFormValues["appInfo"] {
|
function normalizeAppInfo(value: ExternalAppVO["appInfo"]): NonNullable<ExternalAppFormValues["appInfo"]> {
|
||||||
if (!value || typeof value !== "object") {
|
if (!value || typeof value !== "object") {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
@ -320,7 +320,9 @@ export default function ExternalAppManagement() {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<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" } }}>
|
<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" } }}>
|
||||||
<Table rowKey="id" columns={columns} dataSource={pagedRecords} loading={loading} scroll={{ x: 980, y: "calc(100vh - 360px)" }} pagination={getStandardPagination(filteredRecords.length, page, pageSize, (nextPage, nextSize) => { setPage(nextPage); setPageSize(nextSize); })} />
|
<div className="app-page__table-wrap">
|
||||||
|
<Table rowKey="id" columns={columns} dataSource={pagedRecords} loading={loading} scroll={{ x: "max-content", y: "calc(100vh - 360px)" }} pagination={getStandardPagination(filteredRecords.length, page, pageSize, (nextPage: number, nextSize: number) => { setPage(nextPage); setPageSize(nextSize); })} />
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Drawer title={editing ? "编辑外部应用" : "新增外部应用"} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={700} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>取消</Button><Button type="primary" icon={<SaveOutlined />} loading={saving} onClick={() => void handleSubmit()}>保存</Button></div>}>
|
<Drawer title={editing ? "编辑外部应用" : "新增外部应用"} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={700} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>取消</Button><Button type="primary" icon={<SaveOutlined />} loading={saving} onClick={() => void handleSubmit()}>保存</Button></div>}>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import {
|
||||||
updateHotWord,
|
updateHotWord,
|
||||||
type HotWordVO,
|
type HotWordVO,
|
||||||
} from "../../api/business/hotword";
|
} from "../../api/business/hotword";
|
||||||
|
import AppPagination from "../../components/shared/AppPagination";
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
@ -246,10 +247,11 @@ const HotWords: React.FC = () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col overflow-hidden">
|
<div className="app-page">
|
||||||
<Card
|
<Card
|
||||||
className="flex-1 overflow-hidden shadow-sm"
|
className="app-page__content-card shadow-sm"
|
||||||
styles={{ body: { padding: 24, height: "100%", display: "flex", flexDirection: "column", overflow: "hidden" } }}
|
style={{ flex: 1, minHeight: 0 }}
|
||||||
|
styles={{ body: { padding: 0, flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" } }}
|
||||||
title="热词管理"
|
title="热词管理"
|
||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
|
@ -296,25 +298,25 @@ const HotWords: React.FC = () => {
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="min-h-0 flex-1">
|
<div className="app-page__table-wrap" style={{ flex: 1, minHeight: 0, overflow: "auto", padding: "24px 24px 0" }}>
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
scroll={{ y: "calc(100vh - 440px)" }}
|
scroll={{ x: "max-content" }}
|
||||||
pagination={{
|
pagination={false}
|
||||||
current,
|
|
||||||
pageSize: size,
|
|
||||||
total,
|
|
||||||
showTotal: (value) => `共 ${value} 条`,
|
|
||||||
onChange: (page, pageSize) => {
|
|
||||||
setCurrent(page);
|
|
||||||
setSize(pageSize);
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<AppPagination
|
||||||
|
current={current}
|
||||||
|
pageSize={size}
|
||||||
|
total={total}
|
||||||
|
onChange={(page, pageSize) => {
|
||||||
|
setCurrent(page);
|
||||||
|
setSize(pageSize);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { getPromptPage, PromptTemplateVO } from '../../api/business/prompt';
|
||||||
import { getHotWordPage, HotWordVO } from '../../api/business/hotword';
|
import { getHotWordPage, HotWordVO } from '../../api/business/hotword';
|
||||||
import { listUsers } from '../../api';
|
import { listUsers } from '../../api';
|
||||||
import { SysUser } from '../../types';
|
import { SysUser } from '../../types';
|
||||||
|
import AppPagination from '../../components/shared/AppPagination';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
|
@ -225,7 +226,7 @@ const Meetings: React.FC = () => {
|
||||||
const [data, setData] = useState<MeetingVO[]>([]);
|
const [data, setData] = useState<MeetingVO[]>([]);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [current, setCurrent] = useState(1);
|
const [current, setCurrent] = useState(1);
|
||||||
const [size, setSize] = useState(8);
|
const [size, setSize] = useState(10);
|
||||||
const [searchTitle, setSearchTitle] = useState('');
|
const [searchTitle, setSearchTitle] = useState('');
|
||||||
const [viewType, setViewType] = useState<'all' | 'created' | 'involved'>('all');
|
const [viewType, setViewType] = useState<'all' | 'created' | 'involved'>('all');
|
||||||
const [createDrawerVisible, setCreateDrawerVisible] = useState(false);
|
const [createDrawerVisible, setCreateDrawerVisible] = useState(false);
|
||||||
|
|
@ -364,11 +365,7 @@ const Meetings: React.FC = () => {
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{total > 0 && (
|
{total > 0 && <AppPagination current={current} pageSize={size} total={total} onChange={(p, s) => { setCurrent(p); setSize(s); }} />}
|
||||||
<div style={{ flexShrink: 0, display: 'flex', justifyContent: 'center', padding: '16px 0 8px 0' }}>
|
|
||||||
<Pagination current={current} pageSize={size} total={total} onChange={(p, s) => { setCurrent(p); setSize(s); }} showTotal={(total) => <Text type="secondary" style={{ fontSize: 12 }}>为您找到 {total} 场会议</Text>} size="small" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MeetingCreateDrawer
|
<MeetingCreateDrawer
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Card, Button, Input, Space, Drawer, Form, Select, Tag, Popconfirm, Typography, Divider, Tooltip, Row, Col, List, Empty, Skeleton, Switch, Modal, Pagination, App } from 'antd';
|
import { Card, Button, Input, Space, Drawer, Form, Select, Tag, Popconfirm, Typography, Divider, Tooltip, Row, Col, List, Empty, Skeleton, Switch, Modal, App } from 'antd';
|
||||||
import { PlusOutlined, EditOutlined, DeleteOutlined, CopyOutlined, SearchOutlined, SaveOutlined, StarFilled } from '@ant-design/icons';
|
import { PlusOutlined, EditOutlined, DeleteOutlined, CopyOutlined, SearchOutlined, SaveOutlined, StarFilled } from '@ant-design/icons';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import { useDict } from '../../hooks/useDict';
|
import { useDict } from '../../hooks/useDict';
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
PromptTemplateVO,
|
PromptTemplateVO,
|
||||||
PromptTemplateDTO
|
PromptTemplateDTO
|
||||||
} from '../../api/business/prompt';
|
} from '../../api/business/prompt';
|
||||||
|
import AppPagination from '../../components/shared/AppPagination';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
|
@ -287,8 +288,8 @@ const PromptTemplates: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '32px', background: 'var(--app-bg-page)', minHeight: '100%', overflowY: 'auto' }}>
|
<div style={{ padding: '32px', background: 'var(--app-bg-page)', height: 'calc(100vh - 64px)', overflow: 'hidden' }}>
|
||||||
<div style={{ maxWidth: 1400, margin: '0 auto' }}>
|
<div style={{ maxWidth: 1400, margin: '0 auto', height: '100%', display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 32 }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 32 }}>
|
||||||
<Title level={3} style={{ margin: 0 }}>提示词模板</Title>
|
<Title level={3} style={{ margin: 0 }}>提示词模板</Title>
|
||||||
<Button type="primary" icon={<PlusOutlined />} size="large" onClick={() => handleOpenDrawer()} style={{ borderRadius: 6 }}>
|
<Button type="primary" icon={<PlusOutlined />} size="large" onClick={() => handleOpenDrawer()} style={{ borderRadius: 6 }}>
|
||||||
|
|
@ -313,40 +314,40 @@ const PromptTemplates: React.FC = () => {
|
||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Skeleton loading={loading} active>
|
<Card className="app-page__content-card" style={{ flex: 1, minHeight: 0 }} styles={{ body: { padding: 0, height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden' } }}>
|
||||||
{Object.keys(groupedData).length === 0 ? (
|
<Skeleton loading={loading} active style={{ height: '100%' }}>
|
||||||
<Empty description="暂无可用模板" />
|
{Object.keys(groupedData).length === 0 ? (
|
||||||
) : (
|
<div className="app-page__empty-state" style={{ padding: 24 }}>
|
||||||
<>
|
<Empty description="暂无可用模板" />
|
||||||
{Object.keys(groupedData).map(catKey => {
|
</div>
|
||||||
const catLabel = categories.find(c => c.itemValue === catKey)?.itemLabel || catKey;
|
) : (
|
||||||
return (
|
<>
|
||||||
<div key={catKey} style={{ marginBottom: 40 }}>
|
<div style={{ flex: 1, minHeight: 0, overflowY: 'auto', padding: '24px 24px 0' }}>
|
||||||
<Title level={4} style={{ marginBottom: 24, paddingLeft: 8, borderLeft: '4px solid #1890ff' }}>{catLabel}</Title>
|
{Object.keys(groupedData).map(catKey => {
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 24 }}>
|
const catLabel = categories.find(c => c.itemValue === catKey)?.itemLabel || catKey;
|
||||||
{groupedData[catKey].map(renderCard)}
|
return (
|
||||||
</div>
|
<div key={catKey} style={{ marginBottom: 40 }}>
|
||||||
</div>
|
<Title level={4} style={{ marginBottom: 24, paddingLeft: 8, borderLeft: '4px solid #1890ff' }}>{catLabel}</Title>
|
||||||
);
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 24 }}>
|
||||||
})}
|
{groupedData[catKey].map(renderCard)}
|
||||||
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', marginTop: 40, paddingBottom: 20 }}>
|
</div>
|
||||||
<Pagination
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<AppPagination
|
||||||
current={current}
|
current={current}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
total={total}
|
total={total}
|
||||||
showSizeChanger
|
|
||||||
showQuickJumper
|
|
||||||
onChange={(page, size) => {
|
onChange={(page, size) => {
|
||||||
setCurrent(page);
|
setCurrent(page);
|
||||||
setPageSize(size);
|
setPageSize(size);
|
||||||
}}
|
}}
|
||||||
showTotal={(total) => `共 ${total} 条模板`}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
</>
|
)}
|
||||||
)}
|
</Skeleton>
|
||||||
</Skeleton>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Drawer
|
<Drawer
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,12 @@ import {
|
||||||
SoundOutlined,
|
SoundOutlined,
|
||||||
SafetyCertificateOutlined
|
SafetyCertificateOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Badge, Button, Col, Empty, Form, Input, List, Pagination, Popconfirm, Progress, Row, Select, Spin, Space, Tabs, Tag, Typography, Upload, Tooltip, Divider, App } from 'antd';
|
import { Badge, Button, Col, Empty, Form, Input, List, Popconfirm, Progress, Row, Select, Spin, Space, Tabs, Tag, Typography, Upload, Tooltip, Divider, App } from 'antd';
|
||||||
import type { UploadProps } from 'antd';
|
import type { UploadProps } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { listUsers } from '../../api';
|
import { listUsers } from '../../api';
|
||||||
import { deleteSpeaker, getSpeakerPage, registerSpeaker, SpeakerVO } from '../../api/business/speaker';
|
import { deleteSpeaker, getSpeakerPage, registerSpeaker, SpeakerVO } from '../../api/business/speaker';
|
||||||
|
import AppPagination from '../../components/shared/AppPagination';
|
||||||
import { useAuth } from '../../hooks/useAuth';
|
import { useAuth } from '../../hooks/useAuth';
|
||||||
import type { SysUser } from '../../types';
|
import type { SysUser } from '../../types';
|
||||||
|
|
||||||
|
|
@ -718,19 +719,16 @@ const SpeakerReg: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
</Spin>
|
</Spin>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: '12px 20px 16px', borderTop: '1px solid var(--app-border-color)', display: 'flex', justifyContent: 'center' }}>
|
<div style={{ flexShrink: 0 }}>
|
||||||
<Pagination
|
<AppPagination
|
||||||
current={current}
|
current={current}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
total={total}
|
total={total}
|
||||||
size="small"
|
|
||||||
showSizeChanger
|
|
||||||
pageSizeOptions={['8', '12', '20', '50']}
|
pageSizeOptions={['8', '12', '20', '50']}
|
||||||
onChange={(page, size) => {
|
onChange={(page, size) => {
|
||||||
setCurrent(page);
|
setCurrent(page);
|
||||||
setPageSize(size);
|
setPageSize(size);
|
||||||
}}
|
}}
|
||||||
showTotal={count => `共 ${count} 条`}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -155,86 +155,88 @@ export default function Devices() {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<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" } }}>
|
<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">
|
||||||
<Table<DeviceInfo>
|
<Table<DeviceInfo>
|
||||||
rowKey="deviceId"
|
rowKey="deviceId"
|
||||||
dataSource={filteredData}
|
dataSource={filteredData}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
size="middle"
|
size="middle"
|
||||||
scroll={{ y: "calc(100vh - 350px)" }}
|
scroll={{ x: "max-content", y: "calc(100vh - 350px)" }}
|
||||||
pagination={getStandardPagination(filteredData.length, 1, 1000)}
|
pagination={getStandardPagination(filteredData.length, 1, 1000)}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: t("devicesExt.device"),
|
title: t("devicesExt.device"),
|
||||||
key: "device",
|
key: "device",
|
||||||
render: (_value: unknown, record) => (
|
render: (_value: unknown, record) => (
|
||||||
<Space>
|
|
||||||
<div className="device-icon-placeholder">
|
|
||||||
<DesktopOutlined aria-hidden="true" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="device-name font-medium">{record.deviceName || t("devicesExt.unnamedDevice")}</div>
|
|
||||||
<div className="device-code text-xs text-gray-400 tabular-nums">{record.deviceCode}</div>
|
|
||||||
</div>
|
|
||||||
</Space>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("devices.owner"),
|
|
||||||
key: "user",
|
|
||||||
render: (_value: unknown, record) => {
|
|
||||||
const owner = userMap[record.userId];
|
|
||||||
return owner ? (
|
|
||||||
<Space>
|
<Space>
|
||||||
<UserOutlined aria-hidden="true" style={{ color: "#8c8c8c" }} />
|
<div className="device-icon-placeholder">
|
||||||
<span>{owner.displayName}</span>
|
<DesktopOutlined aria-hidden="true" />
|
||||||
<Text type="secondary" style={{ fontSize: "12px" }} className="tabular-nums">
|
</div>
|
||||||
({t("devicesExt.ownerId")}: {record.userId})
|
<div>
|
||||||
</Text>
|
<div className="device-name font-medium">{record.deviceName || t("devicesExt.unnamedDevice")}</div>
|
||||||
|
<div className="device-code text-xs text-gray-400 tabular-nums">{record.deviceCode}</div>
|
||||||
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
) : (
|
)
|
||||||
<span className="tabular-nums">{t("devicesExt.ownerId")}: {record.userId}</span>
|
},
|
||||||
);
|
{
|
||||||
|
title: t("devices.owner"),
|
||||||
|
key: "user",
|
||||||
|
render: (_value: unknown, record) => {
|
||||||
|
const owner = userMap[record.userId];
|
||||||
|
return owner ? (
|
||||||
|
<Space>
|
||||||
|
<UserOutlined aria-hidden="true" style={{ color: "#8c8c8c" }} />
|
||||||
|
<span>{owner.displayName}</span>
|
||||||
|
<Text type="secondary" style={{ fontSize: "12px" }} className="tabular-nums">
|
||||||
|
({t("devicesExt.ownerId")}: {record.userId})
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
) : (
|
||||||
|
<span className="tabular-nums">{t("devicesExt.ownerId")}: {record.userId}</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("common.status"),
|
||||||
|
dataIndex: "status",
|
||||||
|
width: 100,
|
||||||
|
render: (status: number) => {
|
||||||
|
const item = statusDict.find((dictItem) => dictItem.itemValue === String(status));
|
||||||
|
return <Tag color={status === 1 ? "green" : "red"}>{item?.itemLabel || (status === 1 ? t("devicesExt.enabled") : t("devicesExt.disabled"))}</Tag>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("devices.updateTime"),
|
||||||
|
dataIndex: "updatedAt",
|
||||||
|
width: 180,
|
||||||
|
render: (text: string) => (
|
||||||
|
<Text type="secondary" className="tabular-nums">
|
||||||
|
{text?.replace("T", " ").substring(0, 19)}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("common.action"),
|
||||||
|
key: "action",
|
||||||
|
width: 120,
|
||||||
|
fixed: "right",
|
||||||
|
render: (_value: unknown, record) => (
|
||||||
|
<Space>
|
||||||
|
{can("device:update") ? (
|
||||||
|
<Button type="text" icon={<EditOutlined aria-hidden="true" />} onClick={() => openEdit(record)} aria-label={t("devicesExt.editDevice")} />
|
||||||
|
) : null}
|
||||||
|
{can("device:delete") ? (
|
||||||
|
<Popconfirm title={t("devicesExt.deleteDevice")} onConfirm={() => remove(record.deviceId)}>
|
||||||
|
<Button type="text" danger icon={<DeleteOutlined aria-hidden="true" />} aria-label={t("common.delete")} />
|
||||||
|
</Popconfirm>
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
]}
|
||||||
{
|
/>
|
||||||
title: t("common.status"),
|
</div>
|
||||||
dataIndex: "status",
|
|
||||||
width: 100,
|
|
||||||
render: (status: number) => {
|
|
||||||
const item = statusDict.find((dictItem) => dictItem.itemValue === String(status));
|
|
||||||
return <Tag color={status === 1 ? "green" : "red"}>{item?.itemLabel || (status === 1 ? t("devicesExt.enabled") : t("devicesExt.disabled"))}</Tag>;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("devices.updateTime"),
|
|
||||||
dataIndex: "updatedAt",
|
|
||||||
width: 180,
|
|
||||||
render: (text: string) => (
|
|
||||||
<Text type="secondary" className="tabular-nums">
|
|
||||||
{text?.replace("T", " ").substring(0, 19)}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("common.action"),
|
|
||||||
key: "action",
|
|
||||||
width: 120,
|
|
||||||
fixed: "right",
|
|
||||||
render: (_value: unknown, record) => (
|
|
||||||
<Space>
|
|
||||||
{can("device:update") ? (
|
|
||||||
<Button type="text" icon={<EditOutlined aria-hidden="true" />} onClick={() => openEdit(record)} aria-label={t("devicesExt.editDevice")} />
|
|
||||||
) : null}
|
|
||||||
{can("device:delete") ? (
|
|
||||||
<Popconfirm title={t("devicesExt.deleteDevice")} onConfirm={() => remove(record.deviceId)}>
|
|
||||||
<Button type="text" danger icon={<DeleteOutlined aria-hidden="true" />} aria-label={t("common.delete")} />
|
|
||||||
</Popconfirm>
|
|
||||||
) : null}
|
|
||||||
</Space>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Drawer
|
<Drawer
|
||||||
|
|
@ -279,4 +281,4 @@ export default function Devices() {
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||||
import { DeleteOutlined, EditOutlined, PhoneOutlined, PlusOutlined, ReloadOutlined, SearchOutlined, ShopOutlined, UserOutlined } from "@ant-design/icons";
|
import { DeleteOutlined, EditOutlined, PhoneOutlined, PlusOutlined, ReloadOutlined, SearchOutlined, ShopOutlined, UserOutlined } from "@ant-design/icons";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { createTenant, deleteTenant, listTenants, updateTenant } from "@/api";
|
import { createTenant, deleteTenant, listTenants, updateTenant } from "@/api";
|
||||||
|
import AppPagination from "@/components/shared/AppPagination";
|
||||||
import { useDict } from "@/hooks/useDict";
|
import { useDict } from "@/hooks/useDict";
|
||||||
import { usePermission } from "@/hooks/usePermission";
|
import { usePermission } from "@/hooks/usePermission";
|
||||||
import PageHeader from "@/components/shared/PageHeader";
|
import PageHeader from "@/components/shared/PageHeader";
|
||||||
|
|
@ -146,26 +147,24 @@ export default function Tenants() {
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto pr-2">
|
<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" } }}>
|
||||||
<List
|
<div className="flex-1 min-h-0 overflow-y-auto" style={{ padding: "24px 24px 0" }}>
|
||||||
grid={{ gutter: 24, xs: 1, sm: 2, md: 2, lg: 3, xl: 4, xxl: 4 }}
|
<List
|
||||||
loading={loading}
|
grid={{ gutter: 24, xs: 1, sm: 2, md: 2, lg: 3, xl: 4, xxl: 4 }}
|
||||||
dataSource={data}
|
loading={loading}
|
||||||
renderItem={renderTenantCard}
|
dataSource={data}
|
||||||
pagination={{
|
renderItem={renderTenantCard}
|
||||||
total,
|
pagination={false}
|
||||||
current: params.current,
|
locale={{ emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t("tenantsExt.emptyText")} /> }}
|
||||||
pageSize: params.size,
|
/>
|
||||||
onChange: (page, size) => setParams({ ...params, current: page, size: size || params.size }),
|
</div>
|
||||||
showSizeChanger: true,
|
<AppPagination
|
||||||
showQuickJumper: true,
|
current={params.current}
|
||||||
showTotal: (count) => t("common.total", { total: count }),
|
pageSize={params.size}
|
||||||
pageSizeOptions: ["10", "20", "50", "100"],
|
total={total}
|
||||||
style: { marginTop: "24px", marginBottom: "24px" }
|
onChange={(page, size) => setParams({ ...params, current: page, size: size || params.size })}
|
||||||
}}
|
|
||||||
locale={{ emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t("tenantsExt.emptyText")} /> }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</Card>
|
||||||
|
|
||||||
<Drawer title={<Space><ShopOutlined aria-hidden="true" /><span>{editing ? t("tenants.drawerTitleEdit") : t("tenants.drawerTitleCreate")}</span></Space>} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={480} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>{t("common.cancel")}</Button><Button type="primary" loading={saving} onClick={submit}>{t("common.save")}</Button></div>}>
|
<Drawer title={<Space><ShopOutlined aria-hidden="true" /><span>{editing ? t("tenants.drawerTitleEdit") : t("tenants.drawerTitleCreate")}</span></Space>} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={480} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>{t("common.cancel")}</Button><Button type="primary" loading={saving} onClick={submit}>{t("common.save")}</Button></div>}>
|
||||||
<Form form={form} layout="vertical">
|
<Form form={form} layout="vertical">
|
||||||
|
|
@ -203,4 +202,4 @@ export default function Tenants() {
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -174,12 +174,7 @@ export default function Dictionaries() {
|
||||||
rowKey="dictTypeId"
|
rowKey="dictTypeId"
|
||||||
loading={loadingTypes}
|
loading={loadingTypes}
|
||||||
dataSource={types}
|
dataSource={types}
|
||||||
pagination={{
|
pagination={getStandardPagination(typeTotal, typeParams.current, typeParams.size, (page, size) => setTypeParams({ ...typeParams, current: page, size }))}
|
||||||
...getStandardPagination(typeTotal, typeParams.current, typeParams.size, (page, size) => setTypeParams({ ...typeParams, current: page, size })),
|
|
||||||
simple: true,
|
|
||||||
size: "small",
|
|
||||||
position: ["bottomCenter"]
|
|
||||||
}}
|
|
||||||
size="small"
|
size="small"
|
||||||
showHeader={false}
|
showHeader={false}
|
||||||
scroll={{ y: "calc(100vh - 480px)" }}
|
scroll={{ y: "calc(100vh - 480px)" }}
|
||||||
|
|
@ -276,4 +271,4 @@ export default function Dictionaries() {
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ import { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { EyeOutlined, InfoCircleOutlined, ReloadOutlined, SearchOutlined, UserOutlined } from "@ant-design/icons";
|
import { EyeOutlined, InfoCircleOutlined, ReloadOutlined, SearchOutlined, UserOutlined } from "@ant-design/icons";
|
||||||
import { fetchLogModules, fetchLogs } from "@/api";
|
import { fetchLogModules, fetchLogs } from "@/api";
|
||||||
|
import AppPagination from "@/components/shared/AppPagination";
|
||||||
import { useDict } from "@/hooks/useDict";
|
import { useDict } from "@/hooks/useDict";
|
||||||
import PageHeader from "@/components/shared/PageHeader";
|
import PageHeader from "@/components/shared/PageHeader";
|
||||||
import ListTable from "@/components/shared/ListTable/ListTable";
|
import ListTable from "@/components/shared/ListTable/ListTable";
|
||||||
import { getStandardPagination } from "@/utils/pagination";
|
|
||||||
import type { SysLog, UserProfile } from "@/types";
|
import type { SysLog, UserProfile } from "@/types";
|
||||||
|
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
|
|
@ -71,11 +71,10 @@ export default function Logs() {
|
||||||
fetchLogModules().then((items) => setModuleOptions(items || [])).catch(() => setModuleOptions([]));
|
fetchLogModules().then((items) => setModuleOptions(items || [])).catch(() => setModuleOptions([]));
|
||||||
}, [activeTab]);
|
}, [activeTab]);
|
||||||
|
|
||||||
const handleTableChange = (pagination: any, _filters: any, sorter: any) => {
|
const handleTableChange = (_pagination: any, _filters: any, sorter: any) => {
|
||||||
setParams({
|
setParams({
|
||||||
...params,
|
...params,
|
||||||
current: pagination.current,
|
current: 1,
|
||||||
size: pagination.pageSize,
|
|
||||||
sortField: sorter.field || "createdAt",
|
sortField: sorter.field || "createdAt",
|
||||||
sortOrder: sorter.order || "descend"
|
sortOrder: sorter.order || "descend"
|
||||||
});
|
});
|
||||||
|
|
@ -275,7 +274,7 @@ export default function Logs() {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex-1 min-h-0 h-full">
|
<div className="app-page__table-wrap" style={{ flex: 1, minHeight: 0, overflow: "auto", padding: "0 24px" }}>
|
||||||
<ListTable
|
<ListTable
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|
@ -283,10 +282,11 @@ export default function Logs() {
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
totalCount={total}
|
totalCount={total}
|
||||||
scroll={{ y: "calc(100vh - 540px)" }}
|
scroll={{ x: "max-content" }}
|
||||||
pagination={getStandardPagination(total, params.current, params.size)}
|
pagination={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<AppPagination current={params.current} pageSize={params.size} total={total} onChange={(page, size) => setParams((prev) => ({ ...prev, current: page, size }))} />
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Modal title={t("logs.detailTitle")} open={detailModalVisible} onCancel={() => setDetailModalVisible(false)} footer={[<Button key="close" onClick={() => setDetailModalVisible(false)}>{t("logsExt.close")}</Button>]} width={700}>
|
<Modal title={t("logs.detailTitle")} open={detailModalVisible} onCancel={() => setDetailModalVisible(false)} footer={[<Button key="close" onClick={() => setDetailModalVisible(false)}>{t("logsExt.close")}</Button>]} width={700}>
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ import { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DeleteOutlined, EditOutlined, InfoCircleOutlined, PlusOutlined, SearchOutlined, SettingOutlined } from "@ant-design/icons";
|
import { DeleteOutlined, EditOutlined, InfoCircleOutlined, PlusOutlined, SearchOutlined, SettingOutlined } from "@ant-design/icons";
|
||||||
import { createParam, deleteParam, pageParams, updateParam } from "@/api";
|
import { createParam, deleteParam, pageParams, updateParam } from "@/api";
|
||||||
|
import AppPagination from "@/components/shared/AppPagination";
|
||||||
import { useDict } from "@/hooks/useDict";
|
import { useDict } from "@/hooks/useDict";
|
||||||
import { usePermission } from "@/hooks/usePermission";
|
import { usePermission } from "@/hooks/usePermission";
|
||||||
import PageHeader from "@/components/shared/PageHeader";
|
import PageHeader from "@/components/shared/PageHeader";
|
||||||
import { getStandardPagination } from "@/utils/pagination";
|
|
||||||
import type { SysParamQuery, SysParamVO } from "@/types";
|
import type { SysParamQuery, SysParamVO } from "@/types";
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
|
|
||||||
|
|
@ -173,15 +173,18 @@ export default function SysParams() {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<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" } }}>
|
<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" } }}>
|
||||||
<Table
|
<div className="app-page__table-wrap" style={{ flex: 1, minHeight: 0, overflow: "auto", padding: "0 24px" }}>
|
||||||
rowKey="paramId"
|
<Table
|
||||||
columns={columns}
|
rowKey="paramId"
|
||||||
dataSource={data}
|
columns={columns}
|
||||||
loading={loading}
|
dataSource={data}
|
||||||
size="middle"
|
loading={loading}
|
||||||
scroll={{ y: "calc(100vh - 350px)" }}
|
size="middle"
|
||||||
pagination={getStandardPagination(total, queryParams.pageNum || 1, queryParams.pageSize || 10, handlePageChange)}
|
scroll={{ x: "max-content" }}
|
||||||
/>
|
pagination={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<AppPagination current={queryParams.pageNum || 1} pageSize={queryParams.pageSize || 10} total={total} onChange={handlePageChange} />
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Drawer
|
<Drawer
|
||||||
|
|
@ -228,4 +231,4 @@ export default function SysParams() {
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,14 @@ import i18n from '../i18n';
|
||||||
* Returns a standardized Ant Design pagination configuration.
|
* Returns a standardized Ant Design pagination configuration.
|
||||||
*/
|
*/
|
||||||
export const getStandardPagination = (
|
export const getStandardPagination = (
|
||||||
total: number,
|
total: number,
|
||||||
current: number,
|
current: number,
|
||||||
pageSize: number,
|
pageSize: number,
|
||||||
onChange?: (page: number, size: number) => void
|
onChange?: (page: number, size: number) => void,
|
||||||
|
overrides: TablePaginationConfig = {}
|
||||||
): TablePaginationConfig => {
|
): TablePaginationConfig => {
|
||||||
|
const mergedClassName = ['app-global-pagination', overrides.className].filter(Boolean).join(' ');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
total,
|
total,
|
||||||
current,
|
current,
|
||||||
|
|
@ -20,6 +23,8 @@ export const getStandardPagination = (
|
||||||
showTotal: (totalCount) => i18n.t('common.total', { total: totalCount }),
|
showTotal: (totalCount) => i18n.t('common.total', { total: totalCount }),
|
||||||
pageSizeOptions: ['10', '20', '50', '100'],
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
size: 'default',
|
size: 'default',
|
||||||
position: ['bottomRight']
|
position: ['bottomRight'],
|
||||||
|
...overrides,
|
||||||
|
className: mergedClassName
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue