Files
order_site/apps/frontend/src/pages/admin/panels/LevelsPanel.tsx
T
yml2213 2412df8b54 资料字段支持下拉选项与买家截图,大厅按 VIP 等级延迟可见
资料字段:
- CollectField 支持 type(text/select) 与 options,规则字段语法 key:名称#选项1,选项2
- 默认字段改为游戏编号/游戏昵称/系统(安卓,苹果)/区服(QQ区,微信区),旧默认模板字段自动替换为新版
- 采集页与管理端补资料支持单选下拉与买家主页截图上传(新增公开上传接口)
- 管理端工单详情展示买家截图

VIP 延迟可见:
- 等级权限新增 visibleDelaySeconds(默认 VIP1=20s~VIP5=0s,可后台配置)
- 大厅按等级延迟过滤新发布工单,页面提示延迟规则
2026-08-01 18:14:37 +08:00

303 lines
8.6 KiB
TypeScript

import {
CheckOutlined,
FormOutlined,
PlusOutlined,
ReloadOutlined,
SendOutlined,
SyncOutlined,
TeamOutlined,
WarningOutlined,
} from '@ant-design/icons'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import {
App,
Button,
Card,
Descriptions,
Drawer,
Form,
Input,
InputNumber,
Modal,
Select,
Space,
Statistic,
Switch,
Table,
Tabs,
Tag,
Typography,
} from 'antd'
import type { TableColumnsType } from 'antd'
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router'
import JsonPreview from '@/components/admin/JsonPreview'
import ImageUpload from '@/components/files/ImageUpload'
import ImagePreviewList from '@/components/files/ImagePreviewList'
import PageHeader from '@/components/admin/PageHeader'
import {
acceptAdminWorkOrder,
createAdminMockWorkOrder,
creditAdminWorkerWallet,
deleteAdminWorkCategory,
deleteAdminWorkerLevel,
fetchAdminWorkerFinanceConfig,
fetchAdminWorkerFinanceRequests,
fetchAdminWorkCategories,
fetchAdminWorkOrderSharing,
fetchAdminWorkProductRules,
fetchAdminWorkerLevels,
fetchAdminWorkerPlatformSummary,
fetchAdminWorkerUsers,
fetchAdminWorkOrders,
markAdminWorkOrderProblem,
publishAdminWorkOrder,
reviewAdminWorkerFinanceRequest,
resolveAdminProblemWorkOrder,
reviewAdminWorkerUser,
saveAdminWorkerFinanceConfig,
saveAdminWorkCategory,
saveAdminWorkProductRule,
saveAdminWorkerLevel,
submitAdminWorkOrderMaterial,
syncAdminWorkerOrdersFromSource,
unpublishAdminWorkOrder,
updateAdminWorkOrderSharing,
} from '@/services/admin'
import type {
CollectField,
UploadedFile,
WorkCategory,
WorkOrder,
WorkOrderShare,
WorkProductRule,
WorkerFinanceConfig,
WorkerFinanceRequest,
WorkerLevel,
WorkerUser,
} from '@/types/worker-platform'
import {
ADMIN_DEFAULT_PAGE_SIZE,
buildAdminTablePagination,
} from '@/utils/admin-pagination'
import { formatAdminDateTime } from '@/utils/admin-time'
import { formatMoney } from './shared'
export default function LevelsPanel() {
const { message } = App.useApp()
const queryClient = useQueryClient()
const levelsQuery = useQuery({
queryKey: ['admin-worker-platform-levels'],
queryFn: () => fetchAdminWorkerLevels(),
})
const [form] = Form.useForm()
const [editingLevel, setEditingLevel] = useState<WorkerLevel | null>(null)
const [deletingLevel, setDeletingLevel] = useState<WorkerLevel | null>(null)
async function refreshLevels() {
await levelsQuery.refetch()
}
function startEdit(level: WorkerLevel) {
setEditingLevel(level)
form.setFieldsValue({
levelKey: level.levelKey,
name: level.name,
depositFreeAmount: level.depositFreeAmount,
maxActiveOrders: level.maxActiveOrders,
upgradeThreshold: level.upgradeThreshold,
visibleDelaySeconds: level.visibleDelaySeconds,
})
}
function cancelEdit() {
setEditingLevel(null)
form.resetFields()
}
async function saveLevel(values: {
levelKey: string
name: string
depositFreeAmount?: number
maxActiveOrders?: number
upgradeThreshold?: number
visibleDelaySeconds?: number
}) {
try {
await saveAdminWorkerLevel(values)
message.success(editingLevel ? '等级已更新' : '等级已保存')
cancelEdit()
await refreshLevels()
} catch (error) {
message.error(error instanceof Error ? error.message : '保存失败')
}
}
async function toggleLevel(level: WorkerLevel) {
try {
await saveAdminWorkerLevel({
levelKey: level.levelKey,
name: level.name,
depositFreeAmount: level.depositFreeAmount,
maxActiveOrders: level.maxActiveOrders,
upgradeThreshold: level.upgradeThreshold,
visibleDelaySeconds: level.visibleDelaySeconds,
status: level.status === 'active' ? 'disabled' : 'active',
})
message.success(level.status === 'active' ? '等级已停用' : '等级已启用')
await refreshLevels()
} catch (error) {
message.error(error instanceof Error ? error.message : '操作失败')
}
}
async function submitDelete() {
if (!deletingLevel) return
try {
await deleteAdminWorkerLevel(deletingLevel.levelId)
message.success('等级已删除')
setDeletingLevel(null)
await refreshLevels()
await queryClient.invalidateQueries({
queryKey: ['admin-worker-platform-workers'],
})
} catch (error) {
message.error(error instanceof Error ? error.message : '删除失败')
setDeletingLevel(null)
}
}
const columns: TableColumnsType<WorkerLevel> = [
{ title: '等级', dataIndex: 'name' },
{ title: '标识', dataIndex: 'levelKey' },
{
title: '免押额度',
render: (_, row) => formatMoney(row.depositFreeAmount),
},
{ title: '最大同时接单', dataIndex: 'maxActiveOrders' },
{ title: '升级所需累计完成', dataIndex: 'upgradeThreshold' },
{
title: '可见延迟',
width: 110,
render: (_, row) =>
row.visibleDelaySeconds > 0 ? `${row.visibleDelaySeconds} 秒` : '立即',
},
{
title: '状态',
width: 110,
render: (_, row) => (
<Tag color={row.status === 'active' ? 'green' : 'default'}>
{row.status === 'active' ? '启用' : '停用'}
</Tag>
),
},
{
title: '操作',
width: 240,
render: (_, row) => (
<Space wrap>
<Button size="small" onClick={() => startEdit(row)}>
编辑
</Button>
<Button size="small" onClick={() => toggleLevel(row)}>
{row.status === 'active' ? '停用' : '启用'}
</Button>
<Button size="small" danger onClick={() => setDeletingLevel(row)}>
删除
</Button>
</Space>
),
},
]
return (
<section className="platform-panel-stack">
<Card
title={editingLevel ? `编辑等级:${editingLevel.name}` : '保存等级'}
bordered={false}
>
<Form
form={form}
layout="inline"
initialValues={{
levelKey: 'vip1',
name: 'VIP1',
depositFreeAmount: 0,
maxActiveOrders: 1,
upgradeThreshold: 0,
visibleDelaySeconds: 20,
}}
onFinish={saveLevel}
>
<Form.Item
label="标识"
name="levelKey"
rules={[{ required: true, message: '请输入标识' }]}
>
<Input />
</Form.Item>
<Form.Item
label="名称"
name="name"
rules={[{ required: true, message: '请输入名称' }]}
>
<Input />
</Form.Item>
<Form.Item label="免押额度" name="depositFreeAmount">
<InputNumber min={0} step={10} addonAfter="元" />
</Form.Item>
<Form.Item label="最大同时接单" name="maxActiveOrders">
<InputNumber min={1} max={999} />
</Form.Item>
<Form.Item
label="升级所需累计完成单数"
name="upgradeThreshold"
tooltip="打手累计验收通过该单数后自动升级到本等级"
>
<InputNumber min={0} max={100000} />
</Form.Item>
<Form.Item
label="大厅可见延迟(秒)"
name="visibleDelaySeconds"
tooltip="发布到大厅后该等级打手延迟可见秒数,默认 VIP1=20 / VIP2=15 / VIP3=10 / VIP4=5 / VIP5=0,填 -1 使用默认"
>
<InputNumber min={-1} max={3600} />
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit">
保存
</Button>
{editingLevel ? <Button onClick={cancelEdit}>取消</Button> : null}
</Space>
</Form.Item>
</Form>
</Card>
<Card title="等级列表" bordered={false}>
<Table<WorkerLevel>
rowKey="levelId"
loading={levelsQuery.isLoading}
dataSource={levelsQuery.data?.data.items || []}
columns={columns}
pagination={false}
/>
</Card>
<Modal
title="删除等级"
open={Boolean(deletingLevel)}
onOk={submitDelete}
onCancel={() => setDeletingLevel(null)}
okButtonProps={{ danger: true }}
okText="删除"
cancelText="取消"
>
<Typography.Text>
确定删除等级「{deletingLevel?.name}」?正在使用该等级的打手将无法删除,需先调整。
</Typography.Text>
</Modal>
</section>
)
}