From e17f336e1c35606d7f25bb04d1c6b1d9840c6a28 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Wed, 4 Nov 2026 12:16:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(order-service):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E8=B5=84=E6=96=99=E7=A1=AE=E8=AE=A4=E4=B8=8E?= =?UTF-8?q?=E6=A0=B8=E5=AF=B9=E6=91=98=E8=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...34_order_service_material_confirmation.sql | 9 ++ .../order-service-profile-repo.ts | 11 +- .../order-service-profile-service.test.ts | 44 +++--- .../order-service-profile-service.ts | 69 ++++----- .../order-service-public-service.ts | 10 +- .../pages/admin/panels/OrderServicePanel.tsx | 129 +++++++---------- .../src/pages/public/OrderServicePage.tsx | 134 ++++++++++++++---- .../src/services/admin/worker-platform.ts | 13 +- apps/frontend/src/services/worker.ts | 11 +- apps/frontend/src/styles/admin.css | 65 ++++++--- apps/frontend/src/styles/client.css | 106 ++++++++++++-- docs/订单服务收集与状态页设计-2026-11-03.md | 2 +- 12 files changed, 378 insertions(+), 225 deletions(-) create mode 100644 apps/backend/src/db/migrations/134_order_service_material_confirmation.sql diff --git a/apps/backend/src/db/migrations/134_order_service_material_confirmation.sql b/apps/backend/src/db/migrations/134_order_service_material_confirmation.sql new file mode 100644 index 00000000..796c6f8f --- /dev/null +++ b/apps/backend/src/db/migrations/134_order_service_material_confirmation.sql @@ -0,0 +1,9 @@ +-- 134_order_service_material_confirmation.sql +-- 客户提交资料前的确认弹窗配置;同时移除已废弃的页面公告字段。 + +ALTER TABLE order_service_profiles + ADD COLUMN IF NOT EXISTS material_confirmation_json JSONB NOT NULL DEFAULT '{}'::jsonb, + DROP COLUMN IF EXISTS announcement_json; + +COMMENT ON COLUMN order_service_profiles.material_confirmation_json IS + '提交资料确认:enabled/title/content/confirmText/cancelText'; diff --git a/apps/backend/src/repositories/worker-platform/order-service-profile-repo.ts b/apps/backend/src/repositories/worker-platform/order-service-profile-repo.ts index 0e27ea20..3bccc07d 100644 --- a/apps/backend/src/repositories/worker-platform/order-service-profile-repo.ts +++ b/apps/backend/src/repositories/worker-platform/order-service-profile-repo.ts @@ -12,7 +12,7 @@ export type OrderServiceProfileRow = { fields_json: Array> screenshots_json: Record auto_publish_json: Record - announcement_json: Record + material_confirmation_json: Record submit_text: string success_text: string status: string @@ -99,7 +99,7 @@ export async function upsertOrderServiceProfile(input: { fields: Array> screenshots: Record autoPublish: Record - announcement: Record + materialConfirmation: Record submitText: string successText: string adminUserId: number | null @@ -108,7 +108,8 @@ export async function upsertOrderServiceProfile(input: { ` INSERT INTO order_service_profiles ( category_id, name, title, description, - guide_image_json, fields_json, screenshots_json, auto_publish_json, announcement_json, + guide_image_json, fields_json, screenshots_json, auto_publish_json, + material_confirmation_json, submit_text, success_text, status, created_by_admin_user_id ) VALUES ( $1, $2, $3, $4, @@ -123,7 +124,7 @@ export async function upsertOrderServiceProfile(input: { fields_json = EXCLUDED.fields_json, screenshots_json = EXCLUDED.screenshots_json, auto_publish_json = EXCLUDED.auto_publish_json, - announcement_json = EXCLUDED.announcement_json, + material_confirmation_json = EXCLUDED.material_confirmation_json, submit_text = EXCLUDED.submit_text, success_text = EXCLUDED.success_text, current_version_id = CASE @@ -143,7 +144,7 @@ export async function upsertOrderServiceProfile(input: { JSON.stringify(input.fields), JSON.stringify(input.screenshots), JSON.stringify(input.autoPublish), - JSON.stringify(input.announcement), + JSON.stringify(input.materialConfirmation), input.submitText, input.successText, input.adminUserId, diff --git a/apps/backend/src/services/worker-platform/order-service-profile-service.test.ts b/apps/backend/src/services/worker-platform/order-service-profile-service.test.ts index 498d0118..8be0a8aa 100644 --- a/apps/backend/src/services/worker-platform/order-service-profile-service.test.ts +++ b/apps/backend/src/services/worker-platform/order-service-profile-service.test.ts @@ -78,41 +78,33 @@ test('订单服务配置规范化:未知字段类型降级为文本,空下 assert.equal(normalized.fields[0]?.type, 'text') }) -test('订单服务页面公告:旧配置默认展示资料填写错误免责声明', () => { +test('订单服务提交确认:旧配置默认展示不可修改与资料错误免责声明', () => { const normalized = config() - assert.deepEqual(normalized.announcement, { + assert.deepEqual(normalized.materialConfirmation, { enabled: true, - title: '公告', - content: '由于个人资料填写错误,导致的赠送错误,店铺概不负责!', - fontSize: 14, - color: '#8a4b00', - backgroundColor: '#fff7e6', - fontWeight: 'bold', - textAlign: 'left', + title: '提交资料确认', + content: + '请确认资料填写准确无误。资料提交后不能修改,因个人资料填写错误导致的赠送错误,店铺概不负责!', + confirmText: '确认提交', + cancelText: '返回检查', }) }) -test('订单服务页面公告:支持自定义样式并拦截非法颜色与越界字号', () => { +test('订单服务提交确认:支持自定义标题、正文和按钮文字', () => { const normalized = config({ - announcement: { + materialConfirmation: { enabled: false, - title: '填写前必看', - content: '请确认资料无误后再提交。', - fontSize: 99, - color: 'red;position:fixed', - backgroundColor: '#123456', - fontWeight: 'normal', - textAlign: 'center', + title: '请再次核对', + content: '提交后无法修改,请返回检查。', + confirmText: '确认无误', + cancelText: '再检查一下', }, }) - assert.deepEqual(normalized.announcement, { + assert.deepEqual(normalized.materialConfirmation, { enabled: false, - title: '填写前必看', - content: '请确认资料无误后再提交。', - fontSize: 24, - color: '#8a4b00', - backgroundColor: '#123456', - fontWeight: 'normal', - textAlign: 'center', + title: '请再次核对', + content: '提交后无法修改,请返回检查。', + confirmText: '确认无误', + cancelText: '再检查一下', }) }) diff --git a/apps/backend/src/services/worker-platform/order-service-profile-service.ts b/apps/backend/src/services/worker-platform/order-service-profile-service.ts index d5f8b54e..0e19a2b1 100644 --- a/apps/backend/src/services/worker-platform/order-service-profile-service.ts +++ b/apps/backend/src/services/worker-platform/order-service-profile-service.ts @@ -47,15 +47,12 @@ export type OrderServiceAutoPublish = { retryMax: number } -export type OrderServiceAnnouncement = { +export type OrderServiceMaterialConfirmation = { enabled: boolean title: string content: string - fontSize: number - color: string - backgroundColor: string - fontWeight: 'normal' | 'bold' - textAlign: 'left' | 'center' + confirmText: string + cancelText: string } export type OrderServiceProfileConfig = { @@ -66,7 +63,7 @@ export type OrderServiceProfileConfig = { fields: OrderServiceField[] screenshots: OrderServiceScreenshots autoPublish: OrderServiceAutoPublish - announcement: OrderServiceAnnouncement + materialConfirmation: OrderServiceMaterialConfirmation submitText: string successText: string } @@ -88,15 +85,13 @@ const FIELD_TYPES = new Set([ 'date', ]) -export const DEFAULT_ORDER_SERVICE_ANNOUNCEMENT: OrderServiceAnnouncement = { +export const DEFAULT_ORDER_SERVICE_MATERIAL_CONFIRMATION: OrderServiceMaterialConfirmation = { enabled: true, - title: '公告', - content: '由于个人资料填写错误,导致的赠送错误,店铺概不负责!', - fontSize: 14, - color: '#8a4b00', - backgroundColor: '#fff7e6', - fontWeight: 'bold', - textAlign: 'left', + title: '提交资料确认', + content: + '请确认资料填写准确无误。资料提交后不能修改,因个人资料填写错误导致的赠送错误,店铺概不负责!', + confirmText: '确认提交', + cancelText: '返回检查', } export async function listAdminOrderServiceProfiles() { @@ -138,7 +133,9 @@ export async function saveAdminOrderServiceProfile(payload: JsonObject = {}) { const fields = normalizeFields(payload.fields ?? payload.fields_json) const screenshots = normalizeScreenshots(payload.screenshots ?? payload.screenshots_json) const autoPublish = normalizeAutoPublish(payload.autoPublish ?? payload.auto_publish_json) - const announcement = normalizeAnnouncement(payload.announcement ?? payload.announcement_json) + const materialConfirmation = normalizeMaterialConfirmation( + payload.materialConfirmation ?? payload.material_confirmation_json, + ) const guideImage = normalizeGuideImage(payload.guideImage ?? payload.guide_image_json) const adminUserId = Number(payload.adminUserId ?? payload.admin_user_id ?? 0) || null @@ -151,7 +148,7 @@ export async function saveAdminOrderServiceProfile(payload: JsonObject = {}) { fields, screenshots, autoPublish, - announcement, + materialConfirmation, submitText: text(payload.submitText, 30) || '提交资料', successText: text(payload.successText, 80) || '资料已提交,请等待处理', adminUserId, @@ -229,7 +226,7 @@ function mapAdminProfile(row: OrderServiceProfileRow) { fields: row.fields_json, screenshots: row.screenshots_json, autoPublish: row.auto_publish_json, - announcement: normalizeAnnouncement(row.announcement_json), + materialConfirmation: normalizeMaterialConfirmation(row.material_confirmation_json), submitText: row.submit_text, successText: row.success_text, status: row.status, @@ -248,7 +245,7 @@ export function buildProfileConfig(row: { fields_json: unknown screenshots_json: unknown auto_publish_json: unknown - announcement_json: unknown + material_confirmation_json: unknown submit_text: string success_text: string }): OrderServiceProfileConfig & { guideImage: Record } { @@ -260,7 +257,7 @@ export function buildProfileConfig(row: { fields: normalizeFields(row.fields_json), screenshots: normalizeScreenshots(row.screenshots_json), autoPublish: normalizeAutoPublish(row.auto_publish_json), - announcement: normalizeAnnouncement(row.announcement_json), + materialConfirmation: normalizeMaterialConfirmation(row.material_confirmation_json), submitText: row.submit_text || '提交资料', successText: row.success_text || '资料已提交,请等待处理', } @@ -312,7 +309,7 @@ export function normalizeProfileConfig(value: JsonObject): OrderServiceProfileCo fields: normalizeFields(value.fields), screenshots: normalizeScreenshots(value.screenshots), autoPublish: normalizeAutoPublish(value.autoPublish), - announcement: normalizeAnnouncement(value.announcement), + materialConfirmation: normalizeMaterialConfirmation(value.materialConfirmation), submitText: text(value.submitText, 30) || '提交资料', successText: text(value.successText, 80) || '资料已提交,请等待处理', } @@ -391,25 +388,18 @@ function normalizeAutoPublish(value: unknown): OrderServiceAutoPublish { } } -function normalizeAnnouncement(value: unknown): OrderServiceAnnouncement { +function normalizeMaterialConfirmation(value: unknown): OrderServiceMaterialConfirmation { const source = value && typeof value === 'object' ? (value as JsonObject) : {} return { enabled: source.enabled !== false, - title: text(source.title, 30) || DEFAULT_ORDER_SERVICE_ANNOUNCEMENT.title, - content: text(source.content, 500) || DEFAULT_ORDER_SERVICE_ANNOUNCEMENT.content, - fontSize: clampInteger(source.fontSize ?? source.font_size, 14, 12, 24), - color: normalizeHexColor(source.color, DEFAULT_ORDER_SERVICE_ANNOUNCEMENT.color), - backgroundColor: normalizeHexColor( - source.backgroundColor ?? source.background_color, - DEFAULT_ORDER_SERVICE_ANNOUNCEMENT.backgroundColor, - ), - fontWeight: - source.fontWeight === 'normal' || source.font_weight === 'normal' - ? 'normal' - : source.fontWeight === 'bold' || source.font_weight === 'bold' - ? 'bold' - : DEFAULT_ORDER_SERVICE_ANNOUNCEMENT.fontWeight, - textAlign: source.textAlign === 'center' || source.text_align === 'center' ? 'center' : 'left', + title: text(source.title, 40) || DEFAULT_ORDER_SERVICE_MATERIAL_CONFIRMATION.title, + content: text(source.content, 500) || DEFAULT_ORDER_SERVICE_MATERIAL_CONFIRMATION.content, + confirmText: + text(source.confirmText ?? source.confirm_text, 12) || + DEFAULT_ORDER_SERVICE_MATERIAL_CONFIRMATION.confirmText, + cancelText: + text(source.cancelText ?? source.cancel_text, 12) || + DEFAULT_ORDER_SERVICE_MATERIAL_CONFIRMATION.cancelText, } } @@ -418,11 +408,6 @@ function normalizeGuideImage(value: unknown): Record { return normalized ? { ...normalized } : {} } -function normalizeHexColor(value: unknown, fallback: string) { - const normalized = text(value, 7) - return /^#[0-9a-fA-F]{6}$/.test(normalized) ? normalized.toLowerCase() : fallback -} - function clampInteger(value: unknown, fallback: number, min: number, max: number) { const parsed = Number(value) if (!Number.isFinite(parsed)) return fallback diff --git a/apps/backend/src/services/worker-platform/order-service-public-service.ts b/apps/backend/src/services/worker-platform/order-service-public-service.ts index 061c68f3..254be8c1 100644 --- a/apps/backend/src/services/worker-platform/order-service-public-service.ts +++ b/apps/backend/src/services/worker-platform/order-service-public-service.ts @@ -31,7 +31,7 @@ import { throwPublicMaterialBlocked, } from './work-order-material-link-service.js' import { - DEFAULT_ORDER_SERVICE_ANNOUNCEMENT, + DEFAULT_ORDER_SERVICE_MATERIAL_CONFIRMATION, getOrderServiceProfileSnapshotByVersion, normalizeProfileConfig, type OrderServiceField, @@ -78,7 +78,7 @@ export type OrderServicePublicItem = { message: string canSubmit: boolean blockedReason: string - announcement?: OrderServiceProfileConfig['announcement'] + materialConfirmation?: OrderServiceProfileConfig['materialConfirmation'] fields?: OrderServiceField[] values?: Record screenshots?: Array> @@ -371,7 +371,9 @@ async function mapPublicOrderItem(order: WorkOrderRow): Promise uploadFile(file, scene, 'admin'), [], ) - const announcementPreview = Form.useWatch('announcement', form) || DEFAULT_ANNOUNCEMENT + const materialConfirmationPreview = + Form.useWatch('materialConfirmation', form) || DEFAULT_MATERIAL_CONFIRMATION useEffect(() => { if (selectedCategoryId !== null || categories.length === 0) return @@ -174,7 +173,7 @@ export default function OrderServicePanel() { successText: nextProfile?.successText || '资料已提交,请等待处理', screenshots: nextProfile?.screenshots || DEFAULT_SCREENSHOTS, autoPublish: nextProfile?.autoPublish || DEFAULT_AUTO_PUBLISH, - announcement: nextProfile?.announcement || DEFAULT_ANNOUNCEMENT, + materialConfirmation: nextProfile?.materialConfirmation || DEFAULT_MATERIAL_CONFIRMATION, }) setFields(normalizeFieldOrder(nextProfile?.fields || [])) setGuideImage(nextProfile?.guideImage || null) @@ -247,7 +246,7 @@ export default function OrderServicePanel() { fields: normalizedFields, screenshots: values.screenshots, autoPublish: values.autoPublish, - announcement: values.announcement, + materialConfirmation: values.materialConfirmation, submitText: values.submitText, successText: values.successText, }) @@ -528,13 +527,15 @@ export default function OrderServicePanel() {
- 页面公告 - 显示在客户资料页顶部,可自定义内容和文字样式。 + 提交资料确认 + + 客户点击提交时弹窗确认,可自定义提示内容和按钮文字。 +
- 显示公告 + 启用确认 @@ -543,63 +544,33 @@ export default function OrderServicePanel() {
- - - + + + - - - + + + - - - - - - - - - - - - - + + + - + - +
@@ -903,28 +874,30 @@ function resolveProfileStatus(profile: AdminOrderServiceProfile | undefined) { return { label: '已归档', tone: 'muted' } } -function AnnouncementPreview({ announcement }: { announcement: OrderServiceAnnouncement }) { +function MaterialConfirmationPreview({ + confirmation, +}: { + confirmation: OrderServiceMaterialConfirmation +}) { return (
- - {announcement.title || '公告'} - -
- {announcement.content || '公告内容'} + {confirmation.title || '提交资料确认'} +
+ 商品:示例商品 + 游戏账号:customer@example.com + 区服:安卓 + 截图:2 张
- {!announcement.enabled ? 当前已关闭,不会在客户页显示 : null} +
+ {confirmation.content || '提交资料确认内容'} +
+
+ {confirmation.cancelText || '返回检查'} + {confirmation.confirmText || '确认提交'} +
+ {!confirmation.enabled ? 当前已关闭,点击提交将直接提交资料 : null}
) } diff --git a/apps/frontend/src/pages/public/OrderServicePage.tsx b/apps/frontend/src/pages/public/OrderServicePage.tsx index 7740ba55..c6de63d2 100644 --- a/apps/frontend/src/pages/public/OrderServicePage.tsx +++ b/apps/frontend/src/pages/public/OrderServicePage.tsx @@ -56,7 +56,7 @@ function resolveErrorTitle(code: string) { } export default function OrderServicePage() { - const { message } = App.useApp() + const { message, modal } = App.useApp() const navigate = useNavigate() const { orderNo: routeOrderNo } = useParams<{ orderNo?: string }>() const orderNo = String(routeOrderNo || '').trim() @@ -68,6 +68,7 @@ export default function OrderServicePage() { const [submittingId, setSubmittingId] = useState(0) const [screenshotsByOrder, setScreenshotsByOrder] = useState>({}) const submittingRef = useRef(false) + const confirmationOpenRef = useRef(false) const requestSeqRef = useRef(0) const loadedOrderRef = useRef('') @@ -146,9 +147,10 @@ export default function OrderServicePage() { void navigate(`/o/${encodeURIComponent(nextOrderNo)}`) } - async function handleSubmit(item: OrderServiceItem, values: Record) { - if (!orderNo || submittingRef.current) return + function handleSubmit(item: OrderServiceItem, values: Record) { + if (!orderNo || submittingRef.current || confirmationOpenRef.current) return const screenshots = screenshotsByOrder[item.workOrderId] ?? item.screenshots ?? [] + const normalizedValues = normalizeFormFieldValues(item, values) const screenshotRule = item.screenshotsRule if ( screenshotRule?.enabled && @@ -159,12 +161,51 @@ export default function OrderServicePage() { return } + const confirmation = item.materialConfirmation + if (!confirmation?.enabled) { + void submitMaterial(item, normalizedValues, screenshots) + return + } + + confirmationOpenRef.current = true + modal.confirm({ + title: confirmation.title || '提交资料确认', + content: ( + + ), + okText: confirmation.confirmText || '确认提交', + cancelText: confirmation.cancelText || '返回检查', + centered: true, + maskClosable: false, + width: 560, + onOk: () => submitMaterial(item, normalizedValues, screenshots), + afterClose: () => { + confirmationOpenRef.current = false + }, + }) + } + + async function submitMaterial( + item: OrderServiceItem, + fields: Record, + screenshots: UploadedFile[], + ) { + if (!orderNo || submittingRef.current) return submittingRef.current = true setSubmittingId(item.workOrderId) try { const response = await submitOrderServiceMaterial(orderNo, { workOrderId: item.workOrderId, - fields: normalizeFormFieldValues(item, values), + fields, screenshots, }) setSubmittedNames((current) => [ @@ -261,31 +302,6 @@ export default function OrderServicePage() {
- {pageConfig?.announcement?.enabled && pageConfig.announcement.content ? ( -
- - {pageConfig.announcement.title || '公告'} - -
- {pageConfig.announcement.content} -
-
- ) : null} - {submittedNames.length > 0 ? ( [number] return } +function MaterialConfirmationSummary({ + item, + platformOrderId, + values, + screenshots, + notice, +}: { + item: OrderServiceItem + platformOrderId: string + values: Record + screenshots: UploadedFile[] + notice: string +}) { + const screenshotRule = item.screenshotsRule + return ( +
+
+ {item.productName || item.categoryName || '订单商品'} + 订单号:{platformOrderId} +
+ + {item.fields && item.fields.length > 0 ? ( +
+ {item.fields.map((field) => { + const value = String(values[field.key] || '').trim() + return ( +
+ {field.label} + {value || '未填写'} +
+ ) + })} +
+ ) : null} + + {screenshotRule?.enabled ? ( +
+
+ {screenshotRule.label || '截图'} + {screenshots.length} 张 +
+ {screenshots.length > 0 ? ( +
+ {screenshots.map((file) => ( + {file.filename + ))} +
+ ) : null} +
+ ) : null} + +
{notice}
+
+ ) +} + function normalizeFormFieldValues(item: OrderServiceItem, values: Record) { const fields = new Map((item.fields || []).map((field) => [field.key, field])) return Object.fromEntries( diff --git a/apps/frontend/src/services/admin/worker-platform.ts b/apps/frontend/src/services/admin/worker-platform.ts index 3dc4e05b..1aaf5a5b 100644 --- a/apps/frontend/src/services/admin/worker-platform.ts +++ b/apps/frontend/src/services/admin/worker-platform.ts @@ -1120,15 +1120,12 @@ export type OrderServiceField = { sortOrder?: number } -export type OrderServiceAnnouncement = { +export type OrderServiceMaterialConfirmation = { enabled: boolean title: string content: string - fontSize: number - color: string - backgroundColor: string - fontWeight: 'normal' | 'bold' - textAlign: 'left' | 'center' + confirmText: string + cancelText: string } export type AdminOrderServiceProfile = { @@ -1152,7 +1149,7 @@ export type AdminOrderServiceProfile = { requireVoucherConsume: boolean retryMax: number } - announcement: OrderServiceAnnouncement + materialConfirmation: OrderServiceMaterialConfirmation submitText: string successText: string status: 'draft' | 'published' | 'archived' | string @@ -1199,7 +1196,7 @@ export function saveAdminOrderServiceProfile(payload: { requireVoucherConsume: boolean retryMax: number } - announcement: OrderServiceAnnouncement + materialConfirmation: OrderServiceMaterialConfirmation submitText?: string successText?: string }) { diff --git a/apps/frontend/src/services/worker.ts b/apps/frontend/src/services/worker.ts index da15409f..63cad84f 100644 --- a/apps/frontend/src/services/worker.ts +++ b/apps/frontend/src/services/worker.ts @@ -576,15 +576,12 @@ export type OrderServiceScreenshots = { helpText?: string } -export type OrderServiceAnnouncement = { +export type OrderServiceMaterialConfirmation = { enabled: boolean title: string content: string - fontSize: number - color: string - backgroundColor: string - fontWeight: 'normal' | 'bold' - textAlign: 'left' | 'center' + confirmText: string + cancelText: string } export type OrderServiceItem = { @@ -600,7 +597,7 @@ export type OrderServiceItem = { message: string canSubmit: boolean blockedReason: '' | 'already_submitted' | 'status' | 'assigned' | 'shares' - announcement?: OrderServiceAnnouncement + materialConfirmation?: OrderServiceMaterialConfirmation fields?: OrderServiceField[] values?: Record screenshots?: UploadedFile[] diff --git a/apps/frontend/src/styles/admin.css b/apps/frontend/src/styles/admin.css index 1f3ada47..faa5b033 100644 --- a/apps/frontend/src/styles/admin.css +++ b/apps/frontend/src/styles/admin.css @@ -1843,31 +1843,64 @@ margin-top: 6px; } -.order-service-color-input { - height: 32px; - padding: 2px 3px; - cursor: pointer; -} - -.order-service-announcement-preview { +.order-service-confirmation-preview { display: grid; - gap: 4px; - padding: 12px 14px; - border: 1px solid rgba(138, 75, 0, 0.16); - border-left-width: 4px; - border-radius: 9px; + gap: 10px; + max-width: 560px; + padding: 16px; + color: #1f2937; + background: #ffffff; + border: 1px solid #e5e7eb; + border-radius: 10px; + box-shadow: 0 6px 20px rgba(15, 23, 42, 0.06); } -.order-service-announcement-preview-title { - color: inherit !important; +.order-service-confirmation-preview-content { + color: #64748b; font-size: 13px; + line-height: 1.65; + white-space: pre-wrap; + overflow-wrap: anywhere; } -.order-service-announcement-preview.is-disabled { +.order-service-confirmation-preview-summary { + display: grid; + gap: 6px; + padding: 10px 12px; + color: #334155; + font-size: 12px; + background: #f8fafc; + border: 1px solid #edf2f7; + border-radius: 8px; +} + +.order-service-confirmation-preview-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 6px; + border-top: 1px solid #f1f5f9; +} + +.order-service-confirmation-preview-actions span { + padding: 3px 11px; + color: #475569; + font-size: 12px; + border: 1px solid #dbe2ea; + border-radius: 6px; +} + +.order-service-confirmation-preview-actions .is-primary { + color: #ffffff; + background: #1677ff; + border-color: #1677ff; +} + +.order-service-confirmation-preview.is-disabled { opacity: 0.55; } -.order-service-announcement-preview > span:last-child { +.order-service-confirmation-preview > small { font-size: 11px; } diff --git a/apps/frontend/src/styles/client.css b/apps/frontend/src/styles/client.css index 3e0b3f46..016251fd 100644 --- a/apps/frontend/src/styles/client.css +++ b/apps/frontend/src/styles/client.css @@ -515,21 +515,104 @@ gap: 12px; } -.collect-link-announcement { - padding: 12px 14px; - border: 1px solid rgba(138, 75, 0, 0.16); - border-left-width: 4px; +.collect-link-confirm { + display: grid; + gap: 12px; + max-height: 58vh; + padding-top: 4px; + padding-right: 4px; + overflow-y: auto; +} + +.collect-link-confirm-meta { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 4px 16px; + color: #64748b; + font-size: 12px; +} + +.collect-link-confirm-meta span:first-child { + color: #0f172a; + font-size: 14px; + font-weight: 600; +} + +.collect-link-confirm-fields { + overflow: hidden; + border: 1px solid #e8edf4; border-radius: 9px; } -.collect-link-announcement-title { - display: block; - margin-bottom: 4px; +.collect-link-confirm-field { + display: grid; + grid-template-columns: 110px minmax(0, 1fr); + gap: 12px; + padding: 9px 12px; font-size: 13px; + border-bottom: 1px solid #eef2f7; } -.collect-link-announcement-content { - line-height: 1.65; +.collect-link-confirm-field:last-child { + border-bottom: 0; +} + +.collect-link-confirm-field span, +.collect-link-confirm-screenshots span { + color: #64748b; +} + +.collect-link-confirm-field strong, +.collect-link-confirm-screenshots strong { + color: #0f172a; + font-weight: 500; + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.collect-link-confirm-field strong.is-empty { + color: #94a3b8; +} + +.collect-link-confirm-screenshots { + display: grid; + gap: 8px; + padding: 10px 12px; + font-size: 13px; + background: #f8fafc; + border-radius: 9px; +} + +.collect-link-confirm-screenshots > div:first-child { + display: flex; + justify-content: space-between; + gap: 12px; +} + +.collect-link-confirm-images { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.collect-link-confirm-images img { + width: 58px; + height: 58px; + object-fit: cover; + background: #ffffff; + border: 1px solid #dbe3ee; + border-radius: 8px; +} + +.collect-link-confirm-content { + padding: 10px 12px; + color: #8a4b00; + font-size: 13px; + line-height: 1.7; + background: #fff7e6; + border: 1px solid #ffe7ba; + border-radius: 9px; white-space: pre-wrap; overflow-wrap: anywhere; } @@ -687,6 +770,11 @@ .collect-link-success-products { padding: 10px 16px; } + + .collect-link-confirm-field { + grid-template-columns: 88px minmax(0, 1fr); + padding: 8px 10px; + } } /* ===== 兑换中心 (Redeem Page) ===== */ diff --git a/docs/订单服务收集与状态页设计-2026-11-03.md b/docs/订单服务收集与状态页设计-2026-11-03.md index 9e688d0c..3528042f 100644 --- a/docs/订单服务收集与状态页设计-2026-11-03.md +++ b/docs/订单服务收集与状态页设计-2026-11-03.md @@ -92,7 +92,7 @@ work_orders.service_profile_snapshot_json - 按平台订单号查询一单多工单。 - 动态文本、长文本、下拉、单选、数字、日期字段。 - 一张分类引导图。 -- 页面公告,可配置标题、正文、字号、文字颜色、背景颜色、字重和对齐方式;旧配置默认显示资料填写错误免责声明。 +- 提交资料确认弹窗,先逐项汇总用户填写的字段值和截图缩略图,再展示可配置的标题、正文、确认/取消按钮文字;旧配置默认提示资料提交后不可修改,并说明资料填写错误的免责规则。 - 图片截图规则,必填时最少 1 张。 - 提交后禁止再次修改。 - 每 15 秒、页面重新可见或窗口重新聚焦时刷新状态。