feat:寄卖增加对接客服,页面移动到接单平台

This commit is contained in:
2026-10-27 18:49:39 +08:00
parent 0d9fb29460
commit 2ddc26091c
6 changed files with 86 additions and 56 deletions
+5 -4
View File
@@ -24,9 +24,6 @@ const AdminFinancePage = lazy(() => import('@/pages/admin/AdminFinancePage'))
const AdminFinanceLedgerPage = lazy(() => import('@/pages/admin/AdminFinanceLedgerPage'))
const AdminSalesProfitPage = lazy(() => import('@/pages/admin/AdminSalesProfitPage'))
const AdminCostCenterPage = lazy(() => import('@/pages/admin/AdminCostCenterPage'))
const AdminConsignmentContactsPage = lazy(
() => import('@/pages/admin/AdminConsignmentContactsPage'),
)
const AdminFundPage = lazy(() => import('@/pages/admin/AdminFundPage'))
const AdminAuditLogsPage = lazy(() => import('@/pages/admin/AdminAuditLogsPage'))
const AdminKuaishouIndustryPage = lazy(() => import('@/pages/admin/AdminKuaishouIndustryPage'))
@@ -161,6 +158,11 @@ function AdminApplication() {
<Route path="fund" element={<AdminFundPage />} />
</Route>
<Route path="worker-platform" element={<AdminWorkerPlatformPage />} />
{/* 旧「配置 → 对接客服」地址已并入接单平台 tab,书签继续可用。 */}
<Route
path="consignment-contacts"
element={<Navigate to="/admin/worker-platform?tab=consignment-contacts" replace />}
/>
<Route element={<RequireRole roles={['admin', 'operator', 'support']} />}>
<Route path="kuaishou-industry" element={<AdminKuaishouIndustryPage />} />
<Route
@@ -171,7 +173,6 @@ function AdminApplication() {
</Route>
<Route element={<RequireRole roles={['admin']} />}>
<Route path="users" element={<AdminUsersPage />} />
<Route path="consignment-contacts" element={<AdminConsignmentContactsPage />} />
<Route path="platform-shops" element={<AdminPlatformShopsPage />} />
<Route path="platform-fulfillment" element={<AdminPlatformFulfillmentPage />} />
<Route path="audit-logs" element={<AdminAuditLogsPage />} />
+2 -7
View File
@@ -4,7 +4,6 @@ import {
ApartmentOutlined,
BellOutlined,
BugOutlined,
CustomerServiceOutlined,
DashboardOutlined,
FileSearchOutlined,
LogoutOutlined,
@@ -284,11 +283,6 @@ export default function AdminLayout() {
label: '配置',
children: [
{ key: '/admin/users', icon: <TeamOutlined />, label: '后台用户' },
{
key: '/admin/consignment-contacts',
icon: <CustomerServiceOutlined />,
label: '对接客服',
},
{ key: '/admin/platform-shops', icon: <SettingOutlined />, label: '平台配置' },
{ key: '/admin/platform-fulfillment', icon: <ShopOutlined />, label: '履约配置' },
],
@@ -857,7 +851,8 @@ function resolveSelectedKey(pathname: string) {
if (pathname.startsWith('/admin/finance')) return '/admin/finance'
if (pathname.startsWith('/admin/tasks')) return '/admin/tasks'
if (pathname.startsWith('/admin/worker-platform')) return '/admin/worker-platform'
if (pathname.startsWith('/admin/consignment-contacts')) return '/admin/consignment-contacts'
// 旧「配置 → 对接客服」地址已并入接单平台 tab,保留高亮兼容。
if (pathname.startsWith('/admin/consignment-contacts')) return '/admin/worker-platform'
if (pathname.startsWith('/admin/kuaishou-industry')) return '/admin/kuaishou-industry'
if (pathname === '/admin/platform-shops') return '/admin/platform-shops'
return pathname
@@ -15,6 +15,7 @@ import AfterSalesPanel, { type AfterSalesPanelHandle } from './panels/AfterSales
import AnnouncementPanel from './panels/AnnouncementPanel'
import CancelRequestsPanel from './panels/CancelRequestsPanel'
import CategoriesPanel from './panels/CategoriesPanel'
import ConsignmentContactsPanel from './panels/ConsignmentContactsPanel'
import ConsignmentInventoryPanel from './panels/ConsignmentInventoryPanel'
import ConsignmentSubmissionsPanel from './panels/ConsignmentSubmissionsPanel'
import FeedbacksPanel from './panels/FeedbacksPanel'
@@ -44,8 +45,10 @@ export default function AdminWorkerPlatformPage() {
const isFinance = role === 'finance'
const canViewConsignment = hasAdminPermission('consignment.view')
const isConsignmentTab =
activeTab === 'consignment-inventory' || activeTab === 'consignment-submissions'
// 没有 consignment.view 时这两个 tab 不渲染,把 URL/localStorage 里的旧选择也拉回工单页,
activeTab === 'consignment-inventory' ||
activeTab === 'consignment-submissions' ||
activeTab === 'consignment-contacts'
// 没有 consignment.view 时这些 tab 不渲染,把 URL/localStorage 里的旧选择也拉回工单页,
// 否则会落在一个空的 tab 内容上。
const visibleActiveTab = isFinance
? 'finance'
@@ -113,6 +116,11 @@ export default function AdminWorkerPlatformPage() {
label: '寄售仓库',
children: <ConsignmentInventoryPanel />,
},
{
key: 'consignment-contacts',
label: '对接客服',
children: <ConsignmentContactsPanel />,
},
{
key: 'consignment-submissions',
label: '寄卖提报审核',
@@ -16,8 +16,8 @@ import {
} from 'antd'
import { useEffect, useState } from 'react'
import PageHeader from '@/components/admin/PageHeader'
import PercentTable from '@/components/common/PercentTable'
import { fetchAdminSession } from '@/services/admin/auth'
import {
createAdminConsignmentContact,
deleteAdminConsignmentContact,
@@ -26,6 +26,7 @@ import {
updateAdminConsignmentContact,
} from '@/services/admin/consignment'
import type { ConsignmentContactSupporter } from '@/types/consignment'
import { resolveAdminPermission } from '@/utils/admin-auth'
import { formatDateTime } from '@/utils/date-time'
import type { TableColumnsType } from 'antd'
@@ -45,12 +46,12 @@ const ENABLED_FILTER_OPTIONS = [
]
/**
* 配置 → 对接客服。
* 接单平台 → 对接客服。
*
* 独立名单(不复用后台账号):打手发布寄卖时必选,后台按人统计提成与快速联系。
* 已被寄卖记录引用的客服只能禁用,不能删除,保证历史单归属不丢。
*/
export default function AdminConsignmentContactsPage() {
export default function ConsignmentContactsPanel() {
const { message, modal } = App.useApp()
const queryClient = useQueryClient()
const [form] = Form.useForm<ContactForm>()
@@ -68,6 +69,18 @@ export default function AdminConsignmentContactsPage() {
return () => window.clearTimeout(timer)
}, [keywordInput])
const adminSessionQuery = useQuery({
queryKey: ['admin-session'],
queryFn: fetchAdminSession,
retry: false,
})
// 写操作统一按 consignment.manage 门控:后端接口都挂了 requireAdminPermission,
// 没有权限时按钮点了只会 403,所以直接从界面隐藏。
const canManage = resolveAdminPermission(
adminSessionQuery.data?.data.user.permissions,
'consignment.manage',
)
const query = useQuery({
queryKey: ['admin-consignment-contacts', keyword, enabledFilter],
queryFn: () =>
@@ -195,6 +208,7 @@ export default function AdminConsignmentContactsPage() {
<Switch
size="small"
checked={record.enabled}
disabled={!canManage}
loading={actionId === record.id}
checkedChildren="启用"
unCheckedChildren="禁用"
@@ -216,52 +230,55 @@ export default function AdminConsignmentContactsPage() {
title: '操作',
key: 'actions',
width: 140,
render: (_, record) => (
<Space size={4}>
<Button size="small" type="link" onClick={() => openEdit(record)}>
编辑
</Button>
<Button size="small" type="link" danger onClick={() => handleDelete(record)}>
删除
</Button>
</Space>
),
render: (_, record) =>
canManage ? (
<Space size={4}>
<Button size="small" type="link" onClick={() => openEdit(record)}>
编辑
</Button>
<Button size="small" type="link" danger onClick={() => handleDelete(record)}>
删除
</Button>
</Space>
) : (
<Text type="secondary">-</Text>
),
},
]
return (
<div>
<PageHeader
title="对接客服"
description="打手发布寄卖时必须选择对接客服;本名单独立于后台账号,用于提成归属与联系。"
extra={
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
新增对接客服
</Button>
}
/>
<Card size="small" style={{ marginBottom: 16 }}>
<Space wrap size={12}>
<Input
allowClear
prefix={<SearchOutlined />}
placeholder="搜索姓名 / 备注"
value={keywordInput}
onChange={(event) => setKeywordInput(event.target.value)}
style={{ width: 280 }}
/>
<Select
value={enabledFilter}
options={ENABLED_FILTER_OPTIONS}
onChange={setEnabledFilter}
style={{ width: 140 }}
/>
<Button icon={<ReloadOutlined />} onClick={() => void query.refetch()}>
刷新
</Button>
<Text type="secondary">
共 {total} 个,本页启用 {enabledCount} 个
<Space direction="vertical" size={10} style={{ width: '100%' }}>
<Space wrap size={12}>
<Input
allowClear
prefix={<SearchOutlined />}
placeholder="搜索姓名 / 备注"
value={keywordInput}
onChange={(event) => setKeywordInput(event.target.value)}
style={{ width: 280 }}
/>
<Select
value={enabledFilter}
options={ENABLED_FILTER_OPTIONS}
onChange={setEnabledFilter}
style={{ width: 140 }}
/>
<Button icon={<ReloadOutlined />} onClick={() => void query.refetch()}>
刷新
</Button>
<Text type="secondary">
共 {total} 个,本页启用 {enabledCount} 个
</Text>
{canManage ? (
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
新增对接客服
</Button>
) : null}
</Space>
<Text type="secondary" style={{ fontSize: 12 }}>
打手发布寄卖时必须选择对接客服;已被寄卖记录使用的客服只能禁用,不能删除。
</Text>
</Space>
</Card>
@@ -14,6 +14,7 @@
export const WORKER_PLATFORM_TAB_KEYS: readonly string[] = [
'orders',
'consignment-inventory',
'consignment-contacts',
'consignment-submissions',
'leaderboard',
'cancel-requests',
@@ -38,6 +39,7 @@ export const WORKER_PLATFORM_TAB_KEYS: readonly string[] = [
*/
export const WORKER_PLATFORM_TAB_QUERY_KEYS: Record<string, string[][]> = {
'consignment-inventory': [['admin-consignment-inventories'], ['admin-consignment-stats']],
'consignment-contacts': [['admin-consignment-contacts']],
'consignment-submissions': [
['admin-consignment-submissions'],
['admin-consignment-submissions-pending-total'],
@@ -18,6 +18,13 @@ test('风控 tab 必须被 isWorkerPlatformTab 接受(漏登记会导致点击
assert.equal(isWorkerPlatformTab('risk-control'), true)
})
test('对接客服 tab 必须被 isWorkerPlatformTab 接受,并登记切页刷新缓存', () => {
assert.equal(isWorkerPlatformTab('consignment-contacts'), true)
assert.deepEqual(WORKER_PLATFORM_TAB_QUERY_KEYS['consignment-contacts'], [
['admin-consignment-contacts'],
])
})
test('每个登记了切页缓存的 tab 都必须在合法 tab 名单里', () => {
const nonTabs = new Set(WORKER_PLATFORM_NON_TAB_QUERY_KEYS)
for (const key of Object.keys(WORKER_PLATFORM_TAB_QUERY_KEYS)) {