import { useCallback, useEffect, useMemo, useState } from 'react'; import { Alert, Button, Form, Input, Modal, Select, Space, Table, Tag, Typography, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { FULFILLMENT_PROVIDER_STATUS_LABELS, FULFILLMENT_PROVIDER_TYPE_LABELS, FulfillmentProviderStatus, FulfillmentProviderType, isXfxProviderCode, type FulfillmentProviderDto, } from '@dukang/shared-types'; import { request } from '../lib/api'; import { fmtTime } from '../lib/constants'; const TYPE_OPTIONS = Object.entries(FULFILLMENT_PROVIDER_TYPE_LABELS).map(([value, label]) => ({ value, label, })); const STATUS_OPTIONS = Object.entries(FULFILLMENT_PROVIDER_STATUS_LABELS).map(([value, label]) => ({ value, label, })); const SIGN_OPTIONS = [ { value: 'MD5', label: 'MD5' }, { value: 'HMAC-SHA256', label: 'HMAC-SHA256' }, ]; const DEFAULT_XFX_API_URL = 'https://beta.51xiaoju.cn/app/api/interface.do'; export default function FulfillmentProvidersPage() { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(false); const [open, setOpen] = useState(false); const [editRow, setEditRow] = useState(null); const [form] = Form.useForm(); const watchedCode = Form.useWatch('code', form); const watchedType = Form.useWatch('type', form); const showXfxFields = useMemo( () => isXfxProviderCode(String(watchedCode || '')) && watchedType === FulfillmentProviderType.API, [watchedCode, watchedType], ); const load = useCallback(async () => { setLoading(true); try { const res = await request('/admin/fulfillment-providers'); setRows(res); } finally { setLoading(false); } }, []); useEffect(() => { void load(); }, [load]); function openCreate() { setEditRow(null); form.resetFields(); form.setFieldsValue({ code: 'XFX', name: '小飞侠', type: FulfillmentProviderType.API, status: FulfillmentProviderStatus.ACTIVE, apiUrl: DEFAULT_XFX_API_URL, signType: 'MD5', }); setOpen(true); } function openEdit(row: FulfillmentProviderDto) { setEditRow(row); const xfx = row.xiaofeixiaConfig; form.setFieldsValue({ code: row.code, name: row.name, type: row.type, status: row.status, apiUrl: xfx?.apiUrl || DEFAULT_XFX_API_URL, mchId: xfx?.mchId || '', apiKey: '', signType: xfx?.signType || 'MD5', appId: xfx?.appId || '', }); setOpen(true); } async function submit() { const v = await form.validateFields(); const payload: Record = { name: v.name, type: v.type, status: v.status, }; if (isXfxProviderCode(String(v.code)) && v.type === FulfillmentProviderType.API) { payload.xiaofeixiaConfig = { apiUrl: v.apiUrl, mchId: v.mchId, apiKey: v.apiKey || undefined, signType: v.signType, appId: v.appId || undefined, }; } if (editRow) { await request(`/admin/fulfillment-providers/${editRow.id}`, { method: 'PUT', body: JSON.stringify(payload), }); message.success('已更新'); } else { await request('/admin/fulfillment-providers', { method: 'POST', body: JSON.stringify({ code: v.code, ...payload, }), }); message.success('已创建'); } setOpen(false); void load(); } const columns: ColumnsType = [ { title: '编码', dataIndex: 'code', width: 100 }, { title: '名称', dataIndex: 'name' }, { title: '类型', dataIndex: 'type', render: (v) => FULFILLMENT_PROVIDER_TYPE_LABELS[v as FulfillmentProviderType] || v, }, { title: '状态', dataIndex: 'status', render: (v) => ( {FULFILLMENT_PROVIDER_STATUS_LABELS[v as FulfillmentProviderStatus] || v} ), }, { title: '接口配置', render: (_, row) => { if (isXfxProviderCode(row.code)) { const cfg = row.xiaofeixiaConfig; if (!cfg?.apiUrl) return 未配置; return ( {cfg.hasApiKey ? '已配置' : '缺 Key'} · {cfg.mchId || '无商户号'} ); } return row.hasConfig ? '已配置' : '—'; }, }, { title: '更新时间', dataIndex: 'updatedAt', width: 170, render: fmtTime }, { title: '操作', width: 80, render: (_, row) => ( ), }, ]; return (
仓配管理 注册第三方履约接口并填写凭证;仓库绑定后,推单将使用此处配置的 API 地址与密钥
setOpen(false)} onOk={() => void submit()} width={560} destroyOnClose >
{showXfxFields && ( <> 小飞侠接口参数 )}
); }