:
}
+ 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' ? (
-
-
-
- ) : (
-
- )}
-
- {headerRightTools}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
diff --git a/frontend/src/pages/business/MeetingPointsManagement.css b/frontend/src/pages/business/MeetingPointsManagement.css
new file mode 100644
index 0000000..ad60bb9
--- /dev/null
+++ b/frontend/src/pages/business/MeetingPointsManagement.css
@@ -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;
+ }
+}
diff --git a/frontend/src/pages/business/MeetingPointsManagement.tsx b/frontend/src/pages/business/MeetingPointsManagement.tsx
index 256d396..39ed167 100644
--- a/frontend/src/pages/business/MeetingPointsManagement.tsx
+++ b/frontend/src/pages/business/MeetingPointsManagement.tsx
@@ -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
(null);
@@ -161,14 +97,14 @@ export default function MeetingPointsManagement() {
const [total, setTotal] = useState(0);
const [transferOpen, setTransferOpen] = useState(false);
const [users, setUsers] = useState([]);
- 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(() => {
- 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 = (
-
-
-
- rowKey="id"
- columns={ledgerColumns}
- dataSource={records}
- loading={loading}
- totalCount={total}
- scroll={{x: 1100, y: 280}}
- pagination={false}
- />
-
-
-
{
- const nextParams = { ...params, current: page, size: pageSize };
- setParams(nextParams);
- void loadPage(nextParams);
- }}
- />
-
-
- );
+ const overviewRows = useMemo(() => {
+ 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 = (
-
-
-
- rowKey="userId"
- columns={personalAccountColumns}
- dataSource={pagedPersonalAccounts}
- totalCount={personalAccountRows.length}
- scroll={{x: 900, y: 280}}
- pagination={false}
- />
-
-
-
{
- setPersonalAccountPagination({ current: page, pageSize });
- }}
- />
-
-
- );
+ const overviewColumns = [
+ {
+ title: "指标",
+ dataIndex: "metric",
+ key: "metric",
+ width: 180,
+ render: (value: string) => {value || "-"},
+ },
+ {
+ title: "数值",
+ dataIndex: "value",
+ key: "value",
+ width: 180,
+ render: (value: string | number, record: OverviewRow) =>
+ record.id === "balanceCheckEnabled" ? (
+ {value}
+ ) : (
+ {value ?? "-"}
+ ),
+ },
+ {
+ title: "说明",
+ dataIndex: "note",
+ key: "note",
+ ellipsis: true,
+ render: (value: string) => {value || "-"},
+ },
+ ];
return (
-
- {showTransferButton ? (
- } onClick={() => void handleOpenTransfer()}>
- 分配积分
-
- ) : null}
- } onClick={() => void handleRefresh()}>
- 刷新
-
-
- }
- toolbar={
-
- setParams((prev) => ({ ...prev, username: event.target.value }))}
- style={{ width: 220 }}
- prefix={}
- allowClear
- />
-
- }
- >
-
-
-
- 账户概览
-
- {/*
*/}
- {/* */}
- {/* 模式:{getAccountModeLabel(overview?.accountMode)}*/}
- {/* */}
- {/* */}
- {/* 优先级:{getChargePriorityLabel(overview?.chargePriority)}*/}
- {/* */}
- {/* */}
- {/* {isUnlimitedBalanceMode ? "无限余额模式" : "校验余额模式"}*/}
- {/* */}
- {/* */}
- {/* {isAdmin ? "管理员视角" : "当前用户视角"}*/}
- {/* */}
- {/**/}
+
+
+
积分管理
+
+ 查看当前租户下的积分账面余额、累计消耗和会议消耗记录。
+
-
- {summaryCards.map((item) => (
-
- {item.title}}
- value={item.value}
- valueStyle={{ fontSize: 24, fontWeight: 700 }}
- />
- {item.note}
-
- ))}
-
-
-
- {isPersonalOnly ? (
-
-
-
- 积分流水
-
-
- {ledgerTableContent}
-
- ) : (
-
-
-
-
- 积分流水
-
+
+
+
+
+ {isLookupTab(activeTabKey) && showTransferButton ? (
+ } onClick={() => void handleOpenTransfer()}>
+ 分配积分
+
+ ) : null}
+
+
+
+ {activeTabKey === "ledger" ? (
+ <>
+ setParams((prev) => ({ ...prev, username: event.target.value }))}
+ style={{ width: 220 }}
+ prefix={}
+ allowClear
+ />
+
+
- {ledgerTableContent}
-
- {showPersonalAccountSection ? (
-
-
-
- 个人账户
-
-
-
- {isAdmin ? "按当前余额展示全部个人账户。" : "展示当前账号的个人积分账户。"}
-
+
+
+
+ {activeTabKey === "overview" ? (
+
+ key="overview"
+ rowKey="id"
+ columns={overviewColumns}
+ dataSource={overviewRows}
+ loading={false}
+ scroll={{ x: 900, y: "100%" }}
+ pagination={false}
+ />
+ ) : activeTabKey === "personal" ? (
+
+ key="personal"
+ rowKey="userId"
+ columns={personalAccountColumns}
+ dataSource={pagedPersonalAccounts}
+ loading={false}
+ scroll={{ x: 900, y: "100%" }}
+ pagination={false}
+ />
+ ) : (
+
+ key="ledger"
+ rowKey="id"
+ columns={ledgerColumns}
+ dataSource={records}
+ loading={loading}
+ scroll={{ x: 1100, y: "100%" }}
+ pagination={false}
+ />
+ )}
- {personalAccountTableContent}
-
- ) : null}
+
+ {activeTabKey === "overview" ? (
+
+
共 {overviewRows.length} 条记录
+
+ ) : activeTabKey === "personal" ? (
+
setPersonalAccountPagination({ current: page, pageSize: size })}
+ />
+ ) : (
+ {
+ const nextParams = { ...params, current: page, size };
+ setParams(nextParams);
+ void loadPage(nextParams);
+ }}
+ />
+ )}
+
+
- )}
+
);
}
+
+function isLookupTab(tabKey: string) {
+ return tabKey === "ledger" || tabKey === "personal";
+}