diff --git a/frontend/src/components/shared/AppPagination/index.tsx b/frontend/src/components/shared/AppPagination/index.tsx
index e8a4687..b0bd923 100644
--- a/frontend/src/components/shared/AppPagination/index.tsx
+++ b/frontend/src/components/shared/AppPagination/index.tsx
@@ -1,22 +1,26 @@
import React from 'react';
import { Pagination, PaginationProps } from 'antd';
import { useTranslation } from 'react-i18next';
+import { getDefaultPageSize, getPageSizeOptions, type PaginationVariant } from '@/utils/pagination';
import './index.css';
export interface AppPaginationProps extends PaginationProps {
total: number;
+ variant?: PaginationVariant;
}
export default function AppPagination(props: AppPaginationProps) {
const { t } = useTranslation();
- const { className, showSizeChanger, showTotal, total, ...restProps } = props;
+ const { className, showSizeChanger, showTotal, total, variant = 'table', pageSizeOptions, ...restProps } = props;
const mergedClassName = ['app-global-pagination', className].filter(Boolean).join(' ');
const mergedShowSizeChanger =
showSizeChanger === undefined || showSizeChanger === true
? { showSearch: false }
: showSizeChanger;
+ const defaultPageSize = getDefaultPageSize(variant);
+ const mergedPageSizeOptions = pageSizeOptions ?? getPageSizeOptions(variant);
const current = Number(restProps.current ?? restProps.defaultCurrent ?? 1);
- const pageSize = Number(restProps.pageSize ?? restProps.defaultPageSize ?? 10);
+ const pageSize = Number(restProps.pageSize ?? restProps.defaultPageSize ?? defaultPageSize);
const rangeStart = total > 0 ? (current - 1) * pageSize + 1 : 0;
const rangeEnd = total > 0 ? Math.min(current * pageSize, total) : 0;
const totalContent = showTotal ? showTotal(total, [rangeStart, rangeEnd]) : t('common.total', { total });
@@ -28,7 +32,8 @@ export default function AppPagination(props: AppPaginationProps) {
className={mergedClassName}
showSizeChanger={mergedShowSizeChanger}
showQuickJumper
- pageSizeOptions={['8','10', '20', '50', '100']}
+ defaultPageSize={defaultPageSize}
+ pageSizeOptions={mergedPageSizeOptions}
size="default"
total={total}
{...restProps}
diff --git a/frontend/src/components/shared/DataListPanel/DataListPanel.css b/frontend/src/components/shared/DataListPanel/DataListPanel.css
new file mode 100644
index 0000000..f510d37
--- /dev/null
+++ b/frontend/src/components/shared/DataListPanel/DataListPanel.css
@@ -0,0 +1,218 @@
+.data-list-panel {
+ width: 100%;
+ height: 100%;
+ min-width: 0;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ box-sizing: border-box;
+ overflow: hidden;
+ padding: 8px 12px;
+ border-radius: 4px;
+ background-color: #fff;
+}
+
+.data-list-panel--auto {
+ height: auto;
+ overflow: visible;
+}
+
+.data-list-panel,
+.data-list-panel .ant-btn,
+.data-list-panel .ant-input,
+.data-list-panel .ant-input-affix-wrapper,
+.data-list-panel .ant-select,
+.data-list-panel .ant-select-selector,
+.data-list-panel .ant-table {
+ font-family: "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;
+ font-size: 14px;
+ letter-spacing: 0;
+}
+
+.data-list-panel .ant-btn {
+ height: 32px;
+ border-radius: 4px !important;
+ box-shadow: none;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.data-list-panel .ant-btn .ant-btn-icon,
+.data-list-panel .ant-input-prefix,
+.data-list-panel .ant-input-prefix .anticon {
+ display: inline-flex;
+ align-items: center;
+ line-height: 1;
+}
+
+.data-list-panel .ant-input,
+.data-list-panel .ant-input-affix-wrapper,
+.data-list-panel .ant-select-selector {
+ height: 32px !important;
+ border-radius: 4px !important;
+}
+
+.data-list-panel .ant-input-affix-wrapper {
+ display: inline-flex;
+ align-items: center;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.data-list-panel .ant-input-affix-wrapper .ant-input {
+ height: 30px !important;
+ line-height: 30px;
+}
+
+.data-list-panel .ant-input-prefix {
+ height: 100%;
+ margin-inline-end: 6px;
+}
+
+.data-list-panel .ant-select-selector {
+ align-items: center;
+}
+
+.data-list-panel__toolbar {
+ flex-shrink: 0;
+ flex-wrap: nowrap;
+ min-width: 0;
+ min-height: 34px;
+ margin-bottom: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.data-list-panel__left-actions {
+ flex: 0 0 auto;
+ min-width: 0;
+ min-height: 32px;
+ display: flex;
+ align-items: center;
+}
+
+.data-list-panel__right-actions {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ justify-content: flex-end;
+}
+
+.data-list-panel__right-actions .ant-space {
+ min-width: 0;
+ min-height: 32px;
+ align-items: center;
+}
+
+.data-list-panel__table-container {
+ flex: 1;
+ height: 100%;
+ min-width: 0;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.data-list-panel--auto .data-list-panel__table-container,
+.data-list-panel--auto .data-list-panel__table-area {
+ height: auto;
+ overflow: visible;
+}
+
+.data-list-panel__table-area {
+ flex: 1;
+ min-width: 0;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.data-list-panel__table-area .app-page__table-wrap,
+.data-list-panel__table-area .list-table-container {
+ height: 100%;
+}
+
+.data-list-panel__table-area .ant-table-thead > tr > th {
+ height: 45px;
+ color: #000;
+ font-size: 14px;
+ font-weight: 600;
+ background: #fafafa !important;
+}
+
+.data-list-panel__table-area .ant-table-tbody > tr > td {
+ height: 47px;
+ color: #000;
+ font-size: 14px;
+ font-weight: 400;
+}
+
+.data-list-panel__table-area .ant-table-cell {
+ line-height: 22px;
+}
+
+.data-list-panel__table-area .ant-table-content,
+.data-list-panel__table-area .ant-table-body {
+ overflow-x: auto !important;
+}
+
+.data-list-panel__table-area .ant-tag {
+ margin-inline-end: 0;
+ border-radius: 4px;
+ font-family: "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;
+}
+
+.data-list-panel__footer {
+ flex-shrink: 0;
+ min-width: 0;
+ min-height: 56px;
+}
+
+.data-list-panel__footer .app-pagination-container {
+ min-width: 0;
+ overflow: visible;
+ border-radius: 0;
+ background: #fff;
+ box-sizing: border-box;
+}
+
+.data-list-panel__footer .app-pagination-container .ant-pagination {
+ min-width: 0;
+ overflow: visible;
+}
+
+.data-list-panel__footer .app-pagination-container .ant-pagination-options {
+ margin-inline-start: 8px;
+}
+
+.data-list-panel__footer .app-pagination-container,
+.data-list-panel__footer .app-pagination-container .ant-pagination,
+.data-list-panel__footer .app-pagination-total {
+ color: #333;
+ font-family: "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;
+ font-size: 14px;
+}
+
+@media (max-width: 768px) {
+ .data-list-panel__toolbar {
+ align-items: stretch;
+ flex-direction: column;
+ }
+
+ .data-list-panel__left-actions,
+ .data-list-panel__right-actions {
+ flex: none;
+ }
+
+ .data-list-panel__right-actions,
+ .data-list-panel__right-actions .ant-space,
+ .data-list-panel__right-actions .ant-input-affix-wrapper,
+ .data-list-panel__right-actions .ant-select {
+ width: 100% !important;
+ }
+}
diff --git a/frontend/src/components/shared/DataListPanel/index.tsx b/frontend/src/components/shared/DataListPanel/index.tsx
new file mode 100644
index 0000000..8119837
--- /dev/null
+++ b/frontend/src/components/shared/DataListPanel/index.tsx
@@ -0,0 +1,42 @@
+import type { ReactNode } from "react";
+import "./DataListPanel.css";
+
+interface DataListPanelProps {
+ leftActions?: ReactNode;
+ rightActions?: ReactNode;
+ children: ReactNode;
+ footer?: ReactNode;
+ layout?: "fixed" | "auto";
+ className?: string;
+ toolbarClassName?: string;
+}
+
+export default function DataListPanel({
+ leftActions,
+ rightActions,
+ children,
+ footer,
+ layout = "fixed",
+ className = "",
+ toolbarClassName = "",
+}: DataListPanelProps) {
+ const classes = ["data-list-panel", `data-list-panel--${layout}`, className].filter(Boolean).join(" ");
+ const toolbarClasses = ["data-list-panel__toolbar", toolbarClassName].filter(Boolean).join(" ");
+
+ return (
+
+ {(leftActions || rightActions) ? (
+
+
{leftActions}
+
{rightActions}
+
+ ) : null}
+
+
+ {footer ?
{footer}
: null}
+
+
+ );
+}
diff --git a/frontend/src/components/shared/SectionCard/SectionCard.css b/frontend/src/components/shared/SectionCard/SectionCard.css
new file mode 100644
index 0000000..9605e25
--- /dev/null
+++ b/frontend/src/components/shared/SectionCard/SectionCard.css
@@ -0,0 +1,180 @@
+.section-card {
+ position: relative;
+ flex: 1;
+ height: 100%;
+ min-width: 0;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ box-sizing: border-box;
+ overflow: hidden;
+ padding: 16px;
+ border: 1px solid #e6e6e6;
+ border-radius: 4px;
+ background-color: #fff;
+ background-image: url("../../../assets/home/mask.png");
+ background-position: right top;
+ background-size: contain;
+ background-repeat: no-repeat;
+}
+
+.section-card--auto {
+ height: auto;
+ min-height: 0;
+ overflow: visible;
+}
+
+.section-card__header {
+ z-index: 1;
+ position: relative;
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+ min-width: 0;
+ flex-shrink: 0;
+}
+
+.section-card__title-wrap {
+ flex: 1;
+ min-width: 0;
+}
+
+.section-card__title {
+ margin: 0;
+ padding-bottom: 8px;
+ color: #333;
+ font-family: "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;
+ font-size: 18px;
+ font-weight: 600;
+ line-height: 28px;
+ letter-spacing: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.section-card__title::before {
+ content: "";
+ flex: 0 0 auto;
+ width: 4px;
+ height: 16px;
+ border-radius: 1px;
+ background: #3c70f5;
+}
+
+.section-card__description {
+ padding: 0 0 16px 12px;
+ color: #9095a1;
+ font-family: "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 24px;
+ letter-spacing: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.section-card__extra {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8px;
+ flex-shrink: 0;
+}
+
+.section-card__tabs {
+ z-index: 1;
+ flex-shrink: 0;
+ min-width: 0;
+}
+
+.section-card__tabs > .ant-tabs {
+ width: 100%;
+}
+
+.section-card__tabs > .ant-tabs > .ant-tabs-nav {
+ margin: 0 !important;
+ border-bottom: none !important;
+}
+
+.section-card__tabs > .ant-tabs > .ant-tabs-nav::before {
+ border-bottom: none !important;
+}
+
+.section-card__tabs .ant-tabs-nav-list {
+ transition: none !important;
+}
+
+.section-card__tabs .ant-tabs-content-holder,
+.section-card__tabs .ant-tabs-ink-bar {
+ display: none !important;
+}
+
+.section-card__tabs .ant-tabs-tab {
+ margin-left: 0 !important;
+ padding: 10px 16px !important;
+ border: 0 solid transparent !important;
+ border-radius: 0 !important;
+ background-color: rgba(249, 250, 254, 0) !important;
+ transition: none !important;
+}
+
+.section-card__tabs .ant-tabs-tab.ant-tabs-tab-active {
+ border: none !important;
+ border-radius: 0 !important;
+ background-color: #f9fafe !important;
+}
+
+.section-card__tabs .ant-tabs-tab-btn {
+ color: #333;
+ font-family: "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;
+ font-size: 14px;
+ line-height: 22px;
+ letter-spacing: 0;
+ transition: none !important;
+}
+
+.section-card__tabs .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
+ color: #1677ff !important;
+ font-weight: 600;
+}
+
+.section-card__content {
+ z-index: 1;
+ flex: 1;
+ height: 100%;
+ min-width: 0;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ box-sizing: border-box;
+ overflow: hidden;
+ padding: 8px;
+ border-radius: 4px;
+ background-color: #f9fafe;
+}
+
+.section-card--auto .section-card__content {
+ height: auto;
+ overflow: visible;
+}
+
+@media (max-width: 768px) {
+ .section-card {
+ padding: 12px;
+ }
+
+ .section-card__header {
+ flex-direction: column;
+ }
+
+ .section-card__extra {
+ width: 100%;
+ justify-content: flex-start;
+ }
+}
diff --git a/frontend/src/components/shared/SectionCard/index.tsx b/frontend/src/components/shared/SectionCard/index.tsx
new file mode 100644
index 0000000..eeb27d6
--- /dev/null
+++ b/frontend/src/components/shared/SectionCard/index.tsx
@@ -0,0 +1,46 @@
+import type { CSSProperties, ReactNode } from "react";
+import "./SectionCard.css";
+
+interface SectionCardProps {
+ title?: ReactNode;
+ description?: ReactNode;
+ extra?: ReactNode;
+ tabs?: ReactNode;
+ children: ReactNode;
+ layout?: "fixed" | "auto";
+ className?: string;
+ contentClassName?: string;
+ style?: CSSProperties;
+}
+
+export default function SectionCard({
+ title,
+ description,
+ extra,
+ tabs,
+ children,
+ layout = "fixed",
+ className = "",
+ contentClassName = "",
+ style,
+}: SectionCardProps) {
+ const hasHeader = Boolean(title) || Boolean(description) || Boolean(extra);
+ const classes = ["section-card", `section-card--${layout}`, className].filter(Boolean).join(" ");
+ const contentClasses = ["section-card__content", contentClassName].filter(Boolean).join(" ");
+
+ return (
+
+ {hasHeader ? (
+
+
+ {title ?
{title}
: null}
+ {description ?
{description}
: null}
+
+ {extra ?
{extra}
: null}
+
+ ) : null}
+ {tabs ? {tabs}
: null}
+ {children}
+
+ );
+}
diff --git a/frontend/src/pages/business/AiModels.css b/frontend/src/pages/business/AiModels.css
new file mode 100644
index 0000000..d3499c7
--- /dev/null
+++ b/frontend/src/pages/business/AiModels.css
@@ -0,0 +1,13 @@
+.ai-models-page {
+ padding: 8px;
+ min-width: 0;
+ background: #f5f6fa;
+}
+
+.ai-models-page > .page-container__body {
+ padding: 0;
+ overflow: hidden;
+ border: none;
+ border-radius: 0;
+ background: transparent;
+}
diff --git a/frontend/src/pages/business/AiModels.tsx b/frontend/src/pages/business/AiModels.tsx
index c68cdf2..c1e7e87 100644
--- a/frontend/src/pages/business/AiModels.tsx
+++ b/frontend/src/pages/business/AiModels.tsx
@@ -1,6 +1,8 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
-import { AutoComplete, Button, Card, Col, Divider, Drawer, Form, Input, InputNumber, Popconfirm, Row, Select, Space, Switch, Table, Tabs, Tag, Tooltip, Typography, App } from 'antd';
+import { AutoComplete, Button, Col, Divider, Drawer, Form, Input, InputNumber, Popconfirm, Row, Select, Space, Switch, Table, Tabs, Tag, Tooltip, Typography, App } from 'antd';
import PageContainer from "@/components/shared/PageContainer";
+import DataListPanel from "@/components/shared/DataListPanel";
+import SectionCard from "@/components/shared/SectionCard";
import {
DeleteOutlined,
EditOutlined,
@@ -26,6 +28,7 @@ import {
} from "../../api/business/aimodel";
import {getMeetingCreateConfig, type MeetingCreateConfig} from "../../api/business/meeting";
import AppPagination from "../../components/shared/AppPagination";
+import "./AiModels.css";
const { Option } = Select;
const { Title } = Typography;
@@ -449,56 +452,65 @@ const AiModels: React.FC = () => {
return (
} onClick={() => openDrawer()}>
- 新增模型
-
- }
- toolbar={
- }
- allowClear
- onPressEnter={(event) => setSearchName((event.target as HTMLInputElement).value)}
- style={{ width: 220 }}
- />
- }
+ title={null}
+ className="ai-models-page"
>
- {
- setActiveType(key as ModelType);
- setCurrent(1);
- }}
- items={[
- { key: "ASR", label: "ASR 模型" },
- { key: "LLM", label: "LLM 模型" },
- ]}
- style={{ marginBottom: 16 }}
- />
-
-
- {
- setCurrent(page);
- setSize(pageSize);
- }}
- />
-
+ {
+ setActiveType(key as ModelType);
+ setCurrent(1);
+ }}
+ items={[
+ { key: "ASR", label: "ASR 模型" },
+ { key: "LLM", label: "LLM 模型" },
+ ]}
+ size="middle"
+ type="card"
+ />
+ }
+ >
+ } onClick={() => openDrawer()}>
+ 新增模型
+
+ }
+ rightActions={
+ }
+ allowClear
+ onPressEnter={(event) => setSearchName((event.target as HTMLInputElement).value)}
+ style={{ width: 220 }}
+ />
+ }
+ footer={
+ {
+ setCurrent(page);
+ setSize(pageSize);
+ }}
+ />
+ }
+ >
+
+
+
.page-container__body {
padding: 0;
+ overflow: hidden;
border: none;
border-radius: 0;
background: transparent;
- overflow: hidden;
}
-.meeting-points-page__card-wrapper {
- position: relative;
- flex: 1;
- min-height: 0;
- min-width: 0;
- padding: 16px;
- border: 1px solid #e6e6e6;
- border-radius: 4px;
- background-color: #fff;
- background-image: url("../../assets/home/mask.png");
- background-position: right top;
- background-size: contain;
- background-repeat: no-repeat;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
- overflow: hidden;
-}
-
-.meeting-points-page__card-title {
- z-index: 1;
- position: relative;
- padding-bottom: 8px;
- color: #333;
- font-family: "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;
- font-size: 18px;
- font-weight: 600;
- line-height: 28px;
- letter-spacing: 0;
-}
-
-.meeting-points-page__card-description {
- z-index: 1;
- padding-bottom: 16px;
- color: #9095a1;
- font-family: "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;
- font-size: 14px;
- font-weight: 400;
- line-height: 24px;
- letter-spacing: 0;
-}
-
-.meeting-points-page__tabs {
- z-index: 1;
- flex-shrink: 0;
- margin-bottom: 0;
- background: transparent;
-}
-
-.meeting-points-page__tabs > .ant-tabs-nav {
- margin: 0 !important;
- border-bottom: none !important;
-}
-
-.meeting-points-page__tabs .ant-tabs-nav-list {
- transition: none !important;
-}
-
-.meeting-points-page__tabs .ant-tabs-content-holder {
- display: none;
-}
-
-.meeting-points-page__tabs > .ant-tabs-nav::before {
- border-bottom: none !important;
-}
-
-.meeting-points-page__tabs .ant-tabs-ink-bar {
- display: none !important;
-}
-
-.meeting-points-page__tabs .ant-tabs-tab.ant-tabs-tab-active {
- background-color: #f9fafe !important;
- border: none !important;
- border-radius: 0 !important;
-}
-
-.meeting-points-page__tabs .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
- color: #1677ff !important;
- font-weight: 600;
-}
-
-.meeting-points-page__tabs .ant-tabs-tab {
- margin-left: 0 !important;
- border: 0 solid transparent !important;
- border-radius: 0 !important;
- background-color: rgba(249, 250, 254, 0) !important;
- padding: 10px 16px !important;
- transition: none !important;
-}
-
-.meeting-points-page__tabs .ant-tabs-tab-btn {
- color: #333;
- font-family: "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;
- font-size: 14px;
- line-height: 22px;
- letter-spacing: 0;
- transition: none !important;
-}
-
-.meeting-points-page__content-wrap {
- z-index: 1;
- flex: 1;
- min-height: 0;
- min-width: 0;
- width: 100%;
- padding: 8px;
- background-color: #f9fafe;
- border-radius: 4px;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
- overflow: hidden;
-}
-
-.meeting-points-page__inner-list {
- width: 100%;
- height: 100%;
- min-height: 0;
- min-width: 0;
- padding: 8px 12px;
- background-color: #fff;
- border-radius: 4px;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
- overflow: hidden;
-}
-
-.meeting-points-page__inner-list,
-.meeting-points-page__inner-list .ant-btn,
-.meeting-points-page__inner-list .ant-input,
-.meeting-points-page__inner-list .ant-input-affix-wrapper,
-.meeting-points-page__inner-list .ant-select,
-.meeting-points-page__inner-list .ant-select-selector,
-.meeting-points-page__inner-list .ant-table {
- font-family: "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;
- font-size: 14px;
- letter-spacing: 0;
-}
-
-.meeting-points-page__inner-list .ant-btn {
- height: 32px;
- border-radius: 4px !important;
- box-shadow: none;
- display: inline-flex;
- align-items: center;
- justify-content: center;
-}
-
-.meeting-points-page__inner-list .ant-btn .ant-btn-icon {
- display: inline-flex;
- align-items: center;
- line-height: 1;
-}
-
-.meeting-points-page__inner-list .ant-input,
-.meeting-points-page__inner-list .ant-input-affix-wrapper,
-.meeting-points-page__inner-list .ant-select-selector {
- height: 32px !important;
- border-radius: 4px !important;
-}
-
-.meeting-points-page__inner-list .ant-input-affix-wrapper {
- display: inline-flex;
- align-items: center;
- padding-top: 0;
- padding-bottom: 0;
-}
-
-.meeting-points-page__inner-list .ant-input-affix-wrapper .ant-input {
- height: 30px !important;
- line-height: 30px;
-}
-
-.meeting-points-page__inner-list .ant-input-prefix {
- display: inline-flex;
- align-items: center;
- height: 100%;
- margin-inline-end: 6px;
-}
-
-.meeting-points-page__inner-list .ant-input-prefix .anticon {
- display: inline-flex;
- align-items: center;
- line-height: 1;
-}
-
-.meeting-points-page__inner-list .ant-select-selector {
- align-items: center;
-}
-
-.meeting-points-page__search-box {
- flex-shrink: 0;
- min-width: 0;
- margin-bottom: 16px;
- min-height: 34px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
-}
-
-.meeting-points-page__left-actions {
- min-width: 0;
- min-height: 32px;
+.meeting-points-page__static-pagination {
+ min-height: 56px;
display: flex;
align-items: center;
}
-
-.meeting-points-page__search-input {
- min-width: 0;
- display: flex;
- justify-content: flex-end;
-}
-
-.meeting-points-page__search-input .ant-space {
- min-width: 0;
- min-height: 32px;
- align-items: center;
-}
-
-.meeting-points-page__table-container {
- flex: 1;
- min-height: 0;
- min-width: 0;
- display: flex;
- flex-direction: column;
- overflow: hidden;
-}
-
-.meeting-points-page__table-area {
- flex: 1;
- min-height: 0;
- min-width: 0;
- display: flex;
- flex-direction: column;
- overflow: hidden;
-}
-
-.meeting-points-page__table-area .app-page__table-wrap,
-.meeting-points-page__table-area .list-table-container {
- height: 100%;
-}
-
-.meeting-points-page__table-area .ant-table-thead > tr > th {
- height: 45px;
- color: #000;
- font-size: 14px;
- font-weight: 600;
- background: #fafafa !important;
-}
-
-.meeting-points-page__table-area .ant-table-tbody > tr > td {
- height: 47px;
- color: #000;
- font-size: 14px;
- font-weight: 400;
-}
-
-.meeting-points-page__table-area .ant-table-cell {
- line-height: 22px;
-}
-
-.meeting-points-page__table-area .ant-table-content,
-.meeting-points-page__table-area .ant-table-body {
- overflow-x: hidden !important;
-}
-
-.meeting-points-page__table-area .ant-tag {
- margin-inline-end: 0;
- border-radius: 4px;
- font-family: "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;
-}
-
-.meeting-points-page .app-pagination-container {
- background: #fff;
- border-radius: 0;
- box-sizing: border-box;
- min-width: 0;
- overflow: visible;
-}
-
-.meeting-points-page .app-pagination-container .ant-pagination {
- min-width: 0;
- overflow: visible;
-}
-
-.meeting-points-page .app-pagination-container .ant-pagination-options {
- margin-inline-start: 8px;
-}
-
-.meeting-points-page .app-pagination-container,
-.meeting-points-page .app-pagination-container .ant-pagination,
-.meeting-points-page .app-pagination-total {
- color: #333;
- font-family: "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;
- font-size: 14px;
-}
-
-@media (max-width: 768px) {
- .meeting-points-page__search-box {
- align-items: stretch;
- flex-direction: column;
- }
-
- .meeting-points-page__search-input,
- .meeting-points-page__search-input .ant-space {
- width: 100%;
- }
-
- .meeting-points-page__search-input .ant-input-affix-wrapper,
- .meeting-points-page__search-input .ant-select {
- width: 100% !important;
- }
-}
diff --git a/frontend/src/pages/business/MeetingPointsManagement.tsx b/frontend/src/pages/business/MeetingPointsManagement.tsx
index 39ed167..e99e89b 100644
--- a/frontend/src/pages/business/MeetingPointsManagement.tsx
+++ b/frontend/src/pages/business/MeetingPointsManagement.tsx
@@ -16,7 +16,9 @@ import {
import { useEffect, useMemo, useState } from "react";
import PageContainer from "@/components/shared/PageContainer";
import AppPagination from "@/components/shared/AppPagination";
+import DataListPanel from "@/components/shared/DataListPanel";
import ListTable from "@/components/shared/ListTable/ListTable";
+import SectionCard from "@/components/shared/SectionCard";
import {
getMeetingPointsLedgerPage,
getMeetingPointsOverview,
@@ -100,11 +102,11 @@ export default function MeetingPointsManagement() {
const [activeTabKey, setActiveTabKey] = useState("ledger");
const [personalAccountPagination, setPersonalAccountPagination] = useState({
current: 1,
- pageSize: 8,
+ pageSize: 10,
});
const [params, setParams] = useState({
current: 1,
- size: 8,
+ size: 10,
username: "",
pointsType: "",
});
@@ -196,7 +198,7 @@ export default function MeetingPointsManagement() {
const handleReset = () => {
const nextParams = {
current: 1,
- size: 8,
+ size: 10,
username: "",
pointsType: "",
};
@@ -369,130 +371,118 @@ export default function MeetingPointsManagement() {
return (
-
-
积分管理
-
- 查看当前租户下的积分账面余额、累计消耗和会议消耗记录。
-
-
-
-
-
-
-
-
-
-
-
- {activeTabKey === "overview" ? (
-
- key="overview"
- rowKey="id"
- columns={overviewColumns}
- dataSource={overviewRows}
- loading={false}
- scroll={{ x: 900, y: "100%" }}
- pagination={false}
- />
- ) : activeTabKey === "personal" ? (
-
- key="personal"
- rowKey="userId"
- columns={personalAccountColumns}
- dataSource={pagedPersonalAccounts}
- loading={false}
- scroll={{ x: 900, y: "100%" }}
- pagination={false}
- />
- ) : (
-
- key="ledger"
- rowKey="id"
- columns={ledgerColumns}
- dataSource={records}
- loading={loading}
- scroll={{ x: 1100, y: "100%" }}
- pagination={false}
- />
- )}
-
-
-
- {activeTabKey === "overview" ? (
+
-
-
-
+ ) : activeTabKey === "personal" ? (
+ setPersonalAccountPagination({ current: page, pageSize: size })}
+ />
+ ) : (
+ {
+ const nextParams = { ...params, current: page, size };
+ setParams(nextParams);
+ void loadPage(nextParams);
+ }}
+ />
+ )
+ }
+ >
+ {activeTabKey === "overview" ? (
+
+ key="overview"
+ rowKey="id"
+ columns={overviewColumns}
+ dataSource={overviewRows}
+ loading={false}
+ scroll={{ x: 900, y: "100%" }}
+ pagination={false}
+ />
+ ) : activeTabKey === "personal" ? (
+
+ key="personal"
+ rowKey="userId"
+ columns={personalAccountColumns}
+ dataSource={pagedPersonalAccounts}
+ loading={false}
+ scroll={{ x: 900, y: "100%" }}
+ pagination={false}
+ />
+ ) : (
+
+ key="ledger"
+ rowKey="id"
+ columns={ledgerColumns}
+ dataSource={records}
+ loading={loading}
+ scroll={{ x: 1100, y: "100%" }}
+ pagination={false}
+ />
+ )}
+
+
{
diff --git a/frontend/src/pages/business/PromptTemplates.tsx b/frontend/src/pages/business/PromptTemplates.tsx
index 2d6a8bd..46e3827 100644
--- a/frontend/src/pages/business/PromptTemplates.tsx
+++ b/frontend/src/pages/business/PromptTemplates.tsx
@@ -54,7 +54,7 @@ const PromptTemplates: React.FC = () => {
const [data, setData] = useState([]);
const [total, setTotal] = useState(0);
const [current, setCurrent] = useState(1);
- const [pageSize, setPageSize] = useState(12);
+ const [pageSize, setPageSize] = useState(8);
const [drawerVisible, setDrawerVisible] = useState(false);
const [editingId, setEditingId] = useState(null);
@@ -379,6 +379,7 @@ const PromptTemplates: React.FC = () => {
})}
{
{
setCurrent(page);
setPageSize(size);
diff --git a/frontend/src/pages/business/TenantMeetingPointsSettings.css b/frontend/src/pages/business/TenantMeetingPointsSettings.css
new file mode 100644
index 0000000..659ad0f
--- /dev/null
+++ b/frontend/src/pages/business/TenantMeetingPointsSettings.css
@@ -0,0 +1,44 @@
+.tenant-meeting-points {
+ padding: 8px;
+ min-width: 0;
+ background: #f5f6fa;
+}
+
+.tenant-meeting-points > .page-container__body {
+ padding: 0;
+ overflow: hidden;
+ border: none;
+ border-radius: 0;
+ background: transparent;
+}
+
+.tenant-meeting-points__tenant-card {
+ width: 100%;
+ padding: 8px 12px;
+ border-radius: 4px;
+ background: #fff;
+}
+
+.tenant-meeting-points__tenant-heading {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ flex-wrap: wrap;
+}
+
+.tenant-meeting-points__mode-card {
+ border-radius: 4px;
+}
+
+.tenant-meeting-points__mode-card--enabled {
+ background: #f6ffed;
+}
+
+.tenant-meeting-points__mode-card--unlimited {
+ background: #fff7e6;
+}
+
+.tenant-meeting-points__stats {
+ width: 100%;
+}
diff --git a/frontend/src/pages/business/TenantMeetingPointsSettings.tsx b/frontend/src/pages/business/TenantMeetingPointsSettings.tsx
index 0f5757f..f28a896 100644
--- a/frontend/src/pages/business/TenantMeetingPointsSettings.tsx
+++ b/frontend/src/pages/business/TenantMeetingPointsSettings.tsx
@@ -1,8 +1,10 @@
import { ReloadOutlined, SearchOutlined } from "@ant-design/icons";
import { getCurrentUser } from "@/api";
import AppPagination from "@/components/shared/AppPagination";
+import DataListPanel from "@/components/shared/DataListPanel";
import ListTable from "@/components/shared/ListTable/ListTable";
import PageContainer from "@/components/shared/PageContainer";
+import SectionCard from "@/components/shared/SectionCard";
import {usePermission} from "@/hooks/usePermission";
import {
getCurrentTenantMeetingPointsSetting,
@@ -13,6 +15,7 @@ import {
import type { UserProfile } from "@/types";
import { Button, Card, Input, message, Modal, Select, Space, Statistic, Tag, Typography } from "antd";
import { useEffect, useState } from "react";
+import "./TenantMeetingPointsSettings.css";
const { Text } = Typography;
const BALANCE_CHECK_UPDATE_PERMISSION = "biz:tenant-meeting-points:balance-check:update";
@@ -37,7 +40,7 @@ export default function TenantMeetingPointsSettings() {
const [currentTenantSetting, setCurrentTenantSetting] = useState(null);
const [params, setParams] = useState({
current: 1,
- size: 20,
+ size: 10,
tenantName: "",
tenantCode: "",
balanceCheckEnabled: "",
@@ -205,9 +208,13 @@ export default function TenantMeetingPointsSettings() {
return null;
}
return (
-
-
-
+
+
+
{currentTenantSetting.tenantName || "当前租户"}
租户编码:{currentTenantSetting.tenantCode || "-"}
@@ -215,7 +222,14 @@ export default function TenantMeetingPointsSettings() {
{renderStatusTag(currentTenantSetting.balanceCheckEnabled)}
-
+
{currentTenantSetting.balanceCheckEnabled ? "当前为校验余额模式" : "当前为无限余额模式"}
@@ -228,7 +242,7 @@ export default function TenantMeetingPointsSettings() {
-
+
@@ -254,101 +268,99 @@ export default function TenantMeetingPointsSettings() {
-
+
);
};
return (
} onClick={() => void handleRefresh()}>
- 刷新
-
- }
- toolbar={isPlatformAdmin ? (
-
- setParams((prev) => ({ ...prev, tenantName: event.target.value }))}
- style={{ width: 220 }}
- prefix={}
- allowClear
- />
- setParams((prev) => ({ ...prev, tenantCode: event.target.value }))}
- style={{ width: 180 }}
- allowClear
- />
-
- ) : undefined}
+ title={null}
+ className="tenant-meeting-points"
>
{isPlatformAdmin ? (
-
-
-
租户列表
-
- 平台管理员可查看并调整全部租户的余额校验开关。
-
-
-
+
+ } onClick={() => void handleRefresh()}>
+ 刷新
+
+ }
+ rightActions={
+
+ setParams((prev) => ({ ...prev, tenantName: event.target.value }))}
+ style={{ width: 220 }}
+ prefix={}
+ allowClear
+ />
+ setParams((prev) => ({ ...prev, tenantCode: event.target.value }))}
+ style={{ width: 180 }}
+ allowClear
+ />
+
+ }
+ footer={
+ {
+ const nextParams = { ...params, current: page, size: pageSize };
+ setParams(nextParams);
+ void loadPlatformPage(nextParams);
+ }}
+ />
+ }
+ >
rowKey="tenantId"
columns={columns}
dataSource={records}
loading={loading}
totalCount={total}
- scroll={{x: 1200, y: "calc(100vh - 380px)"}}
+ scroll={{x: 1200, y: "100%"}}
pagination={false}
/>
-
-
-
{
- const nextParams = { ...params, current: page, size: pageSize };
- setParams(nextParams);
- void loadPlatformPage(nextParams);
- }}
- />
-
-
+
+
) : renderTenantAdminCard()}
);
diff --git a/frontend/src/pages/dashboard/index.css b/frontend/src/pages/dashboard/index.css
new file mode 100644
index 0000000..369b36b
--- /dev/null
+++ b/frontend/src/pages/dashboard/index.css
@@ -0,0 +1,162 @@
+.dashboard-monitor-page {
+ padding: 8px;
+ min-width: 0;
+ background: #f5f6fa;
+}
+
+.dashboard-monitor-page > .page-container__body {
+ padding: 0;
+ overflow: hidden;
+ border: none;
+ border-radius: 0;
+ background: transparent;
+}
+
+.dashboard-monitor-page__content {
+ gap: 12px;
+}
+
+.dashboard-monitor-page__stats {
+ flex-shrink: 0;
+ min-width: 0;
+}
+
+.dashboard-monitor-page__stat-card {
+ height: 100%;
+ border: 1px solid #e6e6e6;
+ border-radius: 4px;
+ background: #fff;
+ box-shadow: none;
+}
+
+.dashboard-monitor-page__stat-label {
+ font-size: 13px;
+}
+
+.dashboard-monitor-page__task-panel {
+ flex: 1;
+ min-height: 0;
+}
+
+.dashboard-monitor-page__task-panel .data-list-panel__toolbar {
+ margin-bottom: 8px;
+}
+
+.dashboard-monitor-page__task-list,
+.dashboard-monitor-page__task-list .ant-list,
+.dashboard-monitor-page__task-list .ant-list-items,
+.dashboard-monitor-page__task-item {
+ min-width: 0;
+}
+
+.dashboard-monitor-page__task-list {
+ flex: 1;
+ height: 100%;
+ min-height: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+ padding-right: 4px;
+ overscroll-behavior: contain;
+}
+
+.dashboard-monitor-page__task-item {
+ padding: 20px 0;
+ border-bottom: 1px solid #f0f2f5;
+}
+
+.dashboard-monitor-page__task-item-inner {
+ width: 100%;
+ min-width: 0;
+}
+
+.dashboard-monitor-page__task-meta {
+ width: 100%;
+}
+
+.dashboard-monitor-page__task-title.ant-typography {
+ margin: 0;
+ cursor: pointer;
+ word-break: break-word;
+}
+
+.dashboard-monitor-page__task-divider {
+ margin: 0;
+}
+
+.dashboard-monitor-page__task-tags {
+ margin-top: 8px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ min-height: 22px;
+}
+
+.dashboard-monitor-page__task-tag {
+ margin-inline-end: 0;
+ border: 1px solid var(--app-border-color);
+ border-radius: 4px;
+ background: color-mix(in srgb, var(--app-primary-color) 12%, var(--app-bg-surface-strong));
+ color: var(--app-text-main);
+ font-size: 11px;
+}
+
+.dashboard-monitor-page__task-action {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+}
+
+.dashboard-monitor-page__steps {
+ width: 100%;
+ max-width: 100%;
+}
+
+.dashboard-monitor-page__success-icon {
+ color: #52c41a;
+}
+
+.dashboard-monitor-page__progress {
+ margin-top: 12px;
+ padding: 12px 16px;
+ border: 1px solid var(--app-border-color);
+ border-radius: 4px;
+ background: var(--app-bg-surface-soft);
+}
+
+.dashboard-monitor-page__progress-header {
+ margin-bottom: 6px;
+ display: flex;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.dashboard-monitor-page__progress-message {
+ min-width: 0;
+ font-size: 12px;
+}
+
+.dashboard-monitor-page__progress-icon {
+ margin-right: 6px;
+ color: #1890ff;
+}
+
+.dashboard-monitor-page__progress-percent {
+ flex-shrink: 0;
+ color: #1890ff;
+}
+
+.dashboard-monitor-page .ant-steps-item-title {
+ font-size: 13px !important;
+ font-weight: 600 !important;
+}
+
+.dashboard-monitor-page .ant-steps-item-description {
+ font-size: 11px !important;
+}
+
+@media (max-width: 1199px) {
+ .dashboard-monitor-page__task-action {
+ justify-content: flex-start;
+ }
+}
diff --git a/frontend/src/pages/dashboard/index.tsx b/frontend/src/pages/dashboard/index.tsx
index f15ab96..42246c9 100644
--- a/frontend/src/pages/dashboard/index.tsx
+++ b/frontend/src/pages/dashboard/index.tsx
@@ -1,6 +1,8 @@
import React, { useEffect, useState } from 'react';
import PageContainer from "@/components/shared/PageContainer";
import AppPagination from '@/components/shared/AppPagination';
+import DataListPanel from "@/components/shared/DataListPanel";
+import SectionCard from "@/components/shared/SectionCard";
import { Row, Col, Card, Statistic, List, Tag, Typography, Button, Space, Empty, Steps, Progress, Divider } from 'antd';
import {
HistoryOutlined,
@@ -19,6 +21,7 @@ import { useNavigate } from 'react-router-dom';
import dayjs from 'dayjs';
import { getDashboardStats, DashboardStats } from '@/api/business/dashboard';
import { MeetingVO, getMeetingPage, getMeetingProgress, MeetingProgress } from '@/api/business/meeting';
+import './index.css';
const { Title, Text } = Typography;
@@ -50,13 +53,13 @@ const MeetingProgressDisplay: React.FC<{ meeting: MeetingVO }> = ({ meeting }) =
const isError = percent < 0;
return (
-
-
-
-
+
+
+
+
{progress?.message || '准备分析中...'}
- {!isError && {percent}%}
+ {!isError && {percent}%}