Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
517d763f17 | ||
|
|
3a557e8bda | ||
|
|
e3a66309a0 |
@@ -21,6 +21,39 @@ test('寄卖仓库按关键字搜索必须能搜到"寄卖单号"(SUB…),
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('寄卖仓库搜索必须覆盖供货打手姓名、打手电话与页面展示的卖家电话', async () => {
|
||||||
|
const source = await readFile(new URL('./consignment-repo.ts', import.meta.url), 'utf8')
|
||||||
|
const inventoryListStart = source.indexOf('export async function listConsignmentInventories')
|
||||||
|
const inventoryListBody = source.slice(
|
||||||
|
inventoryListStart,
|
||||||
|
source.indexOf('\n}\n', inventoryListStart),
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const field of [
|
||||||
|
'ci.worker_name',
|
||||||
|
'ci.worker_phone',
|
||||||
|
'ci.seller_phone',
|
||||||
|
'cs.worker_name',
|
||||||
|
'cs.worker_phone',
|
||||||
|
'cs.seller_phone',
|
||||||
|
'supplier_worker.display_name',
|
||||||
|
'supplier_worker.phone',
|
||||||
|
]) {
|
||||||
|
assert.ok(inventoryListBody.includes(field), `寄售仓库搜索缺少字段:${field}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const countSqlLine = inventoryListBody
|
||||||
|
.split('\n')
|
||||||
|
.find((line) =>
|
||||||
|
line.includes('SELECT COUNT(*)::integer AS total FROM consignment_inventories ci'),
|
||||||
|
)
|
||||||
|
assert.ok(countSqlLine, '未找到寄售仓库计数 SQL')
|
||||||
|
assert.ok(
|
||||||
|
countSqlLine!.includes('LEFT JOIN worker_users supplier_worker'),
|
||||||
|
'计数 SQL 缺少供货打手 join,按姓名/电话搜索会报错',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('匹配候选要带出寄卖单号,并且 join 之后所有列名都必须加 ci. 前缀', async () => {
|
test('匹配候选要带出寄卖单号,并且 join 之后所有列名都必须加 ci. 前缀', async () => {
|
||||||
const source = await readFile(new URL('./consignment-repo.ts', import.meta.url), 'utf8')
|
const source = await readFile(new URL('./consignment-repo.ts', import.meta.url), 'utf8')
|
||||||
const start = source.indexOf('export async function listActiveConsignmentInventoriesForMatching')
|
const start = source.indexOf('export async function listActiveConsignmentInventoriesForMatching')
|
||||||
|
|||||||
@@ -181,13 +181,30 @@ export async function listConsignmentInventories(filters: {
|
|||||||
if (filters.keyword) {
|
if (filters.keyword) {
|
||||||
values.push(`%${filters.keyword.trim()}%`)
|
values.push(`%${filters.keyword.trim()}%`)
|
||||||
conditions.push(
|
conditions.push(
|
||||||
`(ci.item_name ILIKE $${values.length} OR ci.inventory_no ILIKE $${values.length} OR ci.bound_platform_order_id ILIKE $${values.length} OR ci.seller_contact ILIKE $${values.length} OR ci.seller_game_id ILIKE $${values.length} OR ci.seller_payment_info ILIKE $${values.length} OR ci.remark ILIKE $${values.length} OR cs.submission_no ILIKE $${values.length} OR ccs.name ILIKE $${values.length})`,
|
`(ci.item_name ILIKE $${values.length}
|
||||||
|
OR ci.inventory_no ILIKE $${values.length}
|
||||||
|
OR ci.bound_platform_order_id ILIKE $${values.length}
|
||||||
|
OR ci.seller_contact ILIKE $${values.length}
|
||||||
|
OR ci.seller_game_id ILIKE $${values.length}
|
||||||
|
OR ci.seller_phone ILIKE $${values.length}
|
||||||
|
OR ci.seller_payment_info ILIKE $${values.length}
|
||||||
|
OR ci.worker_name ILIKE $${values.length}
|
||||||
|
OR ci.worker_phone ILIKE $${values.length}
|
||||||
|
OR ci.remark ILIKE $${values.length}
|
||||||
|
OR cs.submission_no ILIKE $${values.length}
|
||||||
|
OR cs.seller_phone ILIKE $${values.length}
|
||||||
|
OR cs.worker_name ILIKE $${values.length}
|
||||||
|
OR cs.worker_phone ILIKE $${values.length}
|
||||||
|
OR supplier_worker.display_name ILIKE $${values.length}
|
||||||
|
OR supplier_worker.username ILIKE $${values.length}
|
||||||
|
OR supplier_worker.phone ILIKE $${values.length}
|
||||||
|
OR ccs.name ILIKE $${values.length})`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
||||||
// 计数也要带上提报单 / 对接客服 join:关键字现在包含 cs.submission_no 与 ccs.name
|
// 计数与列表必须使用同一组 join,否则新增的提报单 / 供货打手关键字会直接报错。
|
||||||
const countSql = `SELECT COUNT(*)::integer AS total FROM consignment_inventories ci LEFT JOIN consignment_submissions cs ON cs.id = ci.source_submission_id LEFT JOIN consignment_contact_supporters ccs ON ccs.id = ci.contact_supporter_id ${whereClause}`
|
const countSql = `SELECT COUNT(*)::integer AS total FROM consignment_inventories ci LEFT JOIN consignment_submissions cs ON cs.id = ci.source_submission_id LEFT JOIN worker_users supplier_worker ON supplier_worker.id = ci.worker_id LEFT JOIN consignment_contact_supporters ccs ON ccs.id = ci.contact_supporter_id ${whereClause}`
|
||||||
const countRes = await query<{ total: number }>(countSql, values)
|
const countRes = await query<{ total: number }>(countSql, values)
|
||||||
const total = countRes.rows[0]?.total || 0
|
const total = countRes.rows[0]?.total || 0
|
||||||
|
|
||||||
@@ -213,6 +230,7 @@ export async function listConsignmentInventories(filters: {
|
|||||||
FROM consignment_inventories ci
|
FROM consignment_inventories ci
|
||||||
LEFT JOIN work_categories wc ON wc.id = ci.category_id
|
LEFT JOIN work_categories wc ON wc.id = ci.category_id
|
||||||
LEFT JOIN consignment_submissions cs ON cs.id = ci.source_submission_id
|
LEFT JOIN consignment_submissions cs ON cs.id = ci.source_submission_id
|
||||||
|
LEFT JOIN worker_users supplier_worker ON supplier_worker.id = ci.worker_id
|
||||||
LEFT JOIN consignment_contact_supporters ccs ON ccs.id = ci.contact_supporter_id
|
LEFT JOIN consignment_contact_supporters ccs ON ccs.id = ci.contact_supporter_id
|
||||||
LEFT JOIN work_orders wo ON wo.id = ci.bound_work_order_id
|
LEFT JOIN work_orders wo ON wo.id = ci.bound_work_order_id
|
||||||
${whereClause}
|
${whereClause}
|
||||||
|
|||||||
@@ -94,6 +94,19 @@ test('订单服务提交后说明:旧配置使用默认文案,新配置支
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('用户引导图:说明文字随图片配置保存,旧配置默认不显示说明', () => {
|
||||||
|
assert.equal(config().guideImageDescription, '')
|
||||||
|
const normalized = config({
|
||||||
|
guideImage: {
|
||||||
|
objectKey: 'order-service/guide.webp',
|
||||||
|
url: '/api/v1/files/object?key=order-service%2Fguide.webp',
|
||||||
|
},
|
||||||
|
guideImageDescription: '请截图买家个人主页,并确认游戏 ID 清晰可见。',
|
||||||
|
})
|
||||||
|
assert.equal(normalized.guideImageDescription, '请截图买家个人主页,并确认游戏 ID 清晰可见。')
|
||||||
|
assert.equal(normalized.guideImage.description, '请截图买家个人主页,并确认游戏 ID 清晰可见。')
|
||||||
|
})
|
||||||
|
|
||||||
test('客户状态进度:四个节点支持独立标题和说明,旧配置使用默认文案', () => {
|
test('客户状态进度:四个节点支持独立标题和说明,旧配置使用默认文案', () => {
|
||||||
assert.deepEqual(config().statusSteps, {
|
assert.deepEqual(config().statusSteps, {
|
||||||
submitted: { title: '提交资料', description: '' },
|
submitted: { title: '提交资料', description: '' },
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export type OrderServiceProfileConfig = {
|
|||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
guideImage: Record<string, unknown>
|
guideImage: Record<string, unknown>
|
||||||
|
guideImageDescription: string
|
||||||
fields: OrderServiceField[]
|
fields: OrderServiceField[]
|
||||||
screenshots: OrderServiceScreenshots
|
screenshots: OrderServiceScreenshots
|
||||||
autoPublish: OrderServiceAutoPublish
|
autoPublish: OrderServiceAutoPublish
|
||||||
@@ -157,7 +158,14 @@ export async function saveAdminOrderServiceProfile(payload: JsonObject = {}) {
|
|||||||
payload.materialConfirmation ?? payload.material_confirmation_json,
|
payload.materialConfirmation ?? payload.material_confirmation_json,
|
||||||
)
|
)
|
||||||
const statusSteps = normalizeStatusSteps(payload.statusSteps ?? payload.status_steps_json)
|
const statusSteps = normalizeStatusSteps(payload.statusSteps ?? payload.status_steps_json)
|
||||||
const guideImage = normalizeGuideImage(payload.guideImage ?? payload.guide_image_json)
|
const guideImageDescription = text(
|
||||||
|
payload.guideImageDescription ?? payload.guide_image_description,
|
||||||
|
200,
|
||||||
|
)
|
||||||
|
const guideImage = normalizeGuideImage(
|
||||||
|
payload.guideImage ?? payload.guide_image_json,
|
||||||
|
guideImageDescription,
|
||||||
|
)
|
||||||
const adminUserId = Number(payload.adminUserId ?? payload.admin_user_id ?? 0) || null
|
const adminUserId = Number(payload.adminUserId ?? payload.admin_user_id ?? 0) || null
|
||||||
|
|
||||||
const saved = await upsertOrderServiceProfile({
|
const saved = await upsertOrderServiceProfile({
|
||||||
@@ -246,6 +254,7 @@ function mapAdminProfile(row: OrderServiceProfileRow) {
|
|||||||
title: row.title,
|
title: row.title,
|
||||||
description: row.description,
|
description: row.description,
|
||||||
guideImage: row.guide_image_json,
|
guideImage: row.guide_image_json,
|
||||||
|
guideImageDescription: buildProfileConfig(row).guideImageDescription,
|
||||||
fields: buildProfileConfig(row).fields,
|
fields: buildProfileConfig(row).fields,
|
||||||
screenshots: row.screenshots_json,
|
screenshots: row.screenshots_json,
|
||||||
autoPublish: row.auto_publish_json,
|
autoPublish: row.auto_publish_json,
|
||||||
@@ -276,11 +285,13 @@ export function buildProfileConfig(row: {
|
|||||||
success_text: string
|
success_text: string
|
||||||
post_submit_text: string
|
post_submit_text: string
|
||||||
}): OrderServiceProfileConfig & { guideImage: Record<string, unknown> } {
|
}): OrderServiceProfileConfig & { guideImage: Record<string, unknown> } {
|
||||||
|
const guideImage = normalizeGuideImage(row.guide_image_json)
|
||||||
return {
|
return {
|
||||||
name: row.name,
|
name: row.name,
|
||||||
title: row.title,
|
title: row.title,
|
||||||
description: row.description,
|
description: row.description,
|
||||||
guideImage: normalizeGuideImage(row.guide_image_json),
|
guideImage,
|
||||||
|
guideImageDescription: readGuideImageDescription(guideImage),
|
||||||
fields: normalizeFields(row.fields_json),
|
fields: normalizeFields(row.fields_json),
|
||||||
screenshots: normalizeScreenshots(row.screenshots_json),
|
screenshots: normalizeScreenshots(row.screenshots_json),
|
||||||
autoPublish: normalizeAutoPublish(row.auto_publish_json),
|
autoPublish: normalizeAutoPublish(row.auto_publish_json),
|
||||||
@@ -330,11 +341,16 @@ export function isOrderServiceMaterialRequired(config: OrderServiceProfileConfig
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeProfileConfig(value: JsonObject): OrderServiceProfileConfig {
|
export function normalizeProfileConfig(value: JsonObject): OrderServiceProfileConfig {
|
||||||
|
const guideImage = normalizeGuideImage(
|
||||||
|
value.guideImage ?? value.guide_image,
|
||||||
|
value.guideImageDescription ?? value.guide_image_description,
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
name: text(value.name, 80) || '订单服务配置',
|
name: text(value.name, 80) || '订单服务配置',
|
||||||
title: text(value.title, 80),
|
title: text(value.title, 80),
|
||||||
description: text(value.description, 300),
|
description: text(value.description, 300),
|
||||||
guideImage: normalizeGuideImage(value.guideImage ?? value.guide_image),
|
guideImage,
|
||||||
|
guideImageDescription: readGuideImageDescription(guideImage),
|
||||||
fields: normalizeFields(value.fields),
|
fields: normalizeFields(value.fields),
|
||||||
screenshots: normalizeScreenshots(value.screenshots),
|
screenshots: normalizeScreenshots(value.screenshots),
|
||||||
autoPublish: normalizeAutoPublish(value.autoPublish),
|
autoPublish: normalizeAutoPublish(value.autoPublish),
|
||||||
@@ -459,9 +475,20 @@ function normalizeStatusStep(value: unknown, fallback: { title: string; descript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeGuideImage(value: unknown): Record<string, unknown> {
|
function normalizeGuideImage(value: unknown, descriptionInput?: unknown): Record<string, unknown> {
|
||||||
|
const source =
|
||||||
|
value && typeof value === 'object' && !Array.isArray(value) ? (value as JsonObject) : {}
|
||||||
const normalized = normalizeUploadedFiles([value])[0]
|
const normalized = normalizeUploadedFiles([value])[0]
|
||||||
return normalized ? { ...normalized } : {}
|
if (!normalized) return {}
|
||||||
|
const description = text(descriptionInput ?? source.description ?? source.guideDescription, 200)
|
||||||
|
return {
|
||||||
|
...normalized,
|
||||||
|
...(description ? { description } : {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readGuideImageDescription(value: Record<string, unknown>) {
|
||||||
|
return text(value.description, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
function clampInteger(value: unknown, fallback: number, min: number, max: number) {
|
function clampInteger(value: unknown, fallback: number, min: number, max: number) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import test from 'node:test'
|
|||||||
import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js'
|
import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js'
|
||||||
import {
|
import {
|
||||||
resolveOrderServiceAcceptanceImageUrls,
|
resolveOrderServiceAcceptanceImageUrls,
|
||||||
|
resolveOrderServiceAcceptanceVideo,
|
||||||
resolveOrderServiceStatusLabel,
|
resolveOrderServiceStatusLabel,
|
||||||
resolveOrderServiceStatusMessage,
|
resolveOrderServiceStatusMessage,
|
||||||
} from './order-service-public-service.js'
|
} from './order-service-public-service.js'
|
||||||
@@ -42,6 +43,43 @@ test('客户页验收图片:仅交付相关状态展示图片,并按文件
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('客户页验收视频:交付相关状态返回视频地址,未交付状态不返回', () => {
|
||||||
|
const acceptanceJson = {
|
||||||
|
video: {
|
||||||
|
url: '/api/v1/files/object?key=worker-acceptance-video%2Fdemo.webm',
|
||||||
|
thumbnailUrl: '',
|
||||||
|
filename: 'acceptance.webm',
|
||||||
|
contentType: 'video/webm',
|
||||||
|
durationSeconds: 8.8,
|
||||||
|
width: 808,
|
||||||
|
height: 720,
|
||||||
|
objectKey: 'worker-acceptance-video/demo.webm',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.deepEqual(
|
||||||
|
resolveOrderServiceAcceptanceVideo({
|
||||||
|
status: WORK_ORDER_STATUS.ACCEPTED,
|
||||||
|
acceptanceJson,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
url: '/api/v1/files/object?key=worker-acceptance-video%2Fdemo.webm',
|
||||||
|
thumbnailUrl: '',
|
||||||
|
filename: 'acceptance.webm',
|
||||||
|
contentType: 'video/webm',
|
||||||
|
durationSeconds: 8.8,
|
||||||
|
width: 808,
|
||||||
|
height: 720,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert.equal(
|
||||||
|
resolveOrderServiceAcceptanceVideo({
|
||||||
|
status: WORK_ORDER_STATUS.IN_PROGRESS,
|
||||||
|
acceptanceJson,
|
||||||
|
}),
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('订单服务状态文案:待抢单提示高峰期等待', () => {
|
test('订单服务状态文案:待抢单提示高峰期等待', () => {
|
||||||
const message = resolveOrderServiceStatusMessage({
|
const message = resolveOrderServiceStatusMessage({
|
||||||
status: WORK_ORDER_STATUS.OPEN,
|
status: WORK_ORDER_STATUS.OPEN,
|
||||||
|
|||||||
@@ -93,7 +93,9 @@ export type OrderServicePublicItem = {
|
|||||||
screenshots?: Array<Record<string, unknown>>
|
screenshots?: Array<Record<string, unknown>>
|
||||||
screenshotsRule?: OrderServiceProfileConfig['screenshots']
|
screenshotsRule?: OrderServiceProfileConfig['screenshots']
|
||||||
guideImage?: Record<string, unknown>
|
guideImage?: Record<string, unknown>
|
||||||
|
guideImageDescription?: string
|
||||||
acceptanceImageUrls?: string[]
|
acceptanceImageUrls?: string[]
|
||||||
|
acceptanceVideo?: OrderServiceAcceptanceVideo
|
||||||
submittedAt?: string
|
submittedAt?: string
|
||||||
acceptanceSubmittedAt?: string
|
acceptanceSubmittedAt?: string
|
||||||
acceptedAt?: string
|
acceptedAt?: string
|
||||||
@@ -163,6 +165,55 @@ export function resolveOrderServiceAcceptanceImageUrls(input: {
|
|||||||
return urls
|
return urls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OrderServiceAcceptanceVideo = {
|
||||||
|
url: string
|
||||||
|
thumbnailUrl: string
|
||||||
|
filename: string
|
||||||
|
contentType: string
|
||||||
|
durationSeconds: number | null
|
||||||
|
width: number | null
|
||||||
|
height: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveOrderServiceAcceptanceVideo(input: {
|
||||||
|
status: string
|
||||||
|
acceptanceJson: unknown
|
||||||
|
}): OrderServiceAcceptanceVideo | null {
|
||||||
|
if (!PUBLIC_ACCEPTANCE_IMAGE_STATUSES.has(String(input.status || '').trim())) return null
|
||||||
|
|
||||||
|
const rawAcceptance = safeParseJson(input.acceptanceJson)
|
||||||
|
const acceptance = mapAcceptanceForResponse(input.acceptanceJson)
|
||||||
|
const candidates = [
|
||||||
|
rawAcceptance.video,
|
||||||
|
...(Array.isArray(acceptance.files) ? acceptance.files : []),
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) continue
|
||||||
|
const source = candidate as Record<string, unknown>
|
||||||
|
const contentType = String(source.contentType || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
const objectKey = String(
|
||||||
|
source.objectKey || extractObjectKeyFromFileUrl(String(source.url || '')) || '',
|
||||||
|
).trim()
|
||||||
|
const videoLike = contentType.startsWith('video/') || /\.(?:mp4|webm|mov|m4v)$/i.test(objectKey)
|
||||||
|
if (!videoLike) continue
|
||||||
|
const url = refreshStoredFileUrl(String(source.url || '').trim())
|
||||||
|
if (!url) continue
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
thumbnailUrl: refreshStoredFileUrl(String(source.thumbnailUrl || '').trim()),
|
||||||
|
filename: String(source.filename || '').trim(),
|
||||||
|
contentType,
|
||||||
|
durationSeconds: normalizeFiniteNumber(source.durationSeconds),
|
||||||
|
width: normalizeFiniteNumber(source.width),
|
||||||
|
height: normalizeFiniteNumber(source.height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export async function getOrderServiceByPlatformOrderId(platformOrderIdInput: unknown) {
|
export async function getOrderServiceByPlatformOrderId(platformOrderIdInput: unknown) {
|
||||||
const platformOrderId = normalizeOrderNo(platformOrderIdInput)
|
const platformOrderId = normalizeOrderNo(platformOrderIdInput)
|
||||||
const orders = await listWorkOrdersByPlatformOrderId(platformOrderId)
|
const orders = await listWorkOrdersByPlatformOrderId(platformOrderId)
|
||||||
@@ -428,6 +479,10 @@ async function mapPublicOrderItem(order: WorkOrderRow): Promise<OrderServicePubl
|
|||||||
status: order.status,
|
status: order.status,
|
||||||
acceptanceJson: order.acceptance_json,
|
acceptanceJson: order.acceptance_json,
|
||||||
})
|
})
|
||||||
|
const acceptanceVideo = resolveOrderServiceAcceptanceVideo({
|
||||||
|
status: order.status,
|
||||||
|
acceptanceJson: order.acceptance_json,
|
||||||
|
})
|
||||||
const screenshots = normalizeUploadedFiles(collect.screenshots || [])
|
const screenshots = normalizeUploadedFiles(collect.screenshots || [])
|
||||||
.map(refreshUploadedFileUrls)
|
.map(refreshUploadedFileUrls)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@@ -457,21 +512,11 @@ async function mapPublicOrderItem(order: WorkOrderRow): Promise<OrderServicePubl
|
|||||||
...(acceptanceSubmittedAt ? { acceptanceSubmittedAt } : {}),
|
...(acceptanceSubmittedAt ? { acceptanceSubmittedAt } : {}),
|
||||||
...(order.accepted_at ? { acceptedAt: String(order.accepted_at) } : {}),
|
...(order.accepted_at ? { acceptedAt: String(order.accepted_at) } : {}),
|
||||||
...(acceptanceImageUrls.length > 0 ? { acceptanceImageUrls } : {}),
|
...(acceptanceImageUrls.length > 0 ? { acceptanceImageUrls } : {}),
|
||||||
|
...(acceptanceVideo ? { acceptanceVideo } : {}),
|
||||||
...(profile?.materialConfirmation
|
...(profile?.materialConfirmation
|
||||||
? { materialConfirmation: profile.materialConfirmation }
|
? { materialConfirmation: profile.materialConfirmation }
|
||||||
: {}),
|
: {}),
|
||||||
statusSteps:
|
fields: profile?.fields || [],
|
||||||
currentPresentationProfile?.config.statusSteps ||
|
|
||||||
profile?.statusSteps ||
|
|
||||||
DEFAULT_ORDER_SERVICE_STATUS_STEPS,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.canSubmit) return base
|
|
||||||
|
|
||||||
const fields = profile?.fields || []
|
|
||||||
return {
|
|
||||||
...base,
|
|
||||||
fields,
|
|
||||||
values,
|
values,
|
||||||
screenshots,
|
screenshots,
|
||||||
screenshotsRule: profile?.screenshots || {
|
screenshotsRule: profile?.screenshots || {
|
||||||
@@ -482,7 +527,18 @@ async function mapPublicOrderItem(order: WorkOrderRow): Promise<OrderServicePubl
|
|||||||
label: '买家主页截图',
|
label: '买家主页截图',
|
||||||
helpText: '',
|
helpText: '',
|
||||||
},
|
},
|
||||||
|
statusSteps:
|
||||||
|
currentPresentationProfile?.config.statusSteps ||
|
||||||
|
profile?.statusSteps ||
|
||||||
|
DEFAULT_ORDER_SERVICE_STATUS_STEPS,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.canSubmit) return base
|
||||||
|
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
guideImage: profile?.guideImage || {},
|
guideImage: profile?.guideImage || {},
|
||||||
|
guideImageDescription: profile?.guideImageDescription || '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,6 +567,7 @@ async function resolveProfileConfig(
|
|||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
guideImage: {},
|
guideImage: {},
|
||||||
|
guideImageDescription: '',
|
||||||
fields: normalizeProfileConfig({
|
fields: normalizeProfileConfig({
|
||||||
name: '通用资料',
|
name: '通用资料',
|
||||||
fields: legacy.fields,
|
fields: legacy.fields,
|
||||||
@@ -585,6 +642,11 @@ function normalizeOrderNo(value: unknown) {
|
|||||||
return normalized
|
return normalized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeFiniteNumber(value: unknown): number | null {
|
||||||
|
const parsed = Number(value)
|
||||||
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null
|
||||||
|
}
|
||||||
|
|
||||||
function notFound() {
|
function notFound() {
|
||||||
return createHttpError('订单号无效,请确认订单号是否正确', {
|
return createHttpError('订单号无效,请确认订单号是否正确', {
|
||||||
statusCode: 404,
|
statusCode: 404,
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ type OrderServiceFormValues = {
|
|||||||
name: string
|
name: string
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
|
guideImageDescription: string
|
||||||
submitText: string
|
submitText: string
|
||||||
successText: string
|
successText: string
|
||||||
postSubmitText: string
|
postSubmitText: string
|
||||||
@@ -230,6 +231,14 @@ function buildPresetFields(keys: string[]) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveGuideImage(value: unknown): UploadedFile | null {
|
||||||
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return null
|
||||||
|
const source = value as Record<string, unknown>
|
||||||
|
const url = String(source.url || '').trim()
|
||||||
|
const objectKey = String(source.objectKey || source.object_key || '').trim()
|
||||||
|
return url || objectKey ? (value as UploadedFile) : null
|
||||||
|
}
|
||||||
|
|
||||||
export default function OrderServicePanel() {
|
export default function OrderServicePanel() {
|
||||||
const { message } = App.useApp()
|
const { message } = App.useApp()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
@@ -275,7 +284,10 @@ export default function OrderServicePanel() {
|
|||||||
).length
|
).length
|
||||||
const selectedCategory = categories.find((item) => item.categoryId === selectedCategoryId) || null
|
const selectedCategory = categories.find((item) => item.categoryId === selectedCategoryId) || null
|
||||||
const profile = (selectedCategoryId ? profileByCategoryId.get(selectedCategoryId) : null) || null
|
const profile = (selectedCategoryId ? profileByCategoryId.get(selectedCategoryId) : null) || null
|
||||||
const guideFiles = useMemo(() => (guideImage ? [guideImage as UploadedFile] : []), [guideImage])
|
const guideFiles = useMemo(() => {
|
||||||
|
const image = resolveGuideImage(guideImage)
|
||||||
|
return image ? [image] : []
|
||||||
|
}, [guideImage])
|
||||||
const handleGuideImageChange = useCallback((files: UploadedFile[]) => {
|
const handleGuideImageChange = useCallback((files: UploadedFile[]) => {
|
||||||
setGuideImage(files[0] || null)
|
setGuideImage(files[0] || null)
|
||||||
}, [])
|
}, [])
|
||||||
@@ -302,6 +314,7 @@ export default function OrderServicePanel() {
|
|||||||
name: nextProfile?.name || selectedCategory?.name || '',
|
name: nextProfile?.name || selectedCategory?.name || '',
|
||||||
title: nextProfile?.title || '',
|
title: nextProfile?.title || '',
|
||||||
description: nextProfile?.description || '',
|
description: nextProfile?.description || '',
|
||||||
|
guideImageDescription: nextProfile?.guideImageDescription || '',
|
||||||
submitText: nextProfile?.submitText || '提交资料',
|
submitText: nextProfile?.submitText || '提交资料',
|
||||||
successText: nextProfile?.successText || '资料已提交,请等待处理',
|
successText: nextProfile?.successText || '资料已提交,请等待处理',
|
||||||
postSubmitText: nextProfile?.postSubmitText || DEFAULT_POST_SUBMIT_TEXT,
|
postSubmitText: nextProfile?.postSubmitText || DEFAULT_POST_SUBMIT_TEXT,
|
||||||
@@ -315,7 +328,7 @@ export default function OrderServicePanel() {
|
|||||||
nextProfile ? nextProfile.fields : buildPresetFields(GAME_BASIC_PRESET_KEYS),
|
nextProfile ? nextProfile.fields : buildPresetFields(GAME_BASIC_PRESET_KEYS),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
setGuideImage(nextProfile?.guideImage || null)
|
setGuideImage(resolveGuideImage(nextProfile?.guideImage))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
@@ -413,6 +426,7 @@ export default function OrderServicePanel() {
|
|||||||
title: values.title || '',
|
title: values.title || '',
|
||||||
description: values.description || '',
|
description: values.description || '',
|
||||||
guideImage: guideImage || {},
|
guideImage: guideImage || {},
|
||||||
|
guideImageDescription: values.guideImageDescription || '',
|
||||||
fields: normalizedFields,
|
fields: normalizedFields,
|
||||||
screenshots: values.screenshots,
|
screenshots: values.screenshots,
|
||||||
autoPublish: values.autoPublish,
|
autoPublish: values.autoPublish,
|
||||||
@@ -652,7 +666,7 @@ export default function OrderServicePanel() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="order-service-basic-grid">
|
<div className="order-service-basic-grid">
|
||||||
<div>
|
<div className="order-service-basic-fields">
|
||||||
<Row gutter={[12, 0]}>
|
<Row gutter={[12, 0]}>
|
||||||
<Col xs={24} md={12}>
|
<Col xs={24} md={12}>
|
||||||
<Form.Item label="配置名称" name="name" rules={[{ required: true }]}>
|
<Form.Item label="配置名称" name="name" rules={[{ required: true }]}>
|
||||||
@@ -699,16 +713,36 @@ export default function OrderServicePanel() {
|
|||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
<div className="order-service-guide">
|
<div className="order-service-guide">
|
||||||
<Text strong>用户引导图</Text>
|
<div className="order-service-guide-head">
|
||||||
<Text type="secondary">首版一张,展示在字段上方</Text>
|
<Text strong>用户引导图</Text>
|
||||||
<ImageUpload
|
<Text type="secondary">
|
||||||
scene="order-service"
|
一张,显示在客户页“买家主页截图”上传区右侧,可点击放大查看
|
||||||
scope="admin"
|
</Text>
|
||||||
maxCount={1}
|
</div>
|
||||||
value={guideFiles}
|
<div className="order-service-guide-upload">
|
||||||
onChange={handleGuideImageChange}
|
<ImageUpload
|
||||||
uploader={uploadGuideImage}
|
scene="order-service"
|
||||||
/>
|
scope="admin"
|
||||||
|
maxCount={1}
|
||||||
|
value={guideFiles}
|
||||||
|
onChange={handleGuideImageChange}
|
||||||
|
uploader={uploadGuideImage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Form.Item
|
||||||
|
className="order-service-guide-description"
|
||||||
|
extra="选填,显示在引导图下方;建议说明截图范围或关键识别信息。"
|
||||||
|
label="图片说明"
|
||||||
|
name="guideImageDescription"
|
||||||
|
>
|
||||||
|
<Input.TextArea
|
||||||
|
autoSize={{ minRows: 2, maxRows: 4 }}
|
||||||
|
disabled={!guideImage}
|
||||||
|
maxLength={200}
|
||||||
|
placeholder="例如:请截取买家个人主页,并确认游戏 ID 清晰可见"
|
||||||
|
showCount
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
Space,
|
Space,
|
||||||
Spin,
|
Spin,
|
||||||
Steps,
|
|
||||||
Tag,
|
Tag,
|
||||||
Typography,
|
Typography,
|
||||||
} from 'antd'
|
} from 'antd'
|
||||||
@@ -42,14 +41,15 @@ import { useOrderServicePageMeta } from './use-order-service-page-meta'
|
|||||||
const { Text, Title } = Typography
|
const { Text, Title } = Typography
|
||||||
const NOT_FOUND_MESSAGE = '订单号无效,请确认订单号是否正确'
|
const NOT_FOUND_MESSAGE = '订单号无效,请确认订单号是否正确'
|
||||||
const STATUS_REFRESH_INTERVAL_MS = 15_000
|
const STATUS_REFRESH_INTERVAL_MS = 15_000
|
||||||
const STATUS_STEP_INDEX: Record<string, number> = {
|
const STATUS_STEP_ORDER = ['submitted', 'scheduled', 'processing', 'completed'] as const
|
||||||
pending_material: 0,
|
const STATUS_STEP_KEY: Record<string, (typeof STATUS_STEP_ORDER)[number]> = {
|
||||||
unassigned: 1,
|
pending_material: 'submitted',
|
||||||
open: 2,
|
unassigned: 'scheduled',
|
||||||
in_progress: 2,
|
open: 'processing',
|
||||||
pending_acceptance: 3,
|
in_progress: 'processing',
|
||||||
accepted: 3,
|
pending_acceptance: 'completed',
|
||||||
problem: 2,
|
accepted: 'completed',
|
||||||
|
problem: 'processing',
|
||||||
}
|
}
|
||||||
const DEFAULT_STATUS_STEPS: OrderServiceItem['statusSteps'] = {
|
const DEFAULT_STATUS_STEPS: OrderServiceItem['statusSteps'] = {
|
||||||
submitted: { title: '提交资料', description: '' },
|
submitted: { title: '提交资料', description: '' },
|
||||||
@@ -292,6 +292,10 @@ export default function OrderServicePage() {
|
|||||||
|
|
||||||
const pageConfig = info?.items[0]
|
const pageConfig = info?.items[0]
|
||||||
const pendingItems = (info?.items || []).filter((item) => item.canSubmit)
|
const pendingItems = (info?.items || []).filter((item) => item.canSubmit)
|
||||||
|
const purchaseSummary =
|
||||||
|
[
|
||||||
|
...new Set(info?.items.map((item) => item.productName || item.categoryName).filter(Boolean)),
|
||||||
|
].join('、') || '订单商品'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="collect-link-page">
|
<main className="collect-link-page">
|
||||||
@@ -313,9 +317,14 @@ export default function OrderServicePage() {
|
|||||||
<div className="collect-link-heading">
|
<div className="collect-link-heading">
|
||||||
<div>
|
<div>
|
||||||
<Title level={4}>{pageConfig?.title || '订单服务'}</Title>
|
<Title level={4}>{pageConfig?.title || '订单服务'}</Title>
|
||||||
<Text type="secondary">订单号:{info.platformOrderId}</Text>
|
<div className="collect-link-order-meta">
|
||||||
|
<span>订单号:{info.platformOrderId}</span>
|
||||||
|
<span>
|
||||||
|
购买物品:<strong>{purchaseSummary}</strong>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{pageConfig?.description ? (
|
{pageConfig?.description ? (
|
||||||
<div style={{ marginTop: 6 }}>
|
<div className="collect-link-description">
|
||||||
<Text type="secondary">{pageConfig.description}</Text>
|
<Text type="secondary">{pageConfig.description}</Text>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -387,9 +396,7 @@ export default function OrderServicePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function OrderServiceStatusCard({ item }: { item: OrderServiceItem }) {
|
function OrderServiceStatusCard({ item }: { item: OrderServiceItem }) {
|
||||||
const currentStep = STATUS_STEP_INDEX[item.state] ?? 0
|
|
||||||
const times = [
|
const times = [
|
||||||
item.submittedAt ? { label: '资料提交时间', value: item.submittedAt } : null,
|
|
||||||
item.state === 'pending_acceptance' && item.acceptanceSubmittedAt
|
item.state === 'pending_acceptance' && item.acceptanceSubmittedAt
|
||||||
? { label: '凭证提交时间', value: item.acceptanceSubmittedAt }
|
? { label: '凭证提交时间', value: item.acceptanceSubmittedAt }
|
||||||
: null,
|
: null,
|
||||||
@@ -398,8 +405,12 @@ function OrderServiceStatusCard({ item }: { item: OrderServiceItem }) {
|
|||||||
: null,
|
: null,
|
||||||
].filter((row): row is { label: string; value: string } => Boolean(row?.value))
|
].filter((row): row is { label: string; value: string } => Boolean(row?.value))
|
||||||
const acceptanceImageUrls = item.acceptanceImageUrls || []
|
const acceptanceImageUrls = item.acceptanceImageUrls || []
|
||||||
const showSteps = item.state !== 'cancelled'
|
const acceptanceVideo = item.acceptanceVideo
|
||||||
const statusSteps = item.statusSteps || DEFAULT_STATUS_STEPS
|
const statusSteps = item.statusSteps || DEFAULT_STATUS_STEPS
|
||||||
|
const statusStepKey = STATUS_STEP_KEY[item.state]
|
||||||
|
const currentStepIndex = statusStepKey ? STATUS_STEP_ORDER.indexOf(statusStepKey) : -1
|
||||||
|
const currentStep = statusStepKey ? statusSteps[statusStepKey] : null
|
||||||
|
const currentStatusText = currentStep?.description?.trim() || item.message
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card size="small" className="collect-link-status-card">
|
<Card size="small" className="collect-link-status-card">
|
||||||
@@ -409,37 +420,24 @@ function OrderServiceStatusCard({ item }: { item: OrderServiceItem }) {
|
|||||||
<Tag color={STATUS_TAG_COLORS[item.state] || 'default'}>{item.stateLabel}</Tag>
|
<Tag color={STATUS_TAG_COLORS[item.state] || 'default'}>{item.stateLabel}</Tag>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showSteps ? (
|
<SubmittedMaterialSummary item={item} />
|
||||||
<Steps
|
|
||||||
className="collect-link-status-steps"
|
|
||||||
current={currentStep}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
title: statusSteps.submitted.title,
|
|
||||||
description: statusSteps.submitted.description || undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: statusSteps.scheduled.title,
|
|
||||||
description: statusSteps.scheduled.description || undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: statusSteps.processing.title,
|
|
||||||
description: statusSteps.processing.description || undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: statusSteps.completed.title,
|
|
||||||
description: statusSteps.completed.description || undefined,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
responsive
|
|
||||||
size="small"
|
|
||||||
status={
|
|
||||||
item.state === 'accepted' ? 'finish' : item.state === 'problem' ? 'error' : 'process'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className={`collect-link-status-message is-${item.state}`}>{item.message}</div>
|
{currentStep && currentStepIndex >= 0 ? (
|
||||||
|
<div className={`collect-link-current-status is-${item.state}`}>
|
||||||
|
<span className="collect-link-current-status-index">{currentStepIndex + 1}</span>
|
||||||
|
<div className="collect-link-current-status-copy">
|
||||||
|
<div>
|
||||||
|
<Text strong>{currentStep.title}</Text>
|
||||||
|
<Text type="secondary">
|
||||||
|
当前进度 {currentStepIndex + 1}/{STATUS_STEP_ORDER.length}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<p>{currentStatusText}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={`collect-link-status-message is-${item.state}`}>{item.message}</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{times.length > 0 ? (
|
{times.length > 0 ? (
|
||||||
<div className="collect-link-status-times">
|
<div className="collect-link-status-times">
|
||||||
@@ -451,19 +449,60 @@ function OrderServiceStatusCard({ item }: { item: OrderServiceItem }) {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{acceptanceImageUrls.length > 0 ? (
|
{acceptanceImageUrls.length > 0 || acceptanceVideo ? (
|
||||||
<div className="collect-link-acceptance">
|
<div className="collect-link-acceptance">
|
||||||
<div>
|
<div>
|
||||||
<Text strong>交付验收图片</Text>
|
<Text strong>交付验收</Text>
|
||||||
<Text type="secondary">点击图片可查看大图</Text>
|
<Text type="secondary">
|
||||||
|
{acceptanceVideo && acceptanceImageUrls.length > 0
|
||||||
|
? '图片可点击查看大图,视频可直接播放'
|
||||||
|
: acceptanceVideo
|
||||||
|
? '可直接播放并确认交付内容'
|
||||||
|
: '点击图片可查看大图'}
|
||||||
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<Image.PreviewGroup>
|
{acceptanceVideo ? (
|
||||||
<div className="collect-link-acceptance-images">
|
<div className="collect-link-acceptance-video">
|
||||||
{acceptanceImageUrls.map((url, index) => (
|
<video
|
||||||
<Image key={url} alt={`验收图片 ${index + 1}`} src={url} width={92} height={92} />
|
controls
|
||||||
))}
|
playsInline
|
||||||
|
preload="metadata"
|
||||||
|
poster={acceptanceVideo.thumbnailUrl || undefined}
|
||||||
|
src={acceptanceVideo.url}
|
||||||
|
>
|
||||||
|
您的浏览器暂不支持视频播放,请
|
||||||
|
<a href={acceptanceVideo.url} rel="noreferrer" target="_blank">
|
||||||
|
点击下载验收视频
|
||||||
|
</a>
|
||||||
|
</video>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">
|
||||||
|
{acceptanceVideo.filename || '验收视频'}
|
||||||
|
{acceptanceVideo.durationSeconds
|
||||||
|
? ` · ${Math.ceil(acceptanceVideo.durationSeconds)} 秒`
|
||||||
|
: ''}
|
||||||
|
</Text>
|
||||||
|
<a href={acceptanceVideo.url} rel="noreferrer" target="_blank">
|
||||||
|
打开原视频
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Image.PreviewGroup>
|
) : null}
|
||||||
|
{acceptanceImageUrls.length > 0 ? (
|
||||||
|
<Image.PreviewGroup>
|
||||||
|
<div className="collect-link-acceptance-images">
|
||||||
|
{acceptanceImageUrls.map((url, index) => (
|
||||||
|
<Image
|
||||||
|
key={url}
|
||||||
|
alt={`验收图片 ${index + 1}`}
|
||||||
|
src={url}
|
||||||
|
width={92}
|
||||||
|
height={92}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Image.PreviewGroup>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</Space>
|
</Space>
|
||||||
@@ -471,6 +510,72 @@ function OrderServiceStatusCard({ item }: { item: OrderServiceItem }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SubmittedMaterialSummary({ item }: { item: OrderServiceItem }) {
|
||||||
|
const values = item.values || {}
|
||||||
|
const rows =
|
||||||
|
item.fields && item.fields.length > 0
|
||||||
|
? item.fields.map((field) => ({
|
||||||
|
key: field.key,
|
||||||
|
label: field.label,
|
||||||
|
value: String(values[field.key] || '').trim(),
|
||||||
|
}))
|
||||||
|
: Object.entries(values).map(([key, value]) => ({
|
||||||
|
key,
|
||||||
|
label: key,
|
||||||
|
value: String(value || '').trim(),
|
||||||
|
}))
|
||||||
|
const filledRows = rows.filter((row) => row.value)
|
||||||
|
const screenshots = item.screenshots || []
|
||||||
|
|
||||||
|
if (filledRows.length === 0 && screenshots.length === 0) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="collect-link-submitted">
|
||||||
|
<div className="collect-link-submitted-head">
|
||||||
|
<span>
|
||||||
|
<Text strong>买家提交的资料</Text>
|
||||||
|
<Text type="secondary">交付时会按以下信息核对</Text>
|
||||||
|
</span>
|
||||||
|
{item.submittedAt ? (
|
||||||
|
<Text type="secondary">提交于 {formatOrderServiceTime(item.submittedAt)}</Text>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{filledRows.length > 0 ? (
|
||||||
|
<div className="collect-link-submitted-fields">
|
||||||
|
{filledRows.map((row) => (
|
||||||
|
<div key={row.key}>
|
||||||
|
<span>{row.label}</span>
|
||||||
|
<strong>{row.value}</strong>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{screenshots.length > 0 ? (
|
||||||
|
<div className="collect-link-submitted-screenshots">
|
||||||
|
<span>
|
||||||
|
{item.screenshotsRule?.label || '买家截图'}:{screenshots.length} 张
|
||||||
|
</span>
|
||||||
|
<Image.PreviewGroup>
|
||||||
|
<div>
|
||||||
|
{screenshots.map((file, index) => (
|
||||||
|
<Image
|
||||||
|
key={file.objectKey || file.url}
|
||||||
|
alt={file.filename || `买家截图 ${index + 1}`}
|
||||||
|
src={file.thumbnailUrl || file.url}
|
||||||
|
width={68}
|
||||||
|
height={68}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Image.PreviewGroup>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function OrderServiceForm({
|
function OrderServiceForm({
|
||||||
item,
|
item,
|
||||||
code,
|
code,
|
||||||
@@ -496,6 +601,8 @@ function OrderServiceForm({
|
|||||||
label: '截图',
|
label: '截图',
|
||||||
helpText: '',
|
helpText: '',
|
||||||
}
|
}
|
||||||
|
const guideImageUrl =
|
||||||
|
item.guideImage && 'url' in item.guideImage ? String(item.guideImage.url || '').trim() : ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card size="small" className="collect-link-order-card">
|
<Card size="small" className="collect-link-order-card">
|
||||||
@@ -513,14 +620,6 @@ function OrderServiceForm({
|
|||||||
{item.title ? <Title level={5}>{item.title}</Title> : null}
|
{item.title ? <Title level={5}>{item.title}</Title> : null}
|
||||||
{item.description ? <Text type="secondary">{item.description}</Text> : null}
|
{item.description ? <Text type="secondary">{item.description}</Text> : null}
|
||||||
|
|
||||||
{item.guideImage && 'url' in item.guideImage && item.guideImage.url ? (
|
|
||||||
<img
|
|
||||||
alt="填写引导图"
|
|
||||||
src={String(item.guideImage.url)}
|
|
||||||
style={{ width: '100%', borderRadius: 8 }}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{item.fields && item.fields.length > 0 ? (
|
{item.fields && item.fields.length > 0 ? (
|
||||||
<Form layout="vertical" initialValues={item.values || {}} onFinish={onSubmit}>
|
<Form layout="vertical" initialValues={item.values || {}} onFinish={onSubmit}>
|
||||||
{item.fields.map((field) => (
|
{item.fields.map((field) => (
|
||||||
@@ -567,24 +666,47 @@ function OrderServiceForm({
|
|||||||
}
|
}
|
||||||
extra={rule.helpText}
|
extra={rule.helpText}
|
||||||
>
|
>
|
||||||
<ImageUpload
|
<div
|
||||||
scene="collect-material"
|
className={`collect-link-screenshot-control ${guideImageUrl ? 'has-guide' : ''}`}
|
||||||
scope="collect"
|
>
|
||||||
maxCount={rule.maxCount}
|
<div className="collect-link-screenshot-upload">
|
||||||
showPasteHint
|
<ImageUpload
|
||||||
captureGlobalPaste
|
scene="collect-material"
|
||||||
value={screenshots}
|
scope="collect"
|
||||||
onChange={onScreenshotsChange}
|
maxCount={rule.maxCount}
|
||||||
uploader={async (file, scene) => {
|
showPasteHint
|
||||||
const response = await uploadOrderServiceFile(
|
captureGlobalPaste
|
||||||
file,
|
value={screenshots}
|
||||||
scene,
|
onChange={onScreenshotsChange}
|
||||||
code,
|
uploader={async (file, scene) => {
|
||||||
item.workOrderId,
|
const response = await uploadOrderServiceFile(
|
||||||
)
|
file,
|
||||||
return response.data.file
|
scene,
|
||||||
}}
|
code,
|
||||||
/>
|
item.workOrderId,
|
||||||
|
)
|
||||||
|
return response.data.file
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{guideImageUrl ? (
|
||||||
|
<aside className="collect-link-guide-example">
|
||||||
|
<Image
|
||||||
|
alt="买家主页截图填写示例"
|
||||||
|
height={88}
|
||||||
|
preview={{ mask: '查看大图' }}
|
||||||
|
src={guideImageUrl}
|
||||||
|
width={128}
|
||||||
|
/>
|
||||||
|
<div className="collect-link-guide-copy">
|
||||||
|
<Text strong>截图填写参考</Text>
|
||||||
|
<Text type="secondary">
|
||||||
|
{item.guideImageDescription?.trim() || '点击图片可查看大图'}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -1143,6 +1143,7 @@ export type AdminOrderServiceProfile = {
|
|||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
guideImage: UploadedFile | Record<string, unknown>
|
guideImage: UploadedFile | Record<string, unknown>
|
||||||
|
guideImageDescription: string
|
||||||
fields: OrderServiceField[]
|
fields: OrderServiceField[]
|
||||||
screenshots: {
|
screenshots: {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
@@ -1192,6 +1193,7 @@ export function saveAdminOrderServiceProfile(payload: {
|
|||||||
title?: string
|
title?: string
|
||||||
description?: string
|
description?: string
|
||||||
guideImage?: UploadedFile | Record<string, unknown>
|
guideImage?: UploadedFile | Record<string, unknown>
|
||||||
|
guideImageDescription?: string
|
||||||
fields: OrderServiceField[]
|
fields: OrderServiceField[]
|
||||||
screenshots: {
|
screenshots: {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
|
|||||||
@@ -613,7 +613,17 @@ export type OrderServiceItem = {
|
|||||||
screenshots?: UploadedFile[]
|
screenshots?: UploadedFile[]
|
||||||
screenshotsRule?: OrderServiceScreenshots
|
screenshotsRule?: OrderServiceScreenshots
|
||||||
guideImage?: UploadedFile | Record<string, unknown>
|
guideImage?: UploadedFile | Record<string, unknown>
|
||||||
|
guideImageDescription?: string
|
||||||
acceptanceImageUrls?: string[]
|
acceptanceImageUrls?: string[]
|
||||||
|
acceptanceVideo?: {
|
||||||
|
url: string
|
||||||
|
thumbnailUrl: string
|
||||||
|
filename: string
|
||||||
|
contentType: string
|
||||||
|
durationSeconds: number | null
|
||||||
|
width: number | null
|
||||||
|
height: number | null
|
||||||
|
}
|
||||||
submittedAt?: string
|
submittedAt?: string
|
||||||
acceptanceSubmittedAt?: string
|
acceptanceSubmittedAt?: string
|
||||||
acceptedAt?: string
|
acceptedAt?: string
|
||||||
|
|||||||
@@ -1858,26 +1858,59 @@
|
|||||||
|
|
||||||
.order-service-basic-grid {
|
.order-service-basic-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) 230px;
|
gap: 14px;
|
||||||
gap: 18px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-service-basic-grid .ant-form-item {
|
.order-service-basic-grid .ant-form-item {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-service-basic-fields {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.order-service-guide {
|
.order-service-guide {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
grid-template-columns: minmax(180px, 220px) minmax(260px, 1fr);
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
align-items: start;
|
||||||
align-content: start;
|
align-content: start;
|
||||||
gap: 4px;
|
gap: 12px 18px;
|
||||||
padding: 12px;
|
padding: 14px;
|
||||||
background: #f8fafc;
|
background: #f8fafc;
|
||||||
border: 1px dashed #d9e1ec;
|
border: 1px dashed #d9e1ec;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-service-guide .ant-upload-wrapper {
|
.order-service-guide-head {
|
||||||
margin-top: 6px;
|
grid-column: 1 / -1;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-service-guide-head .ant-typography:last-child {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-service-guide-upload {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-service-guide-upload .ant-upload-list-item-container,
|
||||||
|
.order-service-guide-upload .ant-upload-select {
|
||||||
|
width: 180px !important;
|
||||||
|
height: 140px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-service-guide-description.ant-form-item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-service-guide-description .ant-form-item-extra {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-service-status-step-grid {
|
.order-service-status-step-grid {
|
||||||
@@ -2170,10 +2203,6 @@
|
|||||||
grid-template-columns: minmax(120px, 1fr) minmax(140px, 1fr) 110px 64px 104px;
|
grid-template-columns: minmax(120px, 1fr) minmax(140px, 1fr) 110px 64px 104px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-service-basic-grid {
|
|
||||||
grid-template-columns: minmax(0, 1fr) 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-service-status-step-grid {
|
.order-service-status-step-grid {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
@@ -2206,21 +2235,8 @@
|
|||||||
padding: 16px 16px 0;
|
padding: 16px 16px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-service-basic-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-service-guide {
|
.order-service-guide {
|
||||||
grid-template-columns: minmax(0, 1fr) 160px;
|
grid-template-columns: minmax(160px, 200px) minmax(0, 1fr);
|
||||||
}
|
|
||||||
|
|
||||||
.order-service-guide > :not(.ant-upload-wrapper) {
|
|
||||||
grid-column: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-service-guide .ant-upload-wrapper {
|
|
||||||
grid-column: 2;
|
|
||||||
grid-row: 1 / span 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-service-section-head {
|
.order-service-section-head {
|
||||||
@@ -2270,12 +2286,11 @@
|
|||||||
|
|
||||||
.order-service-guide {
|
.order-service-guide {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-service-guide > :not(.ant-upload-wrapper),
|
.order-service-guide-head {
|
||||||
.order-service-guide .ant-upload-wrapper {
|
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
grid-row: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-service-status-step-grid {
|
.order-service-status-step-grid {
|
||||||
|
|||||||
@@ -526,19 +526,216 @@
|
|||||||
margin-inline-end: 0;
|
margin-inline-end: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collect-link-status-steps {
|
.collect-link-order-meta {
|
||||||
padding: 4px 2px 8px;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px 18px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collect-link-status-steps .ant-steps-item-title {
|
.collect-link-order-meta strong {
|
||||||
font-size: 12px !important;
|
color: #0f172a;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collect-link-status-steps .ant-steps-item-description {
|
.collect-link-description {
|
||||||
max-width: 140px;
|
margin-top: 6px;
|
||||||
font-size: 11px !important;
|
font-size: 12px;
|
||||||
line-height: 1.45;
|
}
|
||||||
white-space: normal;
|
|
||||||
|
.collect-link-submitted {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e7edf5;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-submitted-head {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 5px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-submitted-head > span {
|
||||||
|
display: grid;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-submitted-head .ant-typography:last-child {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-submitted-head > .ant-typography {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-submitted-fields {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-submitted-fields > div {
|
||||||
|
display: grid;
|
||||||
|
gap: 2px;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 8px 10px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #edf1f6;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-submitted-fields span,
|
||||||
|
.collect-link-submitted-screenshots > span {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-submitted-fields strong {
|
||||||
|
color: #1e293b;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-submitted-screenshots {
|
||||||
|
display: grid;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-submitted-screenshots > div {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-submitted-screenshots .ant-image {
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #dbe3ee;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-submitted-screenshots .ant-image-img {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 13px;
|
||||||
|
color: #475569;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e8edf4;
|
||||||
|
border-left: 4px solid #94a3b8;
|
||||||
|
border-radius: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status-index {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
background: #64748b;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status-copy {
|
||||||
|
display: grid;
|
||||||
|
gap: 5px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status-copy > div {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 3px 12px;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status-copy > div .ant-typography:last-child {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status-copy p {
|
||||||
|
margin: 0;
|
||||||
|
color: inherit;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.7;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status.is-open,
|
||||||
|
.collect-link-current-status.is-pending_material {
|
||||||
|
color: #8a4b00;
|
||||||
|
background: #fffaf0;
|
||||||
|
border-color: #ffe7ba;
|
||||||
|
border-left-color: #faad14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status.is-open .collect-link-current-status-index,
|
||||||
|
.collect-link-current-status.is-pending_material .collect-link-current-status-index {
|
||||||
|
background: #faad14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status.is-in_progress,
|
||||||
|
.collect-link-current-status.is-unassigned {
|
||||||
|
color: #0958d9;
|
||||||
|
background: #f0f7ff;
|
||||||
|
border-color: #d6e8ff;
|
||||||
|
border-left-color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status.is-in_progress .collect-link-current-status-index,
|
||||||
|
.collect-link-current-status.is-unassigned .collect-link-current-status-index {
|
||||||
|
background: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status.is-pending_acceptance {
|
||||||
|
color: #531dab;
|
||||||
|
background: #f9f0ff;
|
||||||
|
border-color: #efdbff;
|
||||||
|
border-left-color: #722ed1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status.is-pending_acceptance .collect-link-current-status-index {
|
||||||
|
background: #722ed1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status.is-accepted {
|
||||||
|
color: #237804;
|
||||||
|
background: #f6ffed;
|
||||||
|
border-color: #d9f7be;
|
||||||
|
border-left-color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status.is-accepted .collect-link-current-status-index {
|
||||||
|
background: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status.is-problem {
|
||||||
|
color: #a8071a;
|
||||||
|
background: #fff1f0;
|
||||||
|
border-color: #ffccc7;
|
||||||
|
border-left-color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-current-status.is-problem .collect-link-current-status-index {
|
||||||
|
background: #ff4d4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collect-link-status-message {
|
.collect-link-status-message {
|
||||||
@@ -626,6 +823,32 @@
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.collect-link-acceptance-video {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-acceptance-video video {
|
||||||
|
width: min(100%, 520px);
|
||||||
|
max-height: 360px;
|
||||||
|
background: #0f172a;
|
||||||
|
border: 1px solid #dbe3ee;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-acceptance-video > div {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px 14px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-acceptance-video a {
|
||||||
|
color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
.collect-link-title {
|
.collect-link-title {
|
||||||
margin: 0 0 4px !important;
|
margin: 0 0 4px !important;
|
||||||
}
|
}
|
||||||
@@ -837,6 +1060,64 @@
|
|||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.collect-link-screenshot-control {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-screenshot-control.has-guide {
|
||||||
|
grid-template-columns: 120px minmax(180px, 260px);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-guide-example {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
align-content: start;
|
||||||
|
justify-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-guide-example .ant-image {
|
||||||
|
display: block;
|
||||||
|
width: 128px !important;
|
||||||
|
height: 88px !important;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #dbe3ee;
|
||||||
|
border-radius: 7px;
|
||||||
|
cursor: zoom-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-guide-example .ant-image-img {
|
||||||
|
display: block;
|
||||||
|
width: 128px !important;
|
||||||
|
height: 88px !important;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-guide-copy {
|
||||||
|
display: grid;
|
||||||
|
align-content: center;
|
||||||
|
gap: 3px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-guide-copy .ant-typography:first-child {
|
||||||
|
color: #334155;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-guide-copy .ant-typography:last-child {
|
||||||
|
max-width: 220px;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.55;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.collect-link-actions {
|
.collect-link-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@@ -863,6 +1144,21 @@
|
|||||||
padding: 18px;
|
padding: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.collect-link-screenshot-control.has-guide {
|
||||||
|
grid-template-columns: 112px minmax(160px, 220px);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-guide-example .ant-image {
|
||||||
|
width: 116px !important;
|
||||||
|
height: 80px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-guide-example .ant-image-img {
|
||||||
|
width: 116px !important;
|
||||||
|
height: 80px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.collect-link-actions {
|
.collect-link-actions {
|
||||||
justify-content: stretch;
|
justify-content: stretch;
|
||||||
}
|
}
|
||||||
@@ -881,6 +1177,33 @@
|
|||||||
padding: 14px;
|
padding: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.collect-link-screenshot-control.has-guide {
|
||||||
|
grid-template-columns: 104px minmax(0, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-guide-example {
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-guide-example .ant-image {
|
||||||
|
width: 96px !important;
|
||||||
|
height: 68px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-guide-example .ant-image-img {
|
||||||
|
width: 96px !important;
|
||||||
|
height: 68px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-guide-copy .ant-typography:first-child {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-guide-copy .ant-typography:last-child {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.collect-link-success {
|
.collect-link-success {
|
||||||
min-height: 320px;
|
min-height: 320px;
|
||||||
padding: 16px 4px 4px;
|
padding: 16px 4px 4px;
|
||||||
@@ -898,6 +1221,15 @@
|
|||||||
grid-template-columns: 88px minmax(0, 1fr);
|
grid-template-columns: 88px minmax(0, 1fr);
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.collect-link-order-meta,
|
||||||
|
.collect-link-submitted-fields {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collect-link-order-meta {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== 兑换中心 (Redeem Page) ===== */
|
/* ===== 兑换中心 (Redeem Page) ===== */
|
||||||
|
|||||||
Reference in New Issue
Block a user