优化商品列表表格列宽与上架配置面板
This commit is contained in:
@@ -214,20 +214,57 @@ func (h *MerchantHandler) ListWalletLedger(c *gin.Context) {
|
|||||||
response.Page(c, list, total, page, size)
|
response.Page(c, list, total, page, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdminGetWallet 供平台管理员查看指定商户的钱包。
|
||||||
|
func (h *MerchantHandler) AdminGetWallet(c *gin.Context) {
|
||||||
|
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
|
if id == 0 {
|
||||||
|
response.BadRequest(c, "商户 ID 无效")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wallet, err := h.fulfillmentSvc.GetWallet(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
response.ServerError(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OK(c, wallet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminListWalletLedger 供平台管理员查看指定商户的积分流水。
|
||||||
|
func (h *MerchantHandler) AdminListWalletLedger(c *gin.Context) {
|
||||||
|
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
|
if id == 0 {
|
||||||
|
response.BadRequest(c, "商户 ID 无效")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
page, size := pageParams(c)
|
||||||
|
list, total, err := h.fulfillmentSvc.ListWalletLedger(uint(id), page, size, c.Query("reference_no"), c.Query("type"))
|
||||||
|
if err != nil {
|
||||||
|
response.ServerError(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Page(c, list, total, page, size)
|
||||||
|
}
|
||||||
|
|
||||||
type walletAdjustReq struct {
|
type walletAdjustReq struct {
|
||||||
Amount int64 `json:"amount" binding:"required"`
|
Amount int64 `json:"amount" binding:"required"`
|
||||||
IdempotencyKey string `json:"idempotency_key" binding:"required"`
|
IdempotencyKey string `json:"idempotency_key" binding:"required"`
|
||||||
Note string `json:"note"`
|
Note string `json:"note"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MerchantHandler) AdjustWallet(c *gin.Context) {
|
// AdminAdjustWallet 供平台管理员手动调整指定商户的积分,商户侧无任何调账入口。
|
||||||
|
func (h *MerchantHandler) AdminAdjustWallet(c *gin.Context) {
|
||||||
|
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
|
if id == 0 {
|
||||||
|
response.BadRequest(c, "商户 ID 无效")
|
||||||
|
return
|
||||||
|
}
|
||||||
var req walletAdjustReq
|
var req walletAdjustReq
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
response.BadRequest(c, "参数错误:amount 与 idempotency_key 必填")
|
response.BadRequest(c, "参数错误:amount 与 idempotency_key 必填")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wallet, err := h.fulfillmentSvc.AdjustWallet(service.WalletAdjustInput{
|
wallet, err := h.fulfillmentSvc.AdjustWallet(service.WalletAdjustInput{
|
||||||
MerchantID: middleware.GetMerchantID(c),
|
MerchantID: uint(id),
|
||||||
ActorUserID: middleware.GetUserID(c),
|
ActorUserID: middleware.GetUserID(c),
|
||||||
Amount: req.Amount,
|
Amount: req.Amount,
|
||||||
IdempotencyKey: req.IdempotencyKey,
|
IdempotencyKey: req.IdempotencyKey,
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ func Setup(h *Handlers) *gin.Engine {
|
|||||||
merchant.POST("/orders/:order_no/delivery-link/restore", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureOrders), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.RestoreDeliveryLink)
|
merchant.POST("/orders/:order_no/delivery-link/restore", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureOrders), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.RestoreDeliveryLink)
|
||||||
merchant.GET("/wallet", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator, model.MemberRoleFinance), h.Merchant.GetWallet)
|
merchant.GET("/wallet", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator, model.MemberRoleFinance), h.Merchant.GetWallet)
|
||||||
merchant.GET("/wallet/ledger", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleFinance), h.Merchant.ListWalletLedger)
|
merchant.GET("/wallet/ledger", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleFinance), h.Merchant.ListWalletLedger)
|
||||||
merchant.POST("/wallet/adjust", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleFinance), h.Merchant.AdjustWallet)
|
|
||||||
merchant.GET("/api-clients", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), h.Merchant.ListAPIClients)
|
merchant.GET("/api-clients", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), h.Merchant.ListAPIClients)
|
||||||
merchant.POST("/api-clients", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.CreateAPIClient)
|
merchant.POST("/api-clients", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.CreateAPIClient)
|
||||||
merchant.PATCH("/api-clients/:id/status", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.UpdateAPIClientStatus)
|
merchant.PATCH("/api-clients/:id/status", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.UpdateAPIClientStatus)
|
||||||
@@ -151,6 +150,9 @@ func Setup(h *Handlers) *gin.Engine {
|
|||||||
admin.GET("/platform/product-catalog", h.Merchant.ListProductCatalog)
|
admin.GET("/platform/product-catalog", h.Merchant.ListProductCatalog)
|
||||||
admin.GET("/platform/merchants/:id/products", h.Merchant.ListMerchantProductsByAdmin)
|
admin.GET("/platform/merchants/:id/products", h.Merchant.ListMerchantProductsByAdmin)
|
||||||
admin.POST("/platform/merchants/:id/products/assign", h.Merchant.AssignProducts)
|
admin.POST("/platform/merchants/:id/products/assign", h.Merchant.AssignProducts)
|
||||||
|
admin.GET("/platform/merchants/:id/wallet", h.Merchant.AdminGetWallet)
|
||||||
|
admin.GET("/platform/merchants/:id/wallet/ledger", h.Merchant.AdminListWalletLedger)
|
||||||
|
admin.POST("/platform/merchants/:id/wallet/adjust", h.Merchant.AdminAdjustWallet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,8 +79,6 @@ export const merchantApi = {
|
|||||||
request.get('/merchant/wallet').then((r) => r.data.data as WalletAccount),
|
request.get('/merchant/wallet').then((r) => r.data.data as WalletAccount),
|
||||||
ledger: (params?: Record<string, unknown>) =>
|
ledger: (params?: Record<string, unknown>) =>
|
||||||
request.get('/merchant/wallet/ledger', { params }).then((r) => r.data.data as PageResult<WalletLedgerEntry>),
|
request.get('/merchant/wallet/ledger', { params }).then((r) => r.data.data as PageResult<WalletLedgerEntry>),
|
||||||
adjustWallet: (data: { amount: number; idempotency_key: string; note?: string }) =>
|
|
||||||
request.post('/merchant/wallet/adjust', data).then((r) => r.data.data as WalletAccount),
|
|
||||||
apiClients: () =>
|
apiClients: () =>
|
||||||
request.get('/merchant/api-clients').then((r) => r.data.data as ApiClient[]),
|
request.get('/merchant/api-clients').then((r) => r.data.data as ApiClient[]),
|
||||||
createApiClient: (data: {
|
createApiClient: (data: {
|
||||||
@@ -154,4 +152,10 @@ export const platformApi = {
|
|||||||
request.get(`/platform/merchants/${merchantId}/products`).then((r) => r.data.data as ProductCatalogItem[]),
|
request.get(`/platform/merchants/${merchantId}/products`).then((r) => r.data.data as ProductCatalogItem[]),
|
||||||
assignProducts: (merchantId: number, catalogIds: number[]) =>
|
assignProducts: (merchantId: number, catalogIds: number[]) =>
|
||||||
request.post(`/platform/merchants/${merchantId}/products/assign`, { catalog_ids: catalogIds }).then((r) => r.data.data as { assigned: number }),
|
request.post(`/platform/merchants/${merchantId}/products/assign`, { catalog_ids: catalogIds }).then((r) => r.data.data as { assigned: number }),
|
||||||
|
wallet: (merchantId: number) =>
|
||||||
|
request.get(`/platform/merchants/${merchantId}/wallet`).then((r) => r.data.data as WalletAccount),
|
||||||
|
walletLedger: (merchantId: number, params?: Record<string, unknown>) =>
|
||||||
|
request.get(`/platform/merchants/${merchantId}/wallet/ledger`, { params }).then((r) => r.data.data as PageResult<WalletLedgerEntry>),
|
||||||
|
adjustWallet: (merchantId: number, data: { amount: number; idempotency_key: string; note?: string }) =>
|
||||||
|
request.post(`/platform/merchants/${merchantId}/wallet/adjust`, data).then((r) => r.data.data as WalletAccount),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [productOpen, setProductOpen] = useState(false)
|
const [productOpen, setProductOpen] = useState(false)
|
||||||
const [editingProduct, setEditingProduct] = useState<MerchantProduct | null>(null)
|
const [editingProduct, setEditingProduct] = useState<MerchantProduct | null>(null)
|
||||||
const [walletOpen, setWalletOpen] = useState(false)
|
|
||||||
const [apiClientOpen, setApiClientOpen] = useState(false)
|
const [apiClientOpen, setApiClientOpen] = useState(false)
|
||||||
const [apiCredential, setApiCredential] = useState<ApiCredential | null>(null)
|
const [apiCredential, setApiCredential] = useState<ApiCredential | null>(null)
|
||||||
const [callbackCredential, setCallbackCredential] = useState<CallbackCredential | 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 [testOrderProductsLoading, setTestOrderProductsLoading] = useState(false)
|
||||||
const [testOrderResult, setTestOrderResult] = useState<CreateTestOrderResult | null>(null)
|
const [testOrderResult, setTestOrderResult] = useState<CreateTestOrderResult | null>(null)
|
||||||
const [productForm] = Form.useForm()
|
const [productForm] = Form.useForm()
|
||||||
const [walletForm] = Form.useForm()
|
|
||||||
const [walletFilterForm] = Form.useForm()
|
const [walletFilterForm] = Form.useForm()
|
||||||
const [apiClientForm] = Form.useForm()
|
const [apiClientForm] = Form.useForm()
|
||||||
const [callbackForm] = 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 handleWalletFilter = async () => {
|
||||||
const values = await walletFilterForm.validateFields()
|
const values = await walletFilterForm.validateFields()
|
||||||
loadWallet(1, ledger.size, merchantRole, {
|
loadWallet(1, ledger.size, merchantRole, {
|
||||||
@@ -496,17 +477,63 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
|
|||||||
}
|
}
|
||||||
|
|
||||||
const productColumns: ColumnsType<MerchantProduct> = [
|
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: '商户 SKU',
|
||||||
{ title: '目录编码', dataIndex: ['product', 'code'], width: 140, ellipsis: true, render: (_, r) => r.product?.code || '-' },
|
dataIndex: 'sku',
|
||||||
{ title: '售价', dataIndex: 'price_amount', width: 100, render: money },
|
width: 180,
|
||||||
{ title: '成本', dataIndex: 'cost_amount', width: 100, render: money },
|
ellipsis: true,
|
||||||
{ title: '库存', dataIndex: 'stock', width: 80, render: (v) => (v < 0 ? '无限' : v) },
|
render: (v) => <Typography.Text code copyable={{ tooltips: false }} style={{ maxWidth: '100%' }}>{v}</Typography.Text>,
|
||||||
{ title: '状态', dataIndex: 'status', width: 80, render: productStatusTag },
|
},
|
||||||
|
{
|
||||||
|
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: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
width: 80,
|
width: 90,
|
||||||
|
fixed: 'right',
|
||||||
render: (_, record) => canManage ? (
|
render: (_, record) => canManage ? (
|
||||||
<Button type="link" size="small" onClick={() => openProductEdit(record)}>编辑</Button>
|
<Button type="link" size="small" onClick={() => openProductEdit(record)}>编辑</Button>
|
||||||
) : '-',
|
) : '-',
|
||||||
@@ -609,16 +636,27 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
|
|||||||
|
|
||||||
const productContent = (
|
const productContent = (
|
||||||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||||||
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
|
<Card size="small" style={{ background: '#f8fafc', border: '1px solid #e2e8f0', borderRadius: 8 }}>
|
||||||
<Typography.Text type="secondary">通用商品以商户 SKU 对外销售。</Typography.Text>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
{canManage && <Button type="primary" icon={<PlusOutlined />} onClick={openProductCreate}>新增商品</Button>}
|
<div>
|
||||||
</Space>
|
<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
|
<Table
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
columns={productColumns}
|
columns={productColumns}
|
||||||
dataSource={products.list}
|
dataSource={products.list}
|
||||||
tableLayout="fixed"
|
scroll={{ x: 1100 }}
|
||||||
pagination={pageConfig(products, loadProducts)}
|
pagination={pageConfig(products, loadProducts)}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -661,24 +699,10 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
|
|||||||
{(wallet?.available_balance ?? 0).toLocaleString('zh-CN')}
|
{(wallet?.available_balance ?? 0).toLocaleString('zh-CN')}
|
||||||
<span style={{ fontSize: 13, fontWeight: 500, marginLeft: 4, color: '#64748b' }}>积分</span>
|
<span style={{ fontSize: 13, fontWeight: 500, marginLeft: 4, color: '#64748b' }}>积分</span>
|
||||||
</div>
|
</div>
|
||||||
{canFinance && (
|
|
||||||
<div className="metric-card-footer">
|
<div className="metric-card-footer">
|
||||||
<span>手动划拨/调账</span>
|
<span>充值/调账</span>
|
||||||
<Button
|
<span style={{ fontWeight: 600, color: '#64748b' }}>仅平台管理员可操作</span>
|
||||||
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>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={8}>
|
<Col xs={24} sm={8}>
|
||||||
@@ -996,20 +1020,6 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
|
|||||||
)}
|
)}
|
||||||
</Modal>
|
</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>
|
<Modal title="新增 API 密钥" open={apiClientOpen} onOk={submitAPIClient} onCancel={() => setApiClientOpen(false)} destroyOnClose>
|
||||||
<Form form={apiClientForm} layout="vertical" style={{ marginTop: 16 }}>
|
<Form form={apiClientForm} layout="vertical" style={{ marginTop: 16 }}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -1141,8 +1151,13 @@ function money(value?: number | null) {
|
|||||||
|
|
||||||
function moneyWithSign(value?: number | null) {
|
function moneyWithSign(value?: number | null) {
|
||||||
const amount = Number(value || 0)
|
const amount = Number(value || 0)
|
||||||
const prefix = amount > 0 ? '+' : ''
|
if (amount > 0) {
|
||||||
return `${prefix}${money(amount)}`
|
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) {
|
function productStatusTag(value: string) {
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
message,
|
message,
|
||||||
} from 'antd'
|
} 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 type { ColumnsType } from 'antd/es/table'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { platformApi } from '../api'
|
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 { formatDateTime } from '../utils/time'
|
||||||
import { PageHeader } from '../components/PageHeader'
|
import { PageHeader } from '../components/PageHeader'
|
||||||
|
|
||||||
@@ -51,9 +51,14 @@ export default function PlatformMerchants() {
|
|||||||
const [merchantProducts, setMerchantProducts] = useState<ProductCatalogItem[]>([])
|
const [merchantProducts, setMerchantProducts] = useState<ProductCatalogItem[]>([])
|
||||||
const [selectedCatalogIds, setSelectedCatalogIds] = useState<number[]>([])
|
const [selectedCatalogIds, setSelectedCatalogIds] = useState<number[]>([])
|
||||||
const [selectedMerchant, setSelectedMerchant] = useState<Merchant | null>(null)
|
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 [createForm] = Form.useForm()
|
||||||
const [settingsForm] = Form.useForm()
|
const [settingsForm] = Form.useForm()
|
||||||
const [memberForm] = Form.useForm()
|
const [memberForm] = Form.useForm()
|
||||||
|
const [adjustForm] = Form.useForm()
|
||||||
|
|
||||||
const load = useCallback(async (page = data.page, size = data.size) => {
|
const load = useCallback(async (page = data.page, size = data.size) => {
|
||||||
setLoading(true)
|
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) => {
|
const openAssign = async (record: Merchant) => {
|
||||||
setSelectedMerchant(record)
|
setSelectedMerchant(record)
|
||||||
setAssignOpen(true)
|
setAssignOpen(true)
|
||||||
@@ -255,6 +298,14 @@ export default function PlatformMerchants() {
|
|||||||
>
|
>
|
||||||
设置
|
设置
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
icon={<WalletOutlined />}
|
||||||
|
onClick={() => openAdjust(record)}
|
||||||
|
>
|
||||||
|
调整积分
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -417,6 +468,46 @@ export default function PlatformMerchants() {
|
|||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</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
|
<Modal
|
||||||
title={selectedMerchant ? `添加成员:${selectedMerchant.name}` : '添加成员'}
|
title={selectedMerchant ? `添加成员:${selectedMerchant.name}` : '添加成员'}
|
||||||
open={memberOpen}
|
open={memberOpen}
|
||||||
|
|||||||
Reference in New Issue
Block a user