feat: 优化接单工单资料补充体验

- 区服改为非必填,保持 QQ区/微信区 单选;游戏编号/昵称保持输入框,系统保持安卓/苹果单选
- 后台工单 API 归一化输出资料字段,后台人工补充资料弹窗与收集页字段一致
- 人工补充资料弹窗单选字段由下拉框改为直接点选(Radio)
- 买家主页截图支持全局剪贴板粘贴(Ctrl+V/⌘V),并提示点击/粘贴/拖拽三种上传方式
This commit is contained in:
yml2213
2026-08-14 16:05:55 +08:00
parent 951d0574b1
commit 88b205f4fd
6 changed files with 139 additions and 13 deletions
@@ -365,7 +365,9 @@ export function mapWorkOrderAdmin(workOrder: WorkOrderRow) {
workOrder.deposit_threshold_amount || DEFAULT_DEPOSIT_THRESHOLD_AMOUNT,
),
material: safeParseJson(workOrder.material_json),
requirement: safeParseJson(workOrder.requirement_json),
requirement: {
fields: resolveRequirementFields(workOrder),
},
acceptance: mapAcceptanceForResponse(workOrder.acceptance_json),
problemNote: workOrder.problem_note || '',
worker: workOrder.assigned_worker_id
@@ -502,7 +504,7 @@ export function normalizeRequirementFields(value: unknown) {
{ key: 'gameId', label: '游戏编号', required: true, type: 'text', options: [], mockValue: 'test_uid' },
{ key: 'gameNickname', label: '游戏昵称', required: true, type: 'text', options: [], mockValue: '测试角色' },
{ key: 'system', label: '系统', required: true, type: 'select', options: ['安卓', '苹果'], mockValue: '安卓' },
{ key: 'serverZone', label: '区服', required: true, type: 'select', options: ['QQ区', '微信区'], mockValue: 'QQ区' },
{ key: 'serverZone', label: '区服', required: false, type: 'select', options: ['QQ区', '微信区'], mockValue: 'QQ区' },
]
}
@@ -2,7 +2,11 @@ import assert from 'node:assert/strict'
import test from 'node:test'
import type { WorkOrderRow, WorkerUserRow } from '../../repositories/worker-platform/index.js'
import { assertWorkerLoginAllowed, resolveCollectSubmitTargetWorkOrder } from './index.js'
import {
assertWorkerLoginAllowed,
normalizeRequirementFields,
resolveCollectSubmitTargetWorkOrder,
} from './index.js'
function buildWorkerUserRow(overrides: Partial<WorkerUserRow> = {}): WorkerUserRow {
return {
@@ -67,6 +71,34 @@ test('assertWorkerLoginAllowed blocks disabled workers with worker_disabled', ()
)
})
test('normalizeRequirementFields default template keeps gameId/gameNickname as text inputs', () => {
const fields = normalizeRequirementFields([])
const gameId = fields.find((field) => field.key === 'gameId')
const gameNickname = fields.find((field) => field.key === 'gameNickname')
assert.equal(gameId?.type, 'text')
assert.equal(gameId?.required, true)
assert.deepEqual(gameId?.options, [])
assert.equal(gameNickname?.type, 'text')
assert.equal(gameNickname?.required, true)
})
test('normalizeRequirementFields default template renders system as single-select 安卓/苹果', () => {
const fields = normalizeRequirementFields([])
const system = fields.find((field) => field.key === 'system')
assert.equal(system?.type, 'select')
assert.equal(system?.required, true)
assert.deepEqual(system?.options, ['安卓', '苹果'])
})
test('normalizeRequirementFields default template marks serverZone optional with QQ区/微信区 options', () => {
const fields = normalizeRequirementFields([])
const serverZone = fields.find((field) => field.key === 'serverZone')
assert.equal(serverZone?.type, 'select')
assert.equal(serverZone?.required, false)
assert.deepEqual(serverZone?.options, ['QQ区', '微信区'])
})
function buildWorkOrderRow(
overrides: Partial<WorkOrderRow> = {},
): WorkOrderRow {