feat(auth): 实现登录注册功能并重构 App 组件

- 新增登录和注册页面组件
- 实现用户认证和权限管理逻辑
- 重构 App 组件,使用 Router 和 AuthProvider
- 添加管理员面板和论坛页面组件

Change-Id: Iaa4502616970e75e3268537f73c75dac8f60e24d
diff --git a/src/features/torrents/pages/TorrentListPage.jsx b/src/features/torrents/pages/TorrentListPage.jsx
new file mode 100644
index 0000000..9e1fd49
--- /dev/null
+++ b/src/features/torrents/pages/TorrentListPage.jsx
@@ -0,0 +1,671 @@
+import React, { useState, useEffect } from "react";
+import {
+  Typography,
+  Button,
+  Tabs,
+  Radio,
+  Tag,
+  Divider,
+  Checkbox,
+  Input,
+  Table,
+  Space,
+  Tooltip,
+  Badge,
+  Spin,
+  Skeleton,
+  Result,
+} from "antd";
+import {
+  UploadOutlined,
+  SearchOutlined,
+  DownloadOutlined,
+  EyeOutlined,
+  LinkOutlined,
+  FileTextOutlined,
+  CheckCircleOutlined,
+  ClockCircleOutlined,
+  LoadingOutlined,
+  ReloadOutlined,
+} from "@ant-design/icons";
+import { Link } from "react-router-dom";
+
+const { Title, Text } = Typography;
+const { TabPane } = Tabs;
+const { Search } = Input;
+
+// 模拟种子数据
+const torrentData = Array.from({ length: 100 }, (_, index) => ({
+  key: index + 1,
+  id: index + 1,
+  category: [
+    "电影",
+    "剧集",
+    "音乐",
+    "动漫",
+    "游戏",
+    "综艺",
+    "体育",
+    "软件",
+    "学习",
+    "纪录片",
+    "其他",
+  ][index % 11],
+  subcategory:
+    index % 11 === 0
+      ? "动作"
+      : index % 11 === 1
+      ? "美剧"
+      : index % 11 === 2
+      ? "古典"
+      : index % 11 === 3
+      ? "日漫"
+      : index % 11 === 4
+      ? "RPG"
+      : index % 11 === 5
+      ? "脱口秀"
+      : index % 11 === 6
+      ? "足球"
+      : index % 11 === 7
+      ? "工具"
+      : index % 11 === 8
+      ? "课程"
+      : index % 11 === 9
+      ? "自然"
+      : "其他",
+  title: `种子标题 ${index + 1} ${index % 5 === 0 ? "[中字]" : ""} ${
+    index % 7 === 0 ? "合集" : ""
+  }`,
+  size: `${(Math.random() * 50).toFixed(1)} GB`,
+  fileCount: Math.floor(Math.random() * 100) + 1,
+  views: Math.floor(Math.random() * 1000) + 100,
+  publishTime: `2023-${Math.floor(Math.random() * 12) + 1}-${
+    Math.floor(Math.random() * 28) + 1
+  }`,
+  seeders: Math.floor(Math.random() * 100),
+  leechers: Math.floor(Math.random() * 50),
+  completed: Math.floor(Math.random() * 200),
+  uploader: `user${Math.floor(Math.random() * 10) + 1}`,
+  isOwnTorrent: index % 15 === 0,
+  hasSubtitle: index % 5 === 0,
+  isCollection: index % 7 === 0,
+  isActive: Math.random() > 0.2, // 80%的概率是活种
+}));
+
+// 定义类别及其子类别
+const categories = [
+  {
+    key: "all",
+    name: "全部",
+    count: torrentData.length,
+    subcategories: [],
+  },
+  {
+    key: "movie",
+    name: "电影",
+    count: torrentData.filter((t) => t.category === "电影").length,
+    subcategories: [
+      "动作",
+      "喜剧",
+      "爱情",
+      "科幻",
+      "恐怖",
+      "剧情",
+      "战争",
+      "纪录",
+      "动画",
+      "其他",
+    ],
+  },
+  {
+    key: "tv",
+    name: "剧集",
+    count: torrentData.filter((t) => t.category === "剧集").length,
+    subcategories: ["美剧", "英剧", "韩剧", "日剧", "国产剧", "港台剧", "其他"],
+  },
+  {
+    key: "music",
+    name: "音乐",
+    count: torrentData.filter((t) => t.category === "音乐").length,
+    subcategories: [
+      "流行",
+      "摇滚",
+      "电子",
+      "民谣",
+      "嘻哈",
+      "古典",
+      "爵士",
+      "原声带",
+      "其他",
+    ],
+  },
+  {
+    key: "anime",
+    name: "动漫",
+    count: torrentData.filter((t) => t.category === "动漫").length,
+    subcategories: ["日漫", "国漫", "美漫", "剧场版", "OVA", "其他"],
+  },
+  {
+    key: "game",
+    name: "游戏",
+    count: torrentData.filter((t) => t.category === "游戏").length,
+    subcategories: [
+      "角色扮演",
+      "动作",
+      "射击",
+      "策略",
+      "模拟",
+      "冒险",
+      "体育",
+      "格斗",
+      "其他",
+    ],
+  },
+  {
+    key: "variety",
+    name: "综艺",
+    count: torrentData.filter((t) => t.category === "综艺").length,
+    subcategories: [
+      "真人秀",
+      "脱口秀",
+      "访谈",
+      "选秀",
+      "纪实",
+      "搞笑",
+      "情感",
+      "其他",
+    ],
+  },
+  {
+    key: "sports",
+    name: "体育",
+    count: torrentData.filter((t) => t.category === "体育").length,
+    subcategories: [
+      "足球",
+      "篮球",
+      "网球",
+      "赛车",
+      "拳击",
+      "格斗",
+      "奥运",
+      "其他",
+    ],
+  },
+  {
+    key: "software",
+    name: "软件",
+    count: torrentData.filter((t) => t.category === "软件").length,
+    subcategories: [
+      "操作系统",
+      "应用软件",
+      "图形设计",
+      "音频编辑",
+      "视频制作",
+      "编程开发",
+      "其他",
+    ],
+  },
+  {
+    key: "learning",
+    name: "学习",
+    count: torrentData.filter((t) => t.category === "学习").length,
+    subcategories: [
+      "语言",
+      "编程",
+      "设计",
+      "经济",
+      "管理",
+      "考试",
+      "技能",
+      "其他",
+    ],
+  },
+  {
+    key: "documentary",
+    name: "纪录片",
+    count: torrentData.filter((t) => t.category === "纪录片").length,
+    subcategories: [
+      "自然",
+      "历史",
+      "科学",
+      "人文",
+      "社会",
+      "军事",
+      "传记",
+      "其他",
+    ],
+  },
+  {
+    key: "other",
+    name: "其他",
+    count: torrentData.filter((t) => t.category === "其他").length,
+    subcategories: ["其他"],
+  },
+];
+
+const TorrentListPage = () => {
+  // 状态管理
+  const [activeCategory, setActiveCategory] = useState("all");
+  const [activeSubcategories, setActiveSubcategories] = useState([]);
+  const [statusFilter, setStatusFilter] = useState("all"); // 'all', 'active', 'dead'
+  const [ownTorrentsOnly, setOwnTorrentsOnly] = useState(false);
+  const [subtitledOnly, setSubtitledOnly] = useState(false);
+  const [collectionsOnly, setCollectionsOnly] = useState(false);
+  const [searchText, setSearchText] = useState("");
+  const [currentPage, setCurrentPage] = useState(1);
+  const [pageSize] = useState(50);
+  
+  // 添加加载状态
+  const [loading, setLoading] = useState(true);
+  const [categoryLoading, setCategoryLoading] = useState(false);
+  const [error, setError] = useState(null);
+  const [torrents, setTorrents] = useState([]);
+
+  // 模拟数据加载
+  useEffect(() => {
+    const loadData = async () => {
+      try {
+        setLoading(true);
+        // 模拟网络延迟
+        await new Promise((resolve) => setTimeout(resolve, 1000));
+        setTorrents(torrentData);
+        setError(null);
+      } catch (err) {
+        setError("加载种子数据失败,请稍后再试");
+        setTorrents([]);
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    loadData();
+  }, []);
+
+  // 模拟分类切换加载
+  const handleCategoryChange = (key) => {
+    setCategoryLoading(true);
+    setActiveCategory(key);
+    setActiveSubcategories([]);
+    setCurrentPage(1);
+    
+    // 模拟切换延迟
+    setTimeout(() => {
+      setCategoryLoading(false);
+    }, 500);
+  };
+
+  // 处理二级分类变化
+  const handleSubcategoryChange = (subcategory) => {
+    const newSubcategories = [...activeSubcategories];
+    const index = newSubcategories.indexOf(subcategory);
+
+    if (index > -1) {
+      newSubcategories.splice(index, 1);
+    } else {
+      newSubcategories.push(subcategory);
+    }
+
+    setActiveSubcategories(newSubcategories);
+    setCurrentPage(1);
+  };
+
+  // 过滤种子数据
+  const filteredTorrents = torrents.filter((torrent) => {
+    // 一级分类筛选
+    if (activeCategory !== "all") {
+      const categoryObj = categories.find((cat) => cat.key === activeCategory);
+      if (categoryObj && torrent.category !== categoryObj.name) {
+        return false;
+      }
+    }
+
+    // 二级分类筛选
+    if (
+      activeSubcategories.length > 0 &&
+      !activeSubcategories.includes(torrent.subcategory)
+    ) {
+      return false;
+    }
+
+    // 状态筛选
+    if (statusFilter === "active" && !torrent.isActive) return false;
+    if (statusFilter === "dead" && torrent.isActive) return false;
+
+    // 我的种子筛选
+    if (ownTorrentsOnly && !torrent.isOwnTorrent) return false;
+
+    // 有字幕筛选
+    if (subtitledOnly && !torrent.hasSubtitle) return false;
+
+    // 合集筛选
+    if (collectionsOnly && !torrent.isCollection) return false;
+
+    // 搜索文本筛选
+    if (
+      searchText &&
+      !torrent.title.toLowerCase().includes(searchText.toLowerCase())
+    ) {
+      return false;
+    }
+
+    return true;
+  });
+
+  // 处理搜索,并添加加载状态
+  const handleSearch = (value) => {
+    setLoading(true);
+    setTimeout(() => {
+      setSearchText(value);
+      setLoading(false);
+    }, 500);
+  };
+
+  // 表格列定义
+  const columns = [
+    {
+      title: "类别",
+      dataIndex: "category",
+      key: "category",
+      width: 100,
+      render: (text, record) => (
+        <Space direction="vertical" size={0}>
+          <Tag color="blue">{text}</Tag>
+          <Text type="secondary" style={{ fontSize: "12px" }}>
+            {record.subcategory}
+          </Text>
+        </Space>
+      ),
+    },
+    {
+      title: "名称",
+      dataIndex: "title",
+      key: "title",
+      render: (text, record) => (
+        <Space>
+          <Link to={`/torrent/${record.id}`}>{text}</Link>
+          {record.hasSubtitle && <Tag color="green">中字</Tag>}
+          {record.isCollection && <Tag color="purple">合集</Tag>}
+        </Space>
+      ),
+    },
+    {
+      title: "操作",
+      key: "action",
+      width: 120,
+      render: (_, record) => (
+        <Space size="small">
+          <Tooltip title="下载种子">
+            <Button type="primary" size="small" icon={<DownloadOutlined />} />
+          </Tooltip>
+          <Tooltip title="查看详情">
+            <Button size="small" icon={<EyeOutlined />} />
+          </Tooltip>
+          <Tooltip title="复制链接">
+            <Button size="small" icon={<LinkOutlined />} />
+          </Tooltip>
+        </Space>
+      ),
+    },
+    {
+      title: "大小",
+      dataIndex: "size",
+      key: "size",
+      width: 90,
+      sorter: (a, b) => parseFloat(a.size) - parseFloat(b.size),
+    },
+    {
+      title: "文件",
+      dataIndex: "fileCount",
+      key: "fileCount",
+      width: 70,
+      render: (count) => (
+        <Tooltip title={`${count} 个文件`}>
+          <span>
+            <FileTextOutlined /> {count}
+          </span>
+        </Tooltip>
+      ),
+    },
+    {
+      title: "点击",
+      dataIndex: "views",
+      key: "views",
+      width: 70,
+      sorter: (a, b) => a.views - b.views,
+    },
+    {
+      title: "发布时间",
+      dataIndex: "publishTime",
+      key: "publishTime",
+      width: 100,
+      sorter: (a, b) => new Date(a.publishTime) - new Date(b.publishTime),
+    },
+    {
+      title: "种子数",
+      dataIndex: "seeders",
+      key: "seeders",
+      width: 80,
+      sorter: (a, b) => a.seeders - b.seeders,
+      render: (seeders, record) => (
+        <Badge
+          count={seeders}
+          style={{
+            backgroundColor: seeders > 0 ? "#52c41a" : "#f5222d",
+            fontSize: "12px",
+          }}
+        />
+      ),
+    },
+    {
+      title: "下载量",
+      dataIndex: "leechers",
+      key: "leechers",
+      width: 80,
+      sorter: (a, b) => a.leechers - b.leechers,
+      render: (leechers) => (
+        <Badge
+          count={leechers}
+          style={{
+            backgroundColor: "#faad14",
+            fontSize: "12px",
+          }}
+        />
+      ),
+    },
+    {
+      title: "完成",
+      dataIndex: "completed",
+      key: "completed",
+      width: 70,
+      sorter: (a, b) => a.completed - b.completed,
+    },
+    {
+      title: "发布者",
+      dataIndex: "uploader",
+      key: "uploader",
+      width: 100,
+      render: (text, record) => (
+        <Space>
+          <Link to={`/user/${text}`}>{text}</Link>
+          {record.isOwnTorrent && (
+            <Tooltip title="我的种子">
+              <Tag color="green">我</Tag>
+            </Tooltip>
+          )}
+        </Space>
+      ),
+    },
+  ];
+  
+  // 加载错误时显示错误信息
+  if (error) {
+    return (
+      <Result
+        status="error"
+        title="加载失败"
+        subTitle={error}
+        extra={[
+          <Button 
+            type="primary" 
+            key="reload" 
+            icon={<ReloadOutlined />}
+            onClick={() => window.location.reload()}
+          >
+            重新加载
+          </Button>
+        ]}
+      />
+    );
+  }
+
+  // 自定义加载图标
+  const antIcon = <LoadingOutlined style={{ fontSize: 24 }} spin />;
+
+  return (
+    <div className="space-y-4">
+      <div className="flex justify-between items-center">
+        <Title level={2}>种子列表</Title>
+        <Button type="primary" icon={<UploadOutlined />}>
+          <Link to="/upload">发布种子</Link>
+        </Button>
+      </div>
+
+      {/* 一级分类筛选 */}
+      <Spin spinning={categoryLoading} indicator={antIcon}>
+        <Tabs
+          activeKey={activeCategory}
+          onChange={handleCategoryChange}
+          type="card"
+        >
+          {categories.map((category) => (
+            <TabPane
+              tab={
+                <span>
+                  {category.name} <Tag>{category.count}</Tag>
+                </span>
+              }
+              key={category.key}
+            >
+              {/* 二级分类筛选 */}
+              {category.subcategories.length > 0 && (
+                <div className="flex flex-wrap gap-2 p-3 bg-gray-50 rounded">
+                  {category.subcategories.map((subcategory) => (
+                    <Checkbox
+                      key={subcategory}
+                      checked={activeSubcategories.includes(subcategory)}
+                      onChange={() => handleSubcategoryChange(subcategory)}
+                    >
+                      {subcategory}
+                    </Checkbox>
+                  ))}
+                </div>
+              )}
+            </TabPane>
+          ))}
+        </Tabs>
+      </Spin>
+
+      <Divider style={{ margin: "12px 0" }} />
+
+      {/* 额外筛选条件 */}
+      <div className="flex justify-between items-center flex-wrap gap-4">
+        <div className="flex items-center gap-4 flex-wrap">
+          <Skeleton loading={loading} active paragraph={false} title={{ width: '100%' }} className="inline-block">
+            <Checkbox
+              checked={ownTorrentsOnly}
+              onChange={(e) => setOwnTorrentsOnly(e.target.checked)}
+            >
+              我的种子
+            </Checkbox>
+
+            <Checkbox
+              checked={subtitledOnly}
+              onChange={(e) => setSubtitledOnly(e.target.checked)}
+            >
+              有字幕
+            </Checkbox>
+
+            <Checkbox
+              checked={collectionsOnly}
+              onChange={(e) => setCollectionsOnly(e.target.checked)}
+            >
+              合集
+            </Checkbox>
+
+            <Radio.Group
+              value={statusFilter}
+              onChange={(e) => setStatusFilter(e.target.value)}
+              optionType="button"
+              buttonStyle="solid"
+            >
+              <Radio.Button value="all">全部</Radio.Button>
+              <Radio.Button value="active">
+                <Tooltip title="有做种的资源">
+                  <Space>
+                    <Badge status="success" />
+                    仅活种
+                  </Space>
+                </Tooltip>
+              </Radio.Button>
+              <Radio.Button value="dead">
+                <Tooltip title="无人做种的资源">
+                  <Space>
+                    <Badge status="error" />
+                    仅死种
+                  </Space>
+                </Tooltip>
+              </Radio.Button>
+            </Radio.Group>
+          </Skeleton>
+        </div>
+
+        <div>
+          <Search
+            placeholder="搜索种子"
+            allowClear
+            enterButton={<SearchOutlined />}
+            size="middle"
+            onSearch={handleSearch}
+            style={{ width: 300 }}
+            loading={loading}
+          />
+        </div>
+      </div>
+
+      {/* 种子列表 */}
+      <div className="relative">
+        <Table
+          columns={columns}
+          dataSource={filteredTorrents}
+          pagination={{
+            current: currentPage,
+            pageSize: pageSize,
+            total: filteredTorrents.length,
+            onChange: (page) => {
+              setLoading(true);
+              setTimeout(() => {
+                setCurrentPage(page);
+                setLoading(false);
+              }, 500);
+            },
+            showSizeChanger: false,
+            showTotal: (total) => `共 ${total} 个种子`,
+          }}
+          size="middle"
+          bordered
+          scroll={{ x: 1100 }}
+          loading={{
+            spinning: loading,
+            indicator: <Spin indicator={antIcon} />,
+            tip: "正在加载种子数据...",
+          }}
+          locale={{
+            emptyText: "没有找到符合条件的种子",
+          }}
+        />
+      </div>
+    </div>
+  );
+};
+
+export default TorrentListPage;
diff --git a/src/features/torrents/pages/UploadTorrentPage.jsx b/src/features/torrents/pages/UploadTorrentPage.jsx
new file mode 100644
index 0000000..8e17a9e
--- /dev/null
+++ b/src/features/torrents/pages/UploadTorrentPage.jsx
@@ -0,0 +1,857 @@
+import React, { useState } from "react";
+import {
+  Typography,
+  Tabs,
+  Form,
+  Input,
+  Upload,
+  Button,
+  Select,
+  DatePicker,
+  InputNumber,
+  Radio,
+  Checkbox,
+  Collapse,
+  Alert,
+  Divider,
+  Space,
+  message,
+} from "antd";
+import {
+  UploadOutlined,
+  InboxOutlined,
+  InfoCircleOutlined,
+} from "@ant-design/icons";
+
+const { Title, Paragraph, Text } = Typography;
+const { TabPane } = Tabs;
+const { Option } = Select;
+const { TextArea } = Input;
+const { Panel } = Collapse;
+const { Dragger } = Upload;
+
+// 通用的上传组件配置
+const uploadProps = {
+  name: "torrent",
+  multiple: false,
+  action: "/api/upload-torrent",
+  accept: ".torrent",
+  maxCount: 1,
+  onChange(info) {
+    const { status } = info.file;
+    if (status === "done") {
+      message.success(`${info.file.name} 文件上传成功`);
+    } else if (status === "error") {
+      message.error(`${info.file.name} 文件上传失败`);
+    }
+  },
+  beforeUpload(file) {
+    const isTorrent = file.type === "application/x-bittorrent" || file.name.endsWith(".torrent");
+    if (!isTorrent) {
+      message.error("您只能上传 .torrent 文件!");
+    }
+    return isTorrent || Upload.LIST_IGNORE;
+  },
+};
+
+// 注意事项内容
+const NoticeContent = () => (
+  <div className="space-y-4">
+    <Alert
+      message="请仔细阅读以下注意事项"
+      description="上传种子前,请确保您了解并同意以下规则,否则您的种子可能会被删除或您的账号可能会被处罚。"
+      type="info"
+      showIcon
+    />
+
+    <Collapse defaultActiveKey={["1"]}>
+      <Panel header="一般规则" key="1">
+        <ul className="list-disc pl-8 space-y-2">
+          <li>您上传的资源必须是合法的,不得上传侵犯版权的内容。</li>
+          <li>禁止上传含有恶意代码的文件,所有上传内容会经过安全检查。</li>
+          <li>资源标题必须清晰描述内容,不得使用误导性标题。</li>
+          <li>请确保您提供的信息准确完整,包括分类、标签、简介等。</li>
+          <li>如发现重复资源,管理员有权合并或删除。</li>
+          <li>新用户需要达到一定等级后才能上传资源,具体请参考用户等级规则。</li>
+        </ul>
+      </Panel>
+      <Panel header="做种要求" key="2">
+        <ul className="list-disc pl-8 space-y-2">
+          <li>上传者必须保证做种至少 7 天,或直到至少有 3 个其他用户完成下载。</li>
+          <li>对于大型资源({'>'}20GB),请确保您有足够的上传带宽。</li>
+          <li>对于稀有资源,请尽可能长期做种,这将获得额外的积分奖励。</li>
+          <li>故意停止做种可能导致警告或账号处罚。</li>
+        </ul>
+      </Panel>
+      <Panel header="奖励政策" key="3">
+        <ul className="list-disc pl-8 space-y-2">
+          <li>首发资源将获得额外的积分奖励。</li>
+          <li>高质量的资源(高分辨率、完整音轨等)将获得质量加成。</li>
+          <li>稀有、珍贵的资源会获得特殊奖励。</li>
+          <li>长期保持良好的做种习惯将提高您的用户级别。</li>
+        </ul>
+      </Panel>
+    </Collapse>
+  </div>
+);
+
+// 通用版规组件
+const CategoryRules = ({ category }) => {
+  const rules = {
+    movie: "电影资源上传规则...",
+    tv: "剧集资源上传规则...",
+    music: "音乐资源上传规则...",
+    anime: "动漫资源上传规则...",
+    game: "游戏资源上传规则...",
+    variety: "综艺资源上传规则...",
+    sports: "体育资源上传规则...",
+    software: "软件资源上传规则...",
+    learning: "学习资源上传规则...",
+    documentary: "纪录片资源上传规则...",
+    other: "其他资源上传规则...",
+  };
+
+  return (
+    <Collapse className="mb-6">
+      <Panel header={`${category}资源版规`} key="1">
+        <Paragraph>{rules[category] || "暂无特定规则"}</Paragraph>
+      </Panel>
+    </Collapse>
+  );
+};
+
+// 基础表单组件
+const BaseFormItems = ({ category }) => (
+  <>
+    <Form.Item
+      name="torrentFile"
+      label="种子文件"
+      rules={[{ required: true, message: "请上传种子文件" }]}
+    >
+      <Dragger {...uploadProps}>
+        <p className="ant-upload-drag-icon">
+          <InboxOutlined />
+        </p>
+        <p className="ant-upload-text">点击或拖拽种子文件到此区域上传</p>
+        <p className="ant-upload-hint">
+          支持单个.torrent文件上传,请确保种子文件有效
+        </p>
+      </Dragger>
+    </Form.Item>
+
+    <Form.Item
+      name="title"
+      label="标题"
+      rules={[{ required: true, message: "请输入资源标题" }]}
+    >
+      <Input placeholder="请输入完整、准确的资源标题" />
+    </Form.Item>
+
+    <Form.Item
+      name="chineseName"
+      label="中文名"
+      rules={[{ required: true, message: "请输入资源中文名" }]}
+    >
+      <Input placeholder="请输入资源中文名称" />
+    </Form.Item>
+
+    <Form.Item
+      name="englishName"
+      label="英文名/原名"
+    >
+      <Input placeholder="请输入资源英文名称或原名" />
+    </Form.Item>
+
+    <Form.Item
+      name="year"
+      label="年份"
+      rules={[{ required: true, message: "请选择年份" }]}
+    >
+      <DatePicker picker="year" placeholder="选择年份" />
+    </Form.Item>
+
+    <Form.Item
+      name="region"
+      label="地区"
+      rules={[{ required: true, message: "请选择地区" }]}
+    >
+      <Select placeholder="请选择地区">
+        <Option value="china">中国大陆</Option>
+        <Option value="hongkong">中国香港</Option>
+        <Option value="taiwan">中国台湾</Option>
+        <Option value="japan">日本</Option>
+        <Option value="korea">韩国</Option>
+        <Option value="usa">美国</Option>
+        <Option value="uk">英国</Option>
+        <Option value="france">法国</Option>
+        <Option value="germany">德国</Option>
+        <Option value="italy">意大利</Option>
+        <Option value="spain">西班牙</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="language"
+      label="语言"
+      rules={[{ required: true, message: "请选择语言" }]}
+    >
+      <Select placeholder="请选择语言" mode="multiple">
+        <Option value="chinese">中文</Option>
+        <Option value="english">英语</Option>
+        <Option value="japanese">日语</Option>
+        <Option value="korean">韩语</Option>
+        <Option value="french">法语</Option>
+        <Option value="german">德语</Option>
+        <Option value="italian">意大利语</Option>
+        <Option value="spanish">西班牙语</Option>
+        <Option value="russian">俄语</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="hasSubtitle"
+      valuePropName="checked"
+    >
+      <Checkbox>包含中文字幕</Checkbox>
+    </Form.Item>
+
+    <Form.Item
+      name="description"
+      label="资源简介"
+      rules={[{ required: true, message: "请输入资源简介" }]}
+    >
+      <TextArea rows={6} placeholder="请详细描述资源内容、特点等信息" />
+    </Form.Item>
+
+    <Form.Item
+      name="mediaInfo"
+      label="MediaInfo"
+    >
+      <TextArea rows={4} placeholder="请粘贴MediaInfo信息(如有)" />
+    </Form.Item>
+
+    <Form.Item
+      name="screenshots"
+      label="截图"
+    >
+      <Upload 
+        listType="picture-card"
+        action="/api/upload-screenshot"
+        accept=".jpg,.jpeg,.png,.webp"
+        multiple
+      >
+        <div>
+          <UploadOutlined />
+          <div style={{ marginTop: 8 }}>上传截图</div>
+        </div>
+      </Upload>
+    </Form.Item>
+  </>
+);
+
+// 电影特定表单项
+const MovieFormItems = () => (
+  <>
+    <Form.Item
+      name="director"
+      label="导演"
+    >
+      <Input placeholder="请输入导演名称,多个导演请用逗号分隔" />
+    </Form.Item>
+
+    <Form.Item
+      name="actors"
+      label="主演"
+    >
+      <Input placeholder="请输入主演名称,多个主演请用逗号分隔" />
+    </Form.Item>
+
+    <Form.Item
+      name="movieType"
+      label="电影类型"
+      rules={[{ required: true, message: "请选择电影类型" }]}
+    >
+      <Select placeholder="请选择电影类型" mode="multiple">
+        <Option value="action">动作</Option>
+        <Option value="comedy">喜剧</Option>
+        <Option value="romance">爱情</Option>
+        <Option value="sci-fi">科幻</Option>
+        <Option value="horror">恐怖</Option>
+        <Option value="drama">剧情</Option>
+        <Option value="war">战争</Option>
+        <Option value="documentary">纪录</Option>
+        <Option value="animation">动画</Option>
+        <Option value="thriller">惊悚</Option>
+        <Option value="crime">犯罪</Option>
+        <Option value="fantasy">奇幻</Option>
+        <Option value="adventure">冒险</Option>
+        <Option value="history">历史</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="resolution"
+      label="分辨率"
+      rules={[{ required: true, message: "请选择分辨率" }]}
+    >
+      <Select placeholder="请选择分辨率">
+        <Option value="4K">4K</Option>
+        <Option value="2K">2K</Option>
+        <Option value="1080p">1080p</Option>
+        <Option value="720p">720p</Option>
+        <Option value="480p">480p</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="source"
+      label="片源"
+      rules={[{ required: true, message: "请选择片源" }]}
+    >
+      <Select placeholder="请选择片源">
+        <Option value="bluray">蓝光原盘</Option>
+        <Option value="remux">蓝光重灌</Option>
+        <Option value="encode">压制</Option>
+        <Option value="web-dl">WEB-DL</Option>
+        <Option value="dvd">DVD</Option>
+        <Option value="hdtv">HDTV</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="duration"
+      label="片长(分钟)"
+    >
+      <InputNumber min={1} placeholder="请输入片长" />
+    </Form.Item>
+
+    <Form.Item
+      name="imdbId"
+      label="IMDb ID"
+    >
+      <Input placeholder="例如:tt0111161" />
+    </Form.Item>
+
+    <Form.Item
+      name="doubanId"
+      label="豆瓣ID"
+    >
+      <Input placeholder="例如:1292052" />
+    </Form.Item>
+  </>
+);
+
+// 剧集特定表单项
+const TVFormItems = () => (
+  <>
+    <Form.Item
+      name="season"
+      label="季数"
+    >
+      <InputNumber min={1} placeholder="第几季" />
+    </Form.Item>
+
+    <Form.Item
+      name="episode"
+      label="集数"
+    >
+      <Input placeholder="例如:1-12、全集、SP" />
+    </Form.Item>
+
+    <Form.Item
+      name="isComplete"
+      valuePropName="checked"
+    >
+      <Checkbox>已完结</Checkbox>
+    </Form.Item>
+
+    <Form.Item
+      name="director"
+      label="导演"
+    >
+      <Input placeholder="请输入导演名称,多个导演请用逗号分隔" />
+    </Form.Item>
+
+    <Form.Item
+      name="actors"
+      label="主演"
+    >
+      <Input placeholder="请输入主演名称,多个主演请用逗号分隔" />
+    </Form.Item>
+
+    <Form.Item
+      name="tvType"
+      label="剧集类型"
+      rules={[{ required: true, message: "请选择剧集类型" }]}
+    >
+      <Select placeholder="请选择剧集类型" mode="multiple">
+        <Option value="action">动作</Option>
+        <Option value="comedy">喜剧</Option>
+        <Option value="romance">爱情</Option>
+        <Option value="sci-fi">科幻</Option>
+        <Option value="horror">恐怖</Option>
+        <Option value="drama">剧情</Option>
+        <Option value="crime">犯罪</Option>
+        <Option value="fantasy">奇幻</Option>
+        <Option value="adventure">冒险</Option>
+        <Option value="mystery">悬疑</Option>
+        <Option value="thriller">惊悚</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="resolution"
+      label="分辨率"
+      rules={[{ required: true, message: "请选择分辨率" }]}
+    >
+      <Select placeholder="请选择分辨率">
+        <Option value="4K">4K</Option>
+        <Option value="2K">2K</Option>
+        <Option value="1080p">1080p</Option>
+        <Option value="720p">720p</Option>
+        <Option value="480p">480p</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="source"
+      label="片源"
+      rules={[{ required: true, message: "请选择片源" }]}
+    >
+      <Select placeholder="请选择片源">
+        <Option value="bluray">蓝光原盘</Option>
+        <Option value="remux">蓝光重灌</Option>
+        <Option value="encode">压制</Option>
+        <Option value="web-dl">WEB-DL</Option>
+        <Option value="dvd">DVD</Option>
+        <Option value="hdtv">HDTV</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="imdbId"
+      label="IMDb ID"
+    >
+      <Input placeholder="例如:tt0111161" />
+    </Form.Item>
+
+    <Form.Item
+      name="doubanId"
+      label="豆瓣ID"
+    >
+      <Input placeholder="例如:1292052" />
+    </Form.Item>
+  </>
+);
+
+// 音乐特定表单项
+const MusicFormItems = () => (
+  <>
+    <Form.Item
+      name="artist"
+      label="艺术家"
+      rules={[{ required: true, message: "请输入艺术家名称" }]}
+    >
+      <Input placeholder="请输入艺术家名称" />
+    </Form.Item>
+
+    <Form.Item
+      name="album"
+      label="专辑名称"
+    >
+      <Input placeholder="请输入专辑名称(如果是专辑)" />
+    </Form.Item>
+
+    <Form.Item
+      name="musicType"
+      label="音乐类型"
+      rules={[{ required: true, message: "请选择音乐类型" }]}
+    >
+      <Select placeholder="请选择音乐类型" mode="multiple">
+        <Option value="pop">流行</Option>
+        <Option value="rock">摇滚</Option>
+        <Option value="electronic">电子</Option>
+        <Option value="folk">民谣</Option>
+        <Option value="hip-hop">嘻哈</Option>
+        <Option value="classical">古典</Option>
+        <Option value="jazz">爵士</Option>
+        <Option value="soundtrack">原声带</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="format"
+      label="音频格式"
+      rules={[{ required: true, message: "请选择音频格式" }]}
+    >
+      <Select placeholder="请选择音频格式">
+        <Option value="flac">FLAC</Option>
+        <Option value="ape">APE</Option>
+        <Option value="wav">WAV</Option>
+        <Option value="mp3">MP3</Option>
+        <Option value="aac">AAC</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="bitrate"
+      label="比特率"
+    >
+      <Select placeholder="请选择比特率">
+        <Option value="lossless">无损</Option>
+        <Option value="320">320Kbps</Option>
+        <Option value="256">256Kbps</Option>
+        <Option value="192">192Kbps</Option>
+        <Option value="128">128Kbps</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="tracks"
+      label="曲目数"
+    >
+      <InputNumber min={1} placeholder="请输入曲目数量" />
+    </Form.Item>
+  </>
+);
+
+// 动漫特定表单项
+const AnimeFormItems = () => (
+  <>
+    <Form.Item
+      name="season"
+      label="季数"
+    >
+      <InputNumber min={1} placeholder="第几季" />
+    </Form.Item>
+
+    <Form.Item
+      name="episode"
+      label="集数"
+    >
+      <Input placeholder="例如:1-12、全集、SP、剧场版" />
+    </Form.Item>
+
+    <Form.Item
+      name="isComplete"
+      valuePropName="checked"
+    >
+      <Checkbox>已完结</Checkbox>
+    </Form.Item>
+
+    <Form.Item
+      name="animeType"
+      label="动漫类型"
+      rules={[{ required: true, message: "请选择动漫类型" }]}
+    >
+      <Select placeholder="请选择动漫类型" mode="multiple">
+        <Option value="tv">TV版</Option>
+        <Option value="movie">剧场版</Option>
+        <Option value="ova">OVA</Option>
+        <Option value="special">特别篇</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="resolution"
+      label="分辨率"
+      rules={[{ required: true, message: "请选择分辨率" }]}
+    >
+      <Select placeholder="请选择分辨率">
+        <Option value="4K">4K</Option>
+        <Option value="2K">2K</Option>
+        <Option value="1080p">1080p</Option>
+        <Option value="720p">720p</Option>
+        <Option value="480p">480p</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="source"
+      label="片源"
+      rules={[{ required: true, message: "请选择片源" }]}
+    >
+      <Select placeholder="请选择片源">
+        <Option value="bluray">蓝光原盘</Option>
+        <Option value="remux">蓝光重灌</Option>
+        <Option value="encode">压制</Option>
+        <Option value="web-dl">WEB-DL</Option>
+        <Option value="dvd">DVD</Option>
+        <Option value="hdtv">HDTV</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="subTeam"
+      label="字幕组"
+    >
+      <Input placeholder="请输入字幕组名称" />
+    </Form.Item>
+  </>
+);
+
+// 游戏特定表单项
+const GameFormItems = () => (
+  <>
+    <Form.Item
+      name="platform"
+      label="平台"
+      rules={[{ required: true, message: "请选择游戏平台" }]}
+    >
+      <Select placeholder="请选择游戏平台" mode="multiple">
+        <Option value="pc">PC</Option>
+        <Option value="ps5">PS5</Option>
+        <Option value="ps4">PS4</Option>
+        <Option value="ps3">PS3</Option>
+        <Option value="xbox-series">Xbox Series X/S</Option>
+        <Option value="xbox-one">Xbox One</Option>
+        <Option value="switch">Nintendo Switch</Option>
+        <Option value="ns">Nintendo Switch</Option>
+        <Option value="3ds">Nintendo 3DS</Option>
+        <Option value="mobile">手机</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="gameType"
+      label="游戏类型"
+      rules={[{ required: true, message: "请选择游戏类型" }]}
+    >
+      <Select placeholder="请选择游戏类型" mode="multiple">
+        <Option value="rpg">角色扮演</Option>
+        <Option value="action">动作</Option>
+        <Option value="fps">第一人称射击</Option>
+        <Option value="tps">第三人称射击</Option>
+        <Option value="strategy">策略</Option>
+        <Option value="simulation">模拟</Option>
+        <Option value="adventure">冒险</Option>
+        <Option value="sports">体育</Option>
+        <Option value="racing">竞速</Option>
+        <Option value="fighting">格斗</Option>
+        <Option value="other">其他</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="version"
+      label="版本"
+    >
+      <Input placeholder="请输入游戏版本,例如:V1.02.3" />
+    </Form.Item>
+
+    <Form.Item
+      name="developer"
+      label="开发商"
+    >
+      <Input placeholder="请输入游戏开发商" />
+    </Form.Item>
+
+    <Form.Item
+      name="publisher"
+      label="发行商"
+    >
+      <Input placeholder="请输入游戏发行商" />
+    </Form.Item>
+
+    <Form.Item
+      name="language"
+      label="语言"
+      rules={[{ required: true, message: "请选择游戏语言" }]}
+    >
+      <Select placeholder="请选择游戏语言" mode="multiple">
+        <Option value="chinese">中文</Option>
+        <Option value="english">英文</Option>
+        <Option value="japanese">日文</Option>
+        <Option value="korean">韩文</Option>
+        <Option value="french">法文</Option>
+        <Option value="german">德文</Option>
+        <Option value="russian">俄文</Option>
+        <Option value="multi">多语言</Option>
+      </Select>
+    </Form.Item>
+
+    <Form.Item
+      name="hasCrack"
+      valuePropName="checked"
+    >
+      <Checkbox>已破解/免安装</Checkbox>
+    </Form.Item>
+  </>
+);
+
+// 其他类型特定的表单项(简化)
+const OtherFormItems = ({ category }) => {
+  if (category === "variety") {
+    return (
+      <>
+        <Form.Item name="host" label="主持人">
+          <Input placeholder="请输入主持人名称" />
+        </Form.Item>
+        <Form.Item name="episode" label="期数">
+          <Input placeholder="例如:第1-12期、20200101" />
+        </Form.Item>
+      </>
+    );
+  }
+
+  if (category === "sports") {
+    return (
+      <>
+        <Form.Item name="league" label="联赛/赛事名称">
+          <Input placeholder="请输入联赛或赛事名称" />
+        </Form.Item>
+        <Form.Item name="match" label="比赛">
+          <Input placeholder="例如:队伍A vs 队伍B" />
+        </Form.Item>
+      </>
+    );
+  }
+
+  if (category === "software") {
+    return (
+      <>
+        <Form.Item name="version" label="版本">
+          <Input placeholder="请输入软件版本" />
+        </Form.Item>
+        <Form.Item name="os" label="操作系统">
+          <Select placeholder="请选择操作系统" mode="multiple">
+            <Option value="windows">Windows</Option>
+            <Option value="macos">macOS</Option>
+            <Option value="linux">Linux</Option>
+            <Option value="android">Android</Option>
+            <Option value="ios">iOS</Option>
+          </Select>
+        </Form.Item>
+      </>
+    );
+  }
+
+  if (category === "learning") {
+    return (
+      <>
+        <Form.Item name="subject" label="学科/主题">
+          <Input placeholder="请输入学科或主题" />
+        </Form.Item>
+        <Form.Item name="level" label="难度级别">
+          <Select placeholder="请选择难度级别">
+            <Option value="beginner">入门</Option>
+            <Option value="intermediate">中级</Option>
+            <Option value="advanced">高级</Option>
+          </Select>
+        </Form.Item>
+      </>
+    );
+  }
+  
+  if (category === "documentary") {
+    return (
+      <>
+        <Form.Item name="docType" label="纪录片类型">
+          <Select placeholder="请选择纪录片类型" mode="multiple">
+            <Option value="nature">自然</Option>
+            <Option value="history">历史</Option>
+            <Option value="science">科学</Option>
+            <Option value="culture">人文</Option>
+            <Option value="society">社会</Option>
+            <Option value="biography">传记</Option>
+          </Select>
+        </Form.Item>
+        <Form.Item name="episodes" label="集数">
+          <Input placeholder="例如:共8集" />
+        </Form.Item>
+      </>
+    );
+  }
+
+  return null;
+};
+
+// 主组件
+const UploadTorrentPage = () => {
+  const [form] = Form.useForm();
+  const [activeCategory, setActiveCategory] = useState("notice");
+
+  const onFinish = (values) => {
+    console.log("提交的表单数据:", values);
+    message.success("种子上传成功!");
+  };
+
+  const renderFormByCategory = (category) => {
+    if (category === "notice") {
+      return <NoticeContent />;
+    }
+
+    return (
+      <Form
+        form={form}
+        layout="vertical"
+        onFinish={onFinish}
+        scrollToFirstError
+      >
+        <CategoryRules category={category} />
+
+        <BaseFormItems category={category} />
+
+        {/* 根据类别渲染特定的表单项 */}
+        {category === "movie" && <MovieFormItems />}
+        {category === "tv" && <TVFormItems />}
+        {category === "music" && <MusicFormItems />}
+        {category === "anime" && <AnimeFormItems />}
+        {category === "game" && <GameFormItems />}
+        {["variety", "sports", "software", "learning", "documentary", "other"].includes(category) && (
+          <OtherFormItems category={category} />
+        )}
+
+        <Divider />
+
+        <Form.Item>
+          <Space>
+            <Button type="primary" htmlType="submit">
+              提交
+            </Button>
+            <Button onClick={() => form.resetFields()}>重置</Button>
+          </Space>
+        </Form.Item>
+      </Form>
+    );
+  };
+
+  return (
+    <div className="space-y-6">
+      <Title level={2}>发布种子</Title>
+
+      <Tabs activeKey={activeCategory} onChange={setActiveCategory} type="card">
+        <TabPane tab="注意事项" key="notice" />
+        <TabPane tab="电影" key="movie" />
+        <TabPane tab="剧集" key="tv" />
+        <TabPane tab="音乐" key="music" />
+        <TabPane tab="动漫" key="anime" />
+        <TabPane tab="游戏" key="game" />
+        <TabPane tab="综艺" key="variety" />
+        <TabPane tab="体育" key="sports" />
+        <TabPane tab="软件" key="software" />
+        <TabPane tab="学习" key="learning" />
+        <TabPane tab="纪录片" key="documentary" />
+        <TabPane tab="其他" key="other" />
+      </Tabs>
+
+      <div>{renderFormByCategory(activeCategory)}</div>
+    </div>
+  );
+};
+
+export default UploadTorrentPage; 
\ No newline at end of file