618 lines
23 KiB
TypeScript
618 lines
23 KiB
TypeScript
/**
|
|
* 人工 SKU 配置 Tab:扫描 → 配置 → 重新匹配。
|
|
*
|
|
* 口径(2026-10-24 确认):
|
|
* * 配置主键是**内部 SKU**(快手 skuNick / 拼多多 outSkuSn),商品名称仅供识别;
|
|
* * 同名 SKU 算同一个商品,跨渠道共用一份配置;
|
|
* * 客服人工有四个钱包:南航积分 / 乐玩积分 / ROCK 积分 / 备用金现金;
|
|
* * 积分换算率按平台不同(南航、乐玩 1 积分 = 1 分;ROCK 1 积分 = 1 元);
|
|
* * 消耗积分填 0 = 成本不固定,客服确认履约时手填实际成本。
|
|
*
|
|
* 这里没有匹配引擎、没有打分:就是一张带统计的 SKU 配置清单。
|
|
*/
|
|
import {
|
|
DeleteOutlined,
|
|
EditOutlined,
|
|
MinusCircleOutlined,
|
|
PlusOutlined,
|
|
ReloadOutlined,
|
|
ScanOutlined,
|
|
SyncOutlined,
|
|
} from '@ant-design/icons'
|
|
import {
|
|
Alert,
|
|
App,
|
|
Button,
|
|
Card,
|
|
Col,
|
|
DatePicker,
|
|
Form,
|
|
Input,
|
|
InputNumber,
|
|
Modal,
|
|
Popconfirm,
|
|
Row,
|
|
Select,
|
|
Space,
|
|
Statistic,
|
|
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'
|
|
|
|
import {
|
|
deleteAdminManualSku,
|
|
fetchAdminManualPlatforms,
|
|
fetchAdminManualSkuOverview,
|
|
fetchAdminManualSkus,
|
|
rematchAdminManualFulfillments,
|
|
saveAdminManualConfig,
|
|
scanAdminManualSkus,
|
|
} from '@/services/admin'
|
|
import type { ManualPlatformKey, ManualSkuItem } from '@/types/admin'
|
|
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
|
|
import {
|
|
formatCreditQuantity,
|
|
formatCreditUnitPriceLabel,
|
|
formatCreditUnitPriceYuan,
|
|
} from '@/utils/credit-unit-price'
|
|
import dayjs from '@/lib/dayjs'
|
|
import type { Dayjs } from 'dayjs'
|
|
|
|
type ConfigFormValues = {
|
|
channels: Array<{
|
|
platformKey: string
|
|
creditQuantity: number
|
|
}>
|
|
note?: string
|
|
}
|
|
|
|
const PLATFORM_TAG_COLORS: Record<string, string> = {
|
|
nanhang: 'blue',
|
|
lewan: 'purple',
|
|
rock: 'cyan',
|
|
fund_cash: 'orange',
|
|
on_account: 'gold',
|
|
}
|
|
|
|
function isCashChannel(platformKey: string) {
|
|
return platformKey === 'fund_cash' || platformKey === 'on_account'
|
|
}
|
|
|
|
function channelQuantityText(platformKey: string, creditQuantity: number) {
|
|
const quantity = formatCreditQuantity(creditQuantity)
|
|
return isCashChannel(platformKey) ? `¥${quantity}` : `${quantity} 积分`
|
|
}
|
|
|
|
function platformOptionLabel(platformKey: string, label: string, creditUnitPriceFen: number) {
|
|
if (isCashChannel(platformKey)) return `${label} · 按元填写`
|
|
return `${label} · ${formatCreditUnitPriceLabel(creditUnitPriceFen)}`
|
|
}
|
|
|
|
export function ManualSkuConfigPanel() {
|
|
const { message } = App.useApp()
|
|
const queryClient = useQueryClient()
|
|
const [form] = Form.useForm<ConfigFormValues>()
|
|
|
|
const [keyword, setKeyword] = useState('')
|
|
const [status, setStatus] = useState<'all' | 'configured' | 'unconfigured'>('unconfigured')
|
|
const [platformKey, setPlatformKey] = useState('')
|
|
const [page, setPage] = useState(1)
|
|
const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
|
|
const [scanRange, setScanRange] = useState<[Dayjs, Dayjs]>([
|
|
dayjs().startOf('month'),
|
|
dayjs().endOf('month'),
|
|
])
|
|
const [modalOpen, setModalOpen] = useState(false)
|
|
const [editing, setEditing] = useState<ManualSkuItem | null>(null)
|
|
|
|
const listQuery = useQuery({
|
|
queryKey: ['admin-cost-center-manual-skus', keyword, status, platformKey, page, pageSize],
|
|
queryFn: () =>
|
|
fetchAdminManualSkus({
|
|
keyword: keyword || undefined,
|
|
status: status === 'all' ? '' : status,
|
|
platformKey: platformKey || undefined,
|
|
page,
|
|
pageSize,
|
|
}),
|
|
})
|
|
const overviewQuery = useQuery({
|
|
queryKey: ['admin-cost-center-manual-skus-overview'],
|
|
queryFn: fetchAdminManualSkuOverview,
|
|
})
|
|
const platformQuery = useQuery({
|
|
queryKey: ['admin-cost-center-manual-platforms'],
|
|
queryFn: fetchAdminManualPlatforms,
|
|
})
|
|
|
|
const overview = overviewQuery.data?.data
|
|
const platforms = platformQuery.data?.data.items || []
|
|
const platformMap = new Map(platforms.map((item) => [item.key, item]))
|
|
|
|
const scanMutation = useMutation({
|
|
mutationFn: () =>
|
|
scanAdminManualSkus({
|
|
fromDate: scanRange[0].format('YYYY-MM-DD'),
|
|
toDate: scanRange[1].format('YYYY-MM-DD'),
|
|
}),
|
|
onSuccess: (response) => {
|
|
const result = response.data
|
|
message.success(`扫描完成,登记 ${result.registered} 个内部 SKU`)
|
|
void queryClient.invalidateQueries({ queryKey: ['admin-cost-center-manual-skus'] })
|
|
void queryClient.invalidateQueries({
|
|
queryKey: ['admin-cost-center-manual-skus-overview'],
|
|
})
|
|
},
|
|
onError: (error) => {
|
|
message.error(error instanceof Error ? error.message : '扫描失败')
|
|
},
|
|
})
|
|
|
|
const saveMutation = useMutation({
|
|
mutationFn: (values: ConfigFormValues) =>
|
|
saveAdminManualConfig({
|
|
skuKey: editing?.skuKey || '',
|
|
channels: values.channels.map((channel) => ({
|
|
platformKey: channel.platformKey,
|
|
creditQuantity: Number(channel.creditQuantity || 0),
|
|
})),
|
|
note: values.note || '',
|
|
}),
|
|
onSuccess: (response) => {
|
|
const summary = response.data.syncSummary
|
|
const syncHint = summary
|
|
? `;自动关联标准商品并重试 ${summary.pendingAssigned} 个待处理订单项`
|
|
: ''
|
|
message.success(`配置已保存${syncHint}`)
|
|
setModalOpen(false)
|
|
void queryClient.invalidateQueries({ queryKey: ['admin-cost-center'] })
|
|
void queryClient.invalidateQueries({ queryKey: ['cost-center-voucher-products'] })
|
|
void queryClient.invalidateQueries({ queryKey: ['fulfillment-rules'] })
|
|
void queryClient.invalidateQueries({ queryKey: ['fulfillment-pending'] })
|
|
},
|
|
onError: (error) => {
|
|
message.error(error instanceof Error ? error.message : '保存失败')
|
|
},
|
|
})
|
|
|
|
const deleteMutation = useMutation({
|
|
mutationFn: (skuKey: string) => deleteAdminManualSku(skuKey),
|
|
onSuccess: (response) => {
|
|
message.success(
|
|
`人工分类已移除;${response.data.affectedItems} 个订单项已退回待分类,原始订单保留`,
|
|
)
|
|
void queryClient.invalidateQueries({ queryKey: ['admin-cost-center-manual-skus'] })
|
|
void queryClient.invalidateQueries({
|
|
queryKey: ['admin-cost-center-manual-skus-overview'],
|
|
})
|
|
void queryClient.invalidateQueries({ queryKey: ['fulfillment-rules'] })
|
|
void queryClient.invalidateQueries({ queryKey: ['fulfillment-pending'] })
|
|
},
|
|
onError: (error) => {
|
|
message.error(error instanceof Error ? error.message : '删除失败')
|
|
},
|
|
})
|
|
|
|
const rematchMutation = useMutation({
|
|
mutationFn: () => rematchAdminManualFulfillments(),
|
|
onSuccess: (response) => {
|
|
message.success(`重新匹配完成,回填 ${response.data.updatedCount} 个订单项`)
|
|
void queryClient.invalidateQueries({ queryKey: ['admin-cost-center-manual-skus'] })
|
|
},
|
|
onError: (error) => {
|
|
message.error(error instanceof Error ? error.message : '重新匹配失败')
|
|
},
|
|
})
|
|
|
|
function openConfig(item: ManualSkuItem) {
|
|
setEditing(item)
|
|
const configuredChannels = item.config?.channels?.length
|
|
? item.config.channels
|
|
: item.config
|
|
? [
|
|
{
|
|
platformKey: item.config.platformKey,
|
|
creditQuantity: item.config.creditQuantity,
|
|
},
|
|
]
|
|
: [{ platformKey: '', creditQuantity: 0 }]
|
|
form.setFieldsValue({
|
|
channels: configuredChannels.map((channel) => ({
|
|
platformKey: channel.platformKey,
|
|
creditQuantity: channel.creditQuantity,
|
|
})),
|
|
note: item.config?.note || '',
|
|
})
|
|
setModalOpen(true)
|
|
}
|
|
|
|
const columns: TableColumnsType<ManualSkuItem> = [
|
|
{
|
|
title: '内部 SKU(配置主键)',
|
|
dataIndex: 'skuKey',
|
|
width: 280,
|
|
render: (_, row) => (
|
|
<div className="cell-stack">
|
|
<Typography.Text strong copyable={{ text: row.skuKey }}>
|
|
{row.skuKey}
|
|
</Typography.Text>
|
|
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
|
{row.displayName || '(无名称)'}
|
|
</Typography.Text>
|
|
</div>
|
|
),
|
|
},
|
|
{ title: '店铺', dataIndex: 'shopCount', width: 70, align: 'right' },
|
|
{ title: '订单数', dataIndex: 'orderCount', width: 90, align: 'right' },
|
|
{ title: '订单项', dataIndex: 'itemCount', width: 90, align: 'right' },
|
|
{
|
|
title: '收入',
|
|
dataIndex: 'revenueFen',
|
|
width: 110,
|
|
align: 'right',
|
|
render: (value: number) => `¥${(Number(value || 0) / 100).toFixed(0)}`,
|
|
},
|
|
{
|
|
title: '履约渠道',
|
|
key: 'platform',
|
|
width: 150,
|
|
render: (_, row) => {
|
|
if (!row.config) {
|
|
return <Typography.Text type="secondary">未配置</Typography.Text>
|
|
}
|
|
const channels = row.config.channels?.length
|
|
? row.config.channels
|
|
: [
|
|
{
|
|
platformKey: row.config.platformKey,
|
|
platformLabel: row.config.platformLabel,
|
|
creditQuantity: row.config.creditQuantity,
|
|
creditUnitPriceFen: row.config.creditUnitPriceFen,
|
|
costFen: row.config.costFen,
|
|
},
|
|
]
|
|
return (
|
|
<Space size={4} direction="vertical">
|
|
{channels.map((channel) => (
|
|
<div key={`${row.id}-${channel.platformKey}`}>
|
|
<Tag color={PLATFORM_TAG_COLORS[channel.platformKey] || 'default'}>
|
|
{channel.platformLabel}
|
|
</Tag>
|
|
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
|
{channelQuantityText(channel.platformKey, channel.creditQuantity)}
|
|
{' · '}¥{((channel.costFen || 0) / 100).toFixed(2)}
|
|
</Typography.Text>
|
|
</div>
|
|
))}
|
|
</Space>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
title: '成本',
|
|
key: 'cost',
|
|
width: 110,
|
|
align: 'right',
|
|
render: (_, row) =>
|
|
row.config ? (
|
|
(row.config.channels?.length || 0) > 1 ? (
|
|
<Tag color="blue">按实际渠道</Tag>
|
|
) : row.config.costMode === 'fixed' ? (
|
|
<Typography.Text>¥{(row.config.costFen / 100).toFixed(2)}</Typography.Text>
|
|
) : (
|
|
<Tag color="orange">不固定·客服填</Tag>
|
|
)
|
|
) : (
|
|
'-'
|
|
),
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'actions',
|
|
width: 150,
|
|
fixed: 'right',
|
|
render: (_, row) => (
|
|
<Space size={4}>
|
|
<Button size="small" icon={<EditOutlined />} onClick={() => openConfig(row)}>
|
|
{row.configured ? '编辑' : '配置'}
|
|
</Button>
|
|
<Popconfirm
|
|
title="移除该人工分类?"
|
|
description="会删除人工登记与配置、撤销人工渠道规则、清除已生成的人工成本快照,并把关联订单项退回待分类;原始订单不会删除。"
|
|
okText="确认移除"
|
|
cancelText="取消"
|
|
onConfirm={() => deleteMutation.mutate(row.skuKey)}
|
|
>
|
|
<Button size="small" danger icon={<DeleteOutlined />}>
|
|
移除
|
|
</Button>
|
|
</Popconfirm>
|
|
</Space>
|
|
),
|
|
},
|
|
]
|
|
|
|
function previewChannelCost(platformKey: string, creditQuantity: number) {
|
|
const unitPrice = platformMap.get(platformKey as ManualPlatformKey)?.creditUnitPriceFen ?? 0
|
|
return Number(creditQuantity) > 0 ? Math.round(Number(creditQuantity) * unitPrice) : 0
|
|
}
|
|
|
|
return (
|
|
<Card bordered={false} title="人工 SKU 配置">
|
|
<Alert
|
|
type="info"
|
|
showIcon
|
|
style={{ marginBottom: 14 }}
|
|
message="人工履约只需在这里配置一次"
|
|
description="保存时会自动创建或关联 man: 标准商品、同步统一渠道规则、回填最近 30 天订单,并重试待处理的人工渠道订单。消耗积分 × 平台换算率就是这单的人工成本,不需要再去标准商品或渠道规则页重复配置;消耗积分填 0 表示成本不固定,由客服在确认履约时手填。"
|
|
/>
|
|
|
|
<Row gutter={12} style={{ marginBottom: 12 }}>
|
|
<Col span={6}>
|
|
<Statistic title="内部 SKU 总数" value={overview?.totalSku ?? 0} />
|
|
</Col>
|
|
<Col span={6}>
|
|
<Statistic
|
|
title="已配置"
|
|
value={overview?.configuredSku ?? 0}
|
|
valueStyle={{ color: '#389e0d' }}
|
|
/>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Statistic
|
|
title="未配置"
|
|
value={overview?.unconfiguredSku ?? 0}
|
|
valueStyle={{ color: '#cf1322' }}
|
|
/>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Statistic
|
|
title="未配置覆盖的订单项"
|
|
value={overview?.unconfiguredItems ?? 0}
|
|
suffix="单"
|
|
valueStyle={{ color: '#d46b08' }}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
|
|
<Space wrap style={{ marginBottom: 12 }}>
|
|
<DatePicker.RangePicker
|
|
value={scanRange}
|
|
onChange={(value) => {
|
|
if (value?.[0] && value?.[1]) setScanRange([value[0], value[1]])
|
|
}}
|
|
/>
|
|
<Button
|
|
type="primary"
|
|
icon={<ScanOutlined />}
|
|
loading={scanMutation.isPending}
|
|
onClick={() => scanMutation.mutate()}
|
|
>
|
|
扫描内部 SKU
|
|
</Button>
|
|
<Button
|
|
icon={<SyncOutlined />}
|
|
loading={rematchMutation.isPending}
|
|
onClick={() => rematchMutation.mutate()}
|
|
>
|
|
重新匹配已配置
|
|
</Button>
|
|
<Input.Search
|
|
placeholder="搜索内部 SKU 或名称"
|
|
allowClear
|
|
style={{ width: 220 }}
|
|
onSearch={(value) => {
|
|
setKeyword(value.trim())
|
|
setPage(1)
|
|
}}
|
|
/>
|
|
<Select
|
|
value={status}
|
|
style={{ width: 120 }}
|
|
onChange={(value) => {
|
|
setStatus(value)
|
|
setPage(1)
|
|
}}
|
|
options={[
|
|
{ value: 'unconfigured', label: '未配置' },
|
|
{ value: 'configured', label: '已配置' },
|
|
{ value: 'all', label: '全部' },
|
|
]}
|
|
/>
|
|
<Select
|
|
value={platformKey}
|
|
allowClear
|
|
placeholder="全部渠道"
|
|
style={{ width: 140 }}
|
|
onChange={(value) => {
|
|
setPlatformKey(value || '')
|
|
setPage(1)
|
|
}}
|
|
options={platforms.map((item) => ({ value: item.key, label: item.label }))}
|
|
/>
|
|
<Button icon={<ReloadOutlined />} onClick={() => void listQuery.refetch()}>
|
|
刷新
|
|
</Button>
|
|
</Space>
|
|
|
|
<PercentTable
|
|
rowKey="id"
|
|
size="small"
|
|
columns={columns}
|
|
dataSource={listQuery.data?.data.items || []}
|
|
loading={listQuery.isLoading}
|
|
scroll={{ x: 1020 }}
|
|
pagination={buildAdminTablePagination({
|
|
page,
|
|
pageSize,
|
|
total: listQuery.data?.data.pagination.total ?? 0,
|
|
onChange: (nextPage, nextPageSize) => {
|
|
setPage(nextPage)
|
|
if (nextPageSize !== pageSize) setPageSize(nextPageSize)
|
|
},
|
|
})}
|
|
/>
|
|
|
|
<Modal
|
|
open={modalOpen}
|
|
title={`配置人工履约 · ${editing?.skuKey || ''}`}
|
|
okText="保存配置"
|
|
cancelText="取消"
|
|
confirmLoading={saveMutation.isPending}
|
|
onOk={() => {
|
|
void form.validateFields().then((values) => saveMutation.mutate(values))
|
|
}}
|
|
onCancel={() => setModalOpen(false)}
|
|
destroyOnHidden
|
|
width={560}
|
|
>
|
|
<Alert
|
|
type="warning"
|
|
showIcon
|
|
style={{ marginBottom: 14 }}
|
|
message={`商品名称:${editing?.displayName || '(无)'}(仅供识别)`}
|
|
description="一个 SKU 可以配置多个渠道,客服确认履约时选择本次实际渠道。积分渠道填整数积分;备用金现金和统一结算填金额,最多两位小数。填 0 表示成本不固定,由客服确认时填写。"
|
|
/>
|
|
<Form<ConfigFormValues> form={form} layout="vertical">
|
|
<Form.List
|
|
name="channels"
|
|
rules={[
|
|
{
|
|
validator: async (_, channels) => {
|
|
if (!Array.isArray(channels) || channels.length === 0) {
|
|
throw new Error('请至少配置一个履约渠道')
|
|
}
|
|
},
|
|
},
|
|
]}
|
|
>
|
|
{(fields, { add, remove }, { errors }) => (
|
|
<Space direction="vertical" size={12} style={{ width: '100%' }}>
|
|
{fields.map((field, index) => (
|
|
<div key={field.key} className="manual-channel-config-item">
|
|
<div className="manual-channel-config-head">
|
|
<Typography.Text strong>履约渠道 {index + 1}</Typography.Text>
|
|
<Button
|
|
type="text"
|
|
danger
|
|
title="移除此渠道"
|
|
aria-label={`移除履约渠道 ${index + 1}`}
|
|
icon={<MinusCircleOutlined />}
|
|
disabled={fields.length <= 1}
|
|
onClick={() => remove(field.name)}
|
|
/>
|
|
</div>
|
|
<Form.Item
|
|
name={[field.name, 'platformKey']}
|
|
label="履约渠道"
|
|
rules={[{ required: true, message: '请选择履约渠道' }]}
|
|
>
|
|
<Select
|
|
placeholder="请选择履约渠道"
|
|
options={platforms.map((item) => ({
|
|
value: item.key,
|
|
label: platformOptionLabel(item.key, item.label, item.creditUnitPriceFen),
|
|
}))}
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item noStyle shouldUpdate>
|
|
{() => {
|
|
const platformKey = String(
|
|
form.getFieldValue(['channels', field.name, 'platformKey']) || '',
|
|
)
|
|
const creditQuantity = Number(
|
|
form.getFieldValue(['channels', field.name, 'creditQuantity']) || 0,
|
|
)
|
|
const cashChannel = isCashChannel(platformKey)
|
|
const unitPriceFen =
|
|
platformMap.get(platformKey as ManualPlatformKey)?.creditUnitPriceFen ?? 0
|
|
const costFen = previewChannelCost(platformKey, creditQuantity)
|
|
return (
|
|
<Row gutter={12}>
|
|
<Col xs={24} sm={12}>
|
|
<Form.Item
|
|
name={[field.name, 'creditQuantity']}
|
|
label={
|
|
!platformKey
|
|
? '消耗 / 金额'
|
|
: cashChannel
|
|
? '每单金额'
|
|
: '每单消耗积分'
|
|
}
|
|
rules={[
|
|
{ required: true, message: '请输入消耗或金额' },
|
|
{
|
|
validator: async (_, value) => {
|
|
const quantity = Number(value)
|
|
if (!Number.isFinite(quantity) || quantity < 0) {
|
|
throw new Error('请输入大于等于 0 的数字')
|
|
}
|
|
if (!cashChannel && !Number.isInteger(quantity)) {
|
|
throw new Error('积分必须是整数')
|
|
}
|
|
},
|
|
},
|
|
]}
|
|
extra={
|
|
platformKey
|
|
? cashChannel
|
|
? '单位:元,最多两位小数'
|
|
: `换算率:${formatCreditUnitPriceLabel(unitPriceFen)}`
|
|
: '积分填整数,现金和统一结算填金额'
|
|
}
|
|
style={{ marginBottom: 0 }}
|
|
>
|
|
<InputNumber
|
|
min={0}
|
|
precision={cashChannel ? 2 : 0}
|
|
step={cashChannel ? 0.01 : 1}
|
|
style={{ width: '100%' }}
|
|
placeholder={cashChannel ? '例如 12.50' : '例如 10'}
|
|
addonAfter={
|
|
platformKey ? (cashChannel ? '元' : '积分') : undefined
|
|
}
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col xs={24} sm={12}>
|
|
<Form.Item label="折算成本" style={{ marginBottom: 0 }}>
|
|
<div className="manual-channel-cost-preview">
|
|
<Typography.Text strong={costFen > 0}>
|
|
{costFen > 0
|
|
? `¥${(costFen / 100).toFixed(2)}`
|
|
: '成本不固定,由客服确认时填写'}
|
|
</Typography.Text>
|
|
{platformKey && !cashChannel ? (
|
|
<Typography.Text type="secondary">
|
|
1积分={formatCreditUnitPriceYuan(unitPriceFen)}
|
|
</Typography.Text>
|
|
) : null}
|
|
</div>
|
|
</Form.Item>
|
|
</Col>
|
|
</Row>
|
|
)
|
|
}}
|
|
</Form.Item>
|
|
</div>
|
|
))}
|
|
<Button type="dashed" icon={<PlusOutlined />} onClick={() => add()}>
|
|
添加履约渠道
|
|
</Button>
|
|
<Form.ErrorList errors={errors} />
|
|
</Space>
|
|
)}
|
|
</Form.List>
|
|
<Form.Item name="note" label="备注">
|
|
<Input.TextArea rows={2} maxLength={300} showCount />
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</Card>
|
|
)
|
|
}
|