import { Alert, Empty, Modal, Segmented, Space, Tag, TreeSelect, Typography } from 'antd' import { useEffect, useMemo, useState } from 'react' import type { AdminConsignmentProfilesResponse, ConsignmentProfile, ConsignmentProfileConfig, } from '@/types/consignment' import { buildConsignmentCategoryGroups } from '@/utils/consignment-category-tree' const { Text } = Typography export type ConsignmentTemplateCopyMode = 'specs' | 'intake' export type ConsignmentTemplateCopyVersion = 'published' | 'draft' export type ConsignmentTemplateCopySelection = { profile: ConsignmentProfile config: ConsignmentProfileConfig version: ConsignmentTemplateCopyVersion } type Category = AdminConsignmentProfilesResponse['categories'][number] type TemplateOption = { value: number label: string disabled: boolean profile: ConsignmentProfile config: ConsignmentProfileConfig | null fields: Array<{ label: string; detail: string }> } type TemplateTreeNode = { key: string value: number title: string selectable: boolean disabled?: boolean kind: 'group' | 'root' | 'child' option?: TemplateOption groupCount?: number children?: TemplateTreeNode[] } function resolveCategoryPath( profile: ConsignmentProfile, categoryNames: Map, ): string { const ownName = profile.categoryName || categoryNames.get(profile.categoryId) || profile.name || '未命名分类' if (!profile.categoryParentId) return ownName const parentName = profile.categoryParentName || categoryNames.get(profile.categoryParentId) || '上级分类' return `${parentName} / ${ownName}` } function resolveTemplateConfig( profile: ConsignmentProfile, version: ConsignmentTemplateCopyVersion, ): ConsignmentProfileConfig | null { return version === 'published' ? (profile.publishedConfig ?? null) : profile.config } function resolveTemplateFields( config: ConsignmentProfileConfig, mode: ConsignmentTemplateCopyMode, ): Array<{ label: string; detail: string }> { if (mode === 'specs') { return config.specs.map((spec) => ({ label: spec.label || spec.key, detail: spec.type === 'select' || spec.type === 'radio' ? `${spec.type} · ${spec.options.filter((option) => option.enabled !== false).length} 个选项` : spec.type, })) } return (config.intake?.fields ?? []).map((field) => ({ label: field.label || field.key, detail: `${field.type}${field.required ? ' · 必填' : ''}`, })) } /** 按一级分类聚合排序:一级在前,其二级紧随其后,孤立二级排在最后。 */ function orderProfilesByCategory(profiles: ConsignmentProfile[]): ConsignmentProfile[] { const roots: ConsignmentProfile[] = [] const childrenByParent = new Map() for (const profile of profiles) { if (profile.categoryParentId) { const list = childrenByParent.get(profile.categoryParentId) || [] list.push(profile) childrenByParent.set(profile.categoryParentId, list) } else { roots.push(profile) } } const ordered: ConsignmentProfile[] = [] const included = new Set() for (const root of roots) { ordered.push(root) included.add(root.profileId) for (const child of childrenByParent.get(root.categoryId) || []) { if (included.has(child.profileId)) continue ordered.push(child) included.add(child.profileId) } } // 只有二级配置、没有一级配置的历史数据:仍需可选,排到最后。 for (const profile of profiles) { if (included.has(profile.profileId)) continue ordered.push(profile) included.add(profile.profileId) } return ordered } export default function ConsignmentTemplateCopyModal({ open, mode, currentCategoryId, categories, profiles, onCancel, onApply, }: { open: boolean mode: ConsignmentTemplateCopyMode currentCategoryId: number | null categories: Category[] profiles: ConsignmentProfile[] onCancel: () => void onApply: (selection: ConsignmentTemplateCopySelection) => void }) { const [version, setVersion] = useState('published') const [selectedProfileId, setSelectedProfileId] = useState(null) const [treeExpandedKeys, setTreeExpandedKeys] = useState>([]) const categoryNames = useMemo( () => new Map(categories.map((category) => [category.categoryId, category.name])), [categories], ) const selectableProfiles = useMemo( () => orderProfilesByCategory( profiles.filter((profile) => profile.categoryId !== currentCategoryId), ), [profiles, currentCategoryId], ) const hasPublished = selectableProfiles.some((profile) => Boolean(profile.publishedConfig)) const options = useMemo( () => selectableProfiles.map((profile) => { const config = resolveTemplateConfig(profile, version) const fields = config ? resolveTemplateFields(config, mode) : [] const versionLabel = version === 'published' ? profile.currentVersionNo ? `v${profile.currentVersionNo}` : '未发布' : '草稿' const statusSuffix = profile.status === 'archived' ? ' · 已归档' : '' const ownName = profile.categoryName || profile.name || '未命名分类' return { value: profile.profileId, label: `${ownName}(${versionLabel}${statusSuffix})`, disabled: fields.length === 0, profile, config, fields, } }), [selectableProfiles, version, mode], ) const treeData = useMemo(() => { const groups = buildConsignmentCategoryGroups({ items: options, getCategoryId: (option) => option.profile.categoryId, getParentId: (option) => option.profile.categoryParentId, getParentName: (option) => option.profile.categoryParentName, getFallbackName: (option) => resolveCategoryPath(option.profile, categoryNames), }) return groups.flatMap((group) => { const root = group.root const rootLabel = root?.label || group.groupName || '未命名分类' const childNodes: TemplateTreeNode[] = group.children.map((option) => ({ key: `child-${option.value}`, value: option.value, title: option.label, selectable: true, disabled: option.disabled, kind: 'child', option, })) if (root && childNodes.length === 0) { return [ { key: `root-${root.value}`, value: root.value, title: root.label, selectable: true, disabled: root.disabled, kind: 'root', option: root, }, ] } if (!root) { if (childNodes.length === 0) return [] const groupNode: TemplateTreeNode = { key: `group-${group.groupCategoryId}`, value: -group.groupCategoryId, title: rootLabel, selectable: false, kind: 'group', groupCount: childNodes.length, children: childNodes, } return [groupNode] } return [ { key: `group-${group.groupCategoryId}`, value: root.value, title: root.label, selectable: !root.disabled, kind: 'group', option: root, groupCount: childNodes.length, children: childNodes, }, ] }) }, [options, categoryNames]) const treeGroupKeys = useMemo( () => treeData.filter((node) => node.children?.length).map((node) => node.key), [treeData], ) useEffect(() => { if (!open) return setVersion(hasPublished ? 'published' : 'draft') setSelectedProfileId(null) }, [open, hasPublished]) useEffect(() => { if (!open) return setTreeExpandedKeys(treeGroupKeys) }, [open, version, mode, treeGroupKeys]) useEffect(() => { if (!open) return if ( selectedProfileId !== null && options.some((option) => option.value === selectedProfileId && !option.disabled) ) { return } setSelectedProfileId(options.find((option) => !option.disabled)?.value ?? null) }, [open, options, selectedProfileId]) const selectedOption = options.find((option) => option.value === selectedProfileId) || null const selectedFields = selectedOption?.fields || [] return ( { if (!selectedOption?.config) return onApply({ profile: selectedOption.profile, config: selectedOption.config, version, }) }} onCancel={onCancel} > 来源版本 setVersion(value as ConsignmentTemplateCopyVersion)} /> style={{ width: '100%' }} placeholder={ options.some((option) => !option.disabled) ? '请选择要复制的寄卖配置' : '没有可复制的寄卖配置' } value={selectedProfileId ?? undefined} treeData={treeData} treeDefaultExpandAll treeExpandedKeys={treeExpandedKeys} onTreeExpand={(keys) => setTreeExpandedKeys(keys as Array)} showSearch={{ treeNodeFilterProp: 'title', onSearch: () => setTreeExpandedKeys(treeGroupKeys), }} onOpenChange={(open) => { if (!open) setTreeExpandedKeys(treeGroupKeys) }} popupMatchSelectWidth={false} listHeight={320} treeTitleRender={(node) => { const item = node as TemplateTreeNode if (item.kind === 'group') { return ( {item.title} {item.option ? 一级 : null} ) } if (!item.option) return item.title return ( {item.option.label} {item.kind === 'root' ? 一级 : 二级} {item.option.fields.length === 0 ? 无字段 : null} ) }} onChange={(value) => { if (typeof value === 'number' && value > 0) setSelectedProfileId(value) }} /> {version === 'draft' && selectedOption?.profile.status === 'published' ? ( ) : null} {mode === 'specs' && selectedOption?.config?.specSource === 'template' ? ( ) : null} {selectedOption && selectedFields.length > 0 ? (
将复制 {selectedFields.length} 个{mode === 'specs' ? '规格字段' : '联系方式字段'}
{selectedFields.map((field) => ( {field.label} · {field.detail} ))}
{selectedOption.config ? ( 复制只替换当前区块,不影响价格、保证金、服务费、匹配和超时设置;应用后请点「保存草稿」。 ) : null}
) : ( 0 ? '该版本没有可复制的字段,请换一个配置或切换版本' : version === 'published' ? '没有可用的已发布配置,请切换到「当前草稿」' : '没有可复制的字段' } /> )}
) }