优化商品列表表格列宽与上架配置面板

This commit is contained in:
yml2213
2026-08-04 11:16:54 +08:00
parent 71bdbf527c
commit e6f09eaf14
5 changed files with 222 additions and 73 deletions
+81 -66
View File
@@ -108,7 +108,6 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
const [loading, setLoading] = useState(false)
const [productOpen, setProductOpen] = useState(false)
const [editingProduct, setEditingProduct] = useState<MerchantProduct | null>(null)
const [walletOpen, setWalletOpen] = useState(false)
const [apiClientOpen, setApiClientOpen] = useState(false)
const [apiCredential, setApiCredential] = useState<ApiCredential | null>(null)
const [callbackCredential, setCallbackCredential] = useState<CallbackCredential | null>(null)
@@ -118,7 +117,6 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
const [testOrderProductsLoading, setTestOrderProductsLoading] = useState(false)
const [testOrderResult, setTestOrderResult] = useState<CreateTestOrderResult | null>(null)
const [productForm] = Form.useForm()
const [walletForm] = Form.useForm()
const [walletFilterForm] = Form.useForm()
const [apiClientForm] = Form.useForm()
const [callbackForm] = Form.useForm()
@@ -331,23 +329,6 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
}
}
const submitWalletAdjust = async () => {
const values = await walletForm.validateFields()
try {
const walletData = await merchantApi.adjustWallet({
amount: values.amount,
idempotency_key: values.idempotency_key,
note: values.note,
})
setWallet(walletData)
setWalletOpen(false)
message.success('钱包已调整')
loadWallet()
} catch (e) {
message.error(e instanceof Error ? e.message : '调整失败')
}
}
const handleWalletFilter = async () => {
const values = await walletFilterForm.validateFields()
loadWallet(1, ledger.size, merchantRole, {
@@ -496,17 +477,63 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
}
const productColumns: ColumnsType<MerchantProduct> = [
{ title: 'SKU', dataIndex: 'sku', width: 200, ellipsis: true, render: (v) => <Typography.Text code copyable={{ tooltips: false }} style={{ maxWidth: '100%' }} ellipsis>{v}</Typography.Text> },
{ title: '名称', dataIndex: 'display_name', width: 200, ellipsis: true, render: (_, r) => r.display_name || r.product?.name || '-' },
{ title: '目录编码', dataIndex: ['product', 'code'], width: 140, ellipsis: true, render: (_, r) => r.product?.code || '-' },
{ title: '售价', dataIndex: 'price_amount', width: 100, render: money },
{ title: '成本', dataIndex: 'cost_amount', width: 100, render: money },
{ title: '库存', dataIndex: 'stock', width: 80, render: (v) => (v < 0 ? '无限' : v) },
{ title: '状态', dataIndex: 'status', width: 80, render: productStatusTag },
{
title: '商户 SKU',
dataIndex: 'sku',
width: 180,
ellipsis: true,
render: (v) => <Typography.Text code copyable={{ tooltips: false }} style={{ maxWidth: '100%' }}>{v}</Typography.Text>,
},
{
title: '商品名称',
dataIndex: 'display_name',
width: 220,
ellipsis: true,
render: (_, r) => (
<div>
<div style={{ fontWeight: 600, color: '#0f172a' }}>{r.display_name || r.product?.name || '-'}</div>
{r.product?.category && (
<span style={{ fontSize: 11.5, color: '#64748b' }}>{r.product.category}</span>
)}
</div>
),
},
{
title: '目录编码',
dataIndex: ['product', 'code'],
width: 150,
ellipsis: true,
render: (_, r) => r.product?.code ? <Typography.Text code>{r.product.code}</Typography.Text> : '-',
},
{
title: '对外售价',
dataIndex: 'price_amount',
width: 120,
render: (v) => <span style={{ fontWeight: 700, color: '#2563eb' }}>{money(v)}</span>,
},
{
title: '成本价',
dataIndex: 'cost_amount',
width: 120,
render: (v) => <span style={{ color: '#64748b' }}>{money(v)}</span>,
},
{
title: '当前库存',
dataIndex: 'stock',
width: 110,
render: (v) => (v < 0 ? <Tag color="blue"></Tag> : v === 0 ? <Tag color="red"></Tag> : `${v}`),
},
{
title: '状态',
dataIndex: 'status',
width: 100,
render: productStatusTag,
},
{
title: '操作',
key: 'action',
width: 80,
width: 90,
fixed: 'right',
render: (_, record) => canManage ? (
<Button type="link" size="small" onClick={() => openProductEdit(record)}></Button>
) : '-',
@@ -609,16 +636,27 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
const productContent = (
<Space direction="vertical" style={{ width: '100%' }} size="middle">
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
<Typography.Text type="secondary"> SKU </Typography.Text>
{canManage && <Button type="primary" icon={<PlusOutlined />} onClick={openProductCreate}></Button>}
</Space>
<Card size="small" style={{ background: '#f8fafc', border: '1px solid #e2e8f0', borderRadius: 8 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div>
<span style={{ fontWeight: 600, color: '#0f172a', marginRight: 8 }}></span>
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
SKU
</Typography.Text>
</div>
{canManage && (
<Button type="primary" icon={<PlusOutlined />} onClick={openProductCreate}>
</Button>
)}
</div>
</Card>
<Table
rowKey="id"
loading={loading}
columns={productColumns}
dataSource={products.list}
tableLayout="fixed"
scroll={{ x: 1100 }}
pagination={pageConfig(products, loadProducts)}
/>
</Space>
@@ -661,24 +699,10 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
{(wallet?.available_balance ?? 0).toLocaleString('zh-CN')}
<span style={{ fontSize: 13, fontWeight: 500, marginLeft: 4, color: '#64748b' }}></span>
</div>
{canFinance && (
<div className="metric-card-footer">
<span>/</span>
<Button
type="link"
size="small"
icon={<WalletOutlined />}
style={{ padding: 0, height: 'auto' }}
onClick={() => {
walletForm.resetFields()
walletForm.setFieldsValue({ idempotency_key: `manual-${Date.now()}` })
setWalletOpen(true)
}}
>
/
</Button>
</div>
)}
<div className="metric-card-footer">
<span>/</span>
<span style={{ fontWeight: 600, color: '#64748b' }}></span>
</div>
</div>
</Col>
<Col xs={24} sm={8}>
@@ -996,20 +1020,6 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
)}
</Modal>
<Modal title="钱包调整" open={walletOpen} onOk={submitWalletAdjust} onCancel={() => setWalletOpen(false)} destroyOnClose>
<Form form={walletForm} layout="vertical" style={{ marginTop: 16 }}>
<Form.Item name="amount" label="调整积分" rules={[{ required: true }]}>
<InputNumber precision={0} style={{ width: '100%' }} placeholder="正数充值,负数扣减" />
</Form.Item>
<Form.Item name="idempotency_key" label="幂等键" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="note" label="备注">
<Input />
</Form.Item>
</Form>
</Modal>
<Modal title="新增 API 密钥" open={apiClientOpen} onOk={submitAPIClient} onCancel={() => setApiClientOpen(false)} destroyOnClose>
<Form form={apiClientForm} layout="vertical" style={{ marginTop: 16 }}>
<Form.Item
@@ -1141,8 +1151,13 @@ function money(value?: number | null) {
function moneyWithSign(value?: number | null) {
const amount = Number(value || 0)
const prefix = amount > 0 ? '+' : ''
return `${prefix}${money(amount)}`
if (amount > 0) {
return <span style={{ fontWeight: 700, color: '#16a34a' }}>+{money(amount)}</span>
}
if (amount < 0) {
return <span style={{ fontWeight: 700, color: '#dc2626' }}>{money(amount)}</span>
}
return <span style={{ fontWeight: 600, color: '#64748b' }}>{money(amount)}</span>
}
function productStatusTag(value: string) {
+93 -2
View File
@@ -14,11 +14,11 @@ import {
Typography,
message,
} from 'antd'
import { GiftOutlined, PlusOutlined, ReloadOutlined, TeamOutlined } from '@ant-design/icons'
import { GiftOutlined, PlusOutlined, ReloadOutlined, TeamOutlined, WalletOutlined } from '@ant-design/icons'
import type { ColumnsType } from 'antd/es/table'
import { useNavigate } from 'react-router-dom'
import { platformApi } from '../api'
import type { Merchant, MerchantMember, PageResult, ProductCatalogItem } from '../types'
import type { Merchant, MerchantMember, PageResult, ProductCatalogItem, WalletAccount } from '../types'
import { formatDateTime } from '../utils/time'
import { PageHeader } from '../components/PageHeader'
@@ -51,9 +51,14 @@ export default function PlatformMerchants() {
const [merchantProducts, setMerchantProducts] = useState<ProductCatalogItem[]>([])
const [selectedCatalogIds, setSelectedCatalogIds] = useState<number[]>([])
const [selectedMerchant, setSelectedMerchant] = useState<Merchant | null>(null)
const [adjustOpen, setAdjustOpen] = useState(false)
const [adjustLoading, setAdjustLoading] = useState(false)
const [adjustSubmitting, setAdjustSubmitting] = useState(false)
const [adjustWallet, setAdjustWallet] = useState<WalletAccount | null>(null)
const [createForm] = Form.useForm()
const [settingsForm] = Form.useForm()
const [memberForm] = Form.useForm()
const [adjustForm] = Form.useForm()
const load = useCallback(async (page = data.page, size = data.size) => {
setLoading(true)
@@ -128,6 +133,44 @@ export default function PlatformMerchants() {
}
}
const openAdjust = async (record: Merchant) => {
setSelectedMerchant(record)
setAdjustOpen(true)
setAdjustLoading(true)
setAdjustWallet(null)
adjustForm.resetFields()
try {
const walletData = await platformApi.wallet(record.id)
setAdjustWallet(walletData)
adjustForm.setFieldsValue({ idempotency_key: `manual-${Date.now()}` })
} catch (e) {
message.error(e instanceof Error ? e.message : '加载钱包失败')
} finally {
setAdjustLoading(false)
}
}
const submitAdjust = async () => {
if (!selectedMerchant) return
const values = await adjustForm.validateFields()
setAdjustSubmitting(true)
try {
const walletData = await platformApi.adjustWallet(selectedMerchant.id, {
amount: Number(values.amount),
idempotency_key: values.idempotency_key,
note: values.note,
})
setAdjustWallet(walletData)
message.success('积分已调整')
setAdjustOpen(false)
load()
} catch (e) {
message.error(e instanceof Error ? e.message : '调整失败')
} finally {
setAdjustSubmitting(false)
}
}
const openAssign = async (record: Merchant) => {
setSelectedMerchant(record)
setAssignOpen(true)
@@ -255,6 +298,14 @@ export default function PlatformMerchants() {
>
</Button>
<Button
type="link"
size="small"
icon={<WalletOutlined />}
onClick={() => openAdjust(record)}
>
</Button>
<Button
type="link"
size="small"
@@ -417,6 +468,46 @@ export default function PlatformMerchants() {
</Form>
</Modal>
<Modal
title={selectedMerchant ? `调整积分:${selectedMerchant.name}` : '调整积分'}
open={adjustOpen}
onOk={submitAdjust}
onCancel={() => setAdjustOpen(false)}
destroyOnClose
width={520}
confirmLoading={adjustSubmitting}
okText="确认调整"
>
<Spin spinning={adjustLoading}>
<Space direction="vertical" style={{ width: '100%' }} size="middle">
<div className="metric-card-box">
<div className="metric-card-header">
<span className="metric-card-title"></span>
<div className="metric-card-icon metric-card-icon--emerald"><WalletOutlined /></div>
</div>
<div className="metric-card-value" style={{ color: '#16a34a' }}>
{(adjustWallet?.available_balance ?? 0).toLocaleString('zh-CN')}
<span style={{ fontSize: 13, fontWeight: 500, marginLeft: 4, color: '#64748b' }}></span>
</div>
</div>
<Form form={adjustForm} layout="vertical">
<Form.Item name="amount" label="调整积分" rules={[{ required: true, message: '请填写调整积分' }]}>
<InputNumber precision={0} style={{ width: '100%' }} placeholder="正数充值,负数扣减" />
</Form.Item>
<Form.Item name="idempotency_key" label="幂等键" rules={[{ required: true }]}>
<Input disabled />
</Form.Item>
<Form.Item name="note" label="备注" rules={[{ max: 512 }]}>
<Input.TextArea rows={3} placeholder="调整原因,会写入积分流水备注" maxLength={512} />
</Form.Item>
</Form>
<Typography.Text type="secondary">
</Typography.Text>
</Space>
</Spin>
</Modal>
<Modal
title={selectedMerchant ? `添加成员:${selectedMerchant.name}` : '添加成员'}
open={memberOpen}