前端页面提交
Change-Id: I096c8538ded26791a94a46d8f27b0307dbfb8300
diff --git a/front/src/AnimePage.js b/front/src/AnimePage.js
new file mode 100644
index 0000000..fd429b7
--- /dev/null
+++ b/front/src/AnimePage.js
@@ -0,0 +1,137 @@
+import React from "react";
+import MovieIcon from "@mui/icons-material/Movie";
+import EmailIcon from "@mui/icons-material/Email";
+import MusicNoteIcon from "@mui/icons-material/MusicNote";
+import EmojiPeopleIcon from "@mui/icons-material/EmojiPeople";
+import SportsEsportsIcon from "@mui/icons-material/SportsEsports";
+import SportsMartialArtsIcon from "@mui/icons-material/SportsMartialArts";
+import PersonIcon from "@mui/icons-material/Person";
+import AccountCircleIcon from "@mui/icons-material/AccountCircle";
+import "./App.css";
+import { useNavigate } from "react-router-dom";
+
+const navItems = [
+ { label: "电影", icon: <MovieIcon />, path: "/movie" },
+ { label: "剧集", icon: <EmailIcon />, path: "/tv" },
+ { label: "音乐", icon: <MusicNoteIcon />, path: "/music" },
+ { label: "动漫", icon: <EmojiPeopleIcon />, path: "/anime" },
+ { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },
+ { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },
+ { label: "资料", icon: <PersonIcon />, path: "/info" },
+];
+
+const animeTypes = [
+ "华语动漫(大陆)",
+ "欧美动漫",
+ "日韩动漫",
+ "港台动漫",
+ "其他"
+];
+
+const areaTabs = [
+ { label: "大陆", icon: <EmojiPeopleIcon fontSize="small" /> },
+ { label: "港台", icon: <EmailIcon fontSize="small" /> },
+ { label: "欧美", icon: <PersonIcon fontSize="small" /> },
+ { label: "日韩", icon: <EmojiPeopleIcon fontSize="small" /> },
+ { label: "其他", icon: <PersonIcon fontSize="small" /> },
+];
+
+export default function AnimePage() {
+ const navigate = useNavigate();
+ const [activeTab, setActiveTab] = React.useState(0);
+
+ // 每个tab对应的动漫类型
+ const animeTypesList = [
+ ["华语动漫(大陆)", "欧美动漫", "日韩动漫", "港台动漫", "其他"], // 大陆
+ ["港台热血", "港台搞笑", "港台其他"], // 港台
+ ["欧美冒险", "欧美科幻", "欧美其他"], // 欧美
+ ["日韩热血", "日韩恋爱", "日韩其他"], // 日韩
+ ["其他类型1", "其他类型2"] // 其他
+ ];
+ const animeTypes = animeTypesList[activeTab] || [];
+
+ return (
+ <div className="container">
+ {/* 顶部空白与音乐界面一致,用户栏绝对定位在页面右上角 */}
+ <div style={{ height: 80 }} />
+ <div className="user-bar" style={{ position: 'fixed', top: 18, right: 42, zIndex: 100, display: 'flex', alignItems: 'center', background: '#e0f3ff', borderRadius: 12, padding: '6px 18px', boxShadow: '0 2px 8px #b2d8ea', minWidth: 320, minHeight: 48, width: 420 }}>
+ <div style={{ cursor: 'pointer', marginRight: 16 }} onClick={() => navigate('/user')}>
+ <AccountCircleIcon style={{ fontSize: 38, color: '#1a237e', background: '#e0f3ff', borderRadius: '50%' }} />
+ </div>
+ <div style={{ color: '#222', fontWeight: 500, marginRight: 24 }}>用户栏</div>
+ <div style={{ display: 'flex', gap: 28, flex: 1, justifyContent: 'flex-end', alignItems: 'center' }}>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>魔力值: <b>12345</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>分享率: <b>2.56</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>上传量: <b>100GB</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>下载量: <b>50GB</b></span>
+ </div>
+ </div>
+ {/* 下方内容整体下移,留出与音乐界面一致的间距 */}
+ <div style={{ height: 32 }} />
+ <nav className="nav-bar">
+ {navItems.map((item) => (
+ <div
+ key={item.label}
+ className={item.label === "动漫" ? "nav-item active" : "nav-item"}
+ onClick={() => navigate(item.path)}
+ >
+ {item.icon}
+ <span>{item.label}</span>
+ </div>
+ ))}
+ </nav>
+ <div className="search-section card">
+ <input className="search-input" placeholder="输入搜索关键词" />
+ <button className="search-btn">
+ <span role="img" aria-label="search">🔍</span>
+ </button>
+ </div>
+ <div className="area-tabs" style={{ display: 'flex', justifyContent: 'center', gap: 24, margin: '18px 0' }}>
+ {areaTabs.map((tab, idx) => (
+ <div
+ key={tab.label}
+ className={activeTab === idx ? "area-tab active" : "area-tab"}
+ onClick={() => setActiveTab(idx)}
+ >
+ {tab.icon} <span>{tab.label}</span>
+ </div>
+ ))}
+ </div>
+ <div className="table-section">
+ <table className="movie-table">
+ <thead>
+ <tr>
+ <th>动漫类型</th>
+ <th>标题</th>
+ <th>发布者</th>
+ </tr>
+ </thead>
+ <tbody>
+ {animeTypes.map(type => (
+ <tr key={type}>
+ <td>{type}</td>
+ <td></td>
+ <td></td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ <div style={{ height: 32 }} />
+ <Pagination />
+ </div>
+ );
+}
+
+function Pagination() {
+ const [page, setPage] = React.useState(3);
+ const total = 5;
+ return (
+ <div className="pagination">
+ <button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page === 1}>上一页</button>
+ <span className="page-num">{page}/{total}</span>
+ <button onClick={() => setPage(p => Math.min(total, p + 1))} disabled={page === total}>下一页</button>
+ <span className="page-info">第 <b>{page}</b> 页</span>
+ </div>
+ );
+}
diff --git a/front/src/App.css b/front/src/App.css
new file mode 100644
index 0000000..c359190
--- /dev/null
+++ b/front/src/App.css
@@ -0,0 +1,208 @@
+.App {
+ text-align: center;
+}
+
+.App-logo {
+ height: 40vmin;
+ pointer-events: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ .App-logo {
+ animation: App-logo-spin infinite 20s linear;
+ }
+}
+
+.App-header {
+ background-color: #282c34;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: calc(10px + 2vmin);
+ color: white;
+}
+
+.App-link {
+ color: #61dafb;
+}
+
+@keyframes App-logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+body {
+ background: linear-gradient(135deg, #f0f4ff 0%, #e0e7ff 100%);
+ margin: 0;
+ font-family: "Segoe UI", "Microsoft YaHei", Arial, sans-serif;
+ min-height: 100vh;
+ min-width: 100vw;
+}
+
+.container {
+ width: 100vw;
+ min-height: 100vh;
+ margin: 0;
+ background: #fff;
+ border-radius: 0;
+ box-shadow: none;
+ padding: 0 0 32px 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.nav-bar {
+ display: flex;
+ justify-content: center;
+ gap: 36px;
+ margin-bottom: 40px;
+}
+
+.nav-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 22px;
+ color: #3a4a6b;
+ cursor: pointer;
+ transition: color 0.2s;
+ padding: 6px 12px;
+ border-radius: 8px;
+}
+.nav-item:hover {
+ background: #e0e7ff;
+ color: #1a237e;
+}
+.nav-item.active {
+ background: #e0e7ff;
+ color: #1a237e;
+ border-bottom: 3px solid #1a237e;
+}
+
+.search-section {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 24px;
+}
+.search-input {
+ width: 340px;
+ font-size: 18px;
+ padding: 10px 14px;
+ border: 1px solid #bfcfff;
+ border-radius: 8px 0 0 8px;
+ outline: none;
+}
+.search-btn {
+ font-size: 20px;
+ padding: 10px 18px;
+ border: 1px solid #bfcfff;
+ border-left: none;
+ background: #e0e7ff;
+ border-radius: 0 8px 8px 0;
+ cursor: pointer;
+}
+
+.advanced-search {
+ margin: 18px 0 24px 0;
+ font-size: 16px;
+}
+.advanced-input {
+ margin-left: 8px;
+ padding: 6px 10px;
+ border-radius: 6px;
+ border: 1px solid #bfcfff;
+ width: 220px;
+}
+
+.pagination {
+ display: flex;
+ align-items: center;
+ gap: 18px;
+ margin-bottom: 32px;
+ justify-content: center;
+}
+.pagination button {
+ padding: 6px 18px;
+ border-radius: 6px;
+ border: 1px solid #bfcfff;
+ background: #f5f7ff;
+ cursor: pointer;
+ font-size: 15px;
+}
+.page-num {
+ color: #1a237e;
+ font-weight: bold;
+}
+.page-info {
+ margin-left: 8px;
+ font-size: 15px;
+}
+
+.table-section {
+ width: 90vw;
+ margin: 18px auto 0 auto;
+ background: #f8faff;
+ border-radius: 12px;
+ padding: 18px;
+ border: 1px solid #e0e7ff;
+ box-sizing: border-box;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+th, td {
+ padding: 14px 10px;
+ text-align: left;
+ font-size: 18px;
+}
+th {
+ font-size: 22px;
+ color: #1a237e;
+ border-bottom: 2px solid #bfcfff;
+}
+td {
+ color: #3a4a6b;
+ border-bottom: 1px solid #e0e7ff;
+}
+
+.area-tabs {
+ margin-bottom: 10px;
+}
+.area-tab {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 17px;
+ color: #3a4a6b;
+ padding: 4px 16px;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: background 0.2s, color 0.2s;
+}
+.area-tab:hover {
+ background: #e0e7ff;
+ color: #1a237e;
+}
+.area-tab.active {
+ background: #e0e7ff;
+ color: #1a237e;
+ border-bottom: 3px solid #1a237e;
+}
+
+.movie-table {
+ width: 100%;
+ table-layout: fixed;
+}
+.movie-table th, .movie-table td {
+ width: 33.33%;
+ text-align: center;
+}
diff --git a/front/src/App.js b/front/src/App.js
new file mode 100644
index 0000000..3a49898
--- /dev/null
+++ b/front/src/App.js
@@ -0,0 +1,154 @@
+import React from "react";
+import { BrowserRouter as Router, Routes, Route, useNavigate, Link } from "react-router-dom";
+import MovieIcon from "@mui/icons-material/Movie";
+import EmailIcon from "@mui/icons-material/Email";
+import MusicNoteIcon from "@mui/icons-material/MusicNote";
+import EmojiPeopleIcon from "@mui/icons-material/EmojiPeople";
+import SportsEsportsIcon from "@mui/icons-material/SportsEsports";
+import SportsMartialArtsIcon from "@mui/icons-material/SportsMartialArts";
+import PersonIcon from "@mui/icons-material/Person";
+import AccountCircleIcon from "@mui/icons-material/AccountCircle";
+import "./App.css";
+import MoviePage from "./MoviePage";
+import TVPage from "./TVPage";
+import MusicPage from "./MusicPage";
+import AnimePage from "./AnimePage";
+import GamePage from "./GamePage";
+import SportPage from "./SportPage";
+import InfoPage from "./InfoPage";
+import UserProfile from "./UserProfile";
+
+const navItems = [
+ { label: "电影", icon: <MovieIcon />, path: "/movie" },
+ { label: "剧集", icon: <EmailIcon />, path: "/tv" },
+ { label: "音乐", icon: <MusicNoteIcon />, path: "/music" },
+ { label: "动漫", icon: <EmojiPeopleIcon />, path: "/anime" },
+ { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },
+ { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },
+ { label: "资料", icon: <PersonIcon />, path: "/info" },
+];
+
+function Home() {
+ const navigate = useNavigate();
+ return (
+ <div className="container">
+ {/* 顶部空白与电影界面一致 */}
+ <div style={{ height: 80 }} />
+ {/* 用户栏 */}
+ <div className="user-bar" style={{ position: 'fixed', top: 18, right: 42, zIndex: 100, display: 'flex', alignItems: 'center', background: '#e0f3ff', borderRadius: 12, padding: '6px 18px', boxShadow: '0 2px 8px #b2d8ea', minWidth: 320, minHeight: 48, width: 420 }}>
+ <div style={{ cursor: 'pointer', marginRight: 16 }} onClick={() => navigate('/user')}>
+ <AccountCircleIcon style={{ fontSize: 38, color: '#1a237e', background: '#e0f3ff', borderRadius: '50%' }} />
+ </div>
+ <div style={{ color: '#222', fontWeight: 500, marginRight: 24 }}>用户栏</div>
+ <div style={{ display: 'flex', gap: 28, flex: 1, justifyContent: 'flex-end', alignItems: 'center' }}>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>魔力值: <b>12345</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>分享率: <b>2.56</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>上传量: <b>100GB</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>下载量: <b>50GB</b></span>
+ </div>
+ </div>
+ {/* 下方内容整体下移,留出与电影界面一致的间距 */}
+ <div style={{ height: 32 }} />
+ <nav className="nav-bar card">
+ {navItems.map((item) => (
+ <div
+ key={item.label}
+ className={"nav-item"}
+ onClick={() => navigate(item.path)}
+ >
+ {item.icon}
+ <span>{item.label}</span>
+ </div>
+ ))}
+ </nav>
+ <div className="search-section card">
+ <input className="search-input" placeholder="输入搜索关键词" />
+ <button className="search-btn">
+ <span role="img" aria-label="search">🔍</span>
+ </button>
+ </div>
+ {/* 删除高频搜索栏 */}
+ <div className="table-section card">
+ <table className="movie-table">
+ <thead>
+ <tr>
+ <th>类型</th>
+ <th>标题</th>
+ <th>发布者</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>电影</td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>剧集</td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>音乐</td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>动漫</td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>游戏</td>
+ <td></td>
+ <td></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <div style={{ height: 32 }} />
+ <Pagination />
+ </div>
+ );
+}
+
+function Pagination() {
+ const [page, setPage] = React.useState(3);
+ const total = 5;
+ return (
+ <div className="pagination">
+ <button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page === 1}>上一页</button>
+ <span className="page-num">{page}/{total}</span>
+ <button onClick={() => setPage(p => Math.min(total, p + 1))} disabled={page === total}>下一页</button>
+ <span className="page-info">第 <b>{page}</b> 页</span>
+ </div>
+ );
+}
+
+function Page({ label }) {
+ return (
+ <div style={{ padding: 40, fontSize: 32 }}>
+ {label} 页面(可自定义内容)
+ <br />
+ <Link to="/">返回首页</Link>
+ </div>
+ );
+}
+
+export default function App() {
+ return (
+ <Router>
+ <Routes>
+ <Route path="/" element={<Home />} />
+ <Route path="/movie" element={<MoviePage />} />
+ <Route path="/tv" element={<TVPage />} />
+ <Route path="/music" element={<MusicPage />} />
+ <Route path="/anime" element={<AnimePage />} />
+ <Route path="/game" element={<GamePage />} />
+ <Route path="/sport" element={<SportPage />} />
+ <Route path="/info" element={<InfoPage />} />
+ <Route path="/user" element={<UserProfile />} />
+ </Routes>
+ </Router>
+ );
+}
\ No newline at end of file
diff --git a/front/src/App.test.js b/front/src/App.test.js
new file mode 100644
index 0000000..1f03afe
--- /dev/null
+++ b/front/src/App.test.js
@@ -0,0 +1,8 @@
+import { render, screen } from '@testing-library/react';
+import App from './App';
+
+test('renders learn react link', () => {
+ render(<App />);
+ const linkElement = screen.getByText(/learn react/i);
+ expect(linkElement).toBeInTheDocument();
+});
diff --git a/front/src/GamePage.js b/front/src/GamePage.js
new file mode 100644
index 0000000..f7331bb
--- /dev/null
+++ b/front/src/GamePage.js
@@ -0,0 +1,190 @@
+import React, { useState } from "react";
+import MovieIcon from "@mui/icons-material/Movie";
+import EmailIcon from "@mui/icons-material/Email";
+import MusicNoteIcon from "@mui/icons-material/MusicNote";
+import EmojiPeopleIcon from "@mui/icons-material/EmojiPeople";
+import SportsEsportsIcon from "@mui/icons-material/SportsEsports";
+import SportsMartialArtsIcon from "@mui/icons-material/SportsMartialArts";
+import PersonIcon from "@mui/icons-material/Person";
+import AccountCircleIcon from "@mui/icons-material/AccountCircle";
+import "./App.css";
+import { useNavigate } from "react-router-dom";
+
+const navItems = [
+ { label: "电影", icon: <MovieIcon />, path: "/movie" },
+ { label: "剧集", icon: <EmailIcon />, path: "/tv" },
+ { label: "音乐", icon: <MusicNoteIcon />, path: "/music" },
+ { label: "动漫", icon: <EmojiPeopleIcon />, path: "/anime" },
+ { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },
+ { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },
+ { label: "资料", icon: <PersonIcon />, path: "/info" },
+];
+
+const gameTypes = ["PC", "主机", "移动", "掌机", "视频"];
+
+const areaTabs = [
+ { label: "PC", icon: <MovieIcon fontSize="small" /> },
+ { label: "主机", icon: <EmailIcon fontSize="small" /> },
+ { label: "移动", icon: <PersonIcon fontSize="small" /> },
+ { label: "掌机", icon: <EmojiPeopleIcon fontSize="small" /> },
+ { label: "视频", icon: <PersonIcon fontSize="small" /> },
+];
+
+export default function GamePage() {
+ const navigate = useNavigate();
+ const [activeTab, setActiveTab] = useState(0);
+
+ return (
+ <div className="container">
+ {/* 预留顶部空间用于放图片 */}
+ <div style={{ height: 80 }} />
+ <div
+ className="user-bar"
+ style={{
+ position: "fixed",
+ top: 18,
+ right: 42,
+ zIndex: 100,
+ display: "flex",
+ alignItems: "center",
+ background: "#e0f3ff",
+ borderRadius: 12,
+ padding: "6px 18px",
+ boxShadow: "0 2px 8px #b2d8ea",
+ minWidth: 320,
+ minHeight: 48,
+ width: 420,
+ }}
+ >
+ <div
+ style={{ cursor: "pointer", marginRight: 16 }}
+ onClick={() => navigate("/user")}
+ >
+ <AccountCircleIcon
+ style={{
+ fontSize: 38,
+ color: "#1a237e",
+ background: "#e0f3ff",
+ borderRadius: "50%",
+ }}
+ />
+ </div>
+ <div style={{ color: "#222", fontWeight: 500, marginRight: 24 }}>
+ 用户栏
+ </div>
+ <div
+ style={{
+ display: "flex",
+ gap: 28,
+ flex: 1,
+ justifyContent: "flex-end",
+ alignItems: "center",
+ }}
+ >
+ <span style={{ color: "#1976d2", fontWeight: 500 }}>
+ 魔力值: <b>12345</b>
+ </span>
+ <span style={{ color: "#1976d2", fontWeight: 500 }}>
+ 分享率: <b>2.56</b>
+ </span>
+ <span style={{ color: "#1976d2", fontWeight: 500 }}>
+ 上传量: <b>100GB</b>
+ </span>
+ <span style={{ color: "#1976d2", fontWeight: 500 }}>
+ 下载量: <b>50GB</b>
+ </span>
+ </div>
+ </div>
+ {/* 下方内容整体下移,留出与音乐界面一致的间距 */}
+ <div style={{ height: 32 }} />
+ <nav className="nav-bar card">
+ {navItems.map((item) => (
+ <div
+ key={item.label}
+ className={item.label === "游戏" ? "nav-item active" : "nav-item"}
+ onClick={() => navigate(item.path)}
+ >
+ {item.icon}
+ <span>{item.label}</span>
+ </div>
+ ))}
+ </nav>
+ <div className="search-section card">
+ <input className="search-input" placeholder="输入搜索关键词" />
+ <button className="search-btn">
+ <span role="img" aria-label="search">
+ 🔍
+ </span>
+ </button>
+ </div>
+ <div
+ className="area-tabs"
+ style={{
+ display: "flex",
+ justifyContent: "center",
+ gap: 24,
+ margin: "18px 0",
+ }}
+ >
+ {areaTabs.map((tab, idx) => (
+ <div
+ key={tab.label}
+ className={activeTab === idx ? "area-tab active" : "area-tab"}
+ onClick={() => setActiveTab(idx)}
+ >
+ {tab.icon} <span>{tab.label}</span>
+ </div>
+ ))}
+ </div>
+ <div className="table-section">
+ <table className="movie-table">
+ <thead>
+ <tr>
+ <th>游戏类型</th>
+ <th>标题</th>
+ <th>发布者</th>
+ </tr>
+ </thead>
+ <tbody>
+ {gameTypes.map((type, idx) => (
+ <tr key={type}>
+ <td>{type}</td>
+ <td></td>
+ <td></td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ <div style={{ height: 32 }} />
+ <Pagination />
+ </div>
+ );
+}
+
+function Pagination() {
+ const [page, setPage] = React.useState(3);
+ const total = 5;
+ return (
+ <div className="pagination">
+ <button
+ onClick={() => setPage((p) => Math.max(1, p - 1))}
+ disabled={page === 1}
+ >
+ 上一页
+ </button>
+ <span className="page-num">
+ {page}/{total}
+ </span>
+ <button
+ onClick={() => setPage((p) => Math.min(total, p + 1))}
+ disabled={page === total}
+ >
+ 下一页
+ </button>
+ <span className="page-info">
+ 第 <b>{page}</b> 页
+ </span>
+ </div>
+ );
+}
diff --git a/front/src/InfoPage.js b/front/src/InfoPage.js
new file mode 100644
index 0000000..a919afd
--- /dev/null
+++ b/front/src/InfoPage.js
@@ -0,0 +1,200 @@
+import React from "react";
+import MovieIcon from "@mui/icons-material/Movie";
+import EmailIcon from "@mui/icons-material/Email";
+import MusicNoteIcon from "@mui/icons-material/MusicNote";
+import EmojiPeopleIcon from "@mui/icons-material/EmojiPeople";
+import SportsEsportsIcon from "@mui/icons-material/SportsEsports";
+import SportsMartialArtsIcon from "@mui/icons-material/SportsMartialArts";
+import PersonIcon from "@mui/icons-material/Person";
+import AccountCircleIcon from "@mui/icons-material/AccountCircle";
+import "./App.css";
+import { useNavigate } from "react-router-dom";
+
+const navItems = [
+ { label: "电影", icon: <MovieIcon />, path: "/movie" },
+ { label: "剧集", icon: <EmailIcon />, path: "/tv" },
+ { label: "音乐", icon: <MusicNoteIcon />, path: "/music" },
+ { label: "动漫", icon: <EmojiPeopleIcon />, path: "/anime" },
+ { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },
+ { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },
+ { label: "资料", icon: <PersonIcon />, path: "/info" },
+];
+
+const infoTypes = [""];
+
+const areaTabs = [
+ { label: "出版物", icon: <MovieIcon fontSize="small" /> },
+ { label: "学习教程", icon: <EmailIcon fontSize="small" /> },
+ { label: "素材模板", icon: <PersonIcon fontSize="small" /> },
+ { label: "演讲交流", icon: <EmojiPeopleIcon fontSize="small" /> },
+ { label: "日常娱乐", icon: <PersonIcon fontSize="small" />, active: true },
+];
+
+export default function InfoPage() {
+ const navigate = useNavigate();
+ const [activeTab, setActiveTab] = React.useState(0);
+
+ // 每个tab对应的资料类型
+ const infoTypesList = [
+ ["出版物A", "出版物B", "出版物C"], // 出版物
+ ["教程A", "教程B", "教程C"], // 学习教程
+ ["模板A", "模板B"], // 素材模板
+ ["演讲A", "演讲B"], // 演讲交流
+ ["娱乐A", "娱乐B"], // 日常娱乐
+ ];
+ const infoTypes = infoTypesList[activeTab] || [];
+
+ return (
+ <div className="container">
+ {/* 顶部空白与音乐界面一致,用户栏绝对定位在页面右上角 */}
+ <div style={{ height: 80 }} />
+ <div
+ className="user-bar"
+ style={{
+ position: "fixed",
+ top: 18,
+ right: 42,
+ zIndex: 100,
+ display: "flex",
+ alignItems: "center",
+ background: "#e0f3ff",
+ borderRadius: 12,
+ padding: "6px 18px",
+ boxShadow: "0 2px 8px #b2d8ea",
+ minWidth: 320,
+ minHeight: 48,
+ width: 420,
+ }}
+ >
+ <div
+ style={{ cursor: "pointer", marginRight: 16 }}
+ onClick={() => navigate("/user")}
+ >
+ <AccountCircleIcon
+ style={{
+ fontSize: 38,
+ color: "#1a237e",
+ background: "#e0f3ff",
+ borderRadius: "50%",
+ }}
+ />
+ </div>
+ <div style={{ color: "#222", fontWeight: 500, marginRight: 24 }}>
+ 用户栏
+ </div>
+ <div
+ style={{
+ display: "flex",
+ gap: 28,
+ flex: 1,
+ justifyContent: "flex-end",
+ alignItems: "center",
+ }}
+ >
+ <span style={{ color: "#1976d2", fontWeight: 500 }}>
+ 魔力值: <b>12345</b>
+ </span>
+ <span style={{ color: "#1976d2", fontWeight: 500 }}>
+ 分享率: <b>2.56</b>
+ </span>
+ <span style={{ color: "#1976d2", fontWeight: 500 }}>
+ 上传量: <b>100GB</b>
+ </span>
+ <span style={{ color: "#1976d2", fontWeight: 500 }}>
+ 下载量: <b>50GB</b>
+ </span>
+ </div>
+ </div>
+ {/* 下方内容整体下移,留出与音乐界面一致的间距 */}
+ <div style={{ height: 32 }} />
+ <nav className="nav-bar card">
+ {navItems.map((item) => (
+ <div
+ key={item.label}
+ className={item.label === "资料" ? "nav-item active" : "nav-item"}
+ onClick={() => navigate(item.path)}
+ >
+ {item.icon}
+ <span>{item.label}</span>
+ </div>
+ ))}
+ </nav>
+ <div className="search-section card">
+ <input className="search-input" placeholder="输入搜索关键词" />
+ <button className="search-btn">
+ <span role="img" aria-label="search">
+ 🔍
+ </span>
+ </button>
+ </div>
+ <div
+ className="area-tabs"
+ style={{
+ display: "flex",
+ justifyContent: "center",
+ gap: 24,
+ margin: "18px 0",
+ }}
+ >
+ {areaTabs.map((tab, idx) => (
+ <div
+ key={tab.label}
+ className={activeTab === idx ? "area-tab active" : "area-tab"}
+ onClick={() => setActiveTab(idx)}
+ >
+ {tab.icon} <span>{tab.label}</span>
+ </div>
+ ))}
+ </div>
+ <div className="table-section">
+ <table className="movie-table">
+ <thead>
+ <tr>
+ <th>类型</th>
+ <th>标题</th>
+ <th>发布者</th>
+ </tr>
+ </thead>
+ <tbody>
+ {infoTypes.map((type) => (
+ <tr key={type}>
+ <td>{type}</td>
+ <td></td>
+ <td></td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ <div style={{ height: 32 }} />
+ <Pagination />
+ </div>
+ );
+}
+
+function Pagination() {
+ const [page, setPage] = React.useState(3);
+ const total = 5;
+ return (
+ <div className="pagination">
+ <button
+ onClick={() => setPage((p) => Math.max(1, p - 1))}
+ disabled={page === 1}
+ >
+ 上一页
+ </button>
+ <span className="page-num">
+ {page}/{total}
+ </span>
+ <button
+ onClick={() => setPage((p) => Math.min(total, p + 1))}
+ disabled={page === total}
+ >
+ 下一页
+ </button>
+ <span className="page-info">
+ 第 <b>{page}</b> 页
+ </span>
+ </div>
+ );
+}
diff --git a/front/src/MoviePage.js b/front/src/MoviePage.js
new file mode 100644
index 0000000..f8c765c
--- /dev/null
+++ b/front/src/MoviePage.js
@@ -0,0 +1,129 @@
+import React from "react";
+import MovieIcon from "@mui/icons-material/Movie";
+import EmailIcon from "@mui/icons-material/Email";
+import MusicNoteIcon from "@mui/icons-material/MusicNote";
+import EmojiPeopleIcon from "@mui/icons-material/EmojiPeople";
+import SportsEsportsIcon from "@mui/icons-material/SportsEsports";
+import SportsMartialArtsIcon from "@mui/icons-material/SportsMartialArts";
+import PersonIcon from "@mui/icons-material/Person";
+import AccountCircleIcon from "@mui/icons-material/AccountCircle";
+import { useNavigate } from "react-router-dom";
+import "./App.css";
+
+const navItems = [
+ { label: "电影", icon: <MovieIcon />, path: "/movie" },
+ { label: "剧集", icon: <EmailIcon />, path: "/tv" },
+ { label: "音乐", icon: <MusicNoteIcon />, path: "/music" },
+ { label: "动漫", icon: <EmojiPeopleIcon />, path: "/anime" },
+ { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },
+ { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },
+ { label: "资料", icon: <PersonIcon />, path: "/info" },
+];
+
+const areaTabs = [
+ { label: "大陆", icon: <MovieIcon fontSize="small" /> },
+ { label: "港台", icon: <EmailIcon fontSize="small" /> },
+ { label: "欧美", icon: <PersonIcon fontSize="small" /> },
+ { label: "日韩", icon: <EmojiPeopleIcon fontSize="small" /> },
+ { label: "其他", icon: <PersonIcon fontSize="small" /> },
+];
+
+export default function MoviePage() {
+ const navigate = useNavigate();
+ const [activeTab, setActiveTab] = React.useState(0);
+
+ // 每个tab对应的电影类型
+ const movieTypesList = [
+ ["华语电影(大陆)", "欧美电影", "日韩电影", "港台电影", "其他"], // 大陆
+ ["港台动作", "港台爱情", "港台喜剧", "港台其他"], // 港台
+ ["欧美动作", "欧美科幻", "欧美剧情", "欧美其他"], // 欧美
+ ["日韩动画", "日韩爱情", "日韩其他"], // 日韩
+ ["其他类型1", "其他类型2"] // 其他
+ ];
+ const movieTypes = movieTypesList[activeTab] || [];
+
+ return (
+ <div className="container">
+ {/* 顶部空白与音乐界面一致,用户栏绝对定位在页面右上角 */}
+ <div style={{ height: 80 }} />
+ <div className="user-bar" style={{ position: 'fixed', top: 18, right: 42, zIndex: 100, display: 'flex', alignItems: 'center', background: '#e0f3ff', borderRadius: 12, padding: '6px 18px', boxShadow: '0 2px 8px #b2d8ea', minWidth: 320, minHeight: 48, width: 420 }}>
+ <div style={{ cursor: 'pointer', marginRight: 16 }} onClick={() => navigate('/user')}>
+ <AccountCircleIcon style={{ fontSize: 38, color: '#1a237e', background: '#e0f3ff', borderRadius: '50%' }} />
+ </div>
+ <div style={{ color: '#222', fontWeight: 500, marginRight: 24 }}>用户栏</div>
+ <div style={{ display: 'flex', gap: 28, flex: 1, justifyContent: 'flex-end', alignItems: 'center' }}>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>魔力值: <b>12345</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>分享率: <b>2.56</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>上传量: <b>100GB</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>下载量: <b>50GB</b></span>
+ </div>
+ </div>
+ {/* 下方内容整体下移,留出与音乐界面一致的间距 */}
+ <div style={{ height: 32 }} />
+ <nav className="nav-bar card">
+ {navItems.map((item) => (
+ <div
+ key={item.label}
+ className={item.label === "电影" ? "nav-item active" : "nav-item"}
+ onClick={() => navigate(item.path)}
+ >
+ {item.icon}
+ <span>{item.label}</span>
+ </div>
+ ))}
+ </nav>
+ <div className="search-section card">
+ <input className="search-input" placeholder="输入搜索关键词" />
+ <button className="search-btn">
+ <span role="img" aria-label="search">🔍</span>
+ </button>
+ </div>
+ <div className="area-tabs" style={{ display: 'flex', justifyContent: 'center', gap: 24, margin: '18px 0' }}>
+ {areaTabs.map((tab, idx) => (
+ <div
+ key={tab.label}
+ className={activeTab === idx ? "area-tab active" : "area-tab"}
+ onClick={() => setActiveTab(idx)}
+ >
+ {tab.icon} <span>{tab.label}</span>
+ </div>
+ ))}
+ </div>
+ <div className="table-section">
+ <table className="movie-table">
+ <thead>
+ <tr>
+ <th>电影类型</th>
+ <th>标题</th>
+ <th>发布者</th>
+ </tr>
+ </thead>
+ <tbody>
+ {movieTypes.map(type => (
+ <tr key={type}>
+ <td>{type}</td>
+ <td></td>
+ <td></td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ <div style={{ height: 32 }} />
+ <Pagination />
+ </div>
+ );
+}
+
+function Pagination() {
+ const [page, setPage] = React.useState(3);
+ const total = 5;
+ return (
+ <div className="pagination">
+ <button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page === 1}>上一页</button>
+ <span className="page-num">{page}/{total}</span>
+ <button onClick={() => setPage(p => Math.min(total, p + 1))} disabled={page === total}>下一页</button>
+ <span className="page-info">第 <b>{page}</b> 页</span>
+ </div>
+ );
+}
diff --git a/front/src/MusicPage.js b/front/src/MusicPage.js
new file mode 100644
index 0000000..1a63a89
--- /dev/null
+++ b/front/src/MusicPage.js
@@ -0,0 +1,129 @@
+import React from "react";
+import MovieIcon from "@mui/icons-material/Movie";
+import EmailIcon from "@mui/icons-material/Email";
+import MusicNoteIcon from "@mui/icons-material/MusicNote";
+import EmojiPeopleIcon from "@mui/icons-material/EmojiPeople";
+import SportsEsportsIcon from "@mui/icons-material/SportsEsports";
+import SportsMartialArtsIcon from "@mui/icons-material/SportsMartialArts";
+import PersonIcon from "@mui/icons-material/Person";
+import AccountCircleIcon from "@mui/icons-material/AccountCircle";
+import "./App.css";
+import { useNavigate } from "react-router-dom";
+
+const navItems = [
+ { label: "电影", icon: <MovieIcon />, path: "/movie" },
+ { label: "剧集", icon: <EmailIcon />, path: "/tv" },
+ { label: "音乐", icon: <MusicNoteIcon />, path: "/music" },
+ { label: "动漫", icon: <EmojiPeopleIcon />, path: "/anime" },
+ { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },
+ { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },
+ { label: "资料", icon: <PersonIcon />, path: "/info" },
+];
+
+const areaTabs = [
+ { label: "大陆", icon: <MovieIcon fontSize="small" /> },
+ { label: "港台", icon: <EmailIcon fontSize="small" /> },
+ { label: "欧美", icon: <PersonIcon fontSize="small" /> },
+ { label: "日韩", icon: <EmojiPeopleIcon fontSize="small" /> },
+ { label: "其他", icon: <PersonIcon fontSize="small" /> },
+];
+
+export default function MusicPage() {
+ const navigate = useNavigate();
+ const [activeTab, setActiveTab] = React.useState(0);
+
+ // 每个tab对应的音乐类型
+ const musicTypesList = [
+ ["华语音乐(大陆)", "欧美音乐", "日韩音乐", "港台音乐", "其他"], // 大陆
+ ["港台流行", "港台摇滚", "港台其他"], // 港台
+ ["欧美流行", "欧美摇滚", "欧美其他"], // 欧美
+ ["日韩流行", "日韩其他"], // 日韩
+ ["其他类型1", "其他类型2"] // 其他
+ ];
+ const musicTypes = musicTypesList[activeTab] || [];
+
+ return (
+ <div className="container music-bg">
+ {/* 顶部空白与音乐界面一致,用户栏绝对定位在页面右上角 */}
+ <div style={{ height: 80 }} />
+ <div className="user-bar" style={{ position: 'fixed', top: 18, right: 42, zIndex: 100, display: 'flex', alignItems: 'center', background: '#e0f3ff', borderRadius: 12, padding: '6px 18px', boxShadow: '0 2px 8px #b2d8ea', minWidth: 320, minHeight: 48, width: 420 }}>
+ <div style={{ cursor: 'pointer', marginRight: 16 }} onClick={() => navigate('/user')}>
+ <AccountCircleIcon style={{ fontSize: 38, color: '#1a237e', background: '#e0f3ff', borderRadius: '50%' }} />
+ </div>
+ <div style={{ color: '#222', fontWeight: 500, marginRight: 24 }}>用户栏</div>
+ <div style={{ display: 'flex', gap: 28, flex: 1, justifyContent: 'flex-end', alignItems: 'center' }}>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>魔力值: <b>12345</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>分享率: <b>2.56</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>上传量: <b>100GB</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>下载量: <b>50GB</b></span>
+ </div>
+ </div>
+ {/* 下方内容整体下移,留出与其他页面一致的间距 */}
+ <div style={{ height: 32 }} />
+ <nav className="nav-bar card">
+ {navItems.map((item) => (
+ <div
+ key={item.label}
+ className={item.label === "音乐" ? "nav-item active" : "nav-item"}
+ onClick={() => navigate(item.path)}
+ >
+ {item.icon}
+ <span>{item.label}</span>
+ </div>
+ ))}
+ </nav>
+ <div className="search-section card">
+ <input className="search-input" placeholder="输入搜索关键词" />
+ <button className="search-btn">
+ <span role="img" aria-label="search">🔍</span>
+ </button>
+ </div>
+ <div className="area-tabs card" style={{ display: 'flex', justifyContent: 'center', gap: 24, margin: '18px 0' }}>
+ {areaTabs.map((tab, idx) => (
+ <div
+ key={tab.label}
+ className={activeTab === idx ? "area-tab active" : "area-tab"}
+ onClick={() => setActiveTab(idx)}
+ >
+ {tab.icon} <span>{tab.label}</span>
+ </div>
+ ))}
+ </div>
+ <div className="table-section card">
+ <table className="movie-table">
+ <thead>
+ <tr>
+ <th>音乐类型</th>
+ <th>标题</th>
+ <th>发布者</th>
+ </tr>
+ </thead>
+ <tbody>
+ {musicTypes.map(type => (
+ <tr key={type}>
+ <td>{type}</td>
+ <td></td>
+ <td></td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ <div style={{ height: 32 }} />
+ <Pagination />
+ </div>
+ );
+}
+
+function Pagination() {
+ const [page, setPage] = React.useState(3);
+ const total = 5;
+ return (
+ <div className="pagination">
+ <button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page === 1}>上一页</button>
+ <span className="page-num">{page}/{total}</span>
+ <button onClick={() => setPage(p => Math.min(total, p + 1))} disabled={page === total}>下一页</button>
+ <span className="page-info">第 <b>{page}</b> 页</span>
+ </div>
+ );
+}
diff --git a/front/src/SportPage.js b/front/src/SportPage.js
new file mode 100644
index 0000000..81bc2a2
--- /dev/null
+++ b/front/src/SportPage.js
@@ -0,0 +1,200 @@
+import React from "react";
+import MovieIcon from "@mui/icons-material/Movie";
+import EmailIcon from "@mui/icons-material/Email";
+import MusicNoteIcon from "@mui/icons-material/MusicNote";
+import EmojiPeopleIcon from "@mui/icons-material/EmojiPeople";
+import SportsEsportsIcon from "@mui/icons-material/SportsEsports";
+import SportsMartialArtsIcon from "@mui/icons-material/SportsMartialArts";
+import PersonIcon from "@mui/icons-material/Person";
+import AccountCircleIcon from "@mui/icons-material/AccountCircle";
+import "./App.css";
+import { useNavigate } from "react-router-dom";
+
+const navItems = [
+ { label: "电影", icon: <MovieIcon />, path: "/movie" },
+ { label: "剧集", icon: <EmailIcon />, path: "/tv" },
+ { label: "音乐", icon: <MusicNoteIcon />, path: "/music" },
+ { label: "动漫", icon: <EmojiPeopleIcon />, path: "/anime" },
+ { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },
+ { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },
+ { label: "资料", icon: <PersonIcon />, path: "/info" },
+];
+
+const sportTypes = [""];
+
+const areaTabs = [
+ { label: "篮球", icon: <MovieIcon fontSize="small" /> },
+ { label: "足球", icon: <EmailIcon fontSize="small" /> },
+ { label: "羽毛球", icon: <EmojiPeopleIcon fontSize="small" /> },
+ { label: "排球", icon: <PersonIcon fontSize="small" /> },
+ { label: "电竞", icon: <PersonIcon fontSize="small" />, active: true },
+];
+
+export default function SportPage() {
+ const navigate = useNavigate();
+ const [activeTab, setActiveTab] = React.useState(0);
+
+ // 每个tab对应的运动类型
+ const sportTypesList = [
+ ["篮球赛事", "篮球技巧", "篮球明星"], // 篮球
+ ["足球赛事", "足球技巧", "足球明星"], // 足球
+ ["羽毛球赛事", "羽毛球技巧"], // 羽毛球
+ ["排球赛事", "排球技巧"], // 排球
+ ["电竞赛事", "电竞技巧"], // 电竞
+ ];
+ const sportTypes = sportTypesList[activeTab] || [];
+
+ return (
+ <div className="container">
+ {/* 顶部空白与音乐界面一致,用户栏绝对定位在页面右上角 */}
+ <div style={{ height: 80 }} />
+ <div
+ className="user-bar"
+ style={{
+ position: "fixed",
+ top: 18,
+ right: 42,
+ zIndex: 100,
+ display: "flex",
+ alignItems: "center",
+ background: "#e0f3ff",
+ borderRadius: 12,
+ padding: "6px 18px",
+ boxShadow: "0 2px 8px #b2d8ea",
+ minWidth: 320,
+ minHeight: 48,
+ width: 420,
+ }}
+ >
+ <div
+ style={{ cursor: "pointer", marginRight: 16 }}
+ onClick={() => navigate("/user")}
+ >
+ <AccountCircleIcon
+ style={{
+ fontSize: 38,
+ color: "#1a237e",
+ background: "#e0f3ff",
+ borderRadius: "50%",
+ }}
+ />
+ </div>
+ <div style={{ color: "#222", fontWeight: 500, marginRight: 24 }}>
+ 用户栏
+ </div>
+ <div
+ style={{
+ display: "flex",
+ gap: 28,
+ flex: 1,
+ justifyContent: "flex-end",
+ alignItems: "center",
+ }}
+ >
+ <span style={{ color: "#1976d2", fontWeight: 500 }}>
+ 魔力值: <b>12345</b>
+ </span>
+ <span style={{ color: "#1976d2", fontWeight: 500 }}>
+ 分享率: <b>2.56</b>
+ </span>
+ <span style={{ color: "#1976d2", fontWeight: 500 }}>
+ 上传量: <b>100GB</b>
+ </span>
+ <span style={{ color: "#1976d2", fontWeight: 500 }}>
+ 下载量: <b>50GB</b>
+ </span>
+ </div>
+ </div>
+ {/* 下方内容整体下移,留出与音乐界面一致的间距 */}
+ <div style={{ height: 32 }} />
+ <nav className="nav-bar card">
+ {navItems.map((item) => (
+ <div
+ key={item.label}
+ className={item.label === "体育" ? "nav-item active" : "nav-item"}
+ onClick={() => navigate(item.path)}
+ >
+ {item.icon}
+ <span>{item.label}</span>
+ </div>
+ ))}
+ </nav>
+ <div className="search-section card">
+ <input className="search-input" placeholder="输入搜索关键词" />
+ <button className="search-btn">
+ <span role="img" aria-label="search">
+ 🔍
+ </span>
+ </button>
+ </div>
+ <div
+ className="area-tabs"
+ style={{
+ display: "flex",
+ justifyContent: "center",
+ gap: 24,
+ margin: "18px 0",
+ }}
+ >
+ {areaTabs.map((tab, idx) => (
+ <div
+ key={tab.label}
+ className={activeTab === idx ? "area-tab active" : "area-tab"}
+ onClick={() => setActiveTab(idx)}
+ >
+ {tab.icon} <span>{tab.label}</span>
+ </div>
+ ))}
+ </div>
+ <div className="table-section">
+ <table className="movie-table">
+ <thead>
+ <tr>
+ <th>类型</th>
+ <th>标题</th>
+ <th>发布者</th>
+ </tr>
+ </thead>
+ <tbody>
+ {sportTypes.map((type) => (
+ <tr key={type}>
+ <td>{type}</td>
+ <td></td>
+ <td></td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ <div style={{ height: 32 }} />
+ <Pagination />
+ </div>
+ );
+}
+
+function Pagination() {
+ const [page, setPage] = React.useState(3);
+ const total = 5;
+ return (
+ <div className="pagination">
+ <button
+ onClick={() => setPage((p) => Math.max(1, p - 1))}
+ disabled={page === 1}
+ >
+ 上一页
+ </button>
+ <span className="page-num">
+ {page}/{total}
+ </span>
+ <button
+ onClick={() => setPage((p) => Math.min(total, p + 1))}
+ disabled={page === total}
+ >
+ 下一页
+ </button>
+ <span className="page-info">
+ 第 <b>{page}</b> 页
+ </span>
+ </div>
+ );
+}
diff --git a/front/src/TVPage.js b/front/src/TVPage.js
new file mode 100644
index 0000000..02ec0f0
--- /dev/null
+++ b/front/src/TVPage.js
@@ -0,0 +1,137 @@
+import React from "react";
+import MovieIcon from "@mui/icons-material/Movie";
+import EmailIcon from "@mui/icons-material/Email";
+import MusicNoteIcon from "@mui/icons-material/MusicNote";
+import EmojiPeopleIcon from "@mui/icons-material/EmojiPeople";
+import SportsEsportsIcon from "@mui/icons-material/SportsEsports";
+import SportsMartialArtsIcon from "@mui/icons-material/SportsMartialArts";
+import PersonIcon from "@mui/icons-material/Person";
+import AccountCircleIcon from "@mui/icons-material/AccountCircle";
+import { useNavigate } from "react-router-dom";
+import "./App.css";
+
+const navItems = [
+ { label: "电影", icon: <MovieIcon />, path: "/movie" },
+ { label: "剧集", icon: <EmailIcon />, path: "/tv" },
+ { label: "音乐", icon: <MusicNoteIcon />, path: "/music" },
+ { label: "动漫", icon: <EmojiPeopleIcon />, path: "/anime" },
+ { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },
+ { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },
+ { label: "资料", icon: <PersonIcon />, path: "/info" },
+];
+
+const tvTypes = [
+ "华语剧集(大陆)",
+ "欧美剧集",
+ "日韩剧集",
+ "港台剧集",
+ "其他"
+];
+
+const areaTabs = [
+ { label: "大陆", icon: <MovieIcon fontSize="small" /> },
+ { label: "港台", icon: <EmailIcon fontSize="small" /> },
+ { label: "欧美", icon: <PersonIcon fontSize="small" /> },
+ { label: "日韩", icon: <EmojiPeopleIcon fontSize="small" /> },
+ { label: "其他", icon: <PersonIcon fontSize="small" /> },
+];
+
+export default function TVPage() {
+ const navigate = useNavigate();
+ const [activeTab, setActiveTab] = React.useState(0);
+
+ // 每个tab对应的剧集类型
+ const tvTypesList = [
+ ["华语剧集(大陆)", "欧美剧集", "日韩剧集", "港台剧集", "其他"], // 大陆
+ ["港台都市", "港台爱情", "港台悬疑", "港台其他"], // 港台
+ ["欧美悬疑", "欧美历史", "欧美其他"], // 欧美
+ ["日韩青春", "日韩家庭", "日韩其他"], // 日韩
+ ["其他类型1", "其他类型2"] // 其他
+ ];
+ const tvTypes = tvTypesList[activeTab] || [];
+
+ return (
+ <div className="container">
+ {/* 顶部空白与音乐界面一致,用户栏绝对定位在页面右上角 */}
+ <div style={{ height: 80 }} />
+ <div className="user-bar" style={{ position: 'fixed', top: 18, right: 42, zIndex: 100, display: 'flex', alignItems: 'center', background: '#e0f3ff', borderRadius: 12, padding: '6px 18px', boxShadow: '0 2px 8px #b2d8ea', minWidth: 320, minHeight: 48, width: 420 }}>
+ <div style={{ cursor: 'pointer', marginRight: 16 }} onClick={() => navigate('/user')}>
+ <AccountCircleIcon style={{ fontSize: 38, color: '#1a237e', background: '#e0f3ff', borderRadius: '50%' }} />
+ </div>
+ <div style={{ color: '#222', fontWeight: 500, marginRight: 24 }}>用户栏</div>
+ <div style={{ display: 'flex', gap: 28, flex: 1, justifyContent: 'flex-end', alignItems: 'center' }}>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>魔力值: <b>12345</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>分享率: <b>2.56</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>上传量: <b>100GB</b></span>
+ <span style={{ color: '#1976d2', fontWeight: 500 }}>下载量: <b>50GB</b></span>
+ </div>
+ </div>
+ {/* 下方内容整体下移,留出与音乐界面一致的间距 */}
+ <div style={{ height: 32 }} />
+ <nav className="nav-bar card">
+ {navItems.map((item) => (
+ <div
+ key={item.label}
+ className={item.label === "剧集" ? "nav-item active" : "nav-item"}
+ onClick={() => navigate(item.path)}
+ >
+ {item.icon}
+ <span>{item.label}</span>
+ </div>
+ ))}
+ </nav>
+ <div className="search-section card">
+ <input className="search-input" placeholder="输入搜索关键词" />
+ <button className="search-btn">
+ <span role="img" aria-label="search">🔍</span>
+ </button>
+ </div>
+ <div className="area-tabs" style={{ display: 'flex', justifyContent: 'center', gap: 24, margin: '18px 0' }}>
+ {areaTabs.map((tab, idx) => (
+ <div
+ key={tab.label}
+ className={activeTab === idx ? "area-tab active" : "area-tab"}
+ onClick={() => setActiveTab(idx)}
+ >
+ {tab.icon} <span>{tab.label}</span>
+ </div>
+ ))}
+ </div>
+ <div className="table-section">
+ <table className="movie-table">
+ <thead>
+ <tr>
+ <th>剧集类型</th>
+ <th>标题</th>
+ <th>发布者</th>
+ </tr>
+ </thead>
+ <tbody>
+ {tvTypes.map(type => (
+ <tr key={type}>
+ <td>{type}</td>
+ <td></td>
+ <td></td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ <div style={{ height: 32 }} />
+ <Pagination />
+ </div>
+ );
+}
+
+function Pagination() {
+ const [page, setPage] = React.useState(3);
+ const total = 5;
+ return (
+ <div className="pagination">
+ <button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page === 1}>上一页</button>
+ <span className="page-num">{page}/{total}</span>
+ <button onClick={() => setPage(p => Math.min(total, p + 1))} disabled={page === total}>下一页</button>
+ <span className="page-info">第 <b>{page}</b> 页</span>
+ </div>
+ );
+}
diff --git a/front/src/UserProfile.js b/front/src/UserProfile.js
new file mode 100644
index 0000000..0269723
--- /dev/null
+++ b/front/src/UserProfile.js
@@ -0,0 +1,203 @@
+import React, { useState } from "react";
+import AccountCircleIcon from "@mui/icons-material/AccountCircle";
+import { useNavigate } from "react-router-dom";
+import "./App.css";
+
+export default function UserProfile() {
+ const navigate = useNavigate();
+ const [userInfo, setUserInfo] = useState({
+ username: "示例用户",
+ email: "user@example.com",
+ company: "",
+ school: "",
+ birthday: "",
+ });
+ const [tempUserInfo, setTempUserInfo] = useState({ ...userInfo });
+
+ const handleInputChange = (field, value) => {
+ setTempUserInfo({ ...tempUserInfo, [field]: value });
+ };
+
+ const handleSave = () => {
+ setUserInfo({ ...tempUserInfo });
+ alert("信息已保存!");
+ };
+
+ const handleAvatarClick = () => {
+ const avatarUrl = prompt("请输入头像的URL:");
+ if (avatarUrl) {
+ setTempUserInfo({ ...tempUserInfo, avatar: avatarUrl });
+ }
+ };
+
+ return (
+ <div className="container" style={{ minHeight: '100vh', background: 'linear-gradient(135deg, #f0f4ff 0%, #e0e7ff 100%)', display: 'grid', gridTemplateColumns: '1fr 2fr', gridTemplateRows: 'auto 1fr', gap: '20px', padding: '40px' }}>
+ {/* 左侧:用户资料 */}
+ <div style={{ gridColumn: '1 / 2', gridRow: '1 / 3', display: 'flex', flexDirection: 'column', alignItems: 'center', background: '#fff', borderRadius: 18, boxShadow: '0 4px 24px #e0e7ff', padding: '20px' }}>
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: 16 }}>
+ <div onClick={handleAvatarClick} style={{ cursor: 'pointer', position: 'relative' }}>
+ <AccountCircleIcon style={{ fontSize: 90, color: '#1a237e', marginBottom: 12 }} />
+ {tempUserInfo.avatar && (
+ <img
+ src={tempUserInfo.avatar}
+ alt="用户头像"
+ style={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: 90,
+ height: 90,
+ borderRadius: '50%',
+ objectFit: 'cover',
+ }}
+ />
+ )}
+ </div>
+ <h2 style={{ color: '#1a237e', marginBottom: 0, fontSize: 24 }}>用户个人资料</h2>
+ </div>
+ <div className="card" style={{ padding: 28, width: '100%', background: '#fff', borderRadius: 18, boxShadow: '0 4px 24px #e0e7ff', flex: 1 }}>
+ <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>
+ <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>用户名:</b>
+ <input
+ type="text"
+ value={tempUserInfo.username}
+ onChange={(e) => handleInputChange("username", e.target.value)}
+ style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15 }}
+ />
+ </div>
+ <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>
+ <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>邮箱:</b>
+ <input
+ type="email"
+ value={tempUserInfo.email}
+ onChange={(e) => handleInputChange("email", e.target.value)}
+ style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15 }}
+ />
+ </div>
+ <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>
+ <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>公司:</b>
+ <input
+ type="text"
+ value={tempUserInfo.company}
+ onChange={(e) => handleInputChange("company", e.target.value)}
+ style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15 }}
+ />
+ </div>
+ <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>
+ <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>学校:</b>
+ <input
+ type="text"
+ value={tempUserInfo.school}
+ onChange={(e) => handleInputChange("school", e.target.value)}
+ style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15 }}
+ />
+ </div>
+ <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>
+ <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>生日:</b>
+ <input
+ type="date"
+ value={tempUserInfo.birthday}
+ onChange={(e) => handleInputChange("birthday", e.target.value)}
+ style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15 }}
+ />
+ </div>
+ <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>
+ <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>密码:</b>
+ <input
+ type="password"
+ value={tempUserInfo.password || ""}
+ onChange={(e) => handleInputChange("password", e.target.value)}
+ style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15 }}
+ />
+ </div>
+ <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>
+ <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>性别:</b>
+ <div style={{ position: 'relative', flex: 1 }}>
+ <button
+ onClick={() => setTempUserInfo({ ...tempUserInfo, showGenderOptions: !tempUserInfo.showGenderOptions })}
+ style={{
+ width: '100%',
+ padding: '6px 10px',
+ borderRadius: 7,
+ border: '1px solid #b2b2b2',
+ textAlign: 'left',
+ backgroundColor: '#fff',
+ fontSize: 15,
+ cursor: 'pointer',
+ }}
+ >
+ {tempUserInfo.gender || "性别"}
+ </button>
+ {tempUserInfo.showGenderOptions && (
+ <ul
+ style={{
+ position: 'absolute',
+ top: '100%',
+ left: 0,
+ right: 0,
+ backgroundColor: '#fff',
+ border: '1px solid #b2b2b2',
+ borderRadius: 7,
+ listStyle: 'none',
+ margin: 0,
+ padding: 0,
+ zIndex: 10,
+ }}
+ >
+ {["男", "女", "跨性别男", "跨性别女", "无性别"].map((option) => (
+ <li
+ key={option}
+ onClick={() => {
+ setTempUserInfo({ ...tempUserInfo, gender: option, showGenderOptions: false });
+ }}
+ style={{
+ padding: '6px 10px',
+ cursor: 'pointer',
+ borderBottom: '1px solid #e0e0e0',
+ backgroundColor: option === tempUserInfo.gender ? '#f0f0f0' : '#fff',
+ }}
+ >
+ {option}
+ </li>
+ ))}
+ </ul>
+ )}
+ </div>
+ </div>
+ <button
+ onClick={handleSave}
+ style={{
+ marginTop: 20,
+ padding: '10px 20px',
+ backgroundColor: '#1a237e',
+ color: '#fff',
+ border: 'none',
+ borderRadius: 7,
+ cursor: 'pointer',
+ fontSize: 16,
+ alignSelf: 'center', // Center the button horizontally
+ }}
+ onMouseOver={(e) => (e.target.style.backgroundColor = '#0d1b5e')}
+ onMouseOut={(e) => (e.target.style.backgroundColor = '#1a237e')}
+ >
+ 保存
+ </button>
+ </div>
+ </div>
+ {/* 上传种子列表 */}
+ <div style={{ gridColumn: '2 / 3', gridRow: '1 / 2', background: '#fff', borderRadius: 18, boxShadow: '0 4px 24px #e0e7ff', padding: '20px' }}>
+ <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18 }}>个人上传种子列表</h3>
+ <div style={{ border: '1px dashed #b2b2b2', borderRadius: 12, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#b2b2b2', fontSize: 18 }}>
+ (此处显示上传种子列表)
+ </div>
+ </div>
+ {/* 活跃度模块 */}
+ <div style={{ gridColumn: '2 / 3', gridRow: '2 / 3', background: '#fff', borderRadius: 18, boxShadow: '0 4px 24px #e0e7ff', padding: '20px' }}>
+ <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18 }}>活跃度</h3>
+ <div style={{ border: '1px dashed #b2b2b2', borderRadius: 12, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#b2b2b2', fontSize: 18 }}>
+ (此处显示活跃度信息)
+ </div>
+ </div>
+ </div>
+ );
+}
\ No newline at end of file
diff --git a/front/src/index.css b/front/src/index.css
new file mode 100644
index 0000000..ec2585e
--- /dev/null
+++ b/front/src/index.css
@@ -0,0 +1,13 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
diff --git a/front/src/index.js b/front/src/index.js
new file mode 100644
index 0000000..d563c0f
--- /dev/null
+++ b/front/src/index.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+import App from './App';
+import reportWebVitals from './reportWebVitals';
+
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render(
+ <React.StrictMode>
+ <App />
+ </React.StrictMode>
+);
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();
diff --git a/front/src/logo.svg b/front/src/logo.svg
new file mode 100644
index 0000000..9dfc1c0
--- /dev/null
+++ b/front/src/logo.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
\ No newline at end of file
diff --git a/front/src/reportWebVitals.js b/front/src/reportWebVitals.js
new file mode 100644
index 0000000..5253d3a
--- /dev/null
+++ b/front/src/reportWebVitals.js
@@ -0,0 +1,13 @@
+const reportWebVitals = onPerfEntry => {
+ if (onPerfEntry && onPerfEntry instanceof Function) {
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ getCLS(onPerfEntry);
+ getFID(onPerfEntry);
+ getFCP(onPerfEntry);
+ getLCP(onPerfEntry);
+ getTTFB(onPerfEntry);
+ });
+ }
+};
+
+export default reportWebVitals;
diff --git a/front/src/setupTests.js b/front/src/setupTests.js
new file mode 100644
index 0000000..8f2609b
--- /dev/null
+++ b/front/src/setupTests.js
@@ -0,0 +1,5 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// allows you to do things like:
+// expect(element).toHaveTextContent(/react/i)
+// learn more: https://github.com/testing-library/jest-dom
+import '@testing-library/jest-dom';