feat(consignment): 寄卖配置价格/匹配/超时/收货字段全面改造

- 价格:固定价格改为模板价固定下调,移除绝对固定价;没有模板价时统一阻止发布
- 配置面板:按下拉选项动态显隐,超时与降价提示拆成两张卡片,超时单位改为小时(默认 0.5)
- 匹配:新增关闭匹配并设为默认,移除自动匹配开关;关闭后新单直接进大厅
- 超时:新增重新匹配退/扣押金,回内部匹配池并排除原卖家与原库存(迁移 147)
- 日志:新增 consignment_match_logs,自动/手动匹配成功、失败、跳过统一留痕,后台新增匹配日志抽屉
- 打手端:顶部分类下拉替换多 tab,默认苏尔南手枪;联系方式与游戏凭据改为动态字段(默认 7 项,手机号必填)
- 降价提示默认开启、默认 24 小时
This commit is contained in:
2026-11-06 13:32:24 +08:00
parent e0a85359b6
commit 3aa01136a5
31 changed files with 2311 additions and 568 deletions
+103 -8
View File
@@ -1,4 +1,6 @@
import type {
ConsignmentIntakeField,
ConsignmentIntakeFieldType,
ConsignmentPricingConfig,
ConsignmentSpecField,
ConsignmentSpecFieldType,
@@ -38,6 +40,96 @@ export function isOptionSpecField(field: Pick<ConsignmentSpecField, 'type'>): bo
return field.type === 'select' || field.type === 'radio'
}
export const INTAKE_FIELD_TYPE_OPTIONS: Array<{
label: string
value: ConsignmentIntakeFieldType
}> = [
{ label: '文本', value: 'text' },
{ label: '多行文本', value: 'textarea' },
{ label: '手机号', value: 'phone' },
{ label: '数字', value: 'number' },
{ label: '图片', value: 'image' },
{ label: '对接客服', value: 'contact_supporter' },
]
export function intakeFieldTypeLabel(type: ConsignmentIntakeFieldType): string {
return INTAKE_FIELD_TYPE_OPTIONS.find((item) => item.value === type)?.label || '文本'
}
/** 与后端 DEFAULT_CONSIGNMENT_INTAKE_FIELDS 保持一致。 */
export const DEFAULT_CONSIGNMENT_INTAKE_FIELDS: ConsignmentIntakeField[] = [
{
key: 'contactSupporterId',
label: '对接客服',
type: 'contact_supporter',
required: true,
placeholder: '请选择对接客服',
helpText: '用于后续提成归属与联系',
maxCount: 0,
sortOrder: 10,
},
{
key: 'sellerContact',
label: '微信号 / QQ号',
type: 'text',
required: true,
placeholder: '方便派单与紧急联系',
helpText: '',
maxCount: 0,
sortOrder: 20,
},
{
key: 'sellerPhone',
label: '联系电话',
type: 'phone',
required: true,
placeholder: '手机号',
helpText: '默认使用当前接单账号绑定手机号',
maxCount: 0,
sortOrder: 30,
},
{
key: 'characterName',
label: '游戏内角色名',
type: 'text',
required: true,
placeholder: '局内加好友丢枪用',
helpText: '',
maxCount: 0,
sortOrder: 40,
},
{
key: 'characterId',
label: '角色数字ID',
type: 'text',
required: true,
placeholder: '如 1089238472',
helpText: '',
maxCount: 0,
sortOrder: 50,
},
{
key: 'screenshots',
label: '游戏主页截图',
type: 'image',
required: true,
placeholder: '',
helpText: '须包含角色名、ID 及仓库/手枪详情,最多 4 张',
maxCount: 4,
sortOrder: 60,
},
{
key: 'sellerNote',
label: '其他留言与补充',
type: 'textarea',
required: false,
placeholder: '配件、子弹、涂装或在线时段等(选填)',
helpText: '',
maxCount: 0,
sortOrder: 70,
},
]
export function formatSpecValue(
field: ConsignmentSpecField,
value: unknown,
@@ -122,15 +214,18 @@ export function computeSpecPrice(
const breakdown: Array<{ label: string; amountFen: number }> = []
let priceFen = 0
if (pricing.priceType === 'fixed') {
priceFen = Math.max(0, Math.round(Number(pricing.fixedPriceFen || 0)))
if (priceFen > 0) breakdown.push({ label: '固定价格', amountFen: priceFen })
} else if (template > 0) {
const discount = Math.min(10, Math.max(0.1, Number(pricing.discount || 10)))
priceFen = Math.round((template * discount) / 10)
if (template > 0) {
breakdown.push({ label: '接单模板价', amountFen: template })
if (discount !== 10) {
breakdown.push({ label: `${discount} 折`, amountFen: priceFen - template })
if (pricing.priceType === 'template_minus') {
const minusFen = Math.max(0, Math.round(Number(pricing.minusFen || 0)))
priceFen = Math.max(0, template - minusFen)
if (minusFen > 0) breakdown.push({ label: '固定下调', amountFen: -minusFen })
} else {
const discount = Math.min(10, Math.max(0.1, Number(pricing.discount || 10)))
priceFen = Math.round((template * discount) / 10)
if (discount !== 10) {
breakdown.push({ label: `${discount} 折`, amountFen: priceFen - template })
}
}
}