From 4bc661c1b57044d18225afb6a621a839257d1916 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 22 Aug 2026 14:39:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9D=82=E9=A1=B9=E9=85=8D=E7=BD=AE=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF=E7=AE=A1=E7=90=86=E6=9C=AC=E5=9C=B0Mock=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E5=BA=97=E9=93=BA=E6=94=AF=E6=8C=81=E5=A2=9E?= =?UTF-8?q?=E5=88=A0=E6=94=B9=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routes/admin/worker-platform/orders.ts | 58 ++++++++ .../admin-mock-custom-shop-service.ts | 70 ++++++++- .../pages/admin/AdminWorkerPlatformPage.tsx | 2 +- .../pages/admin/panels/AnnouncementPanel.tsx | 133 +++++++++++++++++- .../src/services/admin/worker-platform.ts | 19 +++ 5 files changed, 273 insertions(+), 9 deletions(-) diff --git a/apps/backend/src/routes/admin/worker-platform/orders.ts b/apps/backend/src/routes/admin/worker-platform/orders.ts index fcd692cd..5c860624 100644 --- a/apps/backend/src/routes/admin/worker-platform/orders.ts +++ b/apps/backend/src/routes/admin/worker-platform/orders.ts @@ -2,10 +2,12 @@ import { Router } from 'express' import { acceptAdminWorkOrders, + addAdminMockCustomShop, assignAdminWorkOrderToWorker, cancelAdminWorkOrder, confirmAdminWorkOrderFriendAdded, createAdminMockWorkOrder, + deleteAdminMockCustomShop, deleteAdminWorkOrder, getAdminWorkOrderEvents, getAdminWorkOrderSharing, @@ -13,6 +15,7 @@ import { listAdminWorkOrders, pinAdminWorkOrder, publishAdminWorkOrder, + renameAdminMockCustomShop, reopenAdminCancelledWorkOrder, reprocessAdminKuaishouSendCodeWorkOrders, resetAdminWorkOrderGiftMaterial, @@ -46,6 +49,61 @@ router.get( }), ) +router.post( + '/worker-platform/orders/mock/custom-shops', + requireAdminRoles(['admin']), + createJsonHandler((req) => addAdminMockCustomShop(String(req.body?.name || '')), { + successMessage: '自定义店铺已新增', + errorMessage: '新增自定义店铺失败', + scope: '[admin/worker-platform/orders/mock/custom-shops]', + audit: (req) => ({ + action: 'mock_custom_shop_added', + targetType: 'mock_custom_shop', + targetId: String(req.body?.name || ''), + data: {}, + }), + }), +) + +router.put( + '/worker-platform/orders/mock/custom-shops', + requireAdminRoles(['admin']), + createJsonHandler( + (req) => + renameAdminMockCustomShop( + String(req.body?.name || ''), + String(req.body?.newName || req.body?.nextName || ''), + ), + { + successMessage: '自定义店铺已重命名', + errorMessage: '重命名自定义店铺失败', + scope: '[admin/worker-platform/orders/mock/custom-shops]', + audit: (req) => ({ + action: 'mock_custom_shop_renamed', + targetType: 'mock_custom_shop', + targetId: String(req.body?.name || ''), + data: { newName: String(req.body?.newName || '') }, + }), + }, + ), +) + +router.delete( + '/worker-platform/orders/mock/custom-shops', + requireAdminRoles(['admin']), + createJsonHandler((req) => deleteAdminMockCustomShop(String(req.query?.name || '')), { + successMessage: '自定义店铺已删除', + errorMessage: '删除自定义店铺失败', + scope: '[admin/worker-platform/orders/mock/custom-shops]', + audit: (req) => ({ + action: 'mock_custom_shop_deleted', + targetType: 'mock_custom_shop', + targetId: String(req.query?.name || ''), + data: {}, + }), + }), +) + 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 index b440fbe7..0fe11952 100644 --- 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 @@ -1,4 +1,5 @@ import { APP_CONFIG_KEYS } from '../../config/app-config-keys.js' +import { createHttpError } from '../../utils/http.js' import { readAppConfigEntry, saveAppConfigEntry } from '../config/app-config-store.js' /** 记忆的自定义店铺上限,超出后淘汰最早录入的。 */ @@ -24,12 +25,49 @@ export async function rememberAdminMockCustomShop(shopName: string): Promise { + const name = requireShopName(shopName, '新增') + return saveMockCustomShops([name, ...listAdminMockCustomShops().filter((item) => item !== name)]) +} + +/** 重命名自定义店铺;新名称已存在时合并(移除旧名称)。 */ +export async function renameAdminMockCustomShop( + oldName: string, + nextName: string, +): Promise { + const currentShops = listAdminMockCustomShops() + const from = requireShopName(oldName, '重命名') + const to = requireShopName(nextName, '重命名为') + if (!currentShops.includes(from)) { + throw createHttpError('该自定义店铺不存在', { + statusCode: 404, + errorCode: 'mock_custom_shop_not_found', + }) + } + return saveMockCustomShops( + currentShops.flatMap((item) => { + if (item === from) return [to] + if (item === to) return [] // 新名称原本已存在时合并,避免重复 + return [item] + }), + ) +} + +/** 删除自定义店铺,不影响已用它生成的工单。 */ +export async function deleteAdminMockCustomShop(shopName: string): Promise { + const currentShops = listAdminMockCustomShops() + const name = normalizeShopName(shopName) + if (!name || !currentShops.includes(name)) { + throw createHttpError('该自定义店铺不存在', { + statusCode: 404, + errorCode: 'mock_custom_shop_not_found', + }) + } + return saveMockCustomShops(currentShops.filter((item) => item !== name)) } export function normalizeMockCustomShops(rawValue: unknown): MockCustomShopsConfig { @@ -40,6 +78,26 @@ export function normalizeMockCustomShops(rawValue: unknown): MockCustomShopsConf return { shops: normalized.slice(0, MAX_CUSTOM_SHOPS) } } +async function saveMockCustomShops(shops: string[]): Promise { + const saved = await saveAppConfigEntry({ + configKey: APP_CONFIG_KEYS.workerMockShops, + value: { shops }, + normalize: normalizeMockCustomShops, + }) + return saved.shops +} + +function requireShopName(value: unknown, action: string): string { + const name = normalizeShopName(value) + if (!name) { + throw createHttpError(`请填写要${action}的店铺名称`, { + statusCode: 400, + errorCode: 'mock_custom_shop_name_required', + }) + } + return name +} + function createDefaultMockCustomShops(): MockCustomShopsConfig { return { shops: [] } } diff --git a/apps/frontend/src/pages/admin/AdminWorkerPlatformPage.tsx b/apps/frontend/src/pages/admin/AdminWorkerPlatformPage.tsx index 2c2daf92..1b76eab4 100644 --- a/apps/frontend/src/pages/admin/AdminWorkerPlatformPage.tsx +++ b/apps/frontend/src/pages/admin/AdminWorkerPlatformPage.tsx @@ -89,7 +89,7 @@ export default function AdminWorkerPlatformPage() { { key: 'feedbacks', label: '问题反馈', children: }, afterSalesTab, ...(isAdmin - ? [{ key: 'announcement', label: '公告管理', children: }] + ? [{ key: 'announcement', label: '杂项配置', children: }] : []), { key: 'rules', label: '接单模板', children: }, { key: 'product-match', label: '匹配诊断', children: }, diff --git a/apps/frontend/src/pages/admin/panels/AnnouncementPanel.tsx b/apps/frontend/src/pages/admin/panels/AnnouncementPanel.tsx index 594449c1..a801cfcf 100644 --- a/apps/frontend/src/pages/admin/panels/AnnouncementPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/AnnouncementPanel.tsx @@ -1,11 +1,27 @@ import { useQuery, useQueryClient } from '@tanstack/react-query' -import { App, Button, Card, Form, Input, Space, Switch, Typography } from 'antd' +import { App, Button, Card, Form, Input, Popconfirm, Space, Switch, Typography } from 'antd' import { useEffect, useState } from 'react' -import { fetchAdminWorkerAnnouncement, saveAdminWorkerAnnouncement } from '@/services/admin' +import { + addAdminMockCustomShop, + deleteAdminMockCustomShop, + fetchAdminMockCustomShops, + fetchAdminWorkerAnnouncement, + renameAdminMockCustomShop, + saveAdminWorkerAnnouncement, +} from '@/services/admin' import type { WorkerAnnouncementConfig } from '@/types/worker-platform' export default function AnnouncementPanel() { + return ( + + + + + ) +} + +function AnnouncementCard() { const { message } = App.useApp() const queryClient = useQueryClient() const [form] = Form.useForm() @@ -59,3 +75,116 @@ export default function AnnouncementPanel() { ) } + +function MockCustomShopsCard() { + const { message, modal } = App.useApp() + const queryClient = useQueryClient() + const [adding, setAdding] = useState(false) + const [addForm] = Form.useForm<{ name: string }>() + const customShopsQuery = useQuery({ + queryKey: ['admin-mock-custom-shops'], + queryFn: fetchAdminMockCustomShops, + }) + const shops = customShopsQuery.data?.data.items || [] + + async function refresh() { + await queryClient.invalidateQueries({ queryKey: ['admin-mock-custom-shops'] }) + } + + async function runShopAction(action: () => Promise, successMessage: string) { + try { + await action() + await refresh() + message.success(successMessage) + } catch (error) { + message.error(error instanceof Error ? error.message : '操作失败') + } + } + + async function addShop(values: { name: string }) { + setAdding(true) + try { + await runShopAction( + () => addAdminMockCustomShop(String(values.name || '').trim()), + '自定义店铺已新增', + ) + addForm.resetFields() + } finally { + setAdding(false) + } + } + + function renameShop(name: string) { + let nextName = '' + modal.confirm({ + title: '重命名自定义店铺', + content: ( + { + nextName = String(event.target.value || '') + }} + /> + ), + onOk: async () => { + const trimmed = nextName.trim() + if (!trimmed || trimmed === name) return + await runShopAction(() => renameAdminMockCustomShop(name, trimmed), '自定义店铺已重命名') + }, + }) + } + + return ( + + + 在接单工单「本地 Mock」中录入过的外部平台店铺会自动记忆到这里;也可以直接维护, + 维护后的店铺会出现在 Mock 表单的下拉选项中。 + +
+ + + + + + +
+ {shops.length === 0 ? ( + + 还没有自定义店铺,先在上方新增或通过本地 Mock 录入。 + + ) : ( + + {shops.map((name) => ( + + {name} + + + runShopAction(() => deleteAdminMockCustomShop(name), '自定义店铺已删除') + } + > + + + + ))} + + )} +
+ ) +} diff --git a/apps/frontend/src/services/admin/worker-platform.ts b/apps/frontend/src/services/admin/worker-platform.ts index b49193c0..8cd28a1a 100644 --- a/apps/frontend/src/services/admin/worker-platform.ts +++ b/apps/frontend/src/services/admin/worker-platform.ts @@ -526,6 +526,25 @@ export function fetchAdminMockCustomShops() { return apiGet<{ items: string[] }>('/api/v1/admin/worker-platform/orders/mock/custom-shops') } +export function addAdminMockCustomShop(name: string) { + return apiPost('/api/v1/admin/worker-platform/orders/mock/custom-shops', { + name, + }) +} + +export function renameAdminMockCustomShop(name: string, newName: string) { + return apiPut('/api/v1/admin/worker-platform/orders/mock/custom-shops', { + name, + newName, + }) +} + +export function deleteAdminMockCustomShop(name: string) { + return apiDelete( + `/api/v1/admin/worker-platform/orders/mock/custom-shops?name=${encodeURIComponent(name)}`, + ) +} + export function createAdminMockWorkOrder(payload: { platformOrderId: string sellerId: string