From 3969703ac8793418133da10c9ddf46874fb77cc1 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Wed, 4 Nov 2026 13:04:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(order-service):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E8=AE=A2=E5=8D=95=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../137_order_service_status_steps.sql | 8 +++ .../order-service-profile-repo.ts | 10 ++-- .../order-service-profile-service.test.ts | 25 +++++++++ .../order-service-profile-service.ts | 42 +++++++++++++++ .../order-service-public-service.ts | 16 +++++- .../pages/admin/panels/OrderServicePanel.tsx | 54 +++++++++++++++++++ .../src/pages/public/OrderServicePage.tsx | 32 ++++++++--- .../src/services/admin/worker-platform.ts | 9 ++++ apps/frontend/src/services/worker.ts | 8 +++ apps/frontend/src/styles/admin.css | 26 ++++++++- apps/frontend/src/styles/client.css | 7 +++ docs/订单服务收集与状态页设计-2026-11-03.md | 1 + 12 files changed, 226 insertions(+), 12 deletions(-) create mode 100644 apps/backend/src/db/migrations/137_order_service_status_steps.sql diff --git a/apps/backend/src/db/migrations/137_order_service_status_steps.sql b/apps/backend/src/db/migrations/137_order_service_status_steps.sql new file mode 100644 index 00000000..e2ef851a --- /dev/null +++ b/apps/backend/src/db/migrations/137_order_service_status_steps.sql @@ -0,0 +1,8 @@ +-- 137_order_service_status_steps.sql +-- 客户订单状态进度的四个节点文案,可按分类独立配置。 + +ALTER TABLE order_service_profiles + ADD COLUMN IF NOT EXISTS status_steps_json JSONB NOT NULL DEFAULT '{}'::jsonb; + +COMMENT ON COLUMN order_service_profiles.status_steps_json IS + '客户状态进度文案:submitted/scheduled/processing/completed 的 title 与 description'; 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 4b075bd7..91d0cd96 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 @@ -13,6 +13,7 @@ export type OrderServiceProfileRow = { screenshots_json: Record auto_publish_json: Record material_confirmation_json: Record + status_steps_json: Record submit_text: string success_text: string post_submit_text: string @@ -101,6 +102,7 @@ export async function upsertOrderServiceProfile(input: { screenshots: Record autoPublish: Record materialConfirmation: Record + statusSteps: Record submitText: string successText: string postSubmitText: string @@ -111,12 +113,12 @@ 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, - material_confirmation_json, + material_confirmation_json, status_steps_json, submit_text, success_text, post_submit_text, status, created_by_admin_user_id ) VALUES ( $1, $2, $3, $4, - $5::jsonb, $6::jsonb, $7::jsonb, $8::jsonb, $9::jsonb, - $10, $11, $12, 'draft', $13 + $5::jsonb, $6::jsonb, $7::jsonb, $8::jsonb, $9::jsonb, $10::jsonb, + $11, $12, $13, 'draft', $14 ) ON CONFLICT (category_id) DO UPDATE SET name = EXCLUDED.name, @@ -127,6 +129,7 @@ export async function upsertOrderServiceProfile(input: { screenshots_json = EXCLUDED.screenshots_json, auto_publish_json = EXCLUDED.auto_publish_json, material_confirmation_json = EXCLUDED.material_confirmation_json, + status_steps_json = EXCLUDED.status_steps_json, submit_text = EXCLUDED.submit_text, success_text = EXCLUDED.success_text, post_submit_text = EXCLUDED.post_submit_text, @@ -148,6 +151,7 @@ export async function upsertOrderServiceProfile(input: { JSON.stringify(input.screenshots), JSON.stringify(input.autoPublish), JSON.stringify(input.materialConfirmation), + JSON.stringify(input.statusSteps), input.submitText, input.successText, input.postSubmitText, 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 57c06114..6c72f688 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 @@ -94,6 +94,31 @@ test('订单服务提交后说明:旧配置使用默认文案,新配置支 ) }) +test('客户状态进度:四个节点支持独立标题和说明,旧配置使用默认文案', () => { + assert.deepEqual(config().statusSteps, { + submitted: { title: '提交资料', description: '' }, + scheduled: { title: '客服安排', description: '' }, + processing: { title: '订单处理', description: '' }, + completed: { title: '交付完成', description: '' }, + }) + assert.deepEqual( + config({ + statusSteps: { + submitted: { title: '资料已交', description: '请保持联系方式畅通' }, + scheduled: { title: '客服受理', description: '客服正在核对订单' }, + processing: { title: '正在安排', description: '请耐心等待接单' }, + completed: { title: '处理完成', description: '请检查邮箱' }, + }, + }).statusSteps, + { + submitted: { title: '资料已交', description: '请保持联系方式畅通' }, + scheduled: { title: '客服受理', description: '客服正在核对订单' }, + processing: { title: '正在安排', description: '请耐心等待接单' }, + completed: { title: '处理完成', description: '请检查邮箱' }, + }, + ) +}) + test('订单服务提交确认:旧配置默认展示不可修改与资料错误免责声明', () => { const normalized = config() assert.deepEqual(normalized.materialConfirmation, { 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 9dad4dcc..6513dfb9 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 @@ -57,6 +57,13 @@ export type OrderServiceMaterialConfirmation = { cancelText: string } +export type OrderServiceStatusSteps = { + submitted: { title: string; description: string } + scheduled: { title: string; description: string } + processing: { title: string; description: string } + completed: { title: string; description: string } +} + export type OrderServiceProfileConfig = { name: string title: string @@ -66,6 +73,7 @@ export type OrderServiceProfileConfig = { screenshots: OrderServiceScreenshots autoPublish: OrderServiceAutoPublish materialConfirmation: OrderServiceMaterialConfirmation + statusSteps: OrderServiceStatusSteps submitText: string successText: string postSubmitText: string @@ -99,6 +107,13 @@ export const DEFAULT_ORDER_SERVICE_MATERIAL_CONFIRMATION: OrderServiceMaterialCo export const DEFAULT_ORDER_SERVICE_POST_SUBMIT_TEXT = '资料提交后如需修改,请联系客服。' +export const DEFAULT_ORDER_SERVICE_STATUS_STEPS: OrderServiceStatusSteps = { + submitted: { title: '提交资料', description: '' }, + scheduled: { title: '客服安排', description: '' }, + processing: { title: '订单处理', description: '' }, + completed: { title: '交付完成', description: '' }, +} + export async function listAdminOrderServiceProfiles() { const [categories, profiles] = await Promise.all([ listAllWorkCategories(), @@ -141,6 +156,7 @@ export async function saveAdminOrderServiceProfile(payload: JsonObject = {}) { const materialConfirmation = normalizeMaterialConfirmation( payload.materialConfirmation ?? payload.material_confirmation_json, ) + const statusSteps = normalizeStatusSteps(payload.statusSteps ?? payload.status_steps_json) const guideImage = normalizeGuideImage(payload.guideImage ?? payload.guide_image_json) const adminUserId = Number(payload.adminUserId ?? payload.admin_user_id ?? 0) || null @@ -154,6 +170,7 @@ export async function saveAdminOrderServiceProfile(payload: JsonObject = {}) { screenshots, autoPublish, materialConfirmation, + statusSteps, submitText: text(payload.submitText, 30) || '提交资料', successText: text(payload.successText, 80) || '资料已提交,请等待处理', postSubmitText: text(payload.postSubmitText, 500) || DEFAULT_ORDER_SERVICE_POST_SUBMIT_TEXT, @@ -233,6 +250,7 @@ function mapAdminProfile(row: OrderServiceProfileRow) { screenshots: row.screenshots_json, autoPublish: row.auto_publish_json, materialConfirmation: normalizeMaterialConfirmation(row.material_confirmation_json), + statusSteps: normalizeStatusSteps(row.status_steps_json), submitText: row.submit_text, successText: row.success_text, postSubmitText: buildProfileConfig(row).postSubmitText, @@ -253,6 +271,7 @@ export function buildProfileConfig(row: { screenshots_json: unknown auto_publish_json: unknown material_confirmation_json: unknown + status_steps_json: unknown submit_text: string success_text: string post_submit_text: string @@ -266,6 +285,7 @@ export function buildProfileConfig(row: { screenshots: normalizeScreenshots(row.screenshots_json), autoPublish: normalizeAutoPublish(row.auto_publish_json), materialConfirmation: normalizeMaterialConfirmation(row.material_confirmation_json), + statusSteps: normalizeStatusSteps(row.status_steps_json), submitText: row.submit_text || '提交资料', successText: row.success_text || '资料已提交,请等待处理', postSubmitText: text(row.post_submit_text, 500) || DEFAULT_ORDER_SERVICE_POST_SUBMIT_TEXT, @@ -319,6 +339,7 @@ export function normalizeProfileConfig(value: JsonObject): OrderServiceProfileCo screenshots: normalizeScreenshots(value.screenshots), autoPublish: normalizeAutoPublish(value.autoPublish), materialConfirmation: normalizeMaterialConfirmation(value.materialConfirmation), + statusSteps: normalizeStatusSteps(value.statusSteps), submitText: text(value.submitText, 30) || '提交资料', successText: text(value.successText, 80) || '资料已提交,请等待处理', postSubmitText: text(value.postSubmitText, 500) || DEFAULT_ORDER_SERVICE_POST_SUBMIT_TEXT, @@ -417,6 +438,27 @@ function normalizeMaterialConfirmation(value: unknown): OrderServiceMaterialConf } } +function normalizeStatusSteps(value: unknown): OrderServiceStatusSteps { + const source = value && typeof value === 'object' ? (value as JsonObject) : {} + return { + submitted: normalizeStatusStep(source.submitted, DEFAULT_ORDER_SERVICE_STATUS_STEPS.submitted), + scheduled: normalizeStatusStep(source.scheduled, DEFAULT_ORDER_SERVICE_STATUS_STEPS.scheduled), + processing: normalizeStatusStep( + source.processing, + DEFAULT_ORDER_SERVICE_STATUS_STEPS.processing, + ), + completed: normalizeStatusStep(source.completed, DEFAULT_ORDER_SERVICE_STATUS_STEPS.completed), + } +} + +function normalizeStatusStep(value: unknown, fallback: { title: string; description: string }) { + const source = value && typeof value === 'object' ? (value as JsonObject) : {} + return { + title: text(source.title, 20) || fallback.title, + description: text(source.description, 80), + } +} + function normalizeGuideImage(value: unknown): Record { const normalized = normalizeUploadedFiles([value])[0] return normalized ? { ...normalized } : {} 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 c411b57d..421d0b16 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 @@ -36,6 +36,8 @@ import { import { DEFAULT_ORDER_SERVICE_MATERIAL_CONFIRMATION, DEFAULT_ORDER_SERVICE_POST_SUBMIT_TEXT, + DEFAULT_ORDER_SERVICE_STATUS_STEPS, + getPublishedOrderServiceProfileSnapshotByCategory, getOrderServiceProfileSnapshotByVersion, normalizeProfileConfig, type OrderServiceField, @@ -85,6 +87,7 @@ export type OrderServicePublicItem = { canSubmit: boolean blockedReason: string materialConfirmation?: OrderServiceProfileConfig['materialConfirmation'] + statusSteps: OrderServiceProfileConfig['statusSteps'] fields?: OrderServiceField[] values?: Record screenshots?: Array> @@ -412,6 +415,9 @@ async function resolveTarget(platformOrderId: string, workOrderIdInput: unknown) async function mapPublicOrderItem(order: WorkOrderRow): Promise { const state = resolvePublicMaterialOrderState(order) const profile = await resolveProfileConfig(order) + const currentPresentationProfile = order.category_id + ? await getPublishedOrderServiceProfileSnapshotByCategory(Number(order.category_id)) + : null const material = safeParseJson(order.material_json) const collect = material.collect && typeof material.collect === 'object' ? material.collect : {} const values = normalizeSubmittedFields(collect.fields || {}) @@ -434,7 +440,10 @@ async function mapPublicOrderItem(order: WorkOrderRow): Promise = [ + { key: 'submitted', label: '第 1 步 · 提交资料' }, + { key: 'scheduled', label: '第 2 步 · 客服安排' }, + { key: 'processing', label: '第 3 步 · 订单处理' }, + { key: 'completed', label: '第 4 步 · 交付完成' }, +] + type FieldPreset = Omit const FIELD_PRESETS: Record = { @@ -287,6 +306,7 @@ export default function OrderServicePanel() { screenshots: nextProfile?.screenshots || DEFAULT_SCREENSHOTS, autoPublish: nextProfile?.autoPublish || DEFAULT_AUTO_PUBLISH, materialConfirmation: nextProfile?.materialConfirmation || DEFAULT_MATERIAL_CONFIRMATION, + statusSteps: nextProfile?.statusSteps || DEFAULT_STATUS_STEPS, }) setFields( normalizeFieldOrder( @@ -395,6 +415,7 @@ export default function OrderServicePanel() { screenshots: values.screenshots, autoPublish: values.autoPublish, materialConfirmation: values.materialConfirmation, + statusSteps: values.statusSteps, submitText: values.submitText, successText: values.successText, postSubmitText: values.postSubmitText, @@ -687,6 +708,39 @@ export default function OrderServicePanel() { +
+
+
+ 客户状态进度文案 + + 四个节点独立展示;标题保持简短,补充说明可填写引导文案。 + +
+
+
+ {STATUS_STEP_OPTIONS.map((step) => ( +
+ {step.label} + + + + + + +
+ ))} +
+
+
diff --git a/apps/frontend/src/pages/public/OrderServicePage.tsx b/apps/frontend/src/pages/public/OrderServicePage.tsx index 6bb59af6..8ad5abe5 100644 --- a/apps/frontend/src/pages/public/OrderServicePage.tsx +++ b/apps/frontend/src/pages/public/OrderServicePage.tsx @@ -51,12 +51,12 @@ const STATUS_STEP_INDEX: Record = { accepted: 3, problem: 2, } -const STATUS_STEP_ITEMS = [ - { title: '提交资料' }, - { title: '客服安排' }, - { title: '订单处理' }, - { title: '交付完成' }, -] +const DEFAULT_STATUS_STEPS: OrderServiceItem['statusSteps'] = { + submitted: { title: '提交资料', description: '' }, + scheduled: { title: '客服安排', description: '' }, + processing: { title: '订单处理', description: '' }, + completed: { title: '交付完成', description: '' }, +} const STATUS_TAG_COLORS: Record = { pending_material: 'orange', unassigned: 'blue', @@ -399,6 +399,7 @@ function OrderServiceStatusCard({ item }: { item: OrderServiceItem }) { ].filter((row): row is { label: string; value: string } => Boolean(row?.value)) const acceptanceImageUrls = item.acceptanceImageUrls || [] const showSteps = item.state !== 'cancelled' + const statusSteps = item.statusSteps || DEFAULT_STATUS_STEPS return ( @@ -412,7 +413,24 @@ function OrderServiceStatusCard({ item }: { item: OrderServiceItem }) { screenshots?: UploadedFile[] diff --git a/apps/frontend/src/styles/admin.css b/apps/frontend/src/styles/admin.css index 66825520..2cc29b3c 100644 --- a/apps/frontend/src/styles/admin.css +++ b/apps/frontend/src/styles/admin.css @@ -1843,6 +1843,25 @@ margin-top: 6px; } +.order-service-status-step-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.order-service-status-step-card { + display: grid; + gap: 4px; + padding: 12px; + background: #f8fafc; + border: 1px solid #e5eaf1; + border-radius: 10px; +} + +.order-service-status-step-card .ant-form-item { + margin-bottom: 6px; +} + .order-service-confirmation-preview { display: grid; gap: 10px; @@ -2083,7 +2102,8 @@ .order-service-field-extra, .order-service-switch-grid, - .order-service-switch-grid.is-auto-publish { + .order-service-switch-grid.is-auto-publish, + .order-service-status-step-grid { grid-template-columns: 1fr 1fr; } @@ -2096,4 +2116,8 @@ grid-column: 1; grid-row: auto; } + + .order-service-status-step-grid { + grid-template-columns: 1fr; + } } diff --git a/apps/frontend/src/styles/client.css b/apps/frontend/src/styles/client.css index 0ccf2136..2b131cd3 100644 --- a/apps/frontend/src/styles/client.css +++ b/apps/frontend/src/styles/client.css @@ -534,6 +534,13 @@ font-size: 12px !important; } +.collect-link-status-steps .ant-steps-item-description { + max-width: 140px; + font-size: 11px !important; + line-height: 1.45; + white-space: normal; +} + .collect-link-status-message { padding: 10px 12px; color: #475569; diff --git a/docs/订单服务收集与状态页设计-2026-11-03.md b/docs/订单服务收集与状态页设计-2026-11-03.md index f4c4d389..35f3410b 100644 --- a/docs/订单服务收集与状态页设计-2026-11-03.md +++ b/docs/订单服务收集与状态页设计-2026-11-03.md @@ -105,6 +105,7 @@ work_orders.service_profile_snapshot_json - 图片截图规则,必填时最少 1 张。 - 提交后禁止再次修改。 - 客户页底部可配置多行“提交后说明”,旧配置默认显示资料修改需联系客服。 +- 客户状态进度固定为四个节点,每个节点支持独立标题和补充说明。 - 每 15 秒、页面重新可见或窗口重新聚焦时刷新状态。 客户状态文案: