资料字段: - CollectField 支持 type(text/select) 与 options,规则字段语法 key:名称#选项1,选项2 - 默认字段改为游戏编号/游戏昵称/系统(安卓,苹果)/区服(QQ区,微信区),旧默认模板字段自动替换为新版 - 采集页与管理端补资料支持单选下拉与买家主页截图上传(新增公开上传接口) - 管理端工单详情展示买家截图 VIP 延迟可见: - 等级权限新增 visibleDelaySeconds(默认 VIP1=20s~VIP5=0s,可后台配置) - 大厅按等级延迟过滤新发布工单,页面提示延迟规则
303 lines
8.6 KiB
TypeScript
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>
|
|
)
|
|
}
|
|
|