merge: 同步 main 最新到 dev(接单平台店铺筛选)

This commit is contained in:
yml2213
2026-10-22 15:30:38 +08:00
9 changed files with 200 additions and 17 deletions
@@ -60,6 +60,7 @@ import {
fetchAdminWorkCategories,
fetchAdminSession,
fetchAdminWorkOrderSharing,
fetchAdminWorkOrderShopOptions,
fetchAdminWorkerLevels,
fetchAdminWorkerUsers,
fetchAdminWorkOrders,
@@ -205,6 +206,18 @@ function isConsignmentRevocableOrder(row: WorkOrder) {
)
}
/**
* 快手渠道自动生成的占位店铺名(`快手店铺-<sellerId>`)。
*
* 这类名字不是人工维护的门店,只是同步订单时没拿到店铺名兜底生成的;
* 后台「店铺」筛选中把它们隐藏掉,避免下拉里混进一串数字店铺。
*/
const AUTO_KUAISHOU_SHOP_NAME_PATTERN = /^快手店铺[--]?\s*\d*$/
function isAutoGeneratedKuaishouShopName(name: string) {
return AUTO_KUAISHOU_SHOP_NAME_PATTERN.test(String(name || '').trim())
}
export default function WorkOrdersPanel() {
const { message, modal } = App.useApp()
const navigate = useNavigate()
@@ -266,6 +279,7 @@ export default function WorkOrdersPanel() {
const [createdRange, setCreatedRange] = useState<[string, string] | null>(null)
const [keywordInput, setKeywordInput] = useState('')
const [workerId, setWorkerId] = useState<number | undefined>()
const [shopNames, setShopNames] = useState<string[]>([])
const [categoryId, setCategoryId] = useState<number | undefined>()
const [workerLevelIds, setWorkerLevelIds] = useState<number[]>([])
const [acceptanceMode, setAcceptanceMode] = useState<string | undefined>()
@@ -369,6 +383,7 @@ export default function WorkOrdersPanel() {
setKeyword('')
setKeywordInput('')
setWorkerId(undefined)
setShopNames([])
setWorkerLevelIds([])
// 定位工单是**排他**条件(wo.id = X):只留它一个,其它筛选一并清掉,
// 否则之后按单号搜索时会被残留的接单人员/分类/日期/验收模式再次挡掉。
@@ -395,6 +410,7 @@ export default function WorkOrdersPanel() {
{
statuses: statuses.join(','),
keyword,
shopNames: shopNames.join(','),
workerId,
categoryId,
workerLevelIds: workerLevelIds.join(','),
@@ -417,6 +433,7 @@ export default function WorkOrdersPanel() {
workOrderId: targetWorkOrderId,
categoryId,
keyword,
shopNames: shopNames.length > 0 ? shopNames.join(',') : undefined,
workerId,
workerLevelIds:
showWorkerLevelFilter && workerLevelIds.length > 0 ? workerLevelIds.join(',') : undefined,
@@ -490,6 +507,12 @@ export default function WorkOrdersPanel() {
queryFn: () => fetchAdminWorkerLevels(),
enabled: showWorkerLevelFilter,
})
// 店铺候选固定全量(不随其它筛选变化),下拉本身带搜索框做本地过滤。
const shopOptionsQuery = useQuery({
queryKey: ['admin-worker-platform-order-shop-options'],
queryFn: fetchAdminWorkOrderShopOptions,
staleTime: 60_000,
})
const filterWorkersQuery = useQuery({
queryKey: ['admin-worker-platform-workers', 'order-filter', filterWorkerKeyword],
queryFn: () =>
@@ -713,6 +736,9 @@ export default function WorkOrdersPanel() {
queryClient.invalidateQueries({
queryKey: ['admin-worker-platform-orders'],
}),
queryClient.invalidateQueries({
queryKey: ['admin-worker-platform-order-shop-options'],
}),
queryClient.invalidateQueries({
queryKey: ['admin-worker-platform-summary'],
}),
@@ -1095,6 +1121,10 @@ export default function WorkOrdersPanel() {
setFilterWorkerKeyword('')
cleared.push('接单人员')
}
if (shopNames.length > 0) {
setShopNames([])
cleared.push('店铺')
}
if (categoryId) {
setCategoryId(undefined)
cleared.push('分类')
@@ -1130,6 +1160,7 @@ export default function WorkOrdersPanel() {
setKeyword('')
setStatuses([])
setWorkerId(undefined)
setShopNames([])
setCategoryId(undefined)
setWorkerLevelIds([])
setAcceptanceMode(undefined)
@@ -1841,12 +1872,12 @@ export default function WorkOrdersPanel() {
) : null}
<Card title="订单搜索与筛选" bordered={false}>
<div className="worker-orders-toolbar">
<Space wrap className="filter-form worker-orders-search-controls">
<div className="worker-orders-filter-bar">
<Input
allowClear
value={keywordInput}
placeholder="工单号 / 平台单号 / 寄卖单号 / 商品名 / 游戏ID / 游戏昵称"
style={{ width: 280 }}
placeholder="工单号 / 平台单号 / 商品名 / 游戏ID / 昵称"
className="worker-orders-search-input"
onChange={(event) => {
const nextVal = event.target.value
setKeywordInput(nextVal)
@@ -1857,12 +1888,33 @@ export default function WorkOrdersPanel() {
}}
onPressEnter={searchOrders}
/>
<Select
mode="multiple"
allowClear
showSearch
optionFilterProp="label"
value={shopNames}
placeholder="全部店铺"
className="worker-orders-filter-select worker-orders-filter-select--wide"
maxTagCount="responsive"
loading={shopOptionsQuery.isLoading}
options={(shopOptionsQuery.data?.data.items || [])
.filter((shopName) => !isAutoGeneratedKuaishouShopName(shopName))
.map((shopName) => ({
value: shopName,
label: shopName,
}))}
onChange={(nextShopNames: string[]) => {
setShopNames(nextShopNames)
setPage(1)
}}
/>
<Select
mode="multiple"
allowClear
value={statuses}
placeholder="全部状态(可多选)"
style={{ minWidth: 220, maxWidth: 420 }}
placeholder="全部状态"
className="worker-orders-filter-select worker-orders-filter-select--compact"
maxTagCount="responsive"
listHeight={320}
options={FILTERABLE_WORK_ORDER_STATUSES.map((item) => ({
@@ -1879,8 +1931,8 @@ export default function WorkOrdersPanel() {
showSearch
filterOption={false}
value={workerId}
placeholder="全部接单人员"
style={{ width: 180 }}
placeholder="接单人员"
className="worker-orders-filter-select"
loading={filterWorkersQuery.isLoading}
onSearch={setFilterWorkerKeyword}
options={(filterWorkersQuery.data?.data.items || []).map((worker) => ({
@@ -1901,8 +1953,8 @@ export default function WorkOrdersPanel() {
showSearch
optionFilterProp="label"
value={categoryId}
placeholder="全部接单分类"
style={{ width: 180 }}
placeholder="接单分类"
className="worker-orders-filter-select"
loading={categoriesQuery.isLoading}
options={categoryOptions}
onChange={(nextCategoryId) => {
@@ -1915,8 +1967,8 @@ export default function WorkOrdersPanel() {
mode="multiple"
allowClear
value={workerLevelIds}
placeholder="全部打手 VIP 等级"
style={{ minWidth: 180, maxWidth: 320 }}
placeholder="VIP 等级"
className="worker-orders-filter-select"
maxTagCount="responsive"
loading={workerLevelsQuery.isLoading}
options={(workerLevelsQuery.data?.data.items || []).map((level) => ({
@@ -1932,8 +1984,8 @@ export default function WorkOrdersPanel() {
<Select
allowClear
value={acceptanceMode}
placeholder="全部验收模式"
style={{ width: 160 }}
placeholder="验收模式"
className="worker-orders-filter-select"
options={[
{ value: 'standard', label: '标准验收' },
{ value: 'friend_gift', label: '打手好友赠送' },
@@ -1948,8 +2000,8 @@ export default function WorkOrdersPanel() {
<Select
allowClear
value={giftPhase}
placeholder="全部赠送阶段"
style={{ width: 170 }}
placeholder="赠送阶段"
className="worker-orders-filter-select"
options={GIFT_PHASE_FILTER_OPTIONS.map((phase) => ({
value: phase,
label: (
@@ -1965,6 +2017,7 @@ export default function WorkOrdersPanel() {
/>
) : null}
<AdminRangePicker
className="worker-orders-date-range"
value={createdRange ? [dayjs(createdRange[0]), dayjs(createdRange[1])] : null}
onChange={(dates) => {
const [start, end] = dates || []
@@ -1980,7 +2033,7 @@ export default function WorkOrdersPanel() {
查询
</Button>
<Button onClick={clearOrderFilters}>清空筛选</Button>
</Space>
</div>
<div className="worker-orders-mock">
<Typography.Text type="secondary">本地 Mock</Typography.Text>
<MockWorkOrderForm onCreated={refreshAll} />
@@ -638,6 +638,11 @@ export function fetchAdminWorkOrders(params?: Record<string, unknown>) {
>('/api/v1/admin/worker-platform/orders', params)
}
/** 工单「店铺」筛选候选:订单归属店铺(orders.shop_name)全量去重。 */
export function fetchAdminWorkOrderShopOptions() {
return apiGet<{ items: string[] }>('/api/v1/admin/worker-platform/orders/shop-options')
}
export function fetchAdminAfterSalesCases(params?: Record<string, unknown>) {
return apiGet<WorkerListResponse<WorkerAfterSalesCase>>(
'/api/v1/admin/worker-platform/after-sales-cases',
+42 -1
View File
@@ -121,8 +121,49 @@
min-width: 0;
}
.worker-orders-search-controls {
/* 单行筛选栏:搜索框、筛选条件、清空按钮同一行且不换行;空间不足时行内横向滚动 */
.worker-orders-filter-bar {
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 8px;
width: 100%;
overflow-x: auto;
scrollbar-width: thin;
padding-bottom: 2px;
}
.worker-orders-search-input {
flex: 1.4 1 240px;
min-width: 180px;
max-width: 300px;
}
.worker-orders-filter-select {
flex: 1 1 110px;
min-width: 88px;
}
.worker-orders-filter-select--wide {
flex: 1.6 1 140px;
min-width: 112px;
}
/* 状态:多选标签可截断,宽度让给时间区间 */
.worker-orders-filter-select--compact {
flex: 0.9 1 96px;
min-width: 80px;
}
/* 时间区间:留足两个完整日期(YYYY-MM-DD)与「开始日期/结束日期」占位文案的宽度 */
.worker-orders-date-range {
flex: 0 0 230px;
width: 230px;
min-width: 230px;
}
.worker-orders-filter-bar > .ant-btn {
flex: 0 0 auto;
}
.worker-orders-mock {
@@ -23,6 +23,8 @@ import dayjs from 'dayjs'
export type AdminWorkOrderListFilters = {
statuses: string[]
keyword: string
/** 店铺筛选(订单归属店铺,可多选精确匹配)。 */
shopNames: string[]
workerId: number
categoryId: number
workerLevelIds: number[]
@@ -69,6 +71,10 @@ export function parseAdminWorkOrderListFilters(
.map((value) => value.trim())
.filter(Boolean),
keyword: String(raw.keyword || '').trim(),
shopNames: String(raw.shopNames || '')
.split(',')
.map((value) => value.trim())
.filter(Boolean),
workerId: toNumber(raw.workerId),
categoryId: toNumber(raw.categoryId),
workerLevelIds: String(raw.workerLevelIds || '')
@@ -211,10 +217,13 @@ export function evaluateAdminWorkOrderRealtimeMatch(
// 还有平台单号、工单号的特殊匹配分支;
// - 赠送阶段:gift_ready 是"冷却到点"的实时阶段,含 NOW() 判定;
// - 打手等级:只在服务端按接单人与拼单参与者计算;
// - 店铺:口径是订单归属店铺(orders.shop_name),快照里只有工单 material 的货源店铺名,
// 两者不可互换,无法证伪;
// - VIP 待补图:混在状态列表里的虚拟值,判定依赖 acceptance_json 多个字段与 NOW()。
// 注:显式单号检索已在函数开头短路,走不到这里。
if (filters.acceptanceMode) return 'unknown'
if (vipEvidencePendingActive) return 'unknown'
if (filters.shopNames.length > 0) return 'unknown'
if (filters.keyword) return 'unknown'
if (filters.giftPhase) return 'unknown'
if (filters.workerLevelIds.length > 0) return 'unknown'