Files
video-gen/video-gen-admin/src/pages/homeMaterials/HomeMaterialCategoryPanel.tsx
T
2026-06-30 17:10:51 +08:00

157 lines
6.7 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { Button, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Switch, Table, message } from 'antd';
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
import * as AntIcons from '@ant-design/icons';
import { deleteHomeMaterialCategory, getHomeMaterialCategories, saveHomeMaterialCategory } from '../../api';
import type { HomeMaterialCategory, HomeMaterialCategoryPayload } from '../../types';
const ICON_COMPONENTS = AntIcons as unknown as Record<string, React.ComponentType<{ style?: React.CSSProperties }>>;
const CATEGORY_ICON_OPTIONS = [
{ label: '通用应用', value: 'AppstoreOutlined' },
{ label: '图片素材', value: 'PictureOutlined' },
{ label: '视频素材', value: 'VideoCameraOutlined' },
{ label: '电商购物', value: 'ShoppingOutlined' },
{ label: '门店商铺', value: 'ShopOutlined' },
{ label: '美妆护肤', value: 'SkinOutlined' },
{ label: '餐饮食品', value: 'CoffeeOutlined' },
{ label: '家居生活', value: 'HomeOutlined' },
{ label: '汽车出行', value: 'CarOutlined' },
{ label: '医疗健康', value: 'MedicineBoxOutlined' },
{ label: '教育培训', value: 'BookOutlined' },
{ label: '数码科技', value: 'LaptopOutlined' },
{ label: '旅游定位', value: 'EnvironmentOutlined' },
{ label: '摄影影像', value: 'CameraOutlined' },
{ label: '礼品活动', value: 'GiftOutlined' },
{ label: '热门爆款', value: 'FireOutlined' },
{ label: '品牌服务', value: 'CustomerServiceOutlined' },
{ label: '营销增长', value: 'RocketOutlined' },
{ label: '机构企业', value: 'BankOutlined' },
{ label: '娱乐休闲', value: 'SmileOutlined' },
];
function renderIconOption(value?: string | null, label?: string) {
if (!value) {
return <span style={{ color: '#999' }}>未设置</span>;
}
const Icon = ICON_COMPONENTS[value];
return (
<Space size={6}>
{Icon ? <Icon /> : null}
<span>{label || value}</span>
<span style={{ color: '#999' }}>{value}</span>
</Space>
);
}
const HomeMaterialCategoryPanel: React.FC<{ onChanged?: () => void }> = ({ onChanged }) => {
const [items, setItems] = useState<HomeMaterialCategory[]>([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(1);
const [modalOpen, setModalOpen] = useState(false);
const [editing, setEditing] = useState<HomeMaterialCategory | null>(null);
const [form] = Form.useForm();
const load = async () => {
setLoading(true);
try {
const res = await getHomeMaterialCategories({ page, pageSize: 20 });
setItems(res.items);
setTotal(res.total);
} finally {
setLoading(false);
}
};
useEffect(() => { load(); }, [page]);
const openEdit = (row?: HomeMaterialCategory) => {
setEditing(row || null);
form.setFieldsValue(row ? {
name: row.name,
key: row.key,
description: row.description,
icon: row.icon,
is_active: row.isActive,
sort_order: row.sortOrder,
} : { is_active: true, sort_order: 0 });
setModalOpen(true);
};
const submit = async () => {
const values = await form.validateFields();
await saveHomeMaterialCategory({ ...(values as HomeMaterialCategoryPayload), id: editing?.id });
message.success('保存成功');
setModalOpen(false);
await load();
onChanged?.();
};
return (
<>
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>
<div />
<Button type="primary" icon={<PlusOutlined />} onClick={() => openEdit()}>新增行业</Button>
</div>
<Table
rowKey="id"
loading={loading}
dataSource={items}
pagination={{ current: page, total, pageSize: 20, onChange: setPage }}
columns={[
{ title: '行业名称', dataIndex: 'name' },
{ title: 'Key', dataIndex: 'key' },
{ title: '图标', dataIndex: 'icon', render: (v?: string | null) => renderIconOption(v) },
{ title: '启用', dataIndex: 'isActive', render: (v: boolean) => v ? '启用' : '禁用' },
{ title: '素材数', dataIndex: 'assetCount' },
{ title: '图片/视频', render: (_: unknown, r: HomeMaterialCategory) => `${r.imageCount}/${r.videoCount}` },
{ title: '排序', dataIndex: 'sortOrder' },
{
title: '操作',
render: (_: unknown, row: HomeMaterialCategory) => (
<Space>
<Button size="small" icon={<EditOutlined />} onClick={() => openEdit(row)}>编辑</Button>
<Popconfirm title="确认删除该行业?" onConfirm={async () => { await deleteHomeMaterialCategory(row.id); message.success('删除成功'); await load(); onChanged?.(); }}>
<Button size="small" danger icon={<DeleteOutlined />}>删除</Button>
</Popconfirm>
</Space>
),
},
]}
/>
<Modal title={editing ? '编辑行业' : '新增行业'} open={modalOpen} onOk={submit} onCancel={() => setModalOpen(false)} destroyOnHidden>
<Form form={form} layout="vertical">
<Form.Item name="name" label="行业名称" rules={[{ required: true, message: '请输入行业名称' }]}><Input /></Form.Item>
<Form.Item name="key" label="行业Key" rules={[{ required: true, message: '请输入行业Key' }, { pattern: /^[A-Za-z0-9_-]+$/, message: '只允许字母、数字、下划线、中划线' }]}><Input /></Form.Item>
<Form.Item name="description" label="描述"><Input.TextArea rows={3} /></Form.Item>
<Form.Item name="icon" label="图标">
<Select
allowClear
showSearch
placeholder="请选择行业图标"
optionFilterProp="searchText"
filterOption={(input, option) => {
const text = String((option as any)?.searchText || '').toLowerCase();
const value = String(option?.value || '').toLowerCase();
const keyword = input.toLowerCase();
return text.includes(keyword) || value.includes(keyword);
}}
options={CATEGORY_ICON_OPTIONS.map(item => ({
value: item.value,
searchText: `${item.label} ${item.value}`,
label: renderIconOption(item.value, item.label),
}))}
/>
</Form.Item>
<Form.Item name="sort_order" label="排序"><InputNumber min={0} style={{ width: '100%' }} /></Form.Item>
<Form.Item name="is_active" label="启用" valuePropName="checked"><Switch /></Form.Item>
</Form>
</Modal>
</>
);
};
export default HomeMaterialCategoryPanel;