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