254 lines
8.8 KiB
TypeScript
254 lines
8.8 KiB
TypeScript
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
import { UploadOutlined } from '@ant-design/icons'
|
|
import {
|
|
App,
|
|
Avatar,
|
|
Button,
|
|
Card,
|
|
Descriptions,
|
|
Form,
|
|
Input,
|
|
InputNumber,
|
|
Space,
|
|
Switch,
|
|
Table,
|
|
Tag,
|
|
Upload,
|
|
} from 'antd'
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
|
|
import {
|
|
fetchAdminWorkCategories,
|
|
fetchAdminWorkerHallConfig,
|
|
saveAdminWorkerHallConfig,
|
|
} from '@/services/admin'
|
|
import { uploadFile } from '@/services/files'
|
|
import type { WorkCategory } from '@/types/worker-platform'
|
|
|
|
type HallConfigFormValues = {
|
|
maxVisibleOrders: number
|
|
showSubcategories: boolean
|
|
allIconUrl?: string
|
|
categoryMaxVisibleOrders: Record<string, number>
|
|
}
|
|
|
|
export default function HallConfigPanel() {
|
|
const { message } = App.useApp()
|
|
const queryClient = useQueryClient()
|
|
const [form] = Form.useForm<HallConfigFormValues>()
|
|
const [saving, setSaving] = useState(false)
|
|
const [uploadingAllIcon, setUploadingAllIcon] = useState(false)
|
|
const currentAllIconUrl = Form.useWatch('allIconUrl', form)
|
|
|
|
const configQuery = useQuery({
|
|
queryKey: ['admin-worker-hall-config'],
|
|
queryFn: fetchAdminWorkerHallConfig,
|
|
})
|
|
const config = configQuery.data?.data
|
|
|
|
useEffect(() => {
|
|
if (config) {
|
|
form.setFieldsValue({
|
|
maxVisibleOrders: config.maxVisibleOrders,
|
|
showSubcategories: config.showSubcategories ?? true,
|
|
allIconUrl: config.allIconUrl || '',
|
|
categoryMaxVisibleOrders: config.categoryMaxVisibleOrders || {},
|
|
})
|
|
}
|
|
}, [config, form])
|
|
|
|
const categoriesQuery = useQuery({
|
|
queryKey: ['admin-worker-platform-categories'],
|
|
queryFn: fetchAdminWorkCategories,
|
|
})
|
|
|
|
const treeData = useMemo(() => {
|
|
const rawCategories = categoriesQuery.data?.data.items || []
|
|
const rootCategories = rawCategories.filter((item) => !item.parentId)
|
|
const childrenMap = new Map<number, WorkCategory[]>()
|
|
for (const item of rawCategories) {
|
|
if (item.parentId) {
|
|
const list = childrenMap.get(item.parentId) || []
|
|
list.push(item)
|
|
childrenMap.set(item.parentId, list)
|
|
}
|
|
}
|
|
|
|
return rootCategories.map((root) => {
|
|
const children = childrenMap.get(root.categoryId) || []
|
|
return {
|
|
...root,
|
|
children: children.length > 0 ? children : undefined,
|
|
}
|
|
})
|
|
}, [categoriesQuery.data?.data.items])
|
|
|
|
async function handleUploadAllIcon(file: File) {
|
|
setUploadingAllIcon(true)
|
|
try {
|
|
const uploaded = await uploadFile(file, 'hall_icon', 'admin')
|
|
form.setFieldValue('allIconUrl', uploaded.url)
|
|
message.success('「全部」图标上传成功')
|
|
} catch (error) {
|
|
message.error(error instanceof Error ? error.message : '图标上传失败')
|
|
} finally {
|
|
setUploadingAllIcon(false)
|
|
}
|
|
return false
|
|
}
|
|
|
|
async function saveConfig(values: HallConfigFormValues) {
|
|
setSaving(true)
|
|
try {
|
|
await saveAdminWorkerHallConfig({
|
|
maxVisibleOrders: Number(values.maxVisibleOrders),
|
|
showSubcategories: Boolean(values.showSubcategories),
|
|
allIconUrl: values.allIconUrl ? String(values.allIconUrl).trim() : null,
|
|
categoryMaxVisibleOrders: values.categoryMaxVisibleOrders || {},
|
|
})
|
|
await Promise.all([
|
|
queryClient.invalidateQueries({ queryKey: ['admin-worker-hall-config'] }),
|
|
queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-summary'] }),
|
|
])
|
|
message.success('大厅配置已保存')
|
|
} catch (error) {
|
|
message.error(error instanceof Error ? error.message : '保存大厅配置失败')
|
|
} finally {
|
|
setSaving(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Card title="大厅配置" bordered={false} loading={configQuery.isLoading}>
|
|
<Space direction="vertical" size="middle" style={{ width: '100%', maxWidth: 700 }}>
|
|
<Descriptions size="small" column={3}>
|
|
<Descriptions.Item label="展示中">{config?.visibleOrderCount || 0}</Descriptions.Item>
|
|
<Descriptions.Item label="等待入厅">{config?.queuedOrderCount || 0}</Descriptions.Item>
|
|
<Descriptions.Item label="当前上限">{config?.maxVisibleOrders || '-'}</Descriptions.Item>
|
|
</Descriptions>
|
|
<Form form={form} layout="vertical" onFinish={saveConfig}>
|
|
<Space size="large" align="start" wrap style={{ marginBottom: 8 }}>
|
|
<Form.Item
|
|
name="maxVisibleOrders"
|
|
label="大厅订单总数"
|
|
rules={[{ required: true, message: '请输入大厅订单总数' }]}
|
|
style={{ marginBottom: 12 }}
|
|
>
|
|
<InputNumber min={1} max={500} precision={0} style={{ width: 140 }} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
name="showSubcategories"
|
|
label="大厅二级目录展示"
|
|
valuePropName="checked"
|
|
tooltip="开启后,接单大厅一级分类下方联动展示其二级分类选项;关闭后大厅只展示一级分类。"
|
|
style={{ marginBottom: 12 }}
|
|
>
|
|
<Switch checkedChildren="开启展示" unCheckedChildren="关闭展示" />
|
|
</Form.Item>
|
|
<Form.Item
|
|
name="allIconUrl"
|
|
label="「全部」按钮图标"
|
|
tooltip="接单大厅第一个「全部」按钮展示的自定义图标。留空则显示默认应用图标。"
|
|
style={{ marginBottom: 12, minWidth: 280 }}
|
|
>
|
|
<Space.Compact style={{ width: '100%' }}>
|
|
{currentAllIconUrl ? (
|
|
<Avatar
|
|
src={currentAllIconUrl}
|
|
size={32}
|
|
shape="square"
|
|
style={{ marginRight: 8, flexShrink: 0 }}
|
|
/>
|
|
) : null}
|
|
<Input placeholder="图标 URL(可选)" allowClear />
|
|
<Upload showUploadList={false} beforeUpload={handleUploadAllIcon} accept="image/*">
|
|
<Button icon={<UploadOutlined />} loading={uploadingAllIcon}>
|
|
上传
|
|
</Button>
|
|
</Upload>
|
|
</Space.Compact>
|
|
</Form.Item>
|
|
</Space>
|
|
|
|
<Form.Item>
|
|
<Button type="primary" htmlType="submit" loading={saving}>
|
|
保存配置
|
|
</Button>
|
|
</Form.Item>
|
|
|
|
<div>
|
|
<Tag color="blue">分类最多展示上限</Tag>
|
|
<span style={{ marginLeft: 8, color: '#666', fontSize: 13 }}>
|
|
一级分类限额将限制该游戏及旗下所有子分类的总单量;二级分类可单独设置细化限额。
|
|
</span>
|
|
</div>
|
|
<Table<WorkCategory>
|
|
rowKey="categoryId"
|
|
size="small"
|
|
pagination={false}
|
|
loading={categoriesQuery.isLoading}
|
|
dataSource={treeData}
|
|
defaultExpandAllRows
|
|
columns={[
|
|
{
|
|
title: '分类名称',
|
|
dataIndex: 'name',
|
|
render: (_, category) => (
|
|
<Space size="small">
|
|
{category.iconUrl ? (
|
|
<Avatar src={category.iconUrl} size={20} shape="square" />
|
|
) : null}
|
|
<span>{category.name}</span>
|
|
{category.parentId ? (
|
|
<Tag
|
|
color="blue"
|
|
style={{ fontSize: 11, lineHeight: '18px', padding: '0 4px' }}
|
|
>
|
|
二级
|
|
</Tag>
|
|
) : (
|
|
<Tag
|
|
color="orange"
|
|
style={{ fontSize: 11, lineHeight: '18px', padding: '0 4px' }}
|
|
>
|
|
一级
|
|
</Tag>
|
|
)}
|
|
</Space>
|
|
),
|
|
},
|
|
{
|
|
title: '状态',
|
|
width: 90,
|
|
render: (_, category) => (
|
|
<Tag color={category.status === 'active' ? 'green' : 'default'}>
|
|
{category.status === 'active' ? '启用' : '停用'}
|
|
</Tag>
|
|
),
|
|
},
|
|
{
|
|
title: '最多展示',
|
|
width: 180,
|
|
render: (_, category) => (
|
|
<Form.Item
|
|
name={['categoryMaxVisibleOrders', String(category.categoryId)]}
|
|
style={{ margin: 0 }}
|
|
>
|
|
<InputNumber
|
|
min={1}
|
|
max={500}
|
|
precision={0}
|
|
placeholder={category.parentId ? '跟随父级' : '跟随总上限'}
|
|
style={{ width: '100%' }}
|
|
/>
|
|
</Form.Item>
|
|
),
|
|
},
|
|
]}
|
|
/>
|
|
</Form>
|
|
</Space>
|
|
</Card>
|
|
)
|
|
}
|