style(frontend): 全项目表格统一百分比布局并居中

This commit is contained in:
yml2213
2026-10-24 14:49:23 +08:00
parent a96229a106
commit ce8efa6bad
52 changed files with 346 additions and 145 deletions
@@ -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<RecordType extends object = Record<string, unknown>>(
props: TableProps<RecordType>,
) {
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 (
<AntTable<RecordType>
{...rest}
columns={normalizedColumns}
tableLayout="fixed"
scroll={{ ...scrollConfig, x: '100%' }}
/>
)
}
@@ -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}
<Card>
<Table<AdminAuditLogItem>
<PercentTable<AdminAuditLogItem>
rowKey="logId"
loading={query.isLoading || query.isFetching}
columns={columns}
@@ -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}
<Table<AdminLoginLogItem>
<PercentTable<AdminLoginLogItem>
rowKey="logId"
loading={loginQuery.isLoading || loginQuery.isFetching}
columns={columns}
@@ -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() {
<div className="kuaishou-industry-split">
<Card title="本地券码" className="kuaishou-industry-voucher-card">
<Table
<PercentTable
rowKey="id"
size="middle"
tableLayout="fixed"
@@ -10,10 +10,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'
@@ -224,7 +224,7 @@ export default function AdminFinancePage() {
</Button>
}
>
<Table<AdminFinanceDailyItem>
<PercentTable<AdminFinanceDailyItem>
rowKey="date"
loading={summaryQuery.isLoading}
dataSource={daily}
@@ -252,7 +252,7 @@ export default function AdminFinancePage() {
</Space>
}
>
<Table<AdminFinanceChannelDailyItem>
<PercentTable<AdminFinanceChannelDailyItem>
rowKey={(row) => `${row.date}-${row.channel}-${row.category}`}
loading={summaryQuery.isLoading}
dataSource={channelDaily}
@@ -304,7 +304,7 @@ export default function AdminFinancePage() {
</Space>
}
>
<Table<AdminFinanceTransaction>
<PercentTable<AdminFinanceTransaction>
rowKey={(row) => `${row.category}-${row.sourceId}-${row.occurredAt}`}
loading={transactionsQuery.isLoading}
dataSource={transactionData?.items || []}
@@ -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 (
<Table
<PercentTable
rowKey="id"
size="middle"
tableLayout="fixed"
@@ -1297,7 +1297,7 @@ export default function AdminKuaishouIndustryPage() {
查询售后
</Button>
</Space>
<Table
<PercentTable
rowKey={(row) => String(row.refundId || row.oid || JSON.stringify(row))}
columns={refundColumns}
dataSource={refundRows}
@@ -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() {
</Space>
<Card bordered={false}>
<Table
<PercentTable
rowKey="orderItemId"
size="small"
columns={columns}
@@ -16,10 +16,10 @@ import {
Modal,
Skeleton,
Space,
Table,
Tag,
Typography,
} from 'antd'
import PercentTable from '@/components/common/PercentTable'
import type { TableColumnsType } from 'antd'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useEffect, useState } from 'react'
@@ -318,7 +318,7 @@ export default function AdminOrderDetailPage() {
</Card>
<Card title="商品明细">
<Table<AdminOrderDetail['items'][number]>
<PercentTable<AdminOrderDetail['items'][number]>
rowKey="orderItemId"
pagination={false}
dataSource={detail.items}
@@ -354,7 +354,7 @@ export default function AdminOrderDetailPage() {
</Card>
<Card title="任务">
<Table<AdminTaskListItem>
<PercentTable<AdminTaskListItem>
rowKey="taskId"
pagination={false}
dataSource={detail.tasks}
@@ -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() {
</Card>
<Card>
<Table<AdminOrderListItem>
<PercentTable<AdminOrderListItem>
rowKey="orderId"
loading={query.isLoading || query.isFetching}
columns={columns}
@@ -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: (
<Table
<PercentTable
rowKey="statDate"
size="small"
columns={dailyColumns}
@@ -401,7 +401,7 @@ export default function AdminSalesProfitPage() {
key: 'product',
label: '商品',
children: (
<Table
<PercentTable
rowKey={(row) => `${row.canonicalProductId || 'unmapped'}`}
size="small"
columns={productColumns}
@@ -416,7 +416,7 @@ export default function AdminSalesProfitPage() {
key: 'fulfillment-mode',
label: '履约方式',
children: (
<Table
<PercentTable
rowKey="fulfillmentMode"
size="small"
columns={fulfillmentModeColumns}
@@ -431,7 +431,7 @@ export default function AdminSalesProfitPage() {
key: 'facts',
label: '事实明细',
children: (
<Table
<PercentTable
rowKey="id"
size="small"
columns={factColumns}
@@ -28,12 +28,12 @@ import {
Skeleton,
Space,
Spin,
Table,
Tabs,
Tag,
Tooltip,
Typography,
} from 'antd'
import PercentTable from '@/components/common/PercentTable'
import type { TableColumnsType } from 'antd'
import { useMutation, useQuery } from '@tanstack/react-query'
import { useEffect, useMemo, useState } from 'react'
@@ -898,7 +898,7 @@ export default function AdminTaskDetailPage() {
key: 'events',
label: `事件流水 (${resolvedDetail.events.length})`,
children: (
<Table<TaskEvent>
<PercentTable<TaskEvent>
rowKey="eventId"
pagination={false}
columns={eventColumns}
@@ -1757,7 +1757,7 @@ function KuaishouIndustryVoucherPanel({ voucher }: { voucher: KuaishouIndustryVo
) : null}
{consumeDetails.length > 0 ? (
<Table<Record<string, unknown>>
<PercentTable<Record<string, unknown>>
className="platform-section-gap"
rowKey={(_, index) => String(index ?? 0)}
pagination={false}
@@ -1905,7 +1905,7 @@ function KuaishouCloudPanel({
{flow.rebind.history.length > 0 ? (
<Card title="换绑记录">
<Table
<PercentTable
rowKey={(row) => `${row.attempt}-${row.requestedAt || ''}`}
pagination={false}
dataSource={flow.rebind.history}
@@ -1969,7 +1969,7 @@ function RedeemResolutionPanel({
</Descriptions.Item>
</Descriptions>
<Table
<PercentTable
className="platform-section-gap"
rowKey={(row) => `${row.attempt}-${row.codeMasked}`}
pagination={false}
@@ -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}
<Card>
<Table<AdminTaskListItem>
<PercentTable<AdminTaskListItem>
rowKey="taskId"
loading={query.isLoading || query.isFetching}
columns={columns}
@@ -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() {
</Typography.Text>
}
>
<Table<AdminUserListItem>
<PercentTable<AdminUserListItem>
rowKey="userId"
loading={query.isLoading || query.isFetching}
columns={columns}
@@ -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:<skuCode> 归一,不走渠道映射;这里不维护游戏分类和商品上下架,那些只属于接单平台。"
/>
<Table
<PercentTable
tableLayout="fixed"
rowKey="id"
size="small"
@@ -15,10 +15,10 @@ import {
Select,
Space,
Switch,
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'
@@ -427,7 +427,7 @@ export function ChannelMappingPanel() {
</Button>
<Button onClick={() => setExpandedMappingProductIds([])}>收起全部</Button>
</Space>
<Table<ChannelProductMappingGroup>
<PercentTable<ChannelProductMappingGroup>
tableLayout="fixed"
rowKey={(row) => String(row.canonicalProductId)}
size="small"
@@ -445,7 +445,7 @@ export function ChannelMappingPanel() {
<Typography.Text type="secondary">
该标准商品下共有 {group.mappings.length} 条渠道映射
</Typography.Text>
<Table<ChannelProductMapping>
<PercentTable<ChannelProductMapping>
tableLayout="fixed"
rowKey="id"
size="small"
@@ -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() {
/>
}
>
<Table
<PercentTable
tableLayout="fixed"
rowKey="id"
size="small"
@@ -6,20 +6,8 @@
* 通用成本模型(平台费 / 支付费 / 售后损失 / 其他)对所有履约方式生效。
*/
import { PlusOutlined, SearchOutlined } from '@ant-design/icons'
import {
Alert,
App,
Button,
Card,
Form,
Input,
Modal,
Select,
Space,
Table,
Tag,
Typography,
} from 'antd'
import { Alert, App, Button, Card, Form, Input, Modal, 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'
@@ -244,7 +232,7 @@ export function CostItemPanel() {
style={{ width: 260 }}
/>
</Space>
<Table
<PercentTable
tableLayout="fixed"
rowKey="id"
size="small"
@@ -3,18 +3,8 @@
*
* 履约方式直接读 order_items.fulfillment_mode,页面只做展示,不允许改判。
*/
import {
App,
Alert,
Button,
Card,
Descriptions,
Empty,
InputNumber,
Space,
Table,
Typography,
} from 'antd'
import { App, Alert, Button, Card, Descriptions, Empty, InputNumber, Space, Typography } from 'antd'
import PercentTable from '@/components/common/PercentTable'
import { useState } from 'react'
import { createAdminCostSnapshot, previewAdminCost } from '@/services/admin'
@@ -150,7 +140,7 @@ export function CostPreviewPanel() {
/>
) : null}
<Table
<PercentTable
tableLayout="fixed"
rowKey={(row) => `${row.costItemId}-${row.costVersionId}`}
size="small"
@@ -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 }}
/>
</Space>
<Table
<PercentTable
tableLayout="fixed"
rowKey="id"
size="small"
@@ -33,10 +33,10 @@ import {
Select,
Space,
Statistic,
Table,
Tag,
Typography,
} from 'antd'
import PercentTable from '@/components/common/PercentTable'
import type { TableColumnsType } from 'antd'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useState } from 'react'
@@ -382,7 +382,7 @@ export function ManualSkuConfigPanel() {
</Button>
</Space>
<Table
<PercentTable
rowKey="id"
size="small"
columns={columns}
@@ -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 { useQuery, useQueryClient } from '@tanstack/react-query'
import { useState } from 'react'
@@ -559,7 +559,7 @@ export function ProductCatalogPanel() {
style={{ width: 280 }}
/>
</Space>
<Table
<PercentTable
tableLayout="fixed"
rowKey="id"
size="small"
@@ -24,11 +24,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 type { Dayjs } from 'dayjs'
@@ -1048,7 +1048,7 @@ export default function FundManagementView({ role }: { role: ManagementRole }) {
) : null
}
>
<Table<AdminFundAccount>
<PercentTable<AdminFundAccount>
rowKey="id"
size="small"
columns={buildAccountColumns('agent')}
@@ -1074,7 +1074,7 @@ export default function FundManagementView({ role }: { role: ManagementRole }) {
) : null
}
>
<Table<AdminFundAccount>
<PercentTable<AdminFundAccount>
rowKey="id"
size="small"
columns={buildAccountColumns('supervisor')}
@@ -1139,7 +1139,7 @@ export default function FundManagementView({ role }: { role: ManagementRole }) {
}}
/>
</Space>
<Table<AdminFundExpense>
<PercentTable<AdminFundExpense>
rowKey="id"
size="small"
columns={expenseColumns}
@@ -1188,7 +1188,7 @@ export default function FundManagementView({ role }: { role: ManagementRole }) {
)}
/>
</Space>
<Table<AdminFundReconciliation>
<PercentTable<AdminFundReconciliation>
rowKey="id"
size="small"
columns={reconciliationColumns}
@@ -1227,7 +1227,7 @@ export default function FundManagementView({ role }: { role: ManagementRole }) {
</Space>
}
>
<Table<AdminFundAccount>
<PercentTable<AdminFundAccount>
rowKey="id"
size="small"
columns={buildAccountColumns('supervisor')}
@@ -1262,7 +1262,7 @@ export default function FundManagementView({ role }: { role: ManagementRole }) {
</Space>
}
>
<Table<AdminFundAccount>
<PercentTable<AdminFundAccount>
rowKey="id"
size="small"
columns={buildAccountColumns('agent')}
@@ -1793,7 +1793,7 @@ export default function FundManagementView({ role }: { role: ManagementRole }) {
</Descriptions.Item>
</Descriptions>
<Card title="资金流水" style={{ marginTop: 16 }}>
<Table<AdminFundEntry>
<PercentTable<AdminFundEntry>
rowKey="id"
size="small"
columns={detailEntryColumns}
@@ -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: (
<Table<PlatformCreditEntry>
<PercentTable<PlatformCreditEntry>
rowKey="id"
size="small"
columns={creditEntryColumns}
@@ -514,7 +514,7 @@ export default function FundSupportView() {
key: 'entries',
label: '备用金流水',
children: (
<Table<AdminFundEntry>
<PercentTable<AdminFundEntry>
rowKey="id"
size="small"
columns={entryColumns}
@@ -529,7 +529,7 @@ export default function FundSupportView() {
key: 'expenses',
label: '我的支出',
children: (
<Table<AdminFundExpense>
<PercentTable<AdminFundExpense>
rowKey="id"
size="small"
columns={expenseColumns}
@@ -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() {
<Typography.Title level={5} style={{ margin: '8px 0 12px' }}>
客服积分余额
</Typography.Title>
<Table<PlatformCreditAccount>
<PercentTable<PlatformCreditAccount>
rowKey="id"
size="small"
columns={accountColumns}
@@ -413,7 +413,7 @@ export function PlatformCreditPanel() {
<Typography.Title level={5} style={{ margin: '24px 0 12px' }}>
积分流水
</Typography.Title>
<Table
<PercentTable
rowKey="id"
size="small"
columns={entryColumns}
@@ -11,10 +11,10 @@ import {
Modal,
Select,
Space,
Table,
Tag,
Typography,
} from 'antd'
import PercentTable from '@/components/common/PercentTable'
import { forwardRef, useImperativeHandle, useState } from 'react'
import ImageUpload from '@/components/files/ImageUpload'
@@ -195,7 +195,7 @@ const AfterSalesPanel = forwardRef<AfterSalesPanelHandle>(function AfterSalesPan
}
bordered={false}
>
<Table<WorkerAfterSalesCase>
<PercentTable<WorkerAfterSalesCase>
rowKey="caseId"
loading={afterSalesCasesQuery.isLoading}
dataSource={afterSalesCasesQuery.data?.data.items || []}
@@ -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}
>
<Table<WorkerCancelRequest>
<PercentTable<WorkerCancelRequest>
rowKey="requestId"
size="small"
loading={requestsQuery.isLoading}
@@ -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() {
</Form>
</Card>
<Card title="分类列表(树形层级)" bordered={false}>
<Table<WorkCategory>
<PercentTable<WorkCategory>
rowKey="categoryId"
loading={categoriesQuery.isLoading}
dataSource={treeData}
@@ -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}
<Table
<PercentTable
rowKey="id"
columns={columns}
dataSource={items}
@@ -20,12 +20,12 @@ import {
Row,
Select,
Space,
Table,
Tag,
Tooltip,
Typography,
App,
} from 'antd'
import PercentTable from '@/components/common/PercentTable'
import type { TableColumnsType } from 'antd'
import { useState } from 'react'
@@ -457,7 +457,7 @@ export default function ConsignmentSubmissionsPanel() {
}
/>
) : null}
<Table
<PercentTable
rowKey="id"
columns={submissionColumns}
dataSource={submissions}
@@ -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'
@@ -123,7 +124,7 @@ export default function FeedbacksPanel() {
}
bordered={false}
>
<Table<WorkerOrderFeedback>
<PercentTable<WorkerOrderFeedback>
rowKey="feedbackId"
size="small"
loading={feedbacksQuery.isLoading}
@@ -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}
/>
<Table<WorkerFinanceRequest>
<PercentTable<WorkerFinanceRequest>
rowKey="requestId"
loading={financeRequestsQuery.isLoading}
dataSource={financeRequestsQuery.data?.data.items || []}
@@ -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() {
一级分类限额将限制该游戏及旗下所有子分类的总单量;二级分类可单独设置细化限额。
</span>
</div>
<Table<WorkCategory>
<PercentTable<WorkCategory>
rowKey="categoryId"
size="small"
pagination={false}
@@ -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() {
: ''}
</Typography.Text>
</Space>
<Table<AdminWorkerLeaderboardEntry>
<PercentTable<AdminWorkerLeaderboardEntry>
rowKey="workerId"
columns={columns}
dataSource={items}
@@ -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() {
</Form>
</Card>
<Card title="等级列表" bordered={false}>
<Table<WorkerLevel>
<PercentTable<WorkerLevel>
rowKey="levelId"
loading={levelsQuery.isLoading}
dataSource={levelsQuery.data?.data.items || []}
@@ -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="关闭后台待办后,声音播报会同步关闭;已创建的待办不会被隐藏或删除。"
/>
<Table
<PercentTable
rowKey="key"
columns={columns}
dataSource={notificationEventRows}
@@ -18,11 +18,11 @@ import {
Select,
Space,
Switch,
Table,
Tabs,
Tag,
Typography,
} from 'antd'
import PercentTable from '@/components/common/PercentTable'
import type { TableColumnsType } from 'antd'
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router'
@@ -359,7 +359,7 @@ export default function ProductMatchPanel() {
发现新商品后,点击「绑定到模板」在接单模板页配置匹配
</Typography.Text>
</Space>
<Table<KuaishouMatchSource>
<PercentTable<KuaishouMatchSource>
rowKey={(row) =>
`${row.sellerId}-${row.relItemId}-${row.itemTitle}-${row.relSkuId}-${row.skuNick}`
}
@@ -660,7 +660,7 @@ export default function ProductMatchPanel() {
</Button>
</Popconfirm>
</div>
<Table<WorkProductMatchLog>
<PercentTable<WorkProductMatchLog>
rowKey="logId"
loading={logsQuery.isLoading}
columns={logColumns}
@@ -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() {
</div>
) : null}
<Table<WorkProductRule>
<PercentTable<WorkProductRule>
rowKey="ruleId"
size="small"
loading={rulesQuery.isLoading || mappingsQuery.isLoading}
@@ -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() {
</Button>
}
>
<Table
<PercentTable
rowKey="key"
size="small"
columns={trackerColumns}
@@ -427,7 +427,7 @@ export default function RiskControlPanel() {
</Button>
</Space>
<Table
<PercentTable
rowKey="id"
size="small"
columns={eventColumns}
@@ -32,11 +32,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'
@@ -2076,7 +2076,7 @@ export default function WorkOrdersPanel() {
}
bordered={false}
>
<Table<WorkOrder>
<PercentTable<WorkOrder>
rowKey="workOrderId"
loading={ordersQuery.isLoading}
dataSource={ordersQuery.data?.data.items || []}
@@ -3139,7 +3139,7 @@ export default function WorkOrdersPanel() {
</div>
</Space>
) : null}
<Table<WorkOrderShare>
<PercentTable<WorkOrderShare>
rowKey="shareId"
size="small"
dataSource={sharingShares}
@@ -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 (
<Table<WorkerDepositUnfreeze>
<PercentTable<WorkerDepositUnfreeze>
rowKey="id"
size="small"
columns={columns}
@@ -398,7 +399,7 @@ function LedgersSection({ workerId }: { workerId: number }) {
setPage(1)
}}
/>
<Table<WorkerWalletLedger>
<PercentTable<WorkerWalletLedger>
rowKey="ledgerId"
size="small"
columns={columns}
@@ -463,7 +464,7 @@ function OrdersSection({ workerId }: { workerId: number }) {
},
]
return (
<Table<WorkOrder>
<PercentTable<WorkOrder>
rowKey="workOrderId"
size="small"
columns={columns}
@@ -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() {
: '寄卖白名单已关闭:全部打手均可发布寄卖。下方开关仅在重新开启白名单后生效。'
}
/>
<Table<WorkerUser>
<PercentTable<WorkerUser>
rowKey="workerId"
loading={workersQuery.isLoading}
dataSource={workersQuery.data?.data.items || []}
@@ -18,7 +18,6 @@ import {
Space,
Spin,
Switch,
Table,
Tag,
Typography,
} from 'antd'
@@ -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 ? (
<Empty description="暂无店铺授权" />
) : (
<Table<AdminKuaishouIndustryShopConfig>
<PercentTable<AdminKuaishouIndustryShopConfig>
rowKey={(row, index) => buildIndustryShopRowKey(row, index || 0)}
columns={shopColumns}
dataSource={shops}
@@ -1694,7 +1694,7 @@ function AffiliateDashPlatformPanel() {
<Typography.Text code>apps/backend/.../affiliate-dash/sku-mapping.ts</Typography.Text>
,与平台商品目录对齐。
</Typography.Paragraph>
<Table<SkuMappingRow>
<PercentTable<SkuMappingRow>
rowKey={(row) => row.productNo}
size="small"
pagination={{ pageSize: 10, showSizeChanger: false }}
@@ -1760,7 +1760,7 @@ function AffiliateDashPlatformPanel() {
</Typography.Text>
) : null}
</Space>
<Table<AdminAffiliateDashProductItem>
<PercentTable<AdminAffiliateDashProductItem>
rowKey="sku"
size="small"
loading={productsLoading}
@@ -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() {
</Button>
}
>
<Table
<PercentTable
rowKey="shopId"
columns={columns}
dataSource={config.stores}
@@ -16,11 +16,11 @@ import {
Modal,
Segmented,
Space,
Table,
Tabs,
Typography,
type InputRef,
} from 'antd'
import PercentTable from '@/components/common/PercentTable'
import { useEffect, useRef, useState } from 'react'
import { Navigate, useNavigate } from 'react-router'
@@ -665,7 +665,7 @@ export default function WorkerLoginPage() {
message="验证账号密码后,可删除旧设备。PC 和手机分别最多同时在线 3 台。"
style={{ marginBottom: 16 }}
/>
<Table
<PercentTable
size="small"
rowKey="sessionId"
loading={deviceManagerLoading}
@@ -6,7 +6,8 @@ import {
ThunderboltOutlined,
WarningOutlined,
} from '@ant-design/icons'
import { Alert, Button, Card, Empty, Input, Space, Table, Tabs, Typography } from 'antd'
import { Alert, Button, Card, Empty, Input, Space, Tabs, Typography } from 'antd'
import PercentTable from '@/components/common/PercentTable'
import type { TableColumnsType } from 'antd'
import type { Pagination, WorkerOrderOverviewItem, WorkOrder } from '@/types/worker-platform'
@@ -255,7 +256,7 @@ export function WorkerOrderDesktopList({
/>
) : null}
<Table<WorkOrder>
<PercentTable<WorkOrder>
rowKey="workOrderId"
size="middle"
loading={loading}
@@ -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} 台),删除对应旧设备后才能登录。`}
/>
<Table<WorkerSessionDevice>
<PercentTable<WorkerSessionDevice>
size="small"
loading={devicesLoading}
rowKey="sessionId"
@@ -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({
</Space>
}
>
<Table<WorkerWalletLedger>
<PercentTable<WorkerWalletLedger>
rowKey="ledgerId"
size="small"
loading={loading}
@@ -353,7 +354,7 @@ export function WorkerProfileDesktopRequestRecords({
</Space>
}
>
<Table<WorkerFinanceRequest>
<PercentTable<WorkerFinanceRequest>
rowKey="requestId"
size="small"
loading={loading}
+1
View File
@@ -11,3 +11,4 @@
@import './admin.css';
@import './client.css';
@import './worker.css';
@import './table.css';
+10
View File
@@ -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;
}
@@ -0,0 +1,94 @@
import type { TableColumnsType } from 'antd'
type Column<RecordType extends object> = TableColumnsType<RecordType>[number]
type WidthContext<RecordType extends object> = {
leafPercent: WeakMap<Column<RecordType>, number>
}
/**
* 把业务列宽(px 数值)按相对权重归一化为百分比。
* rowSelection / expandable 等内置列由调用方传入需要预留的百分比。
*/
export function normalizePercentColumns<RecordType extends object>(
columns: TableColumnsType<RecordType>,
options: { reservedPercent?: number } = {},
): TableColumnsType<RecordType> {
const leaves: Column<RecordType>[] = []
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<Column<RecordType>, number>()
for (const column of leaves) {
const weight = numericWidth(column.width) ?? fallbackWeight
leafPercent.set(column, (weight / totalWeight) * budgetPercent)
}
return applyPercentWidths(columns, { leafPercent })
}
function applyPercentWidths<RecordType extends object>(
columns: TableColumnsType<RecordType>,
context: WidthContext<RecordType>,
): TableColumnsType<RecordType> {
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<RecordType extends object>(
columns: TableColumnsType<RecordType>,
leaves: Column<RecordType>[],
) {
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<object>['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<object>['width']) {
if (typeof width !== 'string' || !width.endsWith('%')) return 0
const parsed = Number.parseFloat(width)
return Number.isFinite(parsed) ? parsed : 0
}
@@ -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<Record<string, unknown>> = [
{ 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<Record<string, unknown>> = [
{ 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<Record<string, unknown>> = [
{ 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<Record<string, unknown>> = [
{
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 (/<Table\b/.test(source)) {
offenders.push(path.relative(process.cwd(), file))
}
}
assert.deepEqual(offenders, [], `仍有表格直接使用 AntD Table:${offenders.join(', ')}`)
})
function listTsxFiles(dir: string): string[] {
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const fullPath = path.join(dir, entry.name)
if (entry.isDirectory()) return listTsxFiles(fullPath)
return entry.isFile() && entry.name.endsWith('.tsx') ? [fullPath] : []
})
}