From 15396822b57f788aba6df6c017147c94baf928ef Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 22 Aug 2026 14:31:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=AC=E5=9C=B0Mock=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=BA=97=E9=93=BA=E8=87=AA=E5=8A=A8=E8=AE=B0=E5=BF=86?= =?UTF-8?q?=E6=B2=89=E6=B7=80=E4=B8=BA=E4=B8=8B=E6=8B=89=E5=8F=AF=E9=80=89?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/src/config/app-config-keys.ts | 1 + .../routes/admin/worker-platform/orders.ts | 11 ++++ .../admin-mock-custom-shop-service.ts | 51 +++++++++++++++++++ .../admin-mock-work-order-service.ts | 7 +++ .../src/services/worker-platform/index.ts | 1 + .../admin/panels/WorkOrderPanelParts.tsx | 19 +++++-- .../src/services/admin/worker-platform.ts | 4 ++ 7 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 apps/backend/src/services/worker-platform/admin-mock-custom-shop-service.ts diff --git a/apps/backend/src/config/app-config-keys.ts b/apps/backend/src/config/app-config-keys.ts index aee955d0..f5579e33 100644 --- a/apps/backend/src/config/app-config-keys.ts +++ b/apps/backend/src/config/app-config-keys.ts @@ -7,6 +7,7 @@ export const APP_CONFIG_KEYS = { workerProductMatch: 'worker_product_match', workerAnnouncement: 'worker_announcement', workerHall: 'worker_hall', + workerMockShops: 'worker_mock_shops', cloudtentaclesSources: 'cloudtentacles_sources', cloudtentaclesSession: 'cloudtentacles_session', cloudtentaclesOverrideRules: 'cloudtentacles_override_rules', diff --git a/apps/backend/src/routes/admin/worker-platform/orders.ts b/apps/backend/src/routes/admin/worker-platform/orders.ts index c2f867d0..fcd692cd 100644 --- a/apps/backend/src/routes/admin/worker-platform/orders.ts +++ b/apps/backend/src/routes/admin/worker-platform/orders.ts @@ -9,6 +9,7 @@ import { deleteAdminWorkOrder, getAdminWorkOrderEvents, getAdminWorkOrderSharing, + listAdminMockCustomShops, listAdminWorkOrders, pinAdminWorkOrder, publishAdminWorkOrder, @@ -35,6 +36,16 @@ router.get( }), ) +router.get( + '/worker-platform/orders/mock/custom-shops', + requireAdminRoles(['admin', 'operator', 'support']), + createJsonHandler(() => ({ items: listAdminMockCustomShops() }), { + successMessage: 'ok', + errorMessage: '读取自定义店铺失败', + scope: '[admin/worker-platform/orders/mock/custom-shops]', + }), +) + router.post( '/worker-platform/orders/mock', requireAdminRoles(['admin', 'operator', 'support']), diff --git a/apps/backend/src/services/worker-platform/admin-mock-custom-shop-service.ts b/apps/backend/src/services/worker-platform/admin-mock-custom-shop-service.ts new file mode 100644 index 00000000..b440fbe7 --- /dev/null +++ b/apps/backend/src/services/worker-platform/admin-mock-custom-shop-service.ts @@ -0,0 +1,51 @@ +import { APP_CONFIG_KEYS } from '../../config/app-config-keys.js' +import { readAppConfigEntry, saveAppConfigEntry } from '../config/app-config-store.js' + +/** 记忆的自定义店铺上限,超出后淘汰最早录入的。 */ +const MAX_CUSTOM_SHOPS = 50 + +type MockCustomShopsConfig = { + shops: string[] +} + +/** 本地 Mock 用自定义店铺列表:录入过的外部平台店铺沉淀为可选项。 */ +export function listAdminMockCustomShops(): string[] { + return readAppConfigEntry({ + configKey: APP_CONFIG_KEYS.workerMockShops, + fallback: createDefaultMockCustomShops, + normalize: normalizeMockCustomShops, + }).shops +} + +export async function rememberAdminMockCustomShop(shopName: string): Promise { + const name = normalizeShopName(shopName) + if (!name) return listAdminMockCustomShops() + const nextShops = [name, ...listAdminMockCustomShops().filter((item) => item !== name)].slice( + 0, + MAX_CUSTOM_SHOPS, + ) + const saved = await saveAppConfigEntry({ + configKey: APP_CONFIG_KEYS.workerMockShops, + value: { shops: nextShops }, + normalize: normalizeMockCustomShops, + }) + return saved.shops +} + +export function normalizeMockCustomShops(rawValue: unknown): MockCustomShopsConfig { + const shops = Array.isArray((rawValue as MockCustomShopsConfig | null)?.shops) + ? (rawValue as MockCustomShopsConfig).shops + : [] + const normalized = [...new Set(shops.map((item) => normalizeShopName(item)).filter(Boolean))] + return { shops: normalized.slice(0, MAX_CUSTOM_SHOPS) } +} + +function createDefaultMockCustomShops(): MockCustomShopsConfig { + return { shops: [] } +} + +function normalizeShopName(value: unknown) { + return String(value || '') + .trim() + .slice(0, 64) +} diff --git a/apps/backend/src/services/worker-platform/admin-mock-work-order-service.ts b/apps/backend/src/services/worker-platform/admin-mock-work-order-service.ts index 33fa9375..56054ce0 100644 --- a/apps/backend/src/services/worker-platform/admin-mock-work-order-service.ts +++ b/apps/backend/src/services/worker-platform/admin-mock-work-order-service.ts @@ -20,6 +20,7 @@ import { normalizeInteger, normalizeRequirementFields, } from './mappers.js' +import { rememberAdminMockCustomShop } from './admin-mock-custom-shop-service.js' import { ensureWorkerPlatformDefaults } from './worker-platform-defaults.js' import { normalizeWorkOrderTimeoutPolicy } from './work-order-timeout-policy.js' @@ -94,6 +95,10 @@ export async function createAdminMockWorkOrder(payload: JsonObject = {}) { errorCode: 'work_order_create_failed', }) } + if (input.isCustomShop && shop.shopName) { + // 自定义店铺录入后沉淀为可选项,下次直接下拉选择。 + await rememberAdminMockCustomShop(shop.shopName) + } await createWorkOrderEvent({ workOrderId: workOrder.id, actorType: 'admin', @@ -155,6 +160,7 @@ export function resolveAdminMockWorkOrderInput( } // 未命中已配置店铺时视为自定义店铺(用于其他平台订单手动录入), // 与真实店铺一样落入 material.source,保证链路与展示一致。 + const isCustomShop = !matchedShop const shop = matchedShop || ({ @@ -169,6 +175,7 @@ export function resolveAdminMockWorkOrderInput( platformOrderId, productName, shop, + isCustomShop, rewardAmount: requireMockWorkOrderAmount(payload.rewardAmount, '接单金额', 'reward_amount'), paymentAmount: requireMockWorkOrderAmount( payload.paymentAmount, diff --git a/apps/backend/src/services/worker-platform/index.ts b/apps/backend/src/services/worker-platform/index.ts index 553b41de..5ab9e6d2 100644 --- a/apps/backend/src/services/worker-platform/index.ts +++ b/apps/backend/src/services/worker-platform/index.ts @@ -36,6 +36,7 @@ export * from './admin-work-order-realtime-service.js' export * from './admin-work-order-material-service.js' export * from './admin-work-order-gift-service.js' export * from './admin-mock-work-order-service.js' +export * from './admin-mock-custom-shop-service.js' export * from './admin-work-order-management-service.js' export * from './admin-worker-platform-summary-service.js' export * from './admin-work-order-assignment-service.js' diff --git a/apps/frontend/src/pages/admin/panels/WorkOrderPanelParts.tsx b/apps/frontend/src/pages/admin/panels/WorkOrderPanelParts.tsx index 1f3a78d9..598b83af 100644 --- a/apps/frontend/src/pages/admin/panels/WorkOrderPanelParts.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkOrderPanelParts.tsx @@ -1,9 +1,9 @@ import { PlusOutlined } from '@ant-design/icons' -import { useQuery } from '@tanstack/react-query' +import { useQuery, useQueryClient } from '@tanstack/react-query' import { App, AutoComplete, Button, Card, Form, Input, InputNumber, Select, Switch } from 'antd' import { useState } from 'react' -import { createAdminMockWorkOrder } from '@/services/admin' +import { createAdminMockWorkOrder, fetchAdminMockCustomShops } from '@/services/admin' import { fetchAdminKuaishouIndustryShops } from '@/services/admin/kuaishou-industry' import type { WorkOrderStatistics } from '@/types/worker-platform' import { buildMockWorkOrderShopOptions } from '@/utils/mock-work-order' @@ -58,13 +58,24 @@ export function WorkOrderStatisticsBar({ statistics }: { statistics?: WorkOrderS export function MockWorkOrderForm({ onCreated }: { onCreated: () => Promise }) { const { message } = App.useApp() + const queryClient = useQueryClient() const [loading, setLoading] = useState(false) const [form] = Form.useForm() const shopsQuery = useQuery({ queryKey: ['admin-kuaishou-industry-shops', 'mock-work-order'], queryFn: fetchAdminKuaishouIndustryShops, }) - const shopOptions = buildMockWorkOrderShopOptions(shopsQuery.data?.data.shops || []) + const customShopsQuery = useQuery({ + queryKey: ['admin-mock-custom-shops'], + queryFn: fetchAdminMockCustomShops, + }) + const shopOptions = [ + ...buildMockWorkOrderShopOptions(shopsQuery.data?.data.shops || []), + ...(customShopsQuery.data?.data.items || []).map((name) => ({ + value: name, + label: `${name}(自定义)`, + })), + ] async function submit(values: { platformOrderId: string @@ -83,6 +94,8 @@ export function MockWorkOrderForm({ onCreated }: { onCreated: () => Promise('/api/v1/admin/worker-platform/orders/mock/custom-shops') +} + export function createAdminMockWorkOrder(payload: { platformOrderId: string sellerId: string