Files
order_site/apps/frontend/src/pages/admin/panels/LevelsPanel.tsx
T
2026-08-17 12:29:34 +08:00

252 lines
7.7 KiB
TypeScript

import { useQuery, useQueryClient } from '@tanstack/react-query'
import {
App,
Button,
Card,
Form,
Input,
InputNumber,
Modal,
Space,
Switch,
Table,
Tag,
Typography,
} from 'antd'
import type { TableColumnsType } from 'antd'
import { useState } from 'react'
import {
deleteAdminWorkerLevel,
fetchAdminWorkerLevels,
saveAdminWorkerLevel,
} from '@/services/admin'
import type { WorkerLevel } from '@/types/worker-platform'
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,
autoAcceptWithoutEvidence: level.autoAcceptWithoutEvidence,
visibleDelaySeconds: level.visibleDelaySeconds,
})
}
function cancelEdit() {
setEditingLevel(null)
form.resetFields()
}
async function saveLevel(values: {
levelKey: string
name: string
depositFreeAmount?: number
maxActiveOrders?: number
upgradeThreshold?: number
autoAcceptWithoutEvidence?: boolean
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,
autoAcceptWithoutEvidence: level.autoAcceptWithoutEvidence,
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: 130,
render: (_, row) =>
row.autoAcceptWithoutEvidence ? <Tag color="purple">开启</Tag> : <Tag>关闭</Tag>,
},
{
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,
autoAcceptWithoutEvidence: false,
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="autoAcceptWithoutEvidence"
valuePropName="checked"
tooltip="开启后,打手可无验收图提交并自动验收;验收后仅可在 24 小时内补图。"
>
<Switch />
</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>
)
}