feat(order-service): 优化资料字段预设与游戏ID校验
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
Card,
|
||||
Col,
|
||||
Collapse,
|
||||
Dropdown,
|
||||
Empty,
|
||||
Form,
|
||||
Input,
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
Switch,
|
||||
Tag,
|
||||
Typography,
|
||||
type MenuProps,
|
||||
} from 'antd'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
@@ -99,6 +101,113 @@ const DEFAULT_MATERIAL_CONFIRMATION: OrderServiceMaterialConfirmation = {
|
||||
cancelText: '返回检查',
|
||||
}
|
||||
|
||||
type FieldPreset = Omit<OrderServiceField, 'sortOrder'>
|
||||
|
||||
const FIELD_PRESETS: Record<string, FieldPreset> = {
|
||||
gameId: {
|
||||
key: 'gameId',
|
||||
label: '游戏 ID',
|
||||
type: 'text',
|
||||
validation: 'digits',
|
||||
required: true,
|
||||
placeholder: '请输入纯数字游戏 ID',
|
||||
helpText: '仅支持数字,不会丢失前导零或超长 ID。',
|
||||
options: [],
|
||||
},
|
||||
gameNickname: {
|
||||
key: 'gameNickname',
|
||||
label: '游戏昵称',
|
||||
type: 'text',
|
||||
validation: 'none',
|
||||
required: true,
|
||||
placeholder: '请输入游戏昵称',
|
||||
helpText: '用于交付时核对角色,允许中文、字母和符号。',
|
||||
options: [],
|
||||
},
|
||||
serverZone: {
|
||||
key: 'serverZone',
|
||||
label: '区服',
|
||||
type: 'text',
|
||||
validation: 'none',
|
||||
required: true,
|
||||
placeholder: '例如:微信区 / QQ区 / 具体区服',
|
||||
helpText: '区服不固定的游戏可改为下拉,并填写对应选项。',
|
||||
options: [],
|
||||
},
|
||||
system: {
|
||||
key: 'system',
|
||||
label: '系统',
|
||||
type: 'select',
|
||||
validation: 'none',
|
||||
required: false,
|
||||
placeholder: '',
|
||||
helpText: '按需收集客户使用的系统。',
|
||||
options: ['安卓', '苹果'],
|
||||
},
|
||||
gameAccount: {
|
||||
key: 'gameAccount',
|
||||
label: '游戏登录账号',
|
||||
type: 'text',
|
||||
validation: 'none',
|
||||
required: false,
|
||||
placeholder: '请输入游戏登录账号',
|
||||
helpText: '只有确实需要登录账号时添加,不要收集密码。',
|
||||
options: [],
|
||||
},
|
||||
roleLevel: {
|
||||
key: 'roleLevel',
|
||||
label: '角色等级',
|
||||
type: 'number',
|
||||
validation: 'none',
|
||||
required: false,
|
||||
placeholder: '请输入角色等级',
|
||||
helpText: '选填,用于交付前核对。',
|
||||
options: [],
|
||||
},
|
||||
contactPhone: {
|
||||
key: 'contactPhone',
|
||||
label: '联系电话',
|
||||
type: 'text',
|
||||
validation: 'none',
|
||||
required: false,
|
||||
placeholder: '请输入联系电话',
|
||||
helpText: '仅在业务需要联系客户时添加。',
|
||||
options: [],
|
||||
},
|
||||
orderNote: {
|
||||
key: 'orderNote',
|
||||
label: '订单备注',
|
||||
type: 'textarea',
|
||||
validation: 'none',
|
||||
required: false,
|
||||
placeholder: '请输入补充说明',
|
||||
helpText: '用于收集无法归入标准字段的补充信息。',
|
||||
options: [],
|
||||
},
|
||||
}
|
||||
|
||||
const GAME_BASIC_PRESET_KEYS = ['gameId', 'gameNickname', 'serverZone']
|
||||
const STANDARD_FIELD_KEYS = new Set(Object.keys(FIELD_PRESETS))
|
||||
const FIELD_PRESET_MENU_ITEMS: MenuProps['items'] = [
|
||||
{ key: 'game-basic', label: '游戏基础三件套(ID / 昵称 / 区服)' },
|
||||
{ type: 'divider' },
|
||||
{ key: 'gameId', label: '游戏 ID(默认纯数字)' },
|
||||
{ key: 'gameNickname', label: '游戏昵称' },
|
||||
{ key: 'serverZone', label: '区服' },
|
||||
{ key: 'system', label: '系统(可选)' },
|
||||
{ key: 'gameAccount', label: '游戏登录账号(可选)' },
|
||||
{ key: 'roleLevel', label: '角色等级(可选)' },
|
||||
{ key: 'contactPhone', label: '联系电话(可选)' },
|
||||
{ key: 'orderNote', label: '订单备注(可选)' },
|
||||
]
|
||||
|
||||
function buildPresetFields(keys: string[]) {
|
||||
return keys.flatMap((key) => {
|
||||
const preset = FIELD_PRESETS[key]
|
||||
return preset ? [{ ...preset, options: [...(preset.options || [])] }] : []
|
||||
})
|
||||
}
|
||||
|
||||
export default function OrderServicePanel() {
|
||||
const { message } = App.useApp()
|
||||
const queryClient = useQueryClient()
|
||||
@@ -175,7 +284,11 @@ export default function OrderServicePanel() {
|
||||
autoPublish: nextProfile?.autoPublish || DEFAULT_AUTO_PUBLISH,
|
||||
materialConfirmation: nextProfile?.materialConfirmation || DEFAULT_MATERIAL_CONFIRMATION,
|
||||
})
|
||||
setFields(normalizeFieldOrder(nextProfile?.fields || []))
|
||||
setFields(
|
||||
normalizeFieldOrder(
|
||||
nextProfile ? nextProfile.fields : buildPresetFields(GAME_BASIC_PRESET_KEYS),
|
||||
),
|
||||
)
|
||||
setGuideImage(nextProfile?.guideImage || null)
|
||||
}
|
||||
|
||||
@@ -187,15 +300,27 @@ export default function OrderServicePanel() {
|
||||
])
|
||||
}
|
||||
|
||||
function addField() {
|
||||
function addFieldPreset(presetId: string) {
|
||||
const presetKeys = presetId === 'game-basic' ? GAME_BASIC_PRESET_KEYS : [presetId]
|
||||
const existingKeys = new Set(fields.map((field) => field.key))
|
||||
const missingKeys = presetKeys.filter((key) => FIELD_PRESETS[key] && !existingKeys.has(key))
|
||||
if (missingKeys.length === 0) {
|
||||
message.info('所选字段已存在')
|
||||
return
|
||||
}
|
||||
setFields((current) => normalizeFieldOrder([...current, ...buildPresetFields(missingKeys)]))
|
||||
}
|
||||
|
||||
function addCustomField() {
|
||||
setFields((current) =>
|
||||
normalizeFieldOrder([
|
||||
...current,
|
||||
{
|
||||
key: `field${Date.now().toString(36)}`,
|
||||
label: '新字段',
|
||||
key: '',
|
||||
label: '',
|
||||
type: 'text',
|
||||
required: true,
|
||||
validation: 'none',
|
||||
required: false,
|
||||
placeholder: '',
|
||||
helpText: '',
|
||||
options: [],
|
||||
@@ -207,7 +332,14 @@ export default function OrderServicePanel() {
|
||||
|
||||
function updateField(index: number, patch: Partial<OrderServiceField>) {
|
||||
setFields((current) =>
|
||||
current.map((row, rowIndex) => (rowIndex === index ? { ...row, ...patch } : row)),
|
||||
current.map((row, rowIndex) => {
|
||||
if (rowIndex !== index) return row
|
||||
const next = { ...row, ...patch }
|
||||
if (patch.key !== undefined) {
|
||||
next.validation = patch.key === 'gameId' ? 'digits' : next.validation
|
||||
}
|
||||
return next
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -235,6 +367,18 @@ export default function OrderServicePanel() {
|
||||
message.error(`字段 Key 不能重复:${duplicateKeys.join('、')}`)
|
||||
return
|
||||
}
|
||||
const invalidKey = normalizedFields.find(
|
||||
(field) => !/^[a-zA-Z][a-zA-Z0-9_]{0,59}$/.test(field.key),
|
||||
)
|
||||
if (invalidKey) {
|
||||
message.error('字段 Key 必须以英文字母开头,只能包含字母、数字和下划线')
|
||||
return
|
||||
}
|
||||
const emptyLabel = normalizedFields.find((field) => !String(field.label || '').trim())
|
||||
if (emptyLabel) {
|
||||
message.error('请填写所有字段的显示名称')
|
||||
return
|
||||
}
|
||||
setSaving(true)
|
||||
try {
|
||||
await saveAdminOrderServiceProfile({
|
||||
@@ -577,17 +721,32 @@ export default function OrderServicePanel() {
|
||||
<div className="order-service-section-head">
|
||||
<div>
|
||||
<Text strong>资料字段</Text>
|
||||
<Text type="secondary">客户必须填写或选填的资料,可调整顺序。</Text>
|
||||
<Text type="secondary">
|
||||
优先添加常用字段;标准字段会自动支持搜索、复制和资料匹配。
|
||||
</Text>
|
||||
</div>
|
||||
<Button icon={<PlusOutlined />} onClick={addField} type="dashed">
|
||||
添加字段
|
||||
</Button>
|
||||
<Space wrap>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: FIELD_PRESET_MENU_ITEMS,
|
||||
onClick: ({ key }) => addFieldPreset(String(key)),
|
||||
}}
|
||||
trigger={['click']}
|
||||
>
|
||||
<Button icon={<PlusOutlined />} type="dashed">
|
||||
添加常用字段
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Button icon={<PlusOutlined />} onClick={addCustomField}>
|
||||
自定义字段
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{fields.length === 0 ? (
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description="至少添加一个资料字段后才能发布"
|
||||
description="至少添加一个资料字段后才能发布,推荐先添加游戏基础三件套"
|
||||
/>
|
||||
) : (
|
||||
<div className="order-service-field-list">
|
||||
@@ -597,7 +756,17 @@ export default function OrderServicePanel() {
|
||||
<label>
|
||||
<Text type="secondary">字段 Key</Text>
|
||||
<Input
|
||||
addonAfter={
|
||||
field.validation === 'digits'
|
||||
? '纯数字'
|
||||
: STANDARD_FIELD_KEYS.has(field.key)
|
||||
? '标准'
|
||||
: undefined
|
||||
}
|
||||
disabled={STANDARD_FIELD_KEYS.has(field.key)}
|
||||
value={field.key}
|
||||
maxLength={60}
|
||||
placeholder="例如:accountName"
|
||||
onChange={(event) =>
|
||||
updateField(index, { key: event.target.value })
|
||||
}
|
||||
@@ -673,6 +842,11 @@ export default function OrderServicePanel() {
|
||||
updateField(index, { helpText: event.target.value })
|
||||
}
|
||||
/>
|
||||
{!STANDARD_FIELD_KEYS.has(field.key) ? (
|
||||
<Text type="secondary" className="order-service-field-extra-tip">
|
||||
自定义 Key 仅用于展示;需要搜索、复制或匹配能力请使用标准字段。
|
||||
</Text>
|
||||
) : null}
|
||||
{field.type === 'select' || field.type === 'radio' ? (
|
||||
<Input
|
||||
addonBefore="选项"
|
||||
|
||||
Reference in New Issue
Block a user