| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- import { useEffect, useState, useCallback } from 'react'
- import {
- Table,
- Button,
- Modal,
- Form,
- Input,
- Select,
- Switch,
- Space,
- message,
- Popconfirm,
- Tag,
- Row,
- Col,
- } from 'antd'
- import { PlusOutlined, DeleteOutlined } from '@ant-design/icons'
- import { getKeywords, createKeywords, updateKeyword, deleteKeyword, type Keyword } from '../api'
- const { Option } = Select
- const { TextArea } = Input
- function formatDateTime(dateStr: string) {
- return new Date(dateStr).toLocaleString('zh-CN')
- }
- const tagColors: Record<string, string> = {
- seed: 'volcano',
- '机场': 'blue',
- VPN: 'green',
- }
- const industryOptions = [
- { label: '搜索关键词', value: '机场' },
- { label: '种子频道', value: 'seed' },
- { label: 'VPN', value: 'VPN' },
- ]
- interface BatchFormValues {
- keywords_text: string
- industry_tag: string
- }
- export default function Keywords() {
- const [data, setData] = useState<Keyword[]>([])
- const [total, setTotal] = useState(0)
- const [page, setPage] = useState(1)
- const [loading, setLoading] = useState(false)
- const [modalOpen, setModalOpen] = useState(false)
- const [saving, setSaving] = useState(false)
- const [filterTag, setFilterTag] = useState('')
- const [form] = Form.useForm<BatchFormValues>()
- const fetchData = useCallback(async (currentPage = 1) => {
- setLoading(true)
- try {
- const params: Record<string, unknown> = { page: currentPage, page_size: 20 }
- if (filterTag) params.industry_tag = filterTag
- const res = await getKeywords(params)
- setData(res.data.items)
- setTotal(res.data.total)
- } catch {
- message.error('获取关键词列表失败')
- } finally {
- setLoading(false)
- }
- }, [filterTag])
- useEffect(() => {
- setPage(1)
- fetchData(1)
- }, [filterTag, fetchData])
- const handleBatchAdd = () => {
- form.resetFields()
- setModalOpen(true)
- }
- const handleSave = async () => {
- try {
- const values = await form.validateFields()
- const keywords = values.keywords_text
- .split('\n')
- .map((k: string) => k.trim())
- .filter((k: string) => k.length > 0)
- if (keywords.length === 0) {
- message.warning('请输入至少一个关键词')
- return
- }
- setSaving(true)
- await createKeywords({ keywords, industry_tag: values.industry_tag })
- message.success(`成功添加 ${keywords.length} 个条目`)
- setModalOpen(false)
- fetchData(page)
- } catch (err) {
- if (err && typeof err === 'object' && 'errorFields' in err) return
- message.error('添加失败')
- } finally {
- setSaving(false)
- }
- }
- const handleDelete = async (id: number) => {
- try {
- await deleteKeyword(id)
- message.success('删除成功')
- fetchData(page)
- } catch {
- message.error('删除失败')
- }
- }
- const handleToggle = async (record: Keyword, checked: boolean) => {
- try {
- await updateKeyword(record.id, { enabled: checked })
- message.success('状态已更新')
- fetchData(page)
- } catch {
- message.error('状态更新失败')
- }
- }
- const columns = [
- { title: 'ID', dataIndex: 'id', key: 'id', width: 70 },
- {
- title: '关键词/频道名',
- dataIndex: 'keyword',
- key: 'keyword',
- },
- {
- title: '类型',
- dataIndex: 'industry_tag',
- key: 'industry_tag',
- render: (v: string) => <Tag color={tagColors[v] ?? 'default'}>{v || '未分类'}</Tag>,
- },
- {
- title: '启用',
- dataIndex: 'enabled',
- key: 'enabled',
- render: (v: boolean, record: Keyword) => (
- <Switch
- checked={v}
- onChange={(checked) => handleToggle(record, checked)}
- checkedChildren="启用"
- unCheckedChildren="禁用"
- />
- ),
- },
- {
- title: '创建时间',
- dataIndex: 'created_at',
- key: 'created_at',
- render: (t: string) => formatDateTime(t),
- },
- {
- title: '操作',
- key: 'action',
- render: (_: unknown, record: Keyword) => (
- <Space>
- <Popconfirm
- title="确认删除?"
- onConfirm={() => handleDelete(record.id)}
- okText="确认"
- cancelText="取消"
- >
- <Button size="small" danger icon={<DeleteOutlined />}>删除</Button>
- </Popconfirm>
- </Space>
- ),
- },
- ]
- return (
- <div>
- <Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
- <Col>
- <Button type="primary" icon={<PlusOutlined />} onClick={handleBatchAdd}>
- 批量添加
- </Button>
- </Col>
- <Col>
- <Select
- style={{ width: 160 }}
- value={filterTag}
- onChange={setFilterTag}
- placeholder="按类型筛选"
- allowClear
- >
- <Option value="">全部</Option>
- <Option value="seed">种子频道</Option>
- <Option value="机场">机场</Option>
- <Option value="VPN">VPN</Option>
- </Select>
- </Col>
- </Row>
- <Table
- dataSource={data}
- columns={columns}
- rowKey="id"
- loading={loading}
- pagination={{
- current: page,
- pageSize: 20,
- total,
- onChange: (p) => {
- setPage(p)
- fetchData(p)
- },
- showTotal: (t) => `共 ${t} 条`,
- }}
- />
- <Modal
- title="批量添加"
- open={modalOpen}
- onOk={handleSave}
- onCancel={() => setModalOpen(false)}
- confirmLoading={saving}
- okText="添加"
- cancelText="取消"
- >
- <Form form={form} layout="vertical" style={{ marginTop: 16 }}>
- <Form.Item
- name="industry_tag"
- label="类型"
- rules={[{ required: true, message: '请选择类型' }]}
- >
- <Select placeholder="选择类型">
- {industryOptions.map((o) => (
- <Option key={o.value} value={o.value}>{o.label}</Option>
- ))}
- </Select>
- </Form.Item>
- <Form.Item
- name="keywords_text"
- label="内容(每行一个)"
- rules={[{ required: true, message: '请输入内容' }]}
- >
- <TextArea
- rows={8}
- placeholder="种子频道填频道名(如 bbs3000),关键词填搜索词(如 机场推荐) 每行一个"
- />
- </Form.Item>
- </Form>
- </Modal>
- </div>
- )
- }
|