From ce8efa6baddbb59b4de4d6548b9bf6d9882cf09c Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 24 Oct 2026 14:49:23 +0800 Subject: [PATCH] =?UTF-8?q?style(frontend):=20=E5=85=A8=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC=E7=BB=9F=E4=B8=80=E7=99=BE=E5=88=86=E6=AF=94?= =?UTF-8?q?=E5=B8=83=E5=B1=80=E5=B9=B6=E5=B1=85=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/common/PercentTable.tsx | 32 +++++++ .../src/pages/admin/AdminAuditLogsPage.tsx | 5 +- .../src/pages/admin/AdminDashboardPage.tsx | 4 +- .../AdminDouyinMashidaCertificatePage.tsx | 4 +- .../src/pages/admin/AdminFinancePage.tsx | 8 +- .../pages/admin/AdminKuaishouIndustryPage.tsx | 6 +- .../admin/AdminManualFulfillmentPage.tsx | 4 +- .../src/pages/admin/AdminOrderDetailPage.tsx | 6 +- .../src/pages/admin/AdminOrdersPage.tsx | 4 +- .../src/pages/admin/AdminSalesProfitPage.tsx | 10 +- .../src/pages/admin/AdminTaskDetailPage.tsx | 10 +- .../src/pages/admin/AdminTasksPage.tsx | 4 +- .../src/pages/admin/AdminUsersPage.tsx | 4 +- .../admin/cost-center/AffiliateSkuPanel.tsx | 5 +- .../admin/cost-center/ChannelMappingPanel.tsx | 6 +- .../admin/cost-center/CostExceptionPanel.tsx | 5 +- .../pages/admin/cost-center/CostItemPanel.tsx | 18 +--- .../admin/cost-center/CostPreviewPanel.tsx | 16 +--- .../admin/cost-center/CostVersionPanel.tsx | 4 +- .../cost-center/ManualSkuConfigPanel.tsx | 4 +- .../admin/cost-center/ProductCatalogPanel.tsx | 4 +- .../pages/admin/fund/FundManagementView.tsx | 16 ++-- .../src/pages/admin/fund/FundSupportView.tsx | 8 +- .../pages/admin/fund/PlatformCreditPanel.tsx | 6 +- .../pages/admin/panels/AfterSalesPanel.tsx | 4 +- .../admin/panels/CancelRequestsPanel.tsx | 5 +- .../pages/admin/panels/CategoriesPanel.tsx | 4 +- .../panels/ConsignmentInventoryPanel.tsx | 4 +- .../panels/ConsignmentSubmissionsPanel.tsx | 4 +- .../src/pages/admin/panels/FeedbacksPanel.tsx | 5 +- .../src/pages/admin/panels/FinancePanel.tsx | 4 +- .../pages/admin/panels/HallConfigPanel.tsx | 4 +- .../pages/admin/panels/LeaderboardPanel.tsx | 5 +- .../src/pages/admin/panels/LevelsPanel.tsx | 4 +- .../pages/admin/panels/NotificationsPanel.tsx | 5 +- .../pages/admin/panels/ProductMatchPanel.tsx | 6 +- .../pages/admin/panels/ProductRulesPanel.tsx | 4 +- .../pages/admin/panels/RiskControlPanel.tsx | 6 +- .../pages/admin/panels/WorkOrdersPanel.tsx | 6 +- .../pages/admin/panels/WorkerDetailDrawer.tsx | 9 +- .../src/pages/admin/panels/WorkersPanel.tsx | 4 +- .../platform/AdminPlatformFulfillmentPage.tsx | 1 - .../admin/platform/AdminPlatformShopsPage.tsx | 8 +- .../pages/admin/platform/PddPlatformPanel.tsx | 4 +- .../src/pages/worker/WorkerLoginPage.tsx | 4 +- .../pages/worker/WorkerOrderDesktopList.tsx | 5 +- .../worker/WorkerProfileAccountModals.tsx | 4 +- .../src/pages/worker/WorkerProfileRecords.tsx | 7 +- apps/frontend/src/styles/main.css | 1 + apps/frontend/src/styles/table.css | 10 ++ .../src/utils/percent-table-layout.ts | 94 +++++++++++++++++++ .../tests/percent-table-layout.test.ts | 77 +++++++++++++++ 52 files changed, 346 insertions(+), 145 deletions(-) create mode 100644 apps/frontend/src/components/common/PercentTable.tsx create mode 100644 apps/frontend/src/styles/table.css create mode 100644 apps/frontend/src/utils/percent-table-layout.ts create mode 100644 apps/frontend/tests/percent-table-layout.test.ts diff --git a/apps/frontend/src/components/common/PercentTable.tsx b/apps/frontend/src/components/common/PercentTable.tsx new file mode 100644 index 00000000..48c088c3 --- /dev/null +++ b/apps/frontend/src/components/common/PercentTable.tsx @@ -0,0 +1,32 @@ +import { Table as AntTable } from 'antd' +import type { TableProps } from 'antd' + +import { normalizePercentColumns } from '@/utils/percent-table-layout' + +/** + * 全项目统一表格:列宽转百分比、表头与内容居中。 + * + * 业务表格仍按原列宽数值表达相对比例,由这里统一归一化为 100%。 + * rowSelection / expandable 会额外占用 AntD 内置列,因此预留少量百分比。 + */ +export default function PercentTable>( + props: TableProps, +) { + const { columns, scroll, tableLayout: _tableLayout, ...rest } = props + const reservedPercent = + (props.rowSelection ? 4 : 0) + + (props.expandable && typeof props.expandable === 'object' ? 4 : 0) + const normalizedColumns = columns + ? normalizePercentColumns(columns, { reservedPercent }) + : columns + const scrollConfig = typeof scroll === 'object' && scroll ? scroll : {} + + return ( + + {...rest} + columns={normalizedColumns} + tableLayout="fixed" + scroll={{ ...scrollConfig, x: '100%' }} + /> + ) +} diff --git a/apps/frontend/src/pages/admin/AdminAuditLogsPage.tsx b/apps/frontend/src/pages/admin/AdminAuditLogsPage.tsx index 574fe0d0..a4c48d02 100644 --- a/apps/frontend/src/pages/admin/AdminAuditLogsPage.tsx +++ b/apps/frontend/src/pages/admin/AdminAuditLogsPage.tsx @@ -1,5 +1,6 @@ import { ReloadOutlined, SearchOutlined } from '@ant-design/icons' -import { Alert, Button, Card, Empty, Form, Input, Select, Space, Table, Typography } from 'antd' +import { Alert, Button, Card, Empty, Form, Input, Select, Space, Typography } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useQuery } from '@tanstack/react-query' import { useMemo } from 'react' @@ -203,7 +204,7 @@ export default function AdminAuditLogsPage() { ) : null} - + rowKey="logId" loading={query.isLoading || query.isFetching} columns={columns} diff --git a/apps/frontend/src/pages/admin/AdminDashboardPage.tsx b/apps/frontend/src/pages/admin/AdminDashboardPage.tsx index f5ea414d..e9713d86 100644 --- a/apps/frontend/src/pages/admin/AdminDashboardPage.tsx +++ b/apps/frontend/src/pages/admin/AdminDashboardPage.tsx @@ -10,10 +10,10 @@ import { Skeleton, Space, Statistic, - Table, Tag, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useQuery } from '@tanstack/react-query' import { useMemo, useState } from 'react' @@ -226,7 +226,7 @@ export default function AdminDashboardPage() { /> ) : null} - + rowKey="logId" loading={loginQuery.isLoading || loginQuery.isFetching} columns={columns} diff --git a/apps/frontend/src/pages/admin/AdminDouyinMashidaCertificatePage.tsx b/apps/frontend/src/pages/admin/AdminDouyinMashidaCertificatePage.tsx index 0f747c22..555baaca 100644 --- a/apps/frontend/src/pages/admin/AdminDouyinMashidaCertificatePage.tsx +++ b/apps/frontend/src/pages/admin/AdminDouyinMashidaCertificatePage.tsx @@ -16,10 +16,10 @@ import { Popconfirm, Select, Space, - Table, Tag, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useEffect, useMemo, useRef, useState } from 'react' @@ -377,7 +377,7 @@ export default function AdminDouyinMashidaCertificatePage() {
- } > - + rowKey="date" loading={summaryQuery.isLoading} dataSource={daily} @@ -252,7 +252,7 @@ export default function AdminFinancePage() { } > - + rowKey={(row) => `${row.date}-${row.channel}-${row.category}`} loading={summaryQuery.isLoading} dataSource={channelDaily} @@ -304,7 +304,7 @@ export default function AdminFinancePage() { } > - + rowKey={(row) => `${row.category}-${row.sourceId}-${row.occurredAt}`} loading={transactionsQuery.isLoading} dataSource={transactionData?.items || []} diff --git a/apps/frontend/src/pages/admin/AdminKuaishouIndustryPage.tsx b/apps/frontend/src/pages/admin/AdminKuaishouIndustryPage.tsx index 4d24c107..85f695d0 100644 --- a/apps/frontend/src/pages/admin/AdminKuaishouIndustryPage.tsx +++ b/apps/frontend/src/pages/admin/AdminKuaishouIndustryPage.tsx @@ -18,11 +18,11 @@ import { Popconfirm, Select, Space, - Table, Tabs, Tag, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType, TabsProps } from 'antd' import { useEffect, useMemo, useRef, useState } from 'react' @@ -963,7 +963,7 @@ export default function AdminKuaishouIndustryPage() { function renderVoucherTable(scrollX?: number) { return ( -
-
String(row.refundId || row.oid || JSON.stringify(row))} columns={refundColumns} dataSource={refundRows} diff --git a/apps/frontend/src/pages/admin/AdminManualFulfillmentPage.tsx b/apps/frontend/src/pages/admin/AdminManualFulfillmentPage.tsx index 14408991..7ecef63f 100644 --- a/apps/frontend/src/pages/admin/AdminManualFulfillmentPage.tsx +++ b/apps/frontend/src/pages/admin/AdminManualFulfillmentPage.tsx @@ -20,10 +20,10 @@ import { Modal, Space, Statistic, - Table, Tag, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useState } from 'react' @@ -245,7 +245,7 @@ export default function AdminManualFulfillmentPage() { -
- + rowKey="orderItemId" pagination={false} dataSource={detail.items} @@ -354,7 +354,7 @@ export default function AdminOrderDetailPage() { - + rowKey="taskId" pagination={false} dataSource={detail.tasks} diff --git a/apps/frontend/src/pages/admin/AdminOrdersPage.tsx b/apps/frontend/src/pages/admin/AdminOrdersPage.tsx index 18fa5726..a2103df6 100644 --- a/apps/frontend/src/pages/admin/AdminOrdersPage.tsx +++ b/apps/frontend/src/pages/admin/AdminOrdersPage.tsx @@ -16,11 +16,11 @@ import { Segmented, Select, Space, - Table, Tag, Tooltip, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useQuery } from '@tanstack/react-query' import { useEffect, useMemo } from 'react' @@ -503,7 +503,7 @@ export default function AdminOrdersPage() { - + rowKey="orderId" loading={query.isLoading || query.isFetching} columns={columns} diff --git a/apps/frontend/src/pages/admin/AdminSalesProfitPage.tsx b/apps/frontend/src/pages/admin/AdminSalesProfitPage.tsx index 55b4ca3e..0f97862e 100644 --- a/apps/frontend/src/pages/admin/AdminSalesProfitPage.tsx +++ b/apps/frontend/src/pages/admin/AdminSalesProfitPage.tsx @@ -11,11 +11,11 @@ import { Select, Space, Statistic, - Table, Tabs, Tag, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useState } from 'react' @@ -386,7 +386,7 @@ export default function AdminSalesProfitPage() { key: 'daily', label: '日报', children: ( -
`${row.canonicalProductId || 'unmapped'}`} size="small" columns={productColumns} @@ -416,7 +416,7 @@ export default function AdminSalesProfitPage() { key: 'fulfillment-mode', label: '履约方式', children: ( -
+ rowKey="eventId" pagination={false} columns={eventColumns} @@ -1757,7 +1757,7 @@ function KuaishouIndustryVoucherPanel({ voucher }: { voucher: KuaishouIndustryVo ) : null} {consumeDetails.length > 0 ? ( - > + > className="platform-section-gap" rowKey={(_, index) => String(index ?? 0)} pagination={false} @@ -1905,7 +1905,7 @@ function KuaishouCloudPanel({ {flow.rebind.history.length > 0 ? ( -
`${row.attempt}-${row.requestedAt || ''}`} pagination={false} dataSource={flow.rebind.history} @@ -1969,7 +1969,7 @@ function RedeemResolutionPanel({ -
`${row.attempt}-${row.codeMasked}`} pagination={false} diff --git a/apps/frontend/src/pages/admin/AdminTasksPage.tsx b/apps/frontend/src/pages/admin/AdminTasksPage.tsx index e0d4de29..fc66842e 100644 --- a/apps/frontend/src/pages/admin/AdminTasksPage.tsx +++ b/apps/frontend/src/pages/admin/AdminTasksPage.tsx @@ -19,11 +19,11 @@ import { Select, Space, Spin, - Table, Tag, Tooltip, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useQuery } from '@tanstack/react-query' import { useEffect, useMemo, useState } from 'react' @@ -576,7 +576,7 @@ export default function AdminTasksPage() { ) : null} - + rowKey="taskId" loading={query.isLoading || query.isFetching} columns={columns} diff --git a/apps/frontend/src/pages/admin/AdminUsersPage.tsx b/apps/frontend/src/pages/admin/AdminUsersPage.tsx index b56db2be..b0803b07 100644 --- a/apps/frontend/src/pages/admin/AdminUsersPage.tsx +++ b/apps/frontend/src/pages/admin/AdminUsersPage.tsx @@ -12,10 +12,10 @@ import { Select, Space, Switch, - Table, Tag, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { MenuProps, TableColumnsType } from 'antd' import { useQuery, useQueryClient } from '@tanstack/react-query' import { useMemo, useState } from 'react' @@ -675,7 +675,7 @@ export default function AdminUsersPage() { } > - + rowKey="userId" loading={query.isLoading || query.isFetching} columns={columns} diff --git a/apps/frontend/src/pages/admin/cost-center/AffiliateSkuPanel.tsx b/apps/frontend/src/pages/admin/cost-center/AffiliateSkuPanel.tsx index dfa8225f..08054de9 100644 --- a/apps/frontend/src/pages/admin/cost-center/AffiliateSkuPanel.tsx +++ b/apps/frontend/src/pages/admin/cost-center/AffiliateSkuPanel.tsx @@ -5,7 +5,8 @@ * 不写渠道映射,也不维护游戏分类。 */ import { ReloadOutlined, SyncOutlined } from '@ant-design/icons' -import { Alert, App, Button, Card, Space, Switch, Table, Tag, Typography } from 'antd' +import { Alert, App, Button, Card, Space, Switch, Tag, Typography } from 'antd' +import PercentTable from '@/components/common/PercentTable' import { useQuery, useQueryClient } from '@tanstack/react-query' import { useState } from 'react' @@ -134,7 +135,7 @@ export function AffiliateSkuPanel() { style={{ marginBottom: 12 }} message="自动发货只按上游货源 SKU 配采购成本:订单项直接按 aff: 归一,不走渠道映射;这里不维护游戏分类和商品上下架,那些只属于接单平台。" /> -
- + tableLayout="fixed" rowKey={(row) => String(row.canonicalProductId)} size="small" @@ -445,7 +445,7 @@ export function ChannelMappingPanel() { 该标准商品下共有 {group.mappings.length} 条渠道映射 - + tableLayout="fixed" rowKey="id" size="small" diff --git a/apps/frontend/src/pages/admin/cost-center/CostExceptionPanel.tsx b/apps/frontend/src/pages/admin/cost-center/CostExceptionPanel.tsx index 5b4ebc15..8087bf13 100644 --- a/apps/frontend/src/pages/admin/cost-center/CostExceptionPanel.tsx +++ b/apps/frontend/src/pages/admin/cost-center/CostExceptionPanel.tsx @@ -4,7 +4,8 @@ * 异常表不再存履约载体,履约方式从 order_items.fulfillment_mode join 得到; * 前端按履约方式筛选时传 fulfillmentMode 参数。 */ -import { App, Button, Card, Select, Space, Table, Tag, Typography } from 'antd' +import { App, Button, Card, Select, Space, Tag, Typography } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useQuery } from '@tanstack/react-query' import { useState } from 'react' @@ -160,7 +161,7 @@ export function CostExceptionPanel() { /> } > -
-
) : null} -
`${row.costItemId}-${row.costVersionId}`} size="small" diff --git a/apps/frontend/src/pages/admin/cost-center/CostVersionPanel.tsx b/apps/frontend/src/pages/admin/cost-center/CostVersionPanel.tsx index 4d6da09e..1e1579de 100644 --- a/apps/frontend/src/pages/admin/cost-center/CostVersionPanel.tsx +++ b/apps/frontend/src/pages/admin/cost-center/CostVersionPanel.tsx @@ -17,10 +17,10 @@ import { Modal, Select, Space, - Table, Tag, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useQuery } from '@tanstack/react-query' import { useState } from 'react' @@ -402,7 +402,7 @@ export function CostVersionPanel() { style={{ width: 140 }} /> -
-
-
- + rowKey="id" size="small" columns={buildAccountColumns('agent')} @@ -1074,7 +1074,7 @@ export default function FundManagementView({ role }: { role: ManagementRole }) { ) : null } > - + rowKey="id" size="small" columns={buildAccountColumns('supervisor')} @@ -1139,7 +1139,7 @@ export default function FundManagementView({ role }: { role: ManagementRole }) { }} /> - + rowKey="id" size="small" columns={expenseColumns} @@ -1188,7 +1188,7 @@ export default function FundManagementView({ role }: { role: ManagementRole }) { )} /> - + rowKey="id" size="small" columns={reconciliationColumns} @@ -1227,7 +1227,7 @@ export default function FundManagementView({ role }: { role: ManagementRole }) { } > - + rowKey="id" size="small" columns={buildAccountColumns('supervisor')} @@ -1262,7 +1262,7 @@ export default function FundManagementView({ role }: { role: ManagementRole }) { } > - + rowKey="id" size="small" columns={buildAccountColumns('agent')} @@ -1793,7 +1793,7 @@ export default function FundManagementView({ role }: { role: ManagementRole }) { - + rowKey="id" size="small" columns={detailEntryColumns} diff --git a/apps/frontend/src/pages/admin/fund/FundSupportView.tsx b/apps/frontend/src/pages/admin/fund/FundSupportView.tsx index 584ca31a..d182c6a0 100644 --- a/apps/frontend/src/pages/admin/fund/FundSupportView.tsx +++ b/apps/frontend/src/pages/admin/fund/FundSupportView.tsx @@ -18,11 +18,11 @@ import { Select, Space, Statistic, - Table, Tabs, Tag, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import dayjs from 'dayjs' import { useState } from 'react' @@ -497,7 +497,7 @@ export default function FundSupportView() { key: 'credit-entries', label: '积分流水', children: ( - + rowKey="id" size="small" columns={creditEntryColumns} @@ -514,7 +514,7 @@ export default function FundSupportView() { key: 'entries', label: '备用金流水', children: ( - + rowKey="id" size="small" columns={entryColumns} @@ -529,7 +529,7 @@ export default function FundSupportView() { key: 'expenses', label: '我的支出', children: ( - + rowKey="id" size="small" columns={expenseColumns} diff --git a/apps/frontend/src/pages/admin/fund/PlatformCreditPanel.tsx b/apps/frontend/src/pages/admin/fund/PlatformCreditPanel.tsx index 0fa088d6..eff57d8a 100644 --- a/apps/frontend/src/pages/admin/fund/PlatformCreditPanel.tsx +++ b/apps/frontend/src/pages/admin/fund/PlatformCreditPanel.tsx @@ -24,10 +24,10 @@ import { Select, Space, Statistic, - Table, Tag, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useState } from 'react' @@ -399,7 +399,7 @@ export function PlatformCreditPanel() { 客服积分余额 - + rowKey="id" size="small" columns={accountColumns} @@ -413,7 +413,7 @@ export function PlatformCreditPanel() { 积分流水 -
(function AfterSalesPan } bordered={false} > - + rowKey="caseId" loading={afterSalesCasesQuery.isLoading} dataSource={afterSalesCasesQuery.data?.data.items || []} diff --git a/apps/frontend/src/pages/admin/panels/CancelRequestsPanel.tsx b/apps/frontend/src/pages/admin/panels/CancelRequestsPanel.tsx index 2009c76e..678f954c 100644 --- a/apps/frontend/src/pages/admin/panels/CancelRequestsPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/CancelRequestsPanel.tsx @@ -1,6 +1,7 @@ import { ReloadOutlined } from '@ant-design/icons' import { useQuery, useQueryClient } from '@tanstack/react-query' -import { App, Button, Card, Form, Input, Modal, Space, Table, Tag, Typography } from 'antd' +import { App, Button, Card, Form, Input, Modal, Space, Tag, Typography } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useEffect, useState } from 'react' import { useSearchParams } from 'react-router' @@ -147,7 +148,7 @@ export default function CancelRequestsPanel() { } bordered={false} > - + rowKey="requestId" size="small" loading={requestsQuery.isLoading} diff --git a/apps/frontend/src/pages/admin/panels/CategoriesPanel.tsx b/apps/frontend/src/pages/admin/panels/CategoriesPanel.tsx index d232d955..15a24443 100644 --- a/apps/frontend/src/pages/admin/panels/CategoriesPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/CategoriesPanel.tsx @@ -11,11 +11,11 @@ import { Modal, Select, Space, - Table, Tag, Typography, Upload, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useMemo, useState } from 'react' @@ -312,7 +312,7 @@ export default function CategoriesPanel() { - + rowKey="categoryId" loading={categoriesQuery.isLoading} dataSource={treeData} diff --git a/apps/frontend/src/pages/admin/panels/ConsignmentInventoryPanel.tsx b/apps/frontend/src/pages/admin/panels/ConsignmentInventoryPanel.tsx index c43d3a70..e9d2c4d4 100644 --- a/apps/frontend/src/pages/admin/panels/ConsignmentInventoryPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/ConsignmentInventoryPanel.tsx @@ -18,11 +18,11 @@ import { Space, Statistic, Switch, - Table, Tag, Tooltip, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useState } from 'react' @@ -839,7 +839,7 @@ export default function ConsignmentInventoryPanel() { } /> ) : null} -
) : null} -
- + rowKey="feedbackId" size="small" loading={feedbacksQuery.isLoading} diff --git a/apps/frontend/src/pages/admin/panels/FinancePanel.tsx b/apps/frontend/src/pages/admin/panels/FinancePanel.tsx index d365cbc5..957b4752 100644 --- a/apps/frontend/src/pages/admin/panels/FinancePanel.tsx +++ b/apps/frontend/src/pages/admin/panels/FinancePanel.tsx @@ -13,10 +13,10 @@ import { Select, Space, Switch, - Table, Tag, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useEffect, useState } from 'react' import { useSearchParams } from 'react-router' @@ -585,7 +585,7 @@ export default function FinancePanel() { totalCount={financeRequestsQuery.data?.data.summary?.totalCount} totalAmount={financeRequestsQuery.data?.data.summary?.totalAmount} /> - + rowKey="requestId" loading={financeRequestsQuery.isLoading} dataSource={financeRequestsQuery.data?.data.items || []} diff --git a/apps/frontend/src/pages/admin/panels/HallConfigPanel.tsx b/apps/frontend/src/pages/admin/panels/HallConfigPanel.tsx index c39cdb78..10c7a020 100644 --- a/apps/frontend/src/pages/admin/panels/HallConfigPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/HallConfigPanel.tsx @@ -11,10 +11,10 @@ import { InputNumber, Space, Switch, - Table, Tag, Upload, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import { useEffect, useMemo, useState } from 'react' import { @@ -182,7 +182,7 @@ export default function HallConfigPanel() { 一级分类限额将限制该游戏及旗下所有子分类的总单量;二级分类可单独设置细化限额。 - + rowKey="categoryId" size="small" pagination={false} diff --git a/apps/frontend/src/pages/admin/panels/LeaderboardPanel.tsx b/apps/frontend/src/pages/admin/panels/LeaderboardPanel.tsx index d2fa1520..6eb85a49 100644 --- a/apps/frontend/src/pages/admin/panels/LeaderboardPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/LeaderboardPanel.tsx @@ -1,5 +1,6 @@ import { useQuery } from '@tanstack/react-query' -import { Card, Segmented, Space, Table, Tag, Typography } from 'antd' +import { Card, Segmented, Space, Tag, Typography } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useState } from 'react' @@ -134,7 +135,7 @@ export default function LeaderboardPanel() { : ''} - + rowKey="workerId" columns={columns} dataSource={items} diff --git a/apps/frontend/src/pages/admin/panels/LevelsPanel.tsx b/apps/frontend/src/pages/admin/panels/LevelsPanel.tsx index 6fe53d54..851bae54 100644 --- a/apps/frontend/src/pages/admin/panels/LevelsPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/LevelsPanel.tsx @@ -9,10 +9,10 @@ import { Modal, Space, Switch, - Table, Tag, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useState } from 'react' @@ -225,7 +225,7 @@ export default function LevelsPanel() { - + rowKey="levelId" loading={levelsQuery.isLoading} dataSource={levelsQuery.data?.data.items || []} diff --git a/apps/frontend/src/pages/admin/panels/NotificationsPanel.tsx b/apps/frontend/src/pages/admin/panels/NotificationsPanel.tsx index 0899d2ba..245b36e8 100644 --- a/apps/frontend/src/pages/admin/panels/NotificationsPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/NotificationsPanel.tsx @@ -1,5 +1,6 @@ import { useQuery, useQueryClient } from '@tanstack/react-query' -import { App, Alert, Button, Card, Space, Switch, Table, Typography } from 'antd' +import { App, Alert, Button, Card, Space, Switch, Typography } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useEffect, useState } from 'react' @@ -143,7 +144,7 @@ export default function NotificationsPanel() { message="配置仅影响保存后产生的通知" description="关闭后台待办后,声音播报会同步关闭;已创建的待办不会被隐藏或删除。" /> -
- + rowKey={(row) => `${row.sellerId}-${row.relItemId}-${row.itemTitle}-${row.relSkuId}-${row.skuNick}` } @@ -660,7 +660,7 @@ export default function ProductMatchPanel() { - + rowKey="logId" loading={logsQuery.isLoading} columns={logColumns} diff --git a/apps/frontend/src/pages/admin/panels/ProductRulesPanel.tsx b/apps/frontend/src/pages/admin/panels/ProductRulesPanel.tsx index 8b7c5b76..fdae97c1 100644 --- a/apps/frontend/src/pages/admin/panels/ProductRulesPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/ProductRulesPanel.tsx @@ -23,11 +23,11 @@ import { Select, Space, Switch, - Table, Tabs, Tag, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useEffect, useMemo, useRef, useState } from 'react' import { useNavigate, useSearchParams } from 'react-router' @@ -1350,7 +1350,7 @@ export default function ProductRulesPanel() { ) : null} - + rowKey="ruleId" size="small" loading={rulesQuery.isLoading || mappingsQuery.isLoading} diff --git a/apps/frontend/src/pages/admin/panels/RiskControlPanel.tsx b/apps/frontend/src/pages/admin/panels/RiskControlPanel.tsx index 1d0c798d..0641f849 100644 --- a/apps/frontend/src/pages/admin/panels/RiskControlPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/RiskControlPanel.tsx @@ -11,9 +11,9 @@ import { Select, Space, Switch, - Table, Tag, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { ColumnsType } from 'antd/es/table' import { useEffect, useState } from 'react' @@ -381,7 +381,7 @@ export default function RiskControlPanel() { } > -
-
- + rowKey="workOrderId" loading={ordersQuery.isLoading} dataSource={ordersQuery.data?.data.items || []} @@ -3139,7 +3139,7 @@ export default function WorkOrdersPanel() { ) : null} - + rowKey="shareId" size="small" dataSource={sharingShares} diff --git a/apps/frontend/src/pages/admin/panels/WorkerDetailDrawer.tsx b/apps/frontend/src/pages/admin/panels/WorkerDetailDrawer.tsx index 862a9890..5d9d1952 100644 --- a/apps/frontend/src/pages/admin/panels/WorkerDetailDrawer.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkerDetailDrawer.tsx @@ -1,5 +1,6 @@ import { useState } from 'react' -import { Card, Descriptions, Drawer, Select, Space, Table, Tabs, Tag, Typography } from 'antd' +import { Card, Descriptions, Drawer, Select, Space, Tabs, Tag, Typography } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useQuery } from '@tanstack/react-query' @@ -303,7 +304,7 @@ function UnfreezesSection({ workerId }: { workerId: number }) { }, ] return ( - + rowKey="id" size="small" columns={columns} @@ -398,7 +399,7 @@ function LedgersSection({ workerId }: { workerId: number }) { setPage(1) }} /> - + rowKey="ledgerId" size="small" columns={columns} @@ -463,7 +464,7 @@ function OrdersSection({ workerId }: { workerId: number }) { }, ] return ( - + rowKey="workOrderId" size="small" columns={columns} diff --git a/apps/frontend/src/pages/admin/panels/WorkersPanel.tsx b/apps/frontend/src/pages/admin/panels/WorkersPanel.tsx index 1c46aea6..7c0acd2f 100644 --- a/apps/frontend/src/pages/admin/panels/WorkersPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkersPanel.tsx @@ -13,11 +13,11 @@ import { Select, Space, Switch, - Table, Tag, Tooltip, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useEffect, useRef, useState } from 'react' import { useSearchParams } from 'react-router' @@ -952,7 +952,7 @@ export default function WorkersPanel() { : '寄卖白名单已关闭:全部打手均可发布寄卖。下方开关仅在重新开启白名单后生效。' } /> - + rowKey="workerId" loading={workersQuery.isLoading} dataSource={workersQuery.data?.data.items || []} diff --git a/apps/frontend/src/pages/admin/platform/AdminPlatformFulfillmentPage.tsx b/apps/frontend/src/pages/admin/platform/AdminPlatformFulfillmentPage.tsx index 5775158c..6cb17292 100644 --- a/apps/frontend/src/pages/admin/platform/AdminPlatformFulfillmentPage.tsx +++ b/apps/frontend/src/pages/admin/platform/AdminPlatformFulfillmentPage.tsx @@ -18,7 +18,6 @@ import { Space, Spin, Switch, - Table, Tag, Typography, } from 'antd' diff --git a/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx b/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx index 3235fff9..ce10c11d 100644 --- a/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx +++ b/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx @@ -19,11 +19,11 @@ import { Space, Spin, Switch, - Table, Tabs, Tag, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useEffect, useState } from 'react' import { useSearchParams } from 'react-router' @@ -1106,7 +1106,7 @@ function KuaishouIndustryPanel({ {shops.length === 0 ? ( ) : ( - + rowKey={(row, index) => buildIndustryShopRowKey(row, index || 0)} columns={shopColumns} dataSource={shops} @@ -1694,7 +1694,7 @@ function AffiliateDashPlatformPanel() { apps/backend/.../affiliate-dash/sku-mapping.ts ,与平台商品目录对齐。 - + rowKey={(row) => row.productNo} size="small" pagination={{ pageSize: 10, showSizeChanger: false }} @@ -1760,7 +1760,7 @@ function AffiliateDashPlatformPanel() { ) : null} - + rowKey="sku" size="small" loading={productsLoading} diff --git a/apps/frontend/src/pages/admin/platform/PddPlatformPanel.tsx b/apps/frontend/src/pages/admin/platform/PddPlatformPanel.tsx index 765e47d2..6310bb71 100644 --- a/apps/frontend/src/pages/admin/platform/PddPlatformPanel.tsx +++ b/apps/frontend/src/pages/admin/platform/PddPlatformPanel.tsx @@ -39,11 +39,11 @@ import { Spin, Statistic, Switch, - Table, Tag, Tooltip, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { TableColumnsType } from 'antd' import { useEffect, useState } from 'react' @@ -986,7 +986,7 @@ export function PddPlatformPanel() { } > -
-
) : null} - + rowKey="workOrderId" size="middle" loading={loading} diff --git a/apps/frontend/src/pages/worker/WorkerProfileAccountModals.tsx b/apps/frontend/src/pages/worker/WorkerProfileAccountModals.tsx index 1b873095..8745ded9 100644 --- a/apps/frontend/src/pages/worker/WorkerProfileAccountModals.tsx +++ b/apps/frontend/src/pages/worker/WorkerProfileAccountModals.tsx @@ -8,9 +8,9 @@ import { Input, Modal, Space, - Table, Typography, } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { FormInstance } from 'antd' import type { WorkerFinanceConfig, WorkerSessionDevice, WorkerUser } from '@/types/worker-platform' @@ -93,7 +93,7 @@ export function WorkerProfileAccountModals({ type="info" message={`PC 和手机分别最多同时在线 ${maxDevices} 台(当前 PC ${deviceCounts.pc} 台、手机 ${deviceCounts.mobile} 台),删除对应旧设备后才能登录。`} /> - + size="small" loading={devicesLoading} rowKey="sessionId" diff --git a/apps/frontend/src/pages/worker/WorkerProfileRecords.tsx b/apps/frontend/src/pages/worker/WorkerProfileRecords.tsx index b355b59d..d204833e 100644 --- a/apps/frontend/src/pages/worker/WorkerProfileRecords.tsx +++ b/apps/frontend/src/pages/worker/WorkerProfileRecords.tsx @@ -1,5 +1,6 @@ import { ReloadOutlined } from '@ant-design/icons' -import { Button, Card, Empty, Pagination, Select, Space, Spin, Table, Tag } from 'antd' +import { Button, Card, Empty, Pagination, Select, Space, Spin, Tag } from 'antd' +import PercentTable from '@/components/common/PercentTable' import type { WorkerFinanceRequest, @@ -293,7 +294,7 @@ export function WorkerProfileDesktopLedgerRecords({ } > - + rowKey="ledgerId" size="small" loading={loading} @@ -353,7 +354,7 @@ export function WorkerProfileDesktopRequestRecords({ } > - + rowKey="requestId" size="small" loading={loading} diff --git a/apps/frontend/src/styles/main.css b/apps/frontend/src/styles/main.css index 84d3bb71..fc728f8c 100644 --- a/apps/frontend/src/styles/main.css +++ b/apps/frontend/src/styles/main.css @@ -11,3 +11,4 @@ @import './admin.css'; @import './client.css'; @import './worker.css'; +@import './table.css'; diff --git a/apps/frontend/src/styles/table.css b/apps/frontend/src/styles/table.css new file mode 100644 index 00000000..1819304a --- /dev/null +++ b/apps/frontend/src/styles/table.css @@ -0,0 +1,10 @@ +/* 全项目表格统一规范:宽度百分比自适应,表头与内容全部居中。 */ +.ant-table-wrapper .ant-table { + width: 100%; + table-layout: fixed; +} + +.ant-table-wrapper .ant-table-thead > tr > th, +.ant-table-wrapper .ant-table-tbody > tr > td { + text-align: center !important; +} diff --git a/apps/frontend/src/utils/percent-table-layout.ts b/apps/frontend/src/utils/percent-table-layout.ts new file mode 100644 index 00000000..b7c71428 --- /dev/null +++ b/apps/frontend/src/utils/percent-table-layout.ts @@ -0,0 +1,94 @@ +import type { TableColumnsType } from 'antd' + +type Column = TableColumnsType[number] + +type WidthContext = { + leafPercent: WeakMap, number> +} + +/** + * 把业务列宽(px 数值)按相对权重归一化为百分比。 + * rowSelection / expandable 等内置列由调用方传入需要预留的百分比。 + */ +export function normalizePercentColumns( + columns: TableColumnsType, + options: { reservedPercent?: number } = {}, +): TableColumnsType { + const leaves: Column[] = [] + collectLeaves(columns, leaves) + + const explicitWeights = leaves + .map((column) => numericWidth(column.width)) + .filter((width): width is number => width !== null) + const fallbackWeight = + explicitWeights.length > 0 + ? explicitWeights.reduce((sum, width) => sum + width, 0) / explicitWeights.length + : 100 + const totalWeight = leaves.reduce( + (sum, column) => sum + (numericWidth(column.width) ?? fallbackWeight), + 0, + ) + const budgetPercent = Math.max(60, 100 - Math.max(0, options.reservedPercent ?? 0)) + + const leafPercent = new WeakMap, number>() + for (const column of leaves) { + const weight = numericWidth(column.width) ?? fallbackWeight + leafPercent.set(column, (weight / totalWeight) * budgetPercent) + } + + return applyPercentWidths(columns, { leafPercent }) +} + +function applyPercentWidths( + columns: TableColumnsType, + context: WidthContext, +): TableColumnsType { + return columns.map((column) => { + const children = 'children' in column ? column.children : undefined + if (Array.isArray(children) && children.length > 0) { + const normalizedChildren = applyPercentWidths(children, context) + const width = normalizedChildren.reduce((sum, child) => sum + percentOf(child.width), 0) + return { + ...column, + width: `${width.toFixed(4)}%`, + align: 'center', + children: normalizedChildren, + } + } + + return { + ...column, + width: `${(context.leafPercent.get(column) ?? 0).toFixed(4)}%`, + align: 'center', + } + }) +} + +function collectLeaves( + columns: TableColumnsType, + leaves: Column[], +) { + for (const column of columns) { + const children = 'children' in column ? column.children : undefined + if (Array.isArray(children) && children.length > 0) { + collectLeaves(children, leaves) + } else { + leaves.push(column) + } + } +} + +function numericWidth(width: Column['width']) { + if (typeof width === 'number' && Number.isFinite(width) && width > 0) return width + if (typeof width === 'string') { + const parsed = Number.parseFloat(width) + if (Number.isFinite(parsed) && parsed > 0) return parsed + } + return null +} + +function percentOf(width: Column['width']) { + if (typeof width !== 'string' || !width.endsWith('%')) return 0 + const parsed = Number.parseFloat(width) + return Number.isFinite(parsed) ? parsed : 0 +} diff --git a/apps/frontend/tests/percent-table-layout.test.ts b/apps/frontend/tests/percent-table-layout.test.ts new file mode 100644 index 00000000..69f71da0 --- /dev/null +++ b/apps/frontend/tests/percent-table-layout.test.ts @@ -0,0 +1,77 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import path from 'node:path' +import test from 'node:test' + +import type { TableColumnsType } from 'antd' + +import { normalizePercentColumns } from '../src/utils/percent-table-layout.ts' + +test('百分比表格:按原列宽权重归一化到 100%', () => { + const columns: TableColumnsType> = [ + { title: 'A', dataIndex: 'a', width: 100 }, + { title: 'B', dataIndex: 'b', width: 300 }, + ] + const result = normalizePercentColumns(columns) + assert.deepEqual( + result.map((column) => column.width), + ['25.0000%', '75.0000%'], + ) + assert.ok(result.every((column) => column.align === 'center')) +}) + +test('百分比表格:没有列宽时平均分配', () => { + const columns: TableColumnsType> = [ + { title: 'A', dataIndex: 'a' }, + { title: 'B', dataIndex: 'b' }, + ] + assert.deepEqual( + normalizePercentColumns(columns).map((column) => column.width), + ['50.0000%', '50.0000%'], + ) +}) + +test('百分比表格:选择列预留 4%', () => { + const columns: TableColumnsType> = [ + { title: 'A', dataIndex: 'a', width: 100 }, + { title: 'B', dataIndex: 'b', width: 100 }, + ] + const result = normalizePercentColumns(columns, { reservedPercent: 4 }) + assert.deepEqual( + result.map((column) => column.width), + ['48.0000%', '48.0000%'], + ) +}) + +test('百分比表格:分组列宽等于子列之和', () => { + const columns: TableColumnsType> = [ + { + title: '分组', + children: [ + { title: 'A', dataIndex: 'a', width: 100 }, + { title: 'B', dataIndex: 'b', width: 100 }, + ], + }, + ] + const result = normalizePercentColumns(columns) + assert.equal(result[0]?.width, '100.0000%') +}) + +test('全项目表格必须统一使用 PercentTable', () => { + const offenders: string[] = [] + for (const file of listTsxFiles(path.join(process.cwd(), 'src'))) { + const source = fs.readFileSync(file, 'utf8') + if (/ { + const fullPath = path.join(dir, entry.name) + if (entry.isDirectory()) return listTsxFiles(fullPath) + return entry.isFile() && entry.name.endsWith('.tsx') ? [fullPath] : [] + }) +}