feat: 积分、算力、模型页面重构
parent
08d9024401
commit
6c970536b2
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div className={classes}>
|
||||
{(leftActions || rightActions) ? (
|
||||
<div className={toolbarClasses}>
|
||||
<div className="data-list-panel__left-actions">{leftActions}</div>
|
||||
<div className="data-list-panel__right-actions">{rightActions}</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="data-list-panel__table-container">
|
||||
<div className="data-list-panel__table-area">
|
||||
<div className="app-page__table-wrap">{children}</div>
|
||||
</div>
|
||||
{footer ? <div className="data-list-panel__footer">{footer}</div> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<section className={classes} style={style}>
|
||||
{hasHeader ? (
|
||||
<div className="section-card__header">
|
||||
<div className="section-card__title-wrap">
|
||||
{title ? <h2 className="section-card__title">{title}</h2> : null}
|
||||
{description ? <div className="section-card__description">{description}</div> : null}
|
||||
</div>
|
||||
{extra ? <div className="section-card__extra">{extra}</div> : null}
|
||||
</div>
|
||||
) : null}
|
||||
{tabs ? <div className="section-card__tabs">{tabs}</div> : null}
|
||||
<div className={contentClasses}>{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<PageContainer
|
||||
title="AI 模型配置"
|
||||
subtitle="管理ASR语音识别和LLM大语言模型"
|
||||
headerExtra={
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => openDrawer()}>
|
||||
新增模型
|
||||
</Button>
|
||||
}
|
||||
toolbar={
|
||||
<Input
|
||||
placeholder="搜索模型名称"
|
||||
prefix={<SearchOutlined />}
|
||||
allowClear
|
||||
onPressEnter={(event) => setSearchName((event.target as HTMLInputElement).value)}
|
||||
style={{ width: 220 }}
|
||||
/>
|
||||
}
|
||||
title={null}
|
||||
className="ai-models-page"
|
||||
>
|
||||
<Tabs
|
||||
activeKey={activeType}
|
||||
onChange={(key) => {
|
||||
setActiveType(key as ModelType);
|
||||
setCurrent(1);
|
||||
}}
|
||||
items={[
|
||||
{ key: "ASR", label: "ASR 模型" },
|
||||
{ key: "LLM", label: "LLM 模型" },
|
||||
]}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Card className="app-page__content-card" style={{ flex: 1, minHeight: 0 }} styles={{ body: { padding: 0, flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' } }}>
|
||||
<div className="app-page__table-wrap" style={{ flex: 1, minHeight: 0, overflow: "auto" }}>
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
loading={loading}
|
||||
scroll={{ x: "max-content", y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
</div>
|
||||
<AppPagination
|
||||
current={current}
|
||||
pageSize={size}
|
||||
total={total}
|
||||
onChange={(page, pageSize) => {
|
||||
setCurrent(page);
|
||||
setSize(pageSize);
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
<SectionCard
|
||||
title="AI 模型配置"
|
||||
description="管理 ASR 语音识别和 LLM 大语言模型。"
|
||||
tabs={
|
||||
<Tabs
|
||||
activeKey={activeType}
|
||||
onChange={(key) => {
|
||||
setActiveType(key as ModelType);
|
||||
setCurrent(1);
|
||||
}}
|
||||
items={[
|
||||
{ key: "ASR", label: "ASR 模型" },
|
||||
{ key: "LLM", label: "LLM 模型" },
|
||||
]}
|
||||
size="middle"
|
||||
type="card"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<DataListPanel
|
||||
leftActions={
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => openDrawer()}>
|
||||
新增模型
|
||||
</Button>
|
||||
}
|
||||
rightActions={
|
||||
<Input
|
||||
placeholder="搜索模型名称"
|
||||
prefix={<SearchOutlined />}
|
||||
allowClear
|
||||
onPressEnter={(event) => setSearchName((event.target as HTMLInputElement).value)}
|
||||
style={{ width: 220 }}
|
||||
/>
|
||||
}
|
||||
footer={
|
||||
<AppPagination
|
||||
current={current}
|
||||
pageSize={size}
|
||||
total={total}
|
||||
onChange={(page, pageSize) => {
|
||||
setCurrent(page);
|
||||
setSize(pageSize);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
loading={loading}
|
||||
scroll={{ x: "max-content", y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
</DataListPanel>
|
||||
</SectionCard>
|
||||
|
||||
<Drawer
|
||||
width={600}
|
||||
|
|
|
|||
|
|
@ -1,330 +1,19 @@
|
|||
/* 外层容器:完全对齐 web-fe CardWrapper */
|
||||
.meeting-points-page {
|
||||
padding: 8px;
|
||||
background: #f5f6fa;
|
||||
min-width: 0;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
|
||||
.meeting-points-page > .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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<PageContainer title={null} className="meeting-points-page">
|
||||
<div className="meeting-points-page__card-wrapper">
|
||||
<div className="meeting-points-page__card-title">积分管理</div>
|
||||
<div className="meeting-points-page__card-description">
|
||||
查看当前租户下的积分账面余额、累计消耗和会议消耗记录。
|
||||
</div>
|
||||
|
||||
<Tabs
|
||||
className="meeting-points-page__tabs"
|
||||
activeKey={activeTabKey}
|
||||
onChange={setActiveTabKey}
|
||||
items={sectionTabs}
|
||||
size="middle"
|
||||
type="card"
|
||||
/>
|
||||
|
||||
<div className="meeting-points-page__content-wrap">
|
||||
<div className="meeting-points-page__inner-list">
|
||||
<div className="meeting-points-page__search-box">
|
||||
<div className="meeting-points-page__left-actions">
|
||||
{isLookupTab(activeTabKey) && showTransferButton ? (
|
||||
<Button icon={<PlusOutlined />} onClick={() => void handleOpenTransfer()}>
|
||||
分配积分
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="meeting-points-page__search-input">
|
||||
<Space wrap>
|
||||
{activeTabKey === "ledger" ? (
|
||||
<>
|
||||
<Input
|
||||
placeholder="按用户名搜索"
|
||||
value={params.username}
|
||||
onChange={(event) => setParams((prev) => ({ ...prev, username: event.target.value }))}
|
||||
style={{ width: 220 }}
|
||||
prefix={<SearchOutlined className="text-gray-400" />}
|
||||
allowClear
|
||||
/>
|
||||
<Select
|
||||
style={{ width: 140 }}
|
||||
value={params.pointsType}
|
||||
onChange={(value) => setParams((prev) => ({ ...prev, pointsType: value }))}
|
||||
options={POINTS_TYPE_OPTIONS}
|
||||
/>
|
||||
<Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>
|
||||
查询
|
||||
</Button>
|
||||
<Button onClick={handleReset}>重置</Button>
|
||||
</>
|
||||
) : null}
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => void handleRefresh()}
|
||||
title="刷新"
|
||||
aria-label="刷新"
|
||||
<SectionCard
|
||||
title="积分管理"
|
||||
description="查看当前租户下的积分账面余额、累计消耗和会议消耗记录。"
|
||||
tabs={
|
||||
<Tabs
|
||||
activeKey={activeTabKey}
|
||||
onChange={setActiveTabKey}
|
||||
items={sectionTabs}
|
||||
size="middle"
|
||||
type="card"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<DataListPanel
|
||||
leftActions={
|
||||
isLookupTab(activeTabKey) && showTransferButton ? (
|
||||
<Button icon={<PlusOutlined />} onClick={() => void handleOpenTransfer()}>
|
||||
分配积分
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
rightActions={
|
||||
<Space wrap>
|
||||
{activeTabKey === "ledger" ? (
|
||||
<>
|
||||
<Input
|
||||
placeholder="按用户名搜索"
|
||||
value={params.username}
|
||||
onChange={(event) => setParams((prev) => ({ ...prev, username: event.target.value }))}
|
||||
style={{ width: 220 }}
|
||||
prefix={<SearchOutlined className="text-gray-400" />}
|
||||
allowClear
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="meeting-points-page__table-container">
|
||||
<div className="meeting-points-page__table-area">
|
||||
<div className="app-page__table-wrap">
|
||||
{activeTabKey === "overview" ? (
|
||||
<ListTable<any>
|
||||
key="overview"
|
||||
rowKey="id"
|
||||
columns={overviewColumns}
|
||||
dataSource={overviewRows}
|
||||
loading={false}
|
||||
scroll={{ x: 900, y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
) : activeTabKey === "personal" ? (
|
||||
<ListTable<any>
|
||||
key="personal"
|
||||
rowKey="userId"
|
||||
columns={personalAccountColumns}
|
||||
dataSource={pagedPersonalAccounts}
|
||||
loading={false}
|
||||
scroll={{ x: 900, y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
) : (
|
||||
<ListTable<any>
|
||||
key="ledger"
|
||||
rowKey="id"
|
||||
columns={ledgerColumns}
|
||||
dataSource={records}
|
||||
loading={loading}
|
||||
scroll={{ x: 1100, y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{activeTabKey === "overview" ? (
|
||||
<Select
|
||||
style={{ width: 140 }}
|
||||
value={params.pointsType}
|
||||
onChange={(value) => setParams((prev) => ({ ...prev, pointsType: value }))}
|
||||
options={POINTS_TYPE_OPTIONS}
|
||||
/>
|
||||
<Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>
|
||||
查询
|
||||
</Button>
|
||||
<Button onClick={handleReset}>重置</Button>
|
||||
</>
|
||||
) : null}
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => void handleRefresh()}
|
||||
title="刷新"
|
||||
aria-label="刷新"
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
footer={
|
||||
activeTabKey === "overview" ? (
|
||||
<div className="app-pagination-container meeting-points-page__static-pagination">
|
||||
<div className="app-pagination-total">共 {overviewRows.length} 条记录</div>
|
||||
</div>
|
||||
) : activeTabKey === "personal" ? (
|
||||
<AppPagination
|
||||
current={personalAccountPagination.current}
|
||||
pageSize={personalAccountPagination.pageSize}
|
||||
total={personalAccountRows.length}
|
||||
onChange={(page, size) => setPersonalAccountPagination({ current: page, pageSize: size })}
|
||||
/>
|
||||
) : (
|
||||
<AppPagination
|
||||
current={params.current}
|
||||
pageSize={params.size}
|
||||
total={total}
|
||||
onChange={(page, size) => {
|
||||
const nextParams = { ...params, current: page, size };
|
||||
setParams(nextParams);
|
||||
void loadPage(nextParams);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : activeTabKey === "personal" ? (
|
||||
<AppPagination
|
||||
current={personalAccountPagination.current}
|
||||
pageSize={personalAccountPagination.pageSize}
|
||||
total={personalAccountRows.length}
|
||||
onChange={(page, size) => setPersonalAccountPagination({ current: page, pageSize: size })}
|
||||
/>
|
||||
) : (
|
||||
<AppPagination
|
||||
current={params.current}
|
||||
pageSize={params.size}
|
||||
total={total}
|
||||
onChange={(page, size) => {
|
||||
const nextParams = { ...params, current: page, size };
|
||||
setParams(nextParams);
|
||||
void loadPage(nextParams);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
{activeTabKey === "overview" ? (
|
||||
<ListTable<any>
|
||||
key="overview"
|
||||
rowKey="id"
|
||||
columns={overviewColumns}
|
||||
dataSource={overviewRows}
|
||||
loading={false}
|
||||
scroll={{ x: 900, y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
) : activeTabKey === "personal" ? (
|
||||
<ListTable<any>
|
||||
key="personal"
|
||||
rowKey="userId"
|
||||
columns={personalAccountColumns}
|
||||
dataSource={pagedPersonalAccounts}
|
||||
loading={false}
|
||||
scroll={{ x: 900, y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
) : (
|
||||
<ListTable<any>
|
||||
key="ledger"
|
||||
rowKey="id"
|
||||
columns={ledgerColumns}
|
||||
dataSource={records}
|
||||
loading={loading}
|
||||
scroll={{ x: 1100, y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
)}
|
||||
</DataListPanel>
|
||||
</SectionCard>
|
||||
|
||||
<Modal
|
||||
title="从公共账户分配积分"
|
||||
|
|
|
|||
|
|
@ -986,7 +986,13 @@ const Meetings: React.FC = () => {
|
|||
</div>
|
||||
|
||||
<div style={{ padding: "16px 4px 0", flexShrink: 0, display: 'flex', justifyContent: 'center' }}>
|
||||
<AppPagination current={current} pageSize={size} total={total} onChange={handlePaginationChange} />
|
||||
<AppPagination
|
||||
variant={displayMode === "card" ? "card" : "table"}
|
||||
current={current}
|
||||
pageSize={size}
|
||||
total={total}
|
||||
onChange={handlePaginationChange}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ const PromptTemplates: React.FC = () => {
|
|||
const [data, setData] = useState<PromptTemplateVO[]>([]);
|
||||
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<number | null>(null);
|
||||
|
|
@ -379,6 +379,7 @@ const PromptTemplates: React.FC = () => {
|
|||
})}
|
||||
</div>
|
||||
<AppPagination
|
||||
variant="card"
|
||||
current={current}
|
||||
pageSize={pageSize}
|
||||
total={total}
|
||||
|
|
|
|||
|
|
@ -785,10 +785,10 @@ const SpeakerReg: React.FC = () => {
|
|||
</div>
|
||||
<div style={{ flexShrink: 0 }}>
|
||||
<AppPagination
|
||||
variant="card"
|
||||
current={current}
|
||||
pageSize={pageSize}
|
||||
total={total}
|
||||
pageSizeOptions={['8', '12', '20', '50']}
|
||||
onChange={(page, size) => {
|
||||
setCurrent(page);
|
||||
setPageSize(size);
|
||||
|
|
|
|||
|
|
@ -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%;
|
||||
}
|
||||
|
|
@ -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<TenantMeetingPointsSettingVO | null>(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 (
|
||||
<Card>
|
||||
<Space direction="vertical" size="large" style={{ width: "100%" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 16, flexWrap: "wrap" }}>
|
||||
<SectionCard
|
||||
title="当前租户"
|
||||
description="租户管理员可查看并切换当前租户的积分余额校验模式。"
|
||||
layout="auto"
|
||||
>
|
||||
<Space direction="vertical" size="large" className="tenant-meeting-points__tenant-card">
|
||||
<div className="tenant-meeting-points__tenant-heading">
|
||||
<Space direction="vertical" size={4}>
|
||||
<Text strong style={{ fontSize: 18 }}>{currentTenantSetting.tenantName || "当前租户"}</Text>
|
||||
<Text type="secondary">租户编码:{currentTenantSetting.tenantCode || "-"}</Text>
|
||||
|
|
@ -215,7 +222,14 @@ export default function TenantMeetingPointsSettings() {
|
|||
{renderStatusTag(currentTenantSetting.balanceCheckEnabled)}
|
||||
</div>
|
||||
|
||||
<Card size="small" style={{ background: currentTenantSetting.balanceCheckEnabled ? "#f6ffed" : "#fff7e6" }}>
|
||||
<Card
|
||||
size="small"
|
||||
className={
|
||||
currentTenantSetting.balanceCheckEnabled
|
||||
? "tenant-meeting-points__mode-card tenant-meeting-points__mode-card--enabled"
|
||||
: "tenant-meeting-points__mode-card tenant-meeting-points__mode-card--unlimited"
|
||||
}
|
||||
>
|
||||
<Space direction="vertical" size={4}>
|
||||
<Text strong>
|
||||
{currentTenantSetting.balanceCheckEnabled ? "当前为校验余额模式" : "当前为无限余额模式"}
|
||||
|
|
@ -228,7 +242,7 @@ export default function TenantMeetingPointsSettings() {
|
|||
</Space>
|
||||
</Card>
|
||||
|
||||
<Space size={40} wrap>
|
||||
<Space size={40} wrap className="tenant-meeting-points__stats">
|
||||
<Statistic title="当前可用额度" value={currentTenantSetting.balanceCheckEnabled ? currentTenantSetting.publicBalance ?? 0 : "无限"} />
|
||||
<Statistic title="公共账户余额" value={currentTenantSetting.publicBalance ?? 0} />
|
||||
<Statistic title="公共账户累计消耗" value={currentTenantSetting.publicTotalPointsUsed ?? 0} />
|
||||
|
|
@ -254,101 +268,99 @@ export default function TenantMeetingPointsSettings() {
|
|||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
</Card>
|
||||
</SectionCard>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="租户积分校验"
|
||||
subtitle="按租户控制会议积分是否校验余额,关闭后按无限余额模式记录消耗"
|
||||
headerExtra={
|
||||
<Button icon={<ReloadOutlined />} onClick={() => void handleRefresh()}>
|
||||
刷新
|
||||
</Button>
|
||||
}
|
||||
toolbar={isPlatformAdmin ? (
|
||||
<Space wrap size="middle">
|
||||
<Input
|
||||
placeholder="按租户名称搜索"
|
||||
value={params.tenantName}
|
||||
onChange={(event) => setParams((prev) => ({ ...prev, tenantName: event.target.value }))}
|
||||
style={{ width: 220 }}
|
||||
prefix={<SearchOutlined className="text-gray-400" />}
|
||||
allowClear
|
||||
/>
|
||||
<Input
|
||||
placeholder="按租户编码搜索"
|
||||
value={params.tenantCode}
|
||||
onChange={(event) => setParams((prev) => ({ ...prev, tenantCode: event.target.value }))}
|
||||
style={{ width: 180 }}
|
||||
allowClear
|
||||
/>
|
||||
<Select
|
||||
style={{ width: 180 }}
|
||||
value={params.balanceCheckEnabled}
|
||||
onChange={(value) => setParams((prev) => ({ ...prev, balanceCheckEnabled: value }))}
|
||||
options={[
|
||||
{ label: "全部状态", value: "" },
|
||||
{ label: "校验余额模式", value: "true" },
|
||||
{ label: "无限余额模式", value: "false" },
|
||||
]}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
onClick={() => {
|
||||
const nextParams = { ...params, current: 1 };
|
||||
setParams(nextParams);
|
||||
void loadPlatformPage(nextParams);
|
||||
}}
|
||||
>
|
||||
查询
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const nextParams = { current: 1, size: 20, tenantName: "", tenantCode: "", balanceCheckEnabled: "" };
|
||||
setParams(nextParams);
|
||||
void loadPlatformPage(nextParams);
|
||||
}}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</Space>
|
||||
) : undefined}
|
||||
title={null}
|
||||
className="tenant-meeting-points"
|
||||
>
|
||||
{isPlatformAdmin ? (
|
||||
<Card className="app-page__content-card" styles={{ body: { padding: 0 } }}>
|
||||
<div style={{ padding: "20px 24px 8px" }}>
|
||||
<Text strong style={{ fontSize: 16 }}>租户列表</Text>
|
||||
<div style={{ marginTop: 4 }}>
|
||||
<Text type="secondary">平台管理员可查看并调整全部租户的余额校验开关。</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-page__table-wrap" style={{overflow: "hidden", padding: "0 24px"}}>
|
||||
<SectionCard
|
||||
title="租户积分校验"
|
||||
description="按租户控制会议积分是否校验余额,关闭后按无限余额模式记录消耗。"
|
||||
>
|
||||
<DataListPanel
|
||||
leftActions={
|
||||
<Button icon={<ReloadOutlined />} onClick={() => void handleRefresh()}>
|
||||
刷新
|
||||
</Button>
|
||||
}
|
||||
rightActions={
|
||||
<Space wrap>
|
||||
<Input
|
||||
placeholder="按租户名称搜索"
|
||||
value={params.tenantName}
|
||||
onChange={(event) => setParams((prev) => ({ ...prev, tenantName: event.target.value }))}
|
||||
style={{ width: 220 }}
|
||||
prefix={<SearchOutlined className="text-gray-400" />}
|
||||
allowClear
|
||||
/>
|
||||
<Input
|
||||
placeholder="按租户编码搜索"
|
||||
value={params.tenantCode}
|
||||
onChange={(event) => setParams((prev) => ({ ...prev, tenantCode: event.target.value }))}
|
||||
style={{ width: 180 }}
|
||||
allowClear
|
||||
/>
|
||||
<Select
|
||||
style={{ width: 180 }}
|
||||
value={params.balanceCheckEnabled}
|
||||
onChange={(value) => setParams((prev) => ({ ...prev, balanceCheckEnabled: value }))}
|
||||
options={[
|
||||
{ label: "全部状态", value: "" },
|
||||
{ label: "校验余额模式", value: "true" },
|
||||
{ label: "无限余额模式", value: "false" },
|
||||
]}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SearchOutlined />}
|
||||
onClick={() => {
|
||||
const nextParams = { ...params, current: 1 };
|
||||
setParams(nextParams);
|
||||
void loadPlatformPage(nextParams);
|
||||
}}
|
||||
>
|
||||
查询
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const nextParams = { current: 1, size: 10, tenantName: "", tenantCode: "", balanceCheckEnabled: "" };
|
||||
setParams(nextParams);
|
||||
void loadPlatformPage(nextParams);
|
||||
}}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
footer={
|
||||
<AppPagination
|
||||
current={params.current}
|
||||
pageSize={params.size}
|
||||
total={total}
|
||||
onChange={(page, pageSize) => {
|
||||
const nextParams = { ...params, current: page, size: pageSize };
|
||||
setParams(nextParams);
|
||||
void loadPlatformPage(nextParams);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ListTable<TenantMeetingPointsSettingVO>
|
||||
rowKey="tenantId"
|
||||
columns={columns}
|
||||
dataSource={records}
|
||||
loading={loading}
|
||||
totalCount={total}
|
||||
scroll={{x: 1200, y: "calc(100vh - 380px)"}}
|
||||
scroll={{x: 1200, y: "100%"}}
|
||||
pagination={false}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ padding: "16px 24px" }}>
|
||||
<AppPagination
|
||||
current={params.current}
|
||||
pageSize={params.size}
|
||||
total={total}
|
||||
onChange={(page, pageSize) => {
|
||||
const nextParams = { ...params, current: page, size: pageSize };
|
||||
setParams(nextParams);
|
||||
void loadPlatformPage(nextParams);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</DataListPanel>
|
||||
</SectionCard>
|
||||
) : renderTenantAdminCard()}
|
||||
</PageContainer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<div style={{ marginTop: 12, padding: '12px 16px', background: 'var(--app-bg-surface-soft)', borderRadius: 8, border: '1px solid var(--app-border-color)' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, marginBottom: 6 }}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
<LoadingOutlined style={{ marginRight: 6, color: '#1890ff' }} spin={!isError} />
|
||||
<div className="dashboard-monitor-page__progress">
|
||||
<div className="dashboard-monitor-page__progress-header">
|
||||
<Text type="secondary" className="dashboard-monitor-page__progress-message">
|
||||
<LoadingOutlined className="dashboard-monitor-page__progress-icon" spin={!isError} />
|
||||
{progress?.message || '准备分析中...'}
|
||||
</Text>
|
||||
{!isError && <Text strong style={{ color: '#1890ff' }}>{percent}%</Text>}
|
||||
{!isError && <Text strong className="dashboard-monitor-page__progress-percent">{percent}%</Text>}
|
||||
</div>
|
||||
<Progress
|
||||
percent={isError ? 100 : percent}
|
||||
|
|
@ -129,7 +132,7 @@ export const Dashboard: React.FC = () => {
|
|||
const currentStep = item.status === 4 ? 0 : (item.status === 3 ? 2 : item.status);
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%', maxWidth: '100%' }}>
|
||||
<div className="dashboard-monitor-page__steps">
|
||||
<Steps
|
||||
size="small"
|
||||
current={currentStep}
|
||||
|
|
@ -147,7 +150,7 @@ export const Dashboard: React.FC = () => {
|
|||
},
|
||||
{
|
||||
title: '分析完成',
|
||||
icon: item.status === 3 ? <CheckCircleOutlined style={{ color: '#52c41a' }} /> : <FileTextOutlined />,
|
||||
icon: item.status === 3 ? <CheckCircleOutlined className="dashboard-monitor-page__success-icon" /> : <FileTextOutlined />,
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
|
@ -169,125 +172,105 @@ export const Dashboard: React.FC = () => {
|
|||
|
||||
return (
|
||||
<PageContainer
|
||||
title="仪表盘"
|
||||
subtitle="系统运行概览与最近任务动态"
|
||||
style={{ overflow: 'hidden' }}
|
||||
title={null}
|
||||
className="dashboard-monitor-page"
|
||||
>
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 16, flexShrink: 0 }}>
|
||||
{statCards.map((s, idx) => (
|
||||
<Col xs={24} sm={12} xl={6} key={idx}>
|
||||
<Card variant="borderless" style={{ borderRadius: 16, boxShadow: 'var(--app-shadow)', background: 'var(--app-bg-card)', border: '1px solid var(--app-border-color)', backdropFilter: 'blur(16px)' }}>
|
||||
<Statistic
|
||||
title={<Text type="secondary" style={{ fontSize: 13 }}>{s.label}</Text>}
|
||||
value={s.value || 0}
|
||||
valueStyle={{ color: s.color, fontWeight: 700 }}
|
||||
prefix={React.cloneElement(s.icon as React.ReactElement, { style: { marginRight: 8 } })}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
|
||||
<Card
|
||||
className="dashboard-task-card"
|
||||
title={
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12 }}>
|
||||
<Space><ClockCircleOutlined /> 最近任务动态</Space>
|
||||
<Button type="link" onClick={() => navigate('/meetings')}>查看历史记录</Button>
|
||||
</div>
|
||||
}
|
||||
variant="borderless"
|
||||
style={{ flex: 1, minHeight: 0, borderRadius: 16, boxShadow: 'var(--app-shadow)', background: 'var(--app-bg-card)', border: '1px solid var(--app-border-color)', backdropFilter: 'blur(16px)', overflow: 'hidden' }}
|
||||
styles={{ body: { display: 'flex', flexDirection: 'column', gap: 16, flex: 1, minHeight: 0, overflow: 'hidden',height:'90%' } }}
|
||||
<SectionCard
|
||||
title="任务监控"
|
||||
description="系统运行概览与最近任务动态。"
|
||||
contentClassName="dashboard-monitor-page__content"
|
||||
>
|
||||
<div className="dashboard-task-list">
|
||||
<List
|
||||
loading={dashboardLoading}
|
||||
dataSource={recentTasks}
|
||||
renderItem={(item) => (
|
||||
<List.Item style={{ padding: '24px 0', borderBottom: '1px solid #f0f2f5' }}>
|
||||
<div style={{ width: '100%' }}>
|
||||
<Row gutter={[24, 16]} align="middle">
|
||||
<Col xs={24} xl={8}>
|
||||
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
||||
<Title level={5} style={{ margin: 0, cursor: 'pointer', wordBreak: 'break-word' }} onClick={() => navigate(`/meetings/${item.id}`)}>
|
||||
{item.title}
|
||||
</Title>
|
||||
<Space size={12} wrap split={<Divider type="vertical" style={{ margin: 0 }} />}>
|
||||
<Text type="secondary"><CalendarOutlined /> {dayjs(item.meetingTime).format('MM-DD HH:mm')}</Text>
|
||||
<Text type="secondary"><TeamOutlined /> {item.participants || item.creatorName || '未指定'}</Text>
|
||||
</Space>
|
||||
<div style={{ marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
||||
{item.tags?.split(',').filter(Boolean).map((t) => (
|
||||
<Tag key={t} style={{ marginInlineEnd: 0, border: '1px solid var(--app-border-color)', background: 'color-mix(in srgb, var(--app-primary-color) 12%, var(--app-bg-surface-strong))', color: 'var(--app-text-main)', borderRadius: 4, fontSize: 11 }}>{t}</Tag>
|
||||
))}
|
||||
</div>
|
||||
</Space>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} xl={12}>
|
||||
{renderTaskProgress(item)}
|
||||
</Col>
|
||||
|
||||
<Col xs={24} xl={4}>
|
||||
<div className="dashboard-task-action">
|
||||
<Button
|
||||
type={item.status === 3 ? 'primary' : 'default'}
|
||||
ghost={item.status === 3}
|
||||
icon={item.status === 3 ? <FileTextOutlined /> : <PlayCircleOutlined />}
|
||||
onClick={() => navigate(`/meetings/${item.id}`)}
|
||||
>
|
||||
{item.status === 3 ? '查看纪要' : '监控详情'}
|
||||
</Button>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<MeetingProgressDisplay meeting={item} />
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
locale={{ emptyText: <Empty description="暂无近期分析任务" /> }}
|
||||
/>
|
||||
<div className="dashboard-monitor-page__stats">
|
||||
<Row gutter={[16, 16]}>
|
||||
{statCards.map((s, idx) => (
|
||||
<Col xs={24} sm={12} xl={6} key={idx}>
|
||||
<Card className="dashboard-monitor-page__stat-card" variant="borderless">
|
||||
<Statistic
|
||||
title={<Text type="secondary" className="dashboard-monitor-page__stat-label">{s.label}</Text>}
|
||||
value={s.value || 0}
|
||||
valueStyle={{ color: s.color, fontWeight: 700 }}
|
||||
prefix={React.cloneElement(s.icon as React.ReactElement, { style: { marginRight: 8 } })}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
<AppPagination
|
||||
current={pagination.current}
|
||||
pageSize={pagination.pageSize}
|
||||
total={pagination.total}
|
||||
onChange={handlePageChange}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<style>{`
|
||||
.dashboard-task-card .ant-card-head,
|
||||
.dashboard-task-card .ant-card-body,
|
||||
.dashboard-task-card .ant-list,
|
||||
.dashboard-task-card .ant-list-items,
|
||||
.dashboard-task-card .ant-list-item {
|
||||
min-width: 0;
|
||||
}
|
||||
.dashboard-task-list {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 4px;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
.dashboard-task-action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
.ant-steps-item-title { font-size: 13px !important; font-weight: 600 !important; }
|
||||
.ant-steps-item-description { font-size: 11px !important; }
|
||||
@media (max-width: 1199px) {
|
||||
.dashboard-task-action {
|
||||
justify-content: flex-start;
|
||||
<DataListPanel
|
||||
className="dashboard-monitor-page__task-panel"
|
||||
leftActions={
|
||||
<Space>
|
||||
<ClockCircleOutlined />
|
||||
<Text strong>最近任务动态</Text>
|
||||
</Space>
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
rightActions={
|
||||
<Button type="link" onClick={() => navigate('/meetings')}>
|
||||
查看历史记录
|
||||
</Button>
|
||||
}
|
||||
footer={
|
||||
<AppPagination
|
||||
current={pagination.current}
|
||||
pageSize={pagination.pageSize}
|
||||
total={pagination.total}
|
||||
onChange={handlePageChange}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className="dashboard-monitor-page__task-list">
|
||||
<List
|
||||
loading={dashboardLoading}
|
||||
dataSource={recentTasks}
|
||||
renderItem={(item) => (
|
||||
<List.Item className="dashboard-monitor-page__task-item">
|
||||
<div className="dashboard-monitor-page__task-item-inner">
|
||||
<Row gutter={[24, 16]} align="middle">
|
||||
<Col xs={24} xl={8}>
|
||||
<Space direction="vertical" size={4} className="dashboard-monitor-page__task-meta">
|
||||
<Title level={5} className="dashboard-monitor-page__task-title" onClick={() => navigate(`/meetings/${item.id}`)}>
|
||||
{item.title}
|
||||
</Title>
|
||||
<Space size={12} wrap split={<Divider type="vertical" className="dashboard-monitor-page__task-divider" />}>
|
||||
<Text type="secondary"><CalendarOutlined /> {dayjs(item.meetingTime).format('MM-DD HH:mm')}</Text>
|
||||
<Text type="secondary"><TeamOutlined /> {item.participants || item.creatorName || '未指定'}</Text>
|
||||
</Space>
|
||||
<div className="dashboard-monitor-page__task-tags">
|
||||
{item.tags?.split(',').filter(Boolean).map((t) => (
|
||||
<Tag key={t} className="dashboard-monitor-page__task-tag">{t}</Tag>
|
||||
))}
|
||||
</div>
|
||||
</Space>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} xl={12}>
|
||||
{renderTaskProgress(item)}
|
||||
</Col>
|
||||
|
||||
<Col xs={24} xl={4}>
|
||||
<div className="dashboard-monitor-page__task-action">
|
||||
<Button
|
||||
type={item.status === 3 ? 'primary' : 'default'}
|
||||
ghost={item.status === 3}
|
||||
icon={item.status === 3 ? <FileTextOutlined /> : <PlayCircleOutlined />}
|
||||
onClick={() => navigate(`/meetings/${item.id}`)}
|
||||
>
|
||||
{item.status === 3 ? '查看纪要' : '监控详情'}
|
||||
</Button>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<MeetingProgressDisplay meeting={item} />
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
locale={{ emptyText: <Empty description="暂无近期分析任务" /> }}
|
||||
/>
|
||||
</div>
|
||||
</DataListPanel>
|
||||
</SectionCard>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export default function Tenants() {
|
|||
const [saving, setSaving] = useState(false);
|
||||
const [data, setData] = useState<SysTenant[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [queryParams, setQueryParams] = useState({ current: 1, size: 12, name: "", code: "" });
|
||||
const [queryParams, setQueryParams] = useState({ current: 1, size: 8, name: "", code: "" });
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<SysTenant | null>(null);
|
||||
const [runtime, setRuntime] = useState<PlatformRuntime | null>(null);
|
||||
|
|
@ -56,7 +56,7 @@ export default function Tenants() {
|
|||
|
||||
const handleReset = () => {
|
||||
searchForm.resetFields();
|
||||
setQueryParams({ current: 1, size: 12, name: "", code: "" });
|
||||
setQueryParams({ current: 1, size: 8, name: "", code: "" });
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number, size: number) => {
|
||||
|
|
@ -221,7 +221,7 @@ export default function Tenants() {
|
|||
/>
|
||||
|
||||
<Pagination
|
||||
{...getStandardPagination(total, queryParams.current, queryParams.size, handlePageChange)}
|
||||
{...getStandardPagination(total, queryParams.current, queryParams.size, handlePageChange, { variant: "card" })}
|
||||
className="app-global-pagination"
|
||||
style={{
|
||||
marginTop: 24,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,20 @@
|
|||
import { TablePaginationConfig } from 'antd';
|
||||
import i18n from '../i18n';
|
||||
|
||||
export type PaginationVariant = 'table' | 'card';
|
||||
|
||||
export const TABLE_PAGE_SIZE_OPTIONS = ['10', '20', '50', '100'];
|
||||
export const CARD_PAGE_SIZE_OPTIONS = ['8', '16', '24', '40'];
|
||||
|
||||
export const getDefaultPageSize = (variant: PaginationVariant = 'table') => (variant === 'card' ? 8 : 10);
|
||||
|
||||
export const getPageSizeOptions = (variant: PaginationVariant = 'table') =>
|
||||
variant === 'card' ? CARD_PAGE_SIZE_OPTIONS : TABLE_PAGE_SIZE_OPTIONS;
|
||||
|
||||
export interface StandardPaginationConfig extends TablePaginationConfig {
|
||||
variant?: PaginationVariant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a standardized Ant Design pagination configuration.
|
||||
*/
|
||||
|
|
@ -9,13 +23,14 @@ export const getStandardPagination = (
|
|||
current: number,
|
||||
pageSize: number,
|
||||
onChange?: (page: number, size: number) => void,
|
||||
overrides: TablePaginationConfig = {}
|
||||
overrides: StandardPaginationConfig = {}
|
||||
): TablePaginationConfig => {
|
||||
const mergedClassName = ['app-global-pagination', overrides.className].filter(Boolean).join(' ');
|
||||
const { variant = 'table', pageSizeOptions, className, showSizeChanger, ...restOverrides } = overrides;
|
||||
const mergedClassName = ['app-global-pagination', className].filter(Boolean).join(' ');
|
||||
const mergedShowSizeChanger =
|
||||
overrides.showSizeChanger === undefined || overrides.showSizeChanger === true
|
||||
showSizeChanger === undefined || showSizeChanger === true
|
||||
? { showSearch: false }
|
||||
: overrides.showSizeChanger;
|
||||
: showSizeChanger;
|
||||
|
||||
return {
|
||||
total,
|
||||
|
|
@ -24,10 +39,10 @@ export const getStandardPagination = (
|
|||
onChange,
|
||||
showQuickJumper: true,
|
||||
showTotal: (totalCount) => i18n.t('common.total', { total: totalCount }),
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
pageSizeOptions: pageSizeOptions ?? getPageSizeOptions(variant),
|
||||
size: 'default',
|
||||
position: ['bottomRight'],
|
||||
...overrides,
|
||||
...restOverrides,
|
||||
showSizeChanger: mergedShowSizeChanger,
|
||||
className: mergedClassName
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ export default defineConfig({
|
|||
server: {
|
||||
port: 5174,
|
||||
proxy: {
|
||||
"/auth": "http://localhost:8080",
|
||||
"/sys": "http://localhost:8080",
|
||||
"/api": "http://localhost:8080",
|
||||
"/auth": "http://10.100.53.199:8080",
|
||||
"/sys": "http://10.100.53.199:8080",
|
||||
"/api": "http://10.100.53.199:8080",
|
||||
"/ws": {
|
||||
target: "ws://localhost:8080",
|
||||
target: "ws://10.100.53.199:8080",
|
||||
ws: true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue