初次重构
parent
95f7f766c5
commit
21d356bb58
|
|
@ -39,3 +39,5 @@ backend/src/main/resources/application-local.yml
|
|||
/backend/m2repo_local/
|
||||
/backend/src/test/
|
||||
/backend/target/
|
||||
/.claude
|
||||
/web-fe/
|
||||
|
|
|
|||
|
|
@ -48,12 +48,24 @@ export default function App() {
|
|||
algorithm: themeMode === 'tech' ? theme.darkAlgorithm : theme.defaultAlgorithm,
|
||||
token: {
|
||||
colorPrimary: colorPrimary,
|
||||
borderRadius: 10,
|
||||
colorBgLayout: 'transparent',
|
||||
borderRadius: 4,
|
||||
colorBgLayout: '#f5f6fa',
|
||||
},
|
||||
components: {
|
||||
Card: {
|
||||
borderRadiusLG: 16
|
||||
borderRadiusLG: 4
|
||||
},
|
||||
Button: {
|
||||
borderRadius: 4
|
||||
},
|
||||
Input: {
|
||||
borderRadius: 4
|
||||
},
|
||||
Select: {
|
||||
borderRadius: 4
|
||||
},
|
||||
Modal: {
|
||||
borderRadiusLG: 4
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
|
|
@ -1,15 +1,15 @@
|
|||
import { ColorPicker, Space, Drawer, Segmented, Typography, Button } from 'antd';
|
||||
import { FormatPainterOutlined, LayoutOutlined } from '@ant-design/icons';
|
||||
import { ColorPicker, Space, Drawer, Segmented, Typography } from 'antd';
|
||||
import { FormatPainterOutlined } from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useState } from 'react';
|
||||
import { useThemeStore, type ThemeMode, type LayoutMode } from '@/store/themeStore';
|
||||
import { useThemeStore, type ThemeMode } from '@/store/themeStore';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export default function ThemeSelector() {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const { colorPrimary, themeMode, layoutMode, setColorPrimary, setThemeMode, setLayoutMode } = useThemeStore();
|
||||
const { colorPrimary, themeMode, setColorPrimary, setThemeMode } = useThemeStore();
|
||||
|
||||
const themeOptions = [
|
||||
{ label: t('theme.default', 'Default'), value: 'default' },
|
||||
|
|
@ -17,11 +17,6 @@ export default function ThemeSelector() {
|
|||
{ label: t('theme.tech', 'Tech'), value: 'tech' },
|
||||
];
|
||||
|
||||
const layoutOptions = [
|
||||
{ label: t('theme.layoutSide', 'Side Menu'), value: 'side' },
|
||||
{ label: t('theme.layoutTop', 'Top Menu'), value: 'top' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="theme-selector-container">
|
||||
<Space align="center" style={{ cursor: 'pointer', padding: '0 8px' }} onClick={() => setOpen(true)}>
|
||||
|
|
@ -60,19 +55,6 @@ export default function ThemeSelector() {
|
|||
onChange={(value) => setThemeMode(value as ThemeMode)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style={{ marginBottom: 8, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<LayoutOutlined style={{ color: 'var(--app-primary-color)' }} />
|
||||
<Text strong>{t('theme.layout', 'Navigation Mode')}</Text>
|
||||
</div>
|
||||
<Segmented
|
||||
block
|
||||
options={layoutOptions}
|
||||
value={layoutMode}
|
||||
onChange={(value) => setLayoutMode(value as LayoutMode)}
|
||||
/>
|
||||
</div>
|
||||
</Space>
|
||||
</Drawer>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,21 +2,60 @@
|
|||
margin-top: auto;
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
padding-top: 8px;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.app-pagination-total {
|
||||
flex: 0 0 auto;
|
||||
min-width: 0;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.app-pagination-container .ant-pagination {
|
||||
flex: 1 1 100%;
|
||||
width: 100%;
|
||||
flex: 1 1 auto;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin: 0 !important;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.app-pagination-container .ant-pagination-total-text {
|
||||
margin-right: auto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-pagination-container .ant-select-selection-search-input {
|
||||
caret-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.app-pagination-container {
|
||||
height: auto;
|
||||
min-height: 50px;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.app-pagination-container .ant-pagination {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 8px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,22 +9,28 @@ export interface AppPaginationProps extends PaginationProps {
|
|||
|
||||
export default function AppPagination(props: AppPaginationProps) {
|
||||
const { t } = useTranslation();
|
||||
const { className, showSizeChanger, ...restProps } = props;
|
||||
const { className, showSizeChanger, showTotal, total, ...restProps } = props;
|
||||
const mergedClassName = ['app-global-pagination', className].filter(Boolean).join(' ');
|
||||
const mergedShowSizeChanger =
|
||||
showSizeChanger === undefined || showSizeChanger === true
|
||||
? { showSearch: false }
|
||||
: showSizeChanger;
|
||||
const current = Number(restProps.current ?? restProps.defaultCurrent ?? 1);
|
||||
const pageSize = Number(restProps.pageSize ?? restProps.defaultPageSize ?? 10);
|
||||
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 });
|
||||
|
||||
return (
|
||||
<div className="app-pagination-container">
|
||||
<div className="app-pagination-total">{totalContent}</div>
|
||||
<Pagination
|
||||
className={mergedClassName}
|
||||
showSizeChanger={mergedShowSizeChanger}
|
||||
showQuickJumper
|
||||
showTotal={(total) => t('common.total', { total })}
|
||||
pageSizeOptions={['8','10', '20', '50', '100']}
|
||||
size="default"
|
||||
total={total}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@
|
|||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* 行选中样式 */
|
||||
.list-table-container .row-selected {
|
||||
background-color: var(--item-hover-bg);
|
||||
.list-table-container .row-selected > td {
|
||||
background-color: var(--item-hover-bg) !important;
|
||||
}
|
||||
|
||||
.list-table-container .row-selected:hover > td {
|
||||
|
|
@ -25,7 +27,7 @@
|
|||
}
|
||||
|
||||
.selection-count {
|
||||
color: var(--text-color-secondary);
|
||||
color: #9095a1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
|
@ -48,6 +50,25 @@
|
|||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.list-table-container .ant-table {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.list-table-container .ant-table-thead > tr > th {
|
||||
background: #fafafa !important;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.list-table-container .ant-table-tbody > tr > td {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.list-table-container .ant-table-tbody > tr:hover > td {
|
||||
background: #f5f9ff !important;
|
||||
}
|
||||
|
||||
.list-table-container .list-table-table--y-scroll.ant-table-wrapper,
|
||||
.list-table-container .list-table-table--y-scroll.ant-table-wrapper .ant-spin-nested-loading,
|
||||
.list-table-container .list-table-table--y-scroll.ant-table-wrapper .ant-spin-container,
|
||||
|
|
@ -79,3 +100,17 @@
|
|||
max-height: var(--list-table-scroll-y) !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.list-table-container .ant-table-wrapper,
|
||||
.list-table-container .ant-spin-nested-loading,
|
||||
.list-table-container .ant-spin-container,
|
||||
.list-table-container .ant-table,
|
||||
.list-table-container .ant-table-container {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.list-table-container .ant-table-wrapper {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
.page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
padding: 16px;
|
||||
gap: 0;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.page-container__header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
padding: 16px 16px 0;
|
||||
border: 1px solid #e6e6e6;
|
||||
border-bottom: none;
|
||||
border-radius: 4px 4px 0 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.page-container__header--actions-only {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.page-container__title-wrap {
|
||||
flex: 1;
|
||||
min-width: 220px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.page-container__title.ant-typography {
|
||||
margin: 0 !important;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
color: #333333;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.page-container__title.ant-typography::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
top: 6px;
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
background: #3c70f5;
|
||||
}
|
||||
|
||||
.page-container__subtitle.ant-typography {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
padding-bottom: 16px;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
color: #9095a1;
|
||||
}
|
||||
|
||||
.page-container__header-extra {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.page-container__toolbar {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 0 16px 8px;
|
||||
border-left: 1px solid #e6e6e6;
|
||||
border-right: 1px solid #e6e6e6;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.page-container__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 16px 16px;
|
||||
border: 1px solid #e6e6e6;
|
||||
border-top: none;
|
||||
border-radius: 0 0 4px 4px;
|
||||
background: #fff;
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Space, Typography } from 'antd';
|
||||
import { Typography } from 'antd';
|
||||
import type { ReactNode } from 'react';
|
||||
import './PageContainer.css';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
|
|
@ -23,76 +24,37 @@ const PageContainer: React.FC<PageContainerProps> = ({
|
|||
className = '',
|
||||
style
|
||||
}) => {
|
||||
const hasTitle = title !== null && title !== undefined && title !== false;
|
||||
const hasSubtitle = subtitle !== null && subtitle !== undefined && subtitle !== false;
|
||||
const hasHeader = hasTitle || hasSubtitle || Boolean(headerExtra);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`page-container ${className}`}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
gap: 16,
|
||||
...style
|
||||
}}
|
||||
>
|
||||
{(title || headerExtra) && (
|
||||
<div
|
||||
className="page-container__header"
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
flexWrap: 'wrap',
|
||||
gap: 16,
|
||||
paddingBottom: 12,
|
||||
borderBottom: '1px solid var(--app-border-color)'
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: 1, minWidth: 200 }}>
|
||||
<Title level={4} style={{ margin: 0, fontWeight: 600 }}>
|
||||
{title}
|
||||
</Title>
|
||||
{subtitle && (
|
||||
<Text type="secondary" style={{ display: 'block', fontSize: 14, marginTop: 4 }}>
|
||||
<div className={`page-container ${className}`.trim()} style={style}>
|
||||
{hasHeader && (
|
||||
<div className={`page-container__header ${!hasTitle && !hasSubtitle ? 'page-container__header--actions-only' : ''}`.trim()}>
|
||||
{(hasTitle || hasSubtitle) && (
|
||||
<div className="page-container__title-wrap">
|
||||
{hasTitle && (
|
||||
<Title level={4} className="page-container__title">
|
||||
{title}
|
||||
</Title>
|
||||
)}
|
||||
{hasSubtitle && (
|
||||
<Text type="secondary" className="page-container__subtitle">
|
||||
{subtitle}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
{headerExtra && (
|
||||
<div className="page-container__header-extra" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
{headerExtra}
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{headerExtra && <div className="page-container__header-extra">{headerExtra}</div>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{toolbar && (
|
||||
<div
|
||||
className="page-container__toolbar"
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
gap: 12
|
||||
}}
|
||||
>
|
||||
{toolbar}
|
||||
</div>
|
||||
)}
|
||||
{toolbar && <div className="page-container__toolbar">{toolbar}</div>}
|
||||
|
||||
<div
|
||||
className="page-container__body"
|
||||
style={{
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div className="page-container__body">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageContainer;
|
||||
export default PageContainer;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@
|
|||
--app-bg-surface-soft: rgba(255, 255, 255, 0.56);
|
||||
--app-bg-surface-strong: rgba(255, 255, 255, 0.82);
|
||||
--app-text-muted: #66758f;
|
||||
--item-hover-bg: rgba(22, 119, 255, 0.08);
|
||||
--text-color-secondary: #66758f;
|
||||
--link-color: #1677ff;
|
||||
}
|
||||
|
||||
:root[data-theme="minimal"] {
|
||||
|
|
@ -39,6 +42,9 @@
|
|||
--app-bg-surface-soft: rgba(255, 255, 255, 0.62);
|
||||
--app-bg-surface-strong: rgba(255, 255, 255, 0.86);
|
||||
--app-text-muted: #5b6474;
|
||||
--item-hover-bg: rgba(22, 119, 255, 0.08);
|
||||
--text-color-secondary: #5b6474;
|
||||
--link-color: #1677ff;
|
||||
}
|
||||
|
||||
:root[data-theme="tech"] {
|
||||
|
|
@ -59,6 +65,9 @@
|
|||
--app-bg-surface-soft: rgba(10, 21, 37, 0.72);
|
||||
--app-bg-surface-strong: rgba(8, 17, 31, 0.88);
|
||||
--app-text-muted: rgba(190, 206, 229, 0.74);
|
||||
--item-hover-bg: rgba(88, 151, 255, 0.18);
|
||||
--text-color-secondary: rgba(190, 206, 229, 0.74);
|
||||
--link-color: #60a5fa;
|
||||
}
|
||||
|
||||
html {
|
||||
|
|
@ -1172,3 +1181,98 @@ body::after {
|
|||
0% { opacity: 0.4; }
|
||||
100% { opacity: 1; text-shadow: 0 0 8px rgba(22, 119, 255, 0.4); }
|
||||
}
|
||||
|
||||
/* web-fe baseline overrides */
|
||||
:root,
|
||||
:root[data-theme="minimal"],
|
||||
:root[data-theme="tech"] {
|
||||
--app-bg-main: #f5f6fa;
|
||||
--app-bg-overlay: none;
|
||||
--app-bg-card: #fff;
|
||||
--app-text-main: #333333;
|
||||
--app-text-secondary: #9095a1;
|
||||
--app-border-color: #e6e6e6;
|
||||
--app-shadow: none;
|
||||
--app-bg-page: #f5f6fa;
|
||||
--app-bg-surface: #fff;
|
||||
--app-bg-surface-soft: #fafafa;
|
||||
--app-bg-surface-strong: #fff;
|
||||
--app-text-muted: #9095a1;
|
||||
--text-color-secondary: #9095a1;
|
||||
--link-color: #1677ff;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
background: #f5f6fa;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Microsoft YaHei UI", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
||||
}
|
||||
|
||||
body::before,
|
||||
body::after,
|
||||
#root::before,
|
||||
#root::after {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider {
|
||||
background: #fff !important;
|
||||
border-right: 1px solid #f5f6fa;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
.ant-menu-light {
|
||||
background: transparent !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
.ant-menu-light .ant-menu-item a {
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
.app-page__filter-card,
|
||||
.app-page__content-card,
|
||||
.app-page__panel-card {
|
||||
border: none;
|
||||
border-radius: 4px !important;
|
||||
box-shadow: none;
|
||||
background: #fff;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
.app-page__filter-card {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.app-page__toolbar .ant-input,
|
||||
.app-page__toolbar .ant-input-affix-wrapper,
|
||||
.app-page__toolbar .ant-select-selector,
|
||||
.app-page__toolbar .ant-picker,
|
||||
.app-page__toolbar .ant-input-number,
|
||||
.app-page__toolbar .ant-btn,
|
||||
.app-page__page-actions .ant-btn,
|
||||
.ant-btn,
|
||||
.ant-input,
|
||||
.ant-input-affix-wrapper,
|
||||
.ant-select-selector,
|
||||
.ant-picker {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
.app-page__empty-state {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1px dashed #d9d9d9;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
.ant-table-wrapper .ant-table-pagination.ant-pagination.app-global-pagination,
|
||||
.app-global-pagination.ant-pagination {
|
||||
padding: 8px 0 0;
|
||||
background: #fff;
|
||||
border-top: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,271 @@
|
|||
.main-layout {
|
||||
min-height: 100vh;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
|
||||
.main-shell {
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
|
||||
.main-header {
|
||||
height: 64px;
|
||||
padding: 0 24px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.app-header-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-header-logo img {
|
||||
height: 36px;
|
||||
width: auto;
|
||||
max-width: 160px;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.app-header-logo-title {
|
||||
color: #1f2a37;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.main-sider {
|
||||
position: relative;
|
||||
height: calc(100vh - 64px);
|
||||
overflow: visible;
|
||||
border-right: 1px solid #f5f6fa;
|
||||
border-top: 2px solid #f5f6fa;
|
||||
background: #fff !important;
|
||||
transition:
|
||||
flex-basis 0.24s ease,
|
||||
max-width 0.24s ease,
|
||||
min-width 0.24s ease,
|
||||
width 0.24s ease;
|
||||
will-change: width;
|
||||
}
|
||||
|
||||
.main-sider .ant-layout-sider-children {
|
||||
height: calc(100vh - 64px);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.main-sider .ant-layout-sider-children::-webkit-scrollbar,
|
||||
.main-menu::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.main-sider .ant-layout-sider-children::-webkit-scrollbar-track,
|
||||
.main-menu::-webkit-scrollbar-track {
|
||||
background: #f8f8f8 !important;
|
||||
}
|
||||
|
||||
.main-sider .ant-layout-sider-children::-webkit-scrollbar-thumb,
|
||||
.main-menu::-webkit-scrollbar-thumb {
|
||||
background: #f8f8f8 !important;
|
||||
border-radius: 3px !important;
|
||||
}
|
||||
|
||||
.main-sider .ant-layout-sider-children::-webkit-scrollbar-thumb:hover,
|
||||
.main-menu::-webkit-scrollbar-thumb:hover {
|
||||
background: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
.main-sider .ant-layout-sider-trigger {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main-sider .ant-layout-sider-zero-width-trigger {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main-sider .ant-menu {
|
||||
border-inline-end: none !important;
|
||||
transition:
|
||||
width 0.24s ease,
|
||||
min-width 0.24s ease,
|
||||
max-width 0.24s ease;
|
||||
}
|
||||
|
||||
.main-sider .ant-menu-title-content,
|
||||
.main-sider .ant-menu-submenu-arrow {
|
||||
transition:
|
||||
opacity 0.18s ease,
|
||||
transform 0.18s ease,
|
||||
color 0.18s ease;
|
||||
}
|
||||
|
||||
.main-sider--collapsed .ant-menu-title-content,
|
||||
.main-sider--collapsed .ant-menu-submenu-arrow {
|
||||
opacity: 0;
|
||||
transform: translateX(-4px);
|
||||
}
|
||||
|
||||
.main-sider .ant-menu-item,
|
||||
.main-sider .ant-menu-submenu-title {
|
||||
width: calc(100% - 8px);
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
margin-inline: 4px;
|
||||
margin-block: 4px;
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
background-color 0.18s ease,
|
||||
color 0.18s ease,
|
||||
padding 0.24s ease,
|
||||
margin 0.24s ease;
|
||||
}
|
||||
|
||||
.main-sider--collapsed .ant-menu-item,
|
||||
.main-sider--collapsed .ant-menu-submenu-title {
|
||||
width: 56px;
|
||||
margin-inline: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.main-sider--collapsed .ant-menu-item .ant-menu-item-icon,
|
||||
.main-sider--collapsed .ant-menu-submenu-title .ant-menu-item-icon {
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
|
||||
.main-sider--collapsed .ant-menu-inline-collapsed > .ant-menu-item,
|
||||
.main-sider--collapsed .ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title {
|
||||
padding-inline: 20px !important;
|
||||
}
|
||||
|
||||
.main-sider--collapsed .ant-menu-inline-collapsed > .ant-menu-item .ant-menu-item-icon,
|
||||
.main-sider--collapsed .ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title .ant-menu-item-icon {
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
.main-sider--collapsed .ant-menu-inline-collapsed {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.main-sider--collapsed .main-menu {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.main-sider .collapsedCenter {
|
||||
position: absolute;
|
||||
right: -17px;
|
||||
top: 18px;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 35px;
|
||||
border: 1px solid #f0f0f0;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
|
||||
background: #fff;
|
||||
transition:
|
||||
border-color 0.18s ease,
|
||||
box-shadow 0.18s ease,
|
||||
transform 0.24s ease;
|
||||
}
|
||||
|
||||
.main-sider .collapsedCenter:hover {
|
||||
border-color: rgba(24, 144, 255, 0.4);
|
||||
box-shadow: 0 4px 8px rgba(24, 144, 255, 0.4);
|
||||
}
|
||||
|
||||
.main-sider--collapsed .collapsedCenter {
|
||||
transform: translateX(1px);
|
||||
}
|
||||
|
||||
.main-sider .collapsedCenter .trigger {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
color: #666;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
transition:
|
||||
color 0.18s ease,
|
||||
background-color 0.18s ease,
|
||||
transform 0.24s ease;
|
||||
}
|
||||
|
||||
.main-sider .collapsedCenter .trigger.ant-btn {
|
||||
border-radius: 50% !important;
|
||||
}
|
||||
|
||||
.main-sider .collapsedCenter .trigger .anticon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.main-sider .collapsedCenter .trigger .anticon svg {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.main-sider--collapsed .collapsedCenter .trigger .anticon svg {
|
||||
transform: translateX(1px);
|
||||
}
|
||||
|
||||
.main-sider .collapsedCenter .trigger:hover {
|
||||
color: #1890ff;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.main-menu {
|
||||
height: calc(100% - 10px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
border-inline-end: none !important;
|
||||
padding-top: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.main-content-layout {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
position: relative;
|
||||
height: calc(100vh - 64px);
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import {
|
|||
DashboardOutlined,
|
||||
DesktopOutlined,
|
||||
GlobalOutlined,
|
||||
LayoutOutlined,
|
||||
LogoutOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
|
|
@ -27,9 +26,9 @@ import { useAuth } from "@/hooks/useAuth";
|
|||
import { usePermission } from "@/hooks/usePermission";
|
||||
import type { PlatformRuntime, SysPermission, SysPlatformConfig } from "@/types";
|
||||
import ThemeSelector from "@/components/ThemeSelector/ThemeSelector";
|
||||
import { useThemeStore } from "@/store/themeStore";
|
||||
import "./AppLayout.css";
|
||||
|
||||
const { Header, Sider, Content, Footer } = Layout;
|
||||
const { Header, Sider, Content } = Layout;
|
||||
|
||||
const iconMap: Record<string, ReactNode> = {
|
||||
dashboard: <DashboardOutlined />,
|
||||
|
|
@ -75,7 +74,7 @@ function findActiveMenu(nodes: PermissionMenuNode[], path: string, parentKeys: s
|
|||
for (const node of nodes) {
|
||||
const key = getMenuKey(node);
|
||||
|
||||
if (node.path === path) {
|
||||
if (node.path === path || (node.path && node.path !== "/" && path.startsWith(`${node.path}/`))) {
|
||||
return { key, parentKeys };
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +128,6 @@ export default function AppLayout() {
|
|||
const navigate = useNavigate();
|
||||
const { logout } = useAuth();
|
||||
const { load: loadPermissions } = usePermission();
|
||||
const { layoutMode } = useThemeStore();
|
||||
|
||||
const fetchInitialData = useCallback(async () => {
|
||||
try {
|
||||
|
|
@ -380,155 +378,56 @@ export default function AppLayout() {
|
|||
</Space>
|
||||
);
|
||||
|
||||
const renderLogo = (isTop: boolean = false) => (
|
||||
<div
|
||||
style={{
|
||||
height: 64,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: isTop ? "0 24px 0 0" : "0 16px",
|
||||
gap: "12px",
|
||||
borderBottom: isTop ? "none" : "1px solid var(--app-border-color)",
|
||||
flexShrink: 0
|
||||
}}
|
||||
>
|
||||
<img src={platformConfig?.logoUrl || "/logo.svg"} alt="logo" style={{ width: 32, height: 32, objectFit: "contain" }} />
|
||||
{(!collapsed || isTop) && (
|
||||
<span
|
||||
style={{
|
||||
fontSize: "18px",
|
||||
fontWeight: 700,
|
||||
color: "var(--app-primary-color)",
|
||||
letterSpacing: "0.5px",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap"
|
||||
}}
|
||||
>
|
||||
{platformConfig?.projectName || "UnisBase"}
|
||||
</span>
|
||||
)}
|
||||
const renderLogo = () => (
|
||||
<div className="app-header-logo">
|
||||
<img src={platformConfig?.logoUrl || "/logo.svg"} alt={platformConfig?.projectName || "logo"} />
|
||||
<span className="app-header-logo-title">{platformConfig?.projectName || "智听云"}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout style={{ height: "100vh", overflow: "hidden" }}>
|
||||
{layoutMode === 'side' && (
|
||||
<Layout className="main-layout">
|
||||
<Header className="main-header">
|
||||
{renderLogo()}
|
||||
<div className="header-right">{headerRightTools}</div>
|
||||
</Header>
|
||||
|
||||
<Layout className="main-shell">
|
||||
<Sider
|
||||
trigger={null}
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
className="app-sider"
|
||||
style={{ boxShadow: "var(--app-shadow)", zIndex: 10 }}
|
||||
className={`main-sider app-sider ${collapsed ? "main-sider--collapsed" : ""}`}
|
||||
theme="light"
|
||||
width={180}
|
||||
>
|
||||
{renderLogo(false)}
|
||||
<div style={{ flex: 1, overflowY: "auto", overflowX: "hidden" }}>
|
||||
<Menu
|
||||
mode="inline"
|
||||
selectedKeys={selectedMenuKeys}
|
||||
openKeys={openKeys}
|
||||
onOpenChange={setOpenKeys}
|
||||
items={menuItems}
|
||||
style={{ borderRight: 0, marginTop: 16 }}
|
||||
/>
|
||||
</div>
|
||||
</Sider>
|
||||
)}
|
||||
|
||||
<Layout style={{ flex: 1, minWidth: 0 }}>
|
||||
<Header
|
||||
style={{
|
||||
background: "var(--app-bg-card)",
|
||||
padding: "0 24px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
boxShadow: "var(--app-shadow)",
|
||||
zIndex: 9,
|
||||
height: 64,
|
||||
flexShrink: 0,
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
{layoutMode === 'side' ? (
|
||||
<div className="collapsedCenter">
|
||||
<Button
|
||||
type="text"
|
||||
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
shape="circle"
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
style={{ fontSize: "16px", width: 64, height: 64, color: "var(--app-text-main)", marginLeft: -24 }}
|
||||
className="trigger"
|
||||
aria-label={collapsed ? "展开菜单" : "收起菜单"}
|
||||
/>
|
||||
) : (
|
||||
renderLogo(true)
|
||||
)}
|
||||
|
||||
{layoutMode === 'top' ? (
|
||||
<div style={{ flex: 1, minWidth: 0, padding: "0 24px" }}>
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
selectedKeys={selectedMenuKeys}
|
||||
items={menuItems}
|
||||
style={{
|
||||
borderBottom: 0,
|
||||
lineHeight: '62px',
|
||||
background: 'transparent',
|
||||
color: 'var(--app-text-main)'
|
||||
}}
|
||||
theme={document.documentElement.getAttribute('data-theme') === 'tech' ? 'dark' : 'light'}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ flex: 1, display: "flex", alignItems: "center", paddingLeft: 12 }} />
|
||||
)}
|
||||
|
||||
{headerRightTools}
|
||||
</Header>
|
||||
|
||||
<Content
|
||||
style={{
|
||||
margin: "24px 24px 12px",
|
||||
padding: 24,
|
||||
background: "var(--app-bg-card)",
|
||||
borderRadius: "8px",
|
||||
boxShadow: "var(--app-shadow)",
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flex: 1,
|
||||
minHeight: 0
|
||||
}}
|
||||
>
|
||||
<Outlet />
|
||||
</Content>
|
||||
|
||||
<Footer
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
padding: "0 24px 16px",
|
||||
background: "transparent"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
minHeight: 44,
|
||||
borderRadius: 8,
|
||||
border: "1px solid var(--app-border-color)",
|
||||
background: "var(--app-bg-card)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 16,
|
||||
padding: "10px 16px",
|
||||
color: "var(--app-text-main)",
|
||||
fontSize: 13,
|
||||
textAlign: "center",
|
||||
backdropFilter: "blur(10px)"
|
||||
}}
|
||||
>
|
||||
{platformConfig?.icpInfo ? <span>{platformConfig.icpInfo}</span> : null}
|
||||
{platformConfig?.icpInfo && platformConfig?.copyrightInfo ? <span aria-hidden="true" style={{ color: "#d0d5dd" }}>|</span> : null}
|
||||
{platformConfig?.copyrightInfo ? <span>{platformConfig.copyrightInfo}</span> : null}
|
||||
</div>
|
||||
</Footer>
|
||||
|
||||
<Menu
|
||||
mode="inline"
|
||||
selectedKeys={selectedMenuKeys}
|
||||
openKeys={collapsed ? [] : openKeys}
|
||||
onOpenChange={setOpenKeys}
|
||||
inlineCollapsed={collapsed}
|
||||
items={menuItems}
|
||||
className="main-menu"
|
||||
/>
|
||||
</Sider>
|
||||
|
||||
<Layout className="main-content-layout">
|
||||
<Content className="main-content">
|
||||
<Outlet />
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,330 @@
|
|||
/* 外层容器:完全对齐 web-fe CardWrapper */
|
||||
.meeting-points-page {
|
||||
padding: 8px;
|
||||
background: #f5f6fa;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.meeting-points-page > .page-container__body {
|
||||
padding: 0;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,24 +2,21 @@ import { PlusOutlined, ReloadOutlined, SearchOutlined } from "@ant-design/icons"
|
|||
import { listUsers } from "@/api";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
message,
|
||||
Modal,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Statistic,
|
||||
Tag,
|
||||
Tabs,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import PageContainer from "@/components/shared/PageContainer";
|
||||
import ListTable from "@/components/shared/ListTable/ListTable";
|
||||
import AppPagination from "@/components/shared/AppPagination";
|
||||
import ListTable from "@/components/shared/ListTable/ListTable";
|
||||
import {
|
||||
getMeetingPointsLedgerPage,
|
||||
getMeetingPointsOverview,
|
||||
|
|
@ -29,6 +26,7 @@ import {
|
|||
type MeetingPointsPersonalAccountVO,
|
||||
} from "@/api/business/meetingPoints";
|
||||
import type { SysUser } from "@/types";
|
||||
import "./MeetingPointsManagement.css";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
|
|
@ -83,74 +81,12 @@ function formatDateTime(value?: string) {
|
|||
return value ? value.replace("T", " ").substring(0, 19) : "-";
|
||||
}
|
||||
|
||||
function buildSummaryCards(overview: MeetingPointsOverviewVO | null) {
|
||||
if (!overview) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const isAdmin = Boolean(overview.admin);
|
||||
const isPublicOnly = overview.accountMode === ACCOUNT_MODE_PUBLIC;
|
||||
const isPersonalOnly = overview.accountMode === ACCOUNT_MODE_PERSONAL;
|
||||
const showPublicSummary = !isPersonalOnly || isAdmin;
|
||||
const showPersonalSummary = !isPublicOnly;
|
||||
const isUnlimitedBalanceMode = overview.balanceCheckEnabled === false;
|
||||
|
||||
const cards: Array<{
|
||||
key: string;
|
||||
title: string;
|
||||
value: number | string;
|
||||
note: string;
|
||||
}> = [
|
||||
// {
|
||||
// key: "available-balance",
|
||||
// title: "当前可用额度",
|
||||
// value: isUnlimitedBalanceMode ? "无限" : (overview.totalAvailableBalance ?? 0),
|
||||
// note: isUnlimitedBalanceMode ? "关闭余额校验后只记录消耗和流水" : "按当前账户模式计算的可用额度",
|
||||
// },
|
||||
{
|
||||
key: "charge-count",
|
||||
title: "累计消耗次数",
|
||||
value: overview.totalChargeCount ?? 0,
|
||||
note: "已发生扣费的总结记录数",
|
||||
},
|
||||
];
|
||||
|
||||
if (showPublicSummary) {
|
||||
cards.unshift(
|
||||
{
|
||||
key: "public-balance",
|
||||
title: "公共账户余额",
|
||||
value: overview.publicBalance ?? 0,
|
||||
note: "当前账面公共余额",
|
||||
},
|
||||
{
|
||||
key: "public-used",
|
||||
title: "公共账户累计消耗",
|
||||
value: overview.publicTotalPointsUsed ?? 0,
|
||||
note: "公共账户历史累计消耗",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (showPersonalSummary) {
|
||||
cards.push(
|
||||
{
|
||||
key: "personal-balance",
|
||||
title: isAdmin ? "个人账户余额汇总" : "个人账户余额",
|
||||
value: overview.personalBalance ?? 0,
|
||||
note: isAdmin ? "管理员视角下的个人账户余额汇总" : "当前账号账面余额",
|
||||
},
|
||||
{
|
||||
key: "personal-used",
|
||||
title: isAdmin ? "个人账户累计消耗汇总" : "个人账户累计消耗",
|
||||
value: overview.personalTotalPointsUsed ?? 0,
|
||||
note: isAdmin ? "管理员视角下的个人账户累计消耗" : "当前账号历史累计消耗",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return cards;
|
||||
}
|
||||
type OverviewRow = {
|
||||
id: string;
|
||||
metric: string;
|
||||
value: string | number;
|
||||
note: string;
|
||||
};
|
||||
|
||||
export default function MeetingPointsManagement() {
|
||||
const [overview, setOverview] = useState<MeetingPointsOverviewVO | null>(null);
|
||||
|
|
@ -161,14 +97,14 @@ export default function MeetingPointsManagement() {
|
|||
const [total, setTotal] = useState(0);
|
||||
const [transferOpen, setTransferOpen] = useState(false);
|
||||
const [users, setUsers] = useState<SysUser[]>([]);
|
||||
const [contentTab, setContentTab] = useState("ledger");
|
||||
const [activeTabKey, setActiveTabKey] = useState("ledger");
|
||||
const [personalAccountPagination, setPersonalAccountPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
pageSize: 8,
|
||||
});
|
||||
const [params, setParams] = useState({
|
||||
current: 1,
|
||||
size: 20,
|
||||
size: 8,
|
||||
username: "",
|
||||
pointsType: "",
|
||||
});
|
||||
|
|
@ -176,21 +112,28 @@ export default function MeetingPointsManagement() {
|
|||
|
||||
const isAdmin = Boolean(overview?.admin);
|
||||
const isPublicOnly = overview?.accountMode === ACCOUNT_MODE_PUBLIC;
|
||||
const isPersonalOnly = overview?.accountMode === ACCOUNT_MODE_PERSONAL;
|
||||
const isUnlimitedBalanceMode = overview?.balanceCheckEnabled === false;
|
||||
const showTransferButton = isAdmin && !isPublicOnly && !isUnlimitedBalanceMode;
|
||||
const showPersonalAccountSection = Boolean(overview) && !isPublicOnly;
|
||||
const summaryCards = useMemo(() => buildSummaryCards(overview), [overview]);
|
||||
|
||||
const sectionTabs = useMemo(
|
||||
() => [
|
||||
{ key: "ledger", label: "积分流水" },
|
||||
{ key: "overview", label: "账户概览" },
|
||||
...(showPersonalAccountSection ? [{ key: "personal", label: "个人账户" }] : []),
|
||||
],
|
||||
[showPersonalAccountSection],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sectionTabs.some((tab) => tab.key === activeTabKey)) {
|
||||
setActiveTabKey("ledger");
|
||||
}
|
||||
}, [activeTabKey, sectionTabs]);
|
||||
|
||||
const personalAccountRows = useMemo<MeetingPointsPersonalAccountVO[]>(() => {
|
||||
if (!overview || isPublicOnly) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (isAdmin) {
|
||||
return overview.personalAccounts || [];
|
||||
}
|
||||
|
||||
if (!overview || isPublicOnly) return [];
|
||||
if (isAdmin) return overview.personalAccounts || [];
|
||||
return [
|
||||
{
|
||||
userId: -1,
|
||||
|
|
@ -214,12 +157,6 @@ export default function MeetingPointsManagement() {
|
|||
});
|
||||
}, [personalAccountRows.length]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPersonalOnly && contentTab !== "ledger") {
|
||||
setContentTab("ledger");
|
||||
}
|
||||
}, [contentTab, isPersonalOnly]);
|
||||
|
||||
const loadOverview = async () => {
|
||||
const data = await getMeetingPointsOverview();
|
||||
setOverview(data);
|
||||
|
|
@ -259,7 +196,7 @@ export default function MeetingPointsManagement() {
|
|||
const handleReset = () => {
|
||||
const nextParams = {
|
||||
current: 1,
|
||||
size: 20,
|
||||
size: 8,
|
||||
username: "",
|
||||
pointsType: "",
|
||||
};
|
||||
|
|
@ -386,182 +323,176 @@ export default function MeetingPointsManagement() {
|
|||
},
|
||||
];
|
||||
|
||||
const ledgerTableContent = (
|
||||
<div style={{ display: "flex", flexDirection: "column", height: "400px" }}>
|
||||
<div className="app-page__table-wrap" style={{overflow: "hidden", padding: "0 24px"}}>
|
||||
<ListTable<MeetingPointsLedgerListItemVO>
|
||||
rowKey="id"
|
||||
columns={ledgerColumns}
|
||||
dataSource={records}
|
||||
loading={loading}
|
||||
totalCount={total}
|
||||
scroll={{x: 1100, y: 280}}
|
||||
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 loadPage(nextParams);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const overviewRows = useMemo<OverviewRow[]>(() => {
|
||||
if (!overview) return [];
|
||||
return [
|
||||
{ id: "accountMode", metric: "账户模式", value: getAccountModeLabel(overview.accountMode), note: "当前租户积分账户组合方式" },
|
||||
{ id: "chargePriority", metric: "扣费优先级", value: getChargePriorityLabel(overview.chargePriority), note: "公共账户与个人账户的扣费顺序" },
|
||||
{ id: "balanceCheckEnabled", metric: "余额校验状态", value: overview.balanceCheckEnabled ? "校验余额模式" : "无限余额模式", note: "控制会议提交时是否执行余额拦截" },
|
||||
{ id: "publicBalance", metric: "公共账户余额", value: overview.publicBalance ?? 0, note: "公共账户当前可用积分" },
|
||||
{ id: "publicTotalPointsUsed", metric: "公共账户累计消耗", value: overview.publicTotalPointsUsed ?? 0, note: "公共账户已消耗积分总量" },
|
||||
{ id: "personalBalance", metric: "个人账户余额", value: overview.personalBalance ?? 0, note: "个人账户当前可用积分" },
|
||||
{ id: "personalTotalPointsUsed", metric: "个人账户累计消耗", value: overview.personalTotalPointsUsed ?? 0, note: "个人账户已消耗积分总量" },
|
||||
{ id: "totalAvailableBalance", metric: "总可用余额", value: overview.totalAvailableBalance ?? 0, note: "当前账户体系可直接使用的积分余额" },
|
||||
{ id: "totalChargeCount", metric: "累计扣费次数", value: overview.totalChargeCount ?? 0, note: "已产生的积分扣费记录数" },
|
||||
];
|
||||
}, [overview]);
|
||||
|
||||
const personalAccountTableContent = (
|
||||
<div style={{ display: "flex", flexDirection: "column", height: "400px" }}>
|
||||
<div className="app-page__table-wrap" style={{overflow: "hidden", padding: "0 24px"}}>
|
||||
<ListTable<MeetingPointsPersonalAccountVO>
|
||||
rowKey="userId"
|
||||
columns={personalAccountColumns}
|
||||
dataSource={pagedPersonalAccounts}
|
||||
totalCount={personalAccountRows.length}
|
||||
scroll={{x: 900, y: 280}}
|
||||
pagination={false}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ padding: "16px 24px" }}>
|
||||
<AppPagination
|
||||
current={personalAccountPagination.current}
|
||||
pageSize={personalAccountPagination.pageSize}
|
||||
total={personalAccountRows.length}
|
||||
onChange={(page, pageSize) => {
|
||||
setPersonalAccountPagination({ current: page, pageSize });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const overviewColumns = [
|
||||
{
|
||||
title: "指标",
|
||||
dataIndex: "metric",
|
||||
key: "metric",
|
||||
width: 180,
|
||||
render: (value: string) => <Text strong>{value || "-"}</Text>,
|
||||
},
|
||||
{
|
||||
title: "数值",
|
||||
dataIndex: "value",
|
||||
key: "value",
|
||||
width: 180,
|
||||
render: (value: string | number, record: OverviewRow) =>
|
||||
record.id === "balanceCheckEnabled" ? (
|
||||
<Tag color={overview?.balanceCheckEnabled ? "green" : "volcano"}>{value}</Tag>
|
||||
) : (
|
||||
<Text strong>{value ?? "-"}</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "说明",
|
||||
dataIndex: "note",
|
||||
key: "note",
|
||||
ellipsis: true,
|
||||
render: (value: string) => <Text type="secondary">{value || "-"}</Text>,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="积分管理"
|
||||
subtitle="查看当前租户下的积分账面余额、累计消耗和会议消耗记录"
|
||||
style={{ height: "auto" }}
|
||||
headerExtra={
|
||||
<Space>
|
||||
{showTransferButton ? (
|
||||
<Button icon={<PlusOutlined />} onClick={() => void handleOpenTransfer()}>
|
||||
分配积分
|
||||
</Button>
|
||||
) : null}
|
||||
<Button icon={<ReloadOutlined />} onClick={() => void handleRefresh()}>
|
||||
刷新
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
toolbar={
|
||||
<Space wrap size="middle">
|
||||
<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>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<div style={{ marginBottom: 20, padding: "0 4px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
gap: 16,
|
||||
flexWrap: "wrap",
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<Text strong style={{ fontSize: 16 }}>
|
||||
账户概览
|
||||
</Text>
|
||||
{/*<Space wrap>*/}
|
||||
{/* <Tag color="processing" bordered={false}>*/}
|
||||
{/* 模式:{getAccountModeLabel(overview?.accountMode)}*/}
|
||||
{/* </Tag>*/}
|
||||
{/* <Tag color="blue" bordered={false}>*/}
|
||||
{/* 优先级:{getChargePriorityLabel(overview?.chargePriority)}*/}
|
||||
{/* </Tag>*/}
|
||||
{/* <Tag color={isUnlimitedBalanceMode ? "volcano" : "green"} bordered={false}>*/}
|
||||
{/* {isUnlimitedBalanceMode ? "无限余额模式" : "校验余额模式"}*/}
|
||||
{/* </Tag>*/}
|
||||
{/* <Tag color={isAdmin ? "gold" : "default"} bordered={false}>*/}
|
||||
{/* {isAdmin ? "管理员视角" : "当前用户视角"}*/}
|
||||
{/* </Tag>*/}
|
||||
{/*</Space>*/}
|
||||
<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"
|
||||
/>
|
||||
|
||||
<Row gutter={[40, 16]}>
|
||||
{summaryCards.map((item) => (
|
||||
<Col key={item.key}>
|
||||
<Statistic
|
||||
title={<span style={{ color: "rgba(0,0,0,0.45)", fontSize: 12 }}>{item.title}</span>}
|
||||
value={item.value}
|
||||
valueStyle={{ fontSize: 24, fontWeight: 700 }}
|
||||
/>
|
||||
<div style={{ fontSize: 11, color: "rgba(0,0,0,0.45)", marginTop: 2 }}>{item.note}</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
{isPersonalOnly ? (
|
||||
<Card className="app-page__content-card" styles={{ body: { padding: 0 } }}>
|
||||
<div style={{ padding: "20px 24px 8px" }}>
|
||||
<Text strong style={{ fontSize: 16 }}>
|
||||
积分流水
|
||||
</Text>
|
||||
</div>
|
||||
{ledgerTableContent}
|
||||
</Card>
|
||||
) : (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
||||
<Card className="app-page__content-card" styles={{ body: { padding: 0 } }}>
|
||||
<div style={{ padding: "20px 24px 8px" }}>
|
||||
<Text strong style={{ fontSize: 16 }}>
|
||||
积分流水
|
||||
</Text>
|
||||
<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="刷新"
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
{ledgerTableContent}
|
||||
</Card>
|
||||
|
||||
{showPersonalAccountSection ? (
|
||||
<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">
|
||||
{isAdmin ? "按当前余额展示全部个人账户。" : "展示当前账号的个人积分账户。"}
|
||||
</Text>
|
||||
<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>
|
||||
{personalAccountTableContent}
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
{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>
|
||||
|
||||
<Modal
|
||||
title="从公共账户分配积分"
|
||||
|
|
@ -599,3 +530,7 @@ export default function MeetingPointsManagement() {
|
|||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function isLookupTab(tabKey: string) {
|
||||
return tabKey === "ledger" || tabKey === "personal";
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue