253 lines
7.1 KiB
TypeScript
253 lines
7.1 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react'
|
|
import {
|
|
Button,
|
|
Form,
|
|
Input,
|
|
InputNumber,
|
|
Modal,
|
|
Popconfirm,
|
|
Select,
|
|
Space,
|
|
Table,
|
|
Tag,
|
|
Typography,
|
|
message,
|
|
} from 'antd'
|
|
import { PlusOutlined, ReloadOutlined } from '@ant-design/icons'
|
|
import type { ColumnsType } from 'antd/es/table'
|
|
import { skinApi, orderApi } from '../api'
|
|
import { useAuth } from '../store/auth'
|
|
import type { Skin } from '../types'
|
|
|
|
export default function Skins() {
|
|
const { isAdmin } = useAuth()
|
|
const [list, setList] = useState<Skin[]>([])
|
|
const [total, setTotal] = useState(0)
|
|
const [page, setPage] = useState(1)
|
|
const [size, setSize] = useState(10)
|
|
const [keyword, setKeyword] = useState('')
|
|
const [loading, setLoading] = useState(false)
|
|
const [open, setOpen] = useState(false)
|
|
const [editing, setEditing] = useState<Skin | null>(null)
|
|
const [form] = Form.useForm()
|
|
|
|
const load = useCallback(async () => {
|
|
setLoading(true)
|
|
try {
|
|
const data = await skinApi.list({ page, size, keyword })
|
|
setList(data.list || [])
|
|
setTotal(data.total)
|
|
} catch (e) {
|
|
message.error(e instanceof Error ? e.message : '加载失败')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [page, size, keyword])
|
|
|
|
useEffect(() => {
|
|
load()
|
|
}, [load])
|
|
|
|
const openCreate = () => {
|
|
setEditing(null)
|
|
form.resetFields()
|
|
form.setFieldsValue({ stock: -1, commission: 0.1, status: 1 })
|
|
setOpen(true)
|
|
}
|
|
|
|
const openEdit = (record: Skin) => {
|
|
setEditing(record)
|
|
form.setFieldsValue(record)
|
|
setOpen(true)
|
|
}
|
|
|
|
const onSubmit = async () => {
|
|
const values = await form.validateFields()
|
|
try {
|
|
if (editing) {
|
|
await skinApi.update(editing.id, values)
|
|
message.success('更新成功')
|
|
} else {
|
|
await skinApi.create(values)
|
|
message.success('创建成功')
|
|
}
|
|
setOpen(false)
|
|
load()
|
|
} catch (e) {
|
|
message.error(e instanceof Error ? e.message : '操作失败')
|
|
}
|
|
}
|
|
|
|
const onDelete = async (id: number) => {
|
|
try {
|
|
await skinApi.remove(id)
|
|
message.success('已删除')
|
|
load()
|
|
} catch (e) {
|
|
message.error(e instanceof Error ? e.message : '删除失败')
|
|
}
|
|
}
|
|
|
|
const onOrder = async (skin: Skin) => {
|
|
try {
|
|
await orderApi.create({ skin_id: skin.id, buyer_name: '演示买家' })
|
|
message.success('下单成功')
|
|
} catch (e) {
|
|
message.error(e instanceof Error ? e.message : '下单失败')
|
|
}
|
|
}
|
|
|
|
const columns: ColumnsType<Skin> = [
|
|
{ title: 'ID', dataIndex: 'id', width: 70 },
|
|
{ title: '名称', dataIndex: 'name' },
|
|
{ title: '游戏', dataIndex: 'game', width: 120 },
|
|
{ title: '分类', dataIndex: 'category', width: 100 },
|
|
{
|
|
title: '售价',
|
|
dataIndex: 'price',
|
|
width: 100,
|
|
render: (v: number) => `¥${v.toFixed(2)}`,
|
|
},
|
|
{
|
|
title: '佣金比例',
|
|
dataIndex: 'commission',
|
|
width: 100,
|
|
render: (v: number) => `${(v * 100).toFixed(0)}%`,
|
|
},
|
|
{
|
|
title: '库存',
|
|
dataIndex: 'stock',
|
|
width: 80,
|
|
render: (v: number) => (v < 0 ? '无限' : v),
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'status',
|
|
width: 90,
|
|
render: (v: number) =>
|
|
v === 1 ? <Tag color="green">上架</Tag> : <Tag>下架</Tag>,
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'action',
|
|
width: isAdmin ? 200 : 100,
|
|
render: (_, record) => (
|
|
<Space>
|
|
{isAdmin ? (
|
|
<>
|
|
<Button type="link" size="small" onClick={() => openEdit(record)}>
|
|
编辑
|
|
</Button>
|
|
<Popconfirm title="确认删除?" onConfirm={() => onDelete(record.id)}>
|
|
<Button type="link" size="small" danger>
|
|
删除
|
|
</Button>
|
|
</Popconfirm>
|
|
</>
|
|
) : (
|
|
<Button type="link" size="small" disabled={record.status !== 1} onClick={() => onOrder(record)}>
|
|
下单
|
|
</Button>
|
|
)}
|
|
</Space>
|
|
),
|
|
},
|
|
]
|
|
|
|
return (
|
|
<div>
|
|
<Space style={{ marginBottom: 16, width: '100%', justifyContent: 'space-between' }}>
|
|
<Typography.Title level={4} style={{ margin: 0 }}>
|
|
皮肤商品
|
|
</Typography.Title>
|
|
<Space>
|
|
<Input.Search
|
|
placeholder="搜索名称"
|
|
allowClear
|
|
onSearch={(v) => {
|
|
setPage(1)
|
|
setKeyword(v)
|
|
}}
|
|
style={{ width: 220 }}
|
|
/>
|
|
<Button icon={<ReloadOutlined />} onClick={load}>
|
|
刷新
|
|
</Button>
|
|
{isAdmin && (
|
|
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
|
新增皮肤
|
|
</Button>
|
|
)}
|
|
</Space>
|
|
</Space>
|
|
|
|
<Table
|
|
rowKey="id"
|
|
loading={loading}
|
|
columns={columns}
|
|
dataSource={list}
|
|
pagination={{
|
|
current: page,
|
|
pageSize: size,
|
|
total,
|
|
showSizeChanger: true,
|
|
onChange: (p, s) => {
|
|
setPage(p)
|
|
setSize(s)
|
|
},
|
|
}}
|
|
/>
|
|
|
|
<Modal
|
|
title={editing ? '编辑皮肤' : '新增皮肤'}
|
|
open={open}
|
|
onOk={onSubmit}
|
|
onCancel={() => setOpen(false)}
|
|
destroyOnClose
|
|
width={560}
|
|
>
|
|
<Form form={form} layout="vertical" style={{ marginTop: 16 }}>
|
|
<Form.Item name="name" label="名称" rules={[{ required: true }]}>
|
|
<Input />
|
|
</Form.Item>
|
|
<Space style={{ width: '100%' }} size="middle">
|
|
<Form.Item name="game" label="游戏" style={{ width: 240 }}>
|
|
<Input />
|
|
</Form.Item>
|
|
<Form.Item name="category" label="分类" style={{ width: 240 }}>
|
|
<Input />
|
|
</Form.Item>
|
|
</Space>
|
|
<Space style={{ width: '100%' }} size="middle">
|
|
<Form.Item name="price" label="售价" rules={[{ required: true }]} style={{ width: 160 }}>
|
|
<InputNumber min={0} precision={2} style={{ width: '100%' }} />
|
|
</Form.Item>
|
|
<Form.Item name="cost_price" label="成本价" style={{ width: 160 }}>
|
|
<InputNumber min={0} precision={2} style={{ width: '100%' }} />
|
|
</Form.Item>
|
|
<Form.Item name="commission" label="佣金比例" style={{ width: 160 }}>
|
|
<InputNumber min={0} max={1} step={0.01} style={{ width: '100%' }} />
|
|
</Form.Item>
|
|
</Space>
|
|
<Space style={{ width: '100%' }} size="middle">
|
|
<Form.Item name="stock" label="库存(-1无限)" style={{ width: 240 }}>
|
|
<InputNumber style={{ width: '100%' }} />
|
|
</Form.Item>
|
|
<Form.Item name="status" label="状态" style={{ width: 240 }}>
|
|
<Select
|
|
options={[
|
|
{ value: 1, label: '上架' },
|
|
{ value: 0, label: '下架' },
|
|
]}
|
|
/>
|
|
</Form.Item>
|
|
</Space>
|
|
<Form.Item name="description" label="描述">
|
|
<Input.TextArea rows={3} />
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</div>
|
|
)
|
|
}
|