import { useCallback, useEffect, useMemo, useState } from 'react' import { Button, Descriptions, Form, Input, InputNumber, Modal, Select, Space, Table, Tabs, Tag, Typography, message, } from 'antd' import { ApiOutlined, CopyOutlined, PlusOutlined, ReloadOutlined, WalletOutlined, } from '@ant-design/icons' import type { ColumnsType } from 'antd/es/table' import dayjs from 'dayjs' import { merchantApi } from '../api' import type { ApiClient, ApiCredential, CallbackCredential, CallbackSubscription, FulfillmentOrder, Merchant, MerchantMember, MerchantProduct, PageResult, WalletAccount, WalletLedgerEntry, } from '../types' const fulfillmentStatusMap: Record = { pending: { color: 'orange', text: '待履约' }, processing: { color: 'cyan', text: '履约中' }, succeeded: { color: 'green', text: '已成功' }, failed: { color: 'red', text: '已失败' }, cancelled: { color: 'default', text: '已取消' }, } const paymentStatusMap: Record = { pending: { color: 'orange', text: '待支付' }, paid: { color: 'blue', text: '已支付' }, refunded: { color: 'purple', text: '已退款' }, cancelled: { color: 'default', text: '已取消' }, } const memberRoleOptions = [ { value: 'owner', label: '负责人' }, { value: 'operator', label: '运营' }, { value: 'finance', label: '财务' }, { value: 'viewer', label: '只读' }, ] const scopeOptions = [ { value: 'products:read', label: '商品读取' }, { value: 'orders:read', label: '订单读取' }, { value: 'orders:write', label: '订单写入' }, { value: 'fulfillment:read', label: '履约读取' }, { value: 'fulfillment:write', label: '履约写入' }, { value: 'wallet:read', label: '钱包读取' }, ] const eventOptions = [ { value: 'order.created', label: '订单创建' }, { value: 'order.fulfillment.updated', label: '履约更新' }, { value: 'order.cancelled', label: '订单取消' }, ] export default function MerchantCenter() { const [merchant, setMerchant] = useState(null) const [merchantRole, setMerchantRole] = useState() const [activeTab, setActiveTab] = useState('products') const [products, setProducts] = useState>({ list: [], total: 0, page: 1, size: 10 }) const [orders, setOrders] = useState>({ list: [], total: 0, page: 1, size: 10 }) const [ledger, setLedger] = useState>({ list: [], total: 0, page: 1, size: 10 }) const [wallet, setWallet] = useState(null) const [apiClients, setApiClients] = useState([]) const [callbacks, setCallbacks] = useState([]) const [members, setMembers] = useState([]) const [loading, setLoading] = useState(false) const [productOpen, setProductOpen] = useState(false) const [editingProduct, setEditingProduct] = useState(null) const [walletOpen, setWalletOpen] = useState(false) const [apiClientOpen, setApiClientOpen] = useState(false) const [apiCredential, setApiCredential] = useState(null) const [callbackOpen, setCallbackOpen] = useState(false) const [callbackCredential, setCallbackCredential] = useState(null) const [memberOpen, setMemberOpen] = useState(false) const [productForm] = Form.useForm() const [walletForm] = Form.useForm() const [apiClientForm] = Form.useForm() const [callbackForm] = Form.useForm() const [memberForm] = Form.useForm() const canManage = merchantRole === 'owner' || merchantRole === 'operator' const canFinance = merchantRole === 'owner' || merchantRole === 'finance' const enabledFeatures = useMemo(() => new Set(featuresToList(merchant?.features)), [merchant?.features]) const hasFeature = useCallback((feature: string) => !merchant || enabledFeatures.has(feature), [enabledFeatures, merchant]) const loadCurrent = useCallback(async () => { const data = await merchantApi.current() setMerchant(data.merchant) setMerchantRole(data.role) return data }, []) const loadProducts = useCallback(async (page = products.page, size = products.size) => { const data = await merchantApi.products({ page, size }) setProducts(data) }, [products.page, products.size]) const loadOrders = useCallback(async (page = orders.page, size = orders.size) => { const data = await merchantApi.orders({ page, size }) setOrders(data) }, [orders.page, orders.size]) const loadWallet = useCallback(async ( page = ledger.page, size = ledger.size, role = merchantRole, ) => { const walletData = await merchantApi.wallet() setWallet(walletData) if (role === 'owner' || role === 'finance') { const ledgerData = await merchantApi.ledger({ page, size }) setLedger(ledgerData) } else { setLedger({ list: [], total: 0, page, size }) } }, [ledger.page, ledger.size, merchantRole]) const loadAPIClients = useCallback(async (role = merchantRole) => { if (role !== 'owner' && role !== 'operator') { setApiClients([]) return } const clientData = await merchantApi.apiClients() setApiClients(clientData || []) }, [merchantRole]) const loadCallbacks = useCallback(async (role = merchantRole) => { if (role !== 'owner' && role !== 'operator') { setCallbacks([]) return } const callbackData = await merchantApi.callbacks() setCallbacks(callbackData || []) }, [merchantRole]) const loadMembers = useCallback(async (role = merchantRole) => { if (role !== 'owner' && role !== 'operator') { setMembers([]) return } const memberData = await merchantApi.members() setMembers(memberData || []) }, [merchantRole]) const loadActiveTab = useCallback(async (role = merchantRole, tab = activeTab) => { switch (tab) { case 'products': await loadProducts() return case 'orders': await loadOrders() return case 'wallet': await loadWallet(undefined, undefined, role) return case 'api': await loadAPIClients(role) return case 'callbacks': await loadCallbacks(role) return case 'members': await loadMembers(role) return } }, [activeTab, loadAPIClients, loadCallbacks, loadMembers, loadOrders, loadProducts, loadWallet, merchantRole]) const loadAll = useCallback(async () => { setLoading(true) try { const current = await loadCurrent() const nextTab = resolveEnabledTab(activeTab, current.merchant.features) if (nextTab !== activeTab) { setActiveTab(nextTab) } await loadActiveTab(current.role, nextTab) } catch (e) { message.error(e instanceof Error ? e.message : '加载失败') } finally { setLoading(false) } }, [activeTab, loadActiveTab, loadCurrent]) useEffect(() => { loadAll() }, [loadAll]) const openProductCreate = () => { setEditingProduct(null) productForm.resetFields() productForm.setFieldsValue({ currency: 'POINT', stock: -1, status: 'active', price_amount: 0, cost_amount: 0, }) setProductOpen(true) } const openProductEdit = (record: MerchantProduct) => { setEditingProduct(record) productForm.setFieldsValue({ ...record, }) setProductOpen(true) } const submitProduct = async () => { const values = await productForm.validateFields() try { const payload = { ...values, } if (editingProduct) { await merchantApi.updateProduct(editingProduct.id, payload) message.success('商品已更新') } else { await merchantApi.createProduct(payload) message.success('商品已创建') } setProductOpen(false) loadProducts() } catch (e) { message.error(e instanceof Error ? e.message : '保存失败') } } 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 submitAPIClient = async () => { const values = await apiClientForm.validateFields() try { const credential = await merchantApi.createApiClient({ name: values.name, scopes: (values.scopes || []).join(','), signature_version: values.signature_version, expires_at: values.expires_at, }) setApiCredential(credential) setApiClientOpen(false) message.success('API 客户端已创建') loadAPIClients() } catch (e) { message.error(e instanceof Error ? e.message : '创建失败') } } const submitCallback = async () => { const values = await callbackForm.validateFields() try { const credential = await merchantApi.createCallback({ name: values.name, url: values.url, events: (values.events || []).join(','), }) setCallbackCredential(credential) setCallbackOpen(false) message.success('回调已创建') loadCallbacks() } catch (e) { message.error(e instanceof Error ? e.message : '创建失败') } } const submitMember = async () => { const values = await memberForm.validateFields() try { await merchantApi.addMember({ user_id: values.user_id, role: values.role, is_default: values.is_default, }) setMemberOpen(false) message.success('成员已添加') loadMembers() } catch (e) { message.error(e instanceof Error ? e.message : '添加失败') } } const toggleAPIClient = async (record: ApiClient) => { try { await merchantApi.updateApiClientStatus(record.id, record.status === 'active' ? 'disabled' : 'active') message.success('状态已更新') loadAPIClients() } catch (e) { message.error(e instanceof Error ? e.message : '更新失败') } } const toggleCallback = async (record: CallbackSubscription) => { try { await merchantApi.updateCallbackStatus(record.id, record.status === 'active' ? 'disabled' : 'active') message.success('状态已更新') loadCallbacks() } catch (e) { message.error(e instanceof Error ? e.message : '更新失败') } } const productColumns: ColumnsType = [ { title: 'SKU', dataIndex: 'sku', width: 180, render: (v) => {v} }, { title: '名称', dataIndex: 'display_name', ellipsis: true, render: (_, r) => r.display_name || r.product?.name || '-' }, { title: '目录编码', dataIndex: ['product', 'code'], width: 160, ellipsis: true, render: (_, r) => r.product?.code || '-' }, { title: '售价', dataIndex: 'price_amount', width: 110, render: money }, { title: '成本', dataIndex: 'cost_amount', width: 110, render: money }, { title: '库存', dataIndex: 'stock', width: 90, render: (v) => (v < 0 ? '无限' : v) }, { title: '状态', dataIndex: 'status', width: 90, render: productStatusTag }, { title: '操作', key: 'action', width: 90, render: (_, record) => canManage ? ( ) : '-', }, ] const orderColumns: ColumnsType = [ { title: '平台订单号', dataIndex: 'order_no', width: 210, render: (v) => {v} }, { title: '商户单号', dataIndex: 'client_order_no', width: 160, ellipsis: true }, { title: 'SKU', dataIndex: 'product_sku', width: 150, render: (v) => {v} }, { title: '商品', dataIndex: 'product_name', ellipsis: true }, { title: '基础金额', dataIndex: 'base_amount', width: 100, render: money }, { title: '手续费', dataIndex: 'service_fee_amount', width: 100, render: money }, { title: '扣款合计', dataIndex: 'amount', width: 100, render: money }, { title: '支付', dataIndex: 'payment_status', width: 90, render: paymentStatusTag }, { title: '履约', dataIndex: 'fulfillment_status', width: 100, render: fulfillmentStatusTag }, { title: '上游单号', dataIndex: 'provider_order_no', width: 140, ellipsis: true, render: (v) => v || '-' }, { title: '时间', dataIndex: 'created_at', width: 160, render: formatTime }, ] const ledgerColumns: ColumnsType = [ { title: '流水号', dataIndex: 'entry_no', width: 210, ellipsis: true }, { title: '类型', dataIndex: 'type', width: 90, render: ledgerTypeTag }, { title: '金额', dataIndex: 'amount', width: 110, render: moneyWithSign }, { title: '余额', dataIndex: 'balance_after', width: 110, render: money }, { title: '关联单号', dataIndex: 'reference_no', ellipsis: true }, { title: '备注', dataIndex: 'note', ellipsis: true, render: (v) => v || '-' }, { title: '时间', dataIndex: 'created_at', width: 160, render: formatTime }, ] const apiClientColumns: ColumnsType = [ { title: '名称', dataIndex: 'name', width: 180, ellipsis: true }, { title: 'App Key', dataIndex: 'app_key', width: 260, render: (v) => {v} }, { title: '签名', dataIndex: 'signature_version', width: 110, render: (v) => {v} }, { title: '权限', dataIndex: 'scopes', ellipsis: true }, { title: '状态', dataIndex: 'status', width: 90, render: activeStatusTag }, { title: '最后使用', dataIndex: 'last_used_at', width: 160, render: formatTime }, { title: '操作', key: 'action', width: 90, render: (_, record) => canManage ? ( ) : '-', }, ] const callbackColumns: ColumnsType = [ { title: '名称', dataIndex: 'name', width: 180, ellipsis: true }, { title: 'URL', dataIndex: 'url', ellipsis: true }, { title: '事件', dataIndex: 'events', width: 260, ellipsis: true }, { title: '状态', dataIndex: 'status', width: 90, render: activeStatusTag }, { title: '创建时间', dataIndex: 'created_at', width: 160, render: formatTime }, { title: '操作', key: 'action', width: 90, render: (_, record) => canManage ? ( ) : '-', }, ] const memberColumns: ColumnsType = [ { title: '用户', dataIndex: ['user', 'username'], render: (_, r) => r.user?.username || `#${r.user_id}` }, { title: '昵称', dataIndex: ['user', 'nickname'], render: (_, r) => r.user?.nickname || '-' }, { title: '角色', dataIndex: 'role', width: 120, render: memberRoleTag }, { title: '默认', dataIndex: 'is_default', width: 80, render: (v) => (v ? 默认 : '-') }, { title: '状态', dataIndex: 'status', width: 90, render: (v) => (v === 1 ? 启用 : 禁用) }, ] return (
商户中心 {merchant ? `${merchant.name} / ${merchant.code}` : '加载中'} · {merchantRole ? roleText(merchantRole) : '-'}
通用商品以商户 SKU 对外销售。 {canManage && } ), }, { key: 'orders', label: '履约订单', disabled: !hasFeature('orders'), children: (
), }, { key: 'wallet', label: '钱包', disabled: !hasFeature('wallet'), children: ( {money(wallet?.available_balance ?? 0)} {money(wallet?.frozen_balance ?? 0)} {wallet?.currency || 'POINT'} {canFinance && ( )} {canFinance ? (
) : ( 当前角色可查看余额,钱包流水仅财务或负责人可见。 )} ), }, { key: 'api', label: 'API 客户端', disabled: !hasFeature('api'), children: ( 新密钥只在创建后显示一次。 {canManage && }
), }, { key: 'callbacks', label: '回调', disabled: !hasFeature('callbacks'), children: ( 回调事件通过 outbox 持久化发送。 {canManage && }
), }, { key: 'members', label: '成员', children: ( 成员角色独立于平台账号角色。 {merchantRole === 'owner' && }
), }, ]} /> setProductOpen(false)} destroyOnClose width={620}>
{!editingProduct && ( <> )} {!editingProduct && ( )}
setWalletOpen(false)} destroyOnClose>
setApiClientOpen(false)} destroyOnClose>
setApiCredential(null)} footer={}> {apiCredential && } setCallbackOpen(false)} destroyOnClose>