更新登录
Change-Id: I0b2663f4291f573625222995b09a99ac53c2a081
diff --git a/config/routes.ts b/config/routes.ts
index d380d7c..95e9653 100644
--- a/config/routes.ts
+++ b/config/routes.ts
@@ -156,6 +156,12 @@
component: './Torrent/torrentList.tsx',
hideInMenu: true,
},
+ {
+ name: '我的种子列表',
+ path: '/torrent-list-my',
+ component: './Torrent/torrentListMy.tsx',
+ hideInMenu: true,
+ },
{
name: '种子详情界面',
path: '/torrent-detail/:id',
diff --git a/nginx.conf b/nginx.conf
index 6c64adf..5e1dcde 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -22,7 +22,7 @@
location ^~/api/ {
rewrite ^/api(/.*)$ $1 break;
- proxy_pass http://127.0.0.1:5004/;
+ proxy_pass http://127.0.0.1:5004;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
diff --git a/src/locales/zh-CN/pages.ts b/src/locales/zh-CN/pages.ts
index fc7abfb..313d493 100644
--- a/src/locales/zh-CN/pages.ts
+++ b/src/locales/zh-CN/pages.ts
@@ -8,7 +8,7 @@
'pages.login.username.required': '用户名是必填项!',
'pages.login.password.placeholder': '密码: admin123',
'pages.login.password.required': '密码是必填项!',
- 'pages.login.phoneLogin.tab': '手机号登录',
+ 'pages.login.phoneLogin.tab': '邀请码注册',
'pages.login.phoneLogin.errorMessage': '验证码错误',
'pages.login.phoneNumber.placeholder': '请输入手机号!',
'pages.login.phoneNumber.required': '手机号是必填项!',
diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx
index 7234c32..f9891d7 100644
--- a/src/pages/Home/index.tsx
+++ b/src/pages/Home/index.tsx
@@ -9,6 +9,7 @@
import { Typography } from "@mui/material";
import { useRequest } from '@umijs/max';
import { getPlanetList, updatePlanet, getUserPlanet, getPlanetInfo, PlanetEntity } from '@/services/planets/api';
+import {generatePassKey} from '../../services/bt/index'
const Welcome: React.FC = () => {
// 获取用户信息
@@ -251,9 +252,29 @@
<div>
{/* 第一排:悬浮搜索框 */}
- <div className={styles.searchBarContainer}>
- <input type="text" placeholder="Search for everything..." className={styles.searchInput} />
- </div>
+ <div style={{ display: 'flex', alignItems: 'center' }}>
+ <div className={styles.searchBarContainer}>
+ <input type="text" placeholder="Search for everything..." className={styles.searchInput} />
+ </div>
+ <Button
+ type="primary"
+ style={{ marginLeft: 16 }}
+ onClick={async () => {
+ try {
+ const res = await generatePassKey();
+ if (res && res.code === 0 && res.data && res.data.passkey) {
+ window.alert(`邀请码: ${res.data.passkey}`);
+ } else {
+ window.alert('邀请码生成失败');
+ }
+ } catch (e) {
+ window.alert('邀请码生成失败');
+ }
+ }}
+ >
+ 邀请他人
+ </Button>
+ </div>
<div style={{ display: "flex", marginTop: '40px' }}>
<div className={styles.leftBox}>
<div className={styles.textContent}>
@@ -321,7 +342,7 @@
种子
</div>
<Button type="primary" onClick={() => window.location.href = '/torrent-upload'}>新建种子</Button>
- <Button onClick={() => window.location.href = '/torrent-list'}>我的种子</Button>
+ <Button onClick={() => window.location.href = '/torrent-list-my'}>我的种子</Button>
</Flex>
</Card>
diff --git a/src/pages/Torrent/torrentAudit.tsx b/src/pages/Torrent/torrentAudit.tsx
index e90af3c..3e6a45b 100644
--- a/src/pages/Torrent/torrentAudit.tsx
+++ b/src/pages/Torrent/torrentAudit.tsx
@@ -2,7 +2,7 @@
import { useParams,useSearchParams, useNavigate } from 'react-router-dom';
import { Card, Button, Spin, Tag, message, Input } from 'antd';
import { ArrowLeftOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons';
-import { getTorrentInfo,auditTorrent,downloadTorrent } from '../../services/bt/index';
+import { getTorrentInfo,auditTorrent,downloadTorrent,getCategories } from '../../services/bt/index';
// 状态映射
@@ -17,7 +17,7 @@
const TorrentAudit: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
-
+ const [categories, setCategories] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [torrent, setTorrent] = useState<any>(null);
const [auditLoading, setAuditLoading] = useState(false);
@@ -33,6 +33,7 @@
getTorrentInfo({ id })
.then(res => setTorrent(res.data))
.finally(() => setLoading(false));
+ getCategories().then((res) => setCategories(res.data || []));
}, [id, navigate]);
const handleDownload = async () => {
try {
@@ -68,7 +69,10 @@
setAuditLoading(false);
}
};
-
+const getCategoryName = (catId: number) => {
+ console.log('categories', catId, categories);
+ return categories.find((c) => c.id === catId)?.name || '未知分类';
+};
return (
<div style={{ minHeight: '100vh', background: '#f4f8ff', padding: 32 }}>
<div style={{ maxWidth: 800, margin: '0 auto' }}>
@@ -101,7 +105,7 @@
<>
<h2>{torrent.title}</h2>
<div style={{ marginBottom: 8 }}>
- <Tag color="blue">{torrent.categoryName || '未知分类'}</Tag>
+ <Tag color="blue">{getCategoryName(torrent.category) || '未知分类'}</Tag>
{torrent.tags?.map((tag: string) => (
<Tag key={tag}>{tag}</Tag>
))}
@@ -132,6 +136,7 @@
borderRadius: 8,
marginBottom: 24,
minHeight: 80,
+ color: '#333',
}}
dangerouslySetInnerHTML={{ __html: torrent.description || '' }}
/>
diff --git a/src/pages/Torrent/torrentDetail.tsx b/src/pages/Torrent/torrentDetail.tsx
index 7e194a9..e27be12 100644
--- a/src/pages/Torrent/torrentDetail.tsx
+++ b/src/pages/Torrent/torrentDetail.tsx
@@ -285,6 +285,17 @@
<div style={{ color: '#cbd5e1', marginBottom: 8 }}>
上传者:{torrent?.owner} | 上传时间:{torrent?.createdAt}
</div>
+ <div
+ style={{
+ background: '#f6f8fa',
+ padding: 16,
+ borderRadius: 8,
+ marginBottom: 24,
+ minHeight: 80,
+ color: '#333',
+ }}
+ dangerouslySetInnerHTML={{ __html: torrent.description || '' }}
+ />
<Button
type="primary"
icon={<DownloadOutlined />}
diff --git a/src/pages/Torrent/torrentListMy.tsx b/src/pages/Torrent/torrentListMy.tsx
new file mode 100644
index 0000000..2f336ac
--- /dev/null
+++ b/src/pages/Torrent/torrentListMy.tsx
@@ -0,0 +1,505 @@
+import React, { useEffect, useState } from "react";
+import { styled } from "@mui/material/styles";
+import axios from "axios";
+import {
+ Box,
+ Button,
+ Card,
+ CardContent,
+ Chip,
+ CircularProgress,
+ Container,
+ MenuItem,
+ Pagination,
+ Select,
+ Typography,
+ TextField,
+ InputAdornment,
+ IconButton,
+} from "@mui/material";
+import { getCategories, getMyTorrentList,searchTorrents } from "../../services/bt/index";
+
+// 优化后的星空背景
+const StarBg = styled("div")({
+ minHeight: "100vh",
+ width: "auto",
+ background: "radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%)",
+ overflow: "auto",
+ position: "relative",
+ top: 0,
+ left: 0,
+ "&:before, &:after": {
+ content: '""',
+ position: "absolute",
+ top: 0,
+ left: 0,
+ width: "100%",
+ height: "100%",
+ pointerEvents: "none",
+ },
+ "&:before": {
+ background: `
+ radial-gradient(1px 1px at 20% 30%, rgba(255,255,255,0.8), rgba(255,255,255,0)),
+ radial-gradient(1px 1px at 40% 70%, rgba(255,255,255,0.8), rgba(255,255,255,0)),
+ radial-gradient(1.5px 1.5px at 60% 20%, rgba(255,255,255,0.9), rgba(255,255,255,0)),
+ radial-gradient(1.5px 1.5px at 80% 90%, rgba(255,255,255,0.9), rgba(255,255,255,0))
+ `,
+ backgroundRepeat: "repeat",
+ backgroundSize: "200px 200px",
+ animation: "twinkle 10s infinite ease-in-out",
+ },
+ "&:after": {
+ background: `
+ radial-gradient(1px 1px at 70% 40%, rgba(255,255,255,0.7), rgba(255,255,255,0)),
+ radial-gradient(1.2px 1.2px at 10% 80%, rgba(255,255,255,0.7), rgba(255,255,255,0)),
+ radial-gradient(1.5px 1.5px at 30% 60%, rgba(255,255,255,0.8), rgba(255,255,255,0))
+ `,
+ backgroundRepeat: "repeat",
+ backgroundSize: "300px 300px",
+ animation: "twinkle 15s infinite 5s ease-in-out",
+ },
+ "@keyframes twinkle": {
+ "0%, 100%": { opacity: 0.3 },
+ "50%": { opacity: 0.8 },
+ }
+});
+
+// 分类标签栏
+const CategoryBar = styled(Box)({
+ display: "flex",
+ gap: 12,
+ alignItems: "center",
+ marginBottom: 24,
+ flexWrap: "wrap",
+});
+
+// 种子卡片
+const TorrentCard = styled(Card)({
+ background: "rgba(30, 41, 59, 0.85)",
+ color: "#fff",
+ borderRadius: 16,
+ boxShadow: "0 4px 24px 0 rgba(0,0,0,0.4)",
+ border: "1px solid #334155",
+ transition: "transform 0.2s",
+ "&:hover": {
+ transform: "scale(1.025)",
+ boxShadow: "0 8px 32px 0 #0ea5e9",
+ },
+});
+
+const sortOptions = [
+ { label: "最新", value: { sortField: "create_time", sortDirection: "desc" } },
+ { label: "下载量", value: { sortField: "completions", sortDirection: "desc" } },
+ { label: "推荐", value: { sortField: "seeders", sortDirection: "desc" } },
+];
+
+const statusMap: Record<number, string> = {
+ 0: '审核中',
+ 1: '已发布',
+ 2: '审核不通过',
+ 3: '已上架修改重审中',
+ 10: '已下架',
+};
+
+const PAGE_SIZE = 20;
+
+const TorrentListPage: React.FC = () => {
+ const [categories, setCategories] = useState<
+ { id: number; name: string; remark?: string | null; type?: number | null }[]
+ >([]);
+ const [selectedCat, setSelectedCat] = useState<number | null>(null);
+ const [sort, setSort] = useState(sortOptions[0].value);
+ const [torrents, setTorrents] = useState<any[]>([]);
+ const [loading, setLoading] = useState(false);
+ const [page, setPage] = useState(1);
+ const [total, setTotal] = useState(0);
+
+ // 搜索相关
+ const [searchKeyword, setSearchKeyword] = useState("");
+ const [searching, setSearching] = useState(false);
+ const [isSearchMode, setIsSearchMode] = useState(false);
+
+ // 获取分类
+ useEffect(() => {
+ getCategories().then((res) => {
+ setCategories(res.data || []);
+ });
+ }, []);
+
+ // 获取种子列表
+ useEffect(() => {
+ if (isSearchMode) return;
+ setLoading(true);
+ getMyTorrentList()
+ .then((res) => {
+ setTorrents(res.datarow || []);
+ setTotal(res.data.page?.size || 0);
+ })
+ .finally(() => setLoading(false));
+ }, [selectedCat, sort, page, isSearchMode]);
+
+ // 搜索
+ const handleSearch = async () => {
+ if (!searchKeyword.trim()) {
+ setIsSearchMode(false);
+ setPage(1);
+ return;
+ }
+ setSearching(true);
+ setIsSearchMode(true);
+ try {
+ const res = await searchTorrents({
+ titleKeyword: searchKeyword.trim(),
+ category: selectedCat !== null ? selectedCat : undefined,
+ sortField: sort.sortField,
+ sortDirection: sort.sortDirection,
+ pageNum: page,
+ pageSize: PAGE_SIZE,
+ });
+ setTorrents((res.records || []).map((r: any) => ({
+ ...r.torrent,
+ ownerInfo: r.ownerInfo,
+ })));
+ setTotal(res.total || 0);
+ } finally {
+ setSearching(false);
+ }
+ };
+
+ // 搜索模式下翻页
+ useEffect(() => {
+ if (!isSearchMode) return;
+ handleSearch();
+ // eslint-disable-next-line
+ }, [page, sort, selectedCat]);
+
+ // 切换分类/排序时退出搜索模式
+ useEffect(() => {
+ setIsSearchMode(false);
+ setSearchKeyword("");
+ setPage(1);
+ }, [selectedCat, sort]);
+
+ return (
+ <StarBg>
+ <Container maxWidth="lg" sx={{ pt: 6, pb: 6, position: "relative" }}>
+ <Typography
+ variant="h3"
+ sx={{
+ color: "#fff",
+ fontWeight: 700,
+ mb: 3,
+ letterSpacing: 2,
+ textShadow: "0 2px 16px #0ea5e9",
+ }}
+ >
+ 我的种子
+ </Typography>
+ 搜索框
+ <Box
+ sx={{
+ mb: 4,
+ display: "flex",
+ alignItems: "center",
+ gap: 2,
+ background: "rgba(30,41,59,0.7)",
+ borderRadius: 3,
+ px: 2,
+ py: 1.5,
+ boxShadow: "0 2px 12px 0 #0ea5e950",
+ border: "1px solid #334155",
+ maxWidth: 700,
+ mx: { xs: "auto", md: 0 },
+ }}
+ >
+ <TextField
+ variant="outlined"
+ size="small"
+ placeholder="🔍 搜索种子标题"
+ value={searchKeyword}
+ onChange={(e) => setSearchKeyword(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ setPage(1);
+ handleSearch();
+ }
+ }}
+ sx={{
+ background: "rgba(17,24,39,0.95)",
+ borderRadius: 2,
+ input: {
+ color: "#fff",
+ fontSize: 17,
+ fontWeight: 500,
+ letterSpacing: 1,
+ px: 1,
+ },
+ width: 320,
+ ".MuiOutlinedInput-notchedOutline": { border: 0 },
+ boxShadow: "0 1px 4px 0 #0ea5e930",
+ }}
+ InputProps={{
+ endAdornment: (
+ <InputAdornment position="end">
+ <IconButton
+ onClick={() => {
+ setPage(1);
+ handleSearch();
+ }}
+ edge="end"
+ sx={{
+ color: "#0ea5e9",
+ bgcolor: "#1e293b",
+ borderRadius: 2,
+ "&:hover": { bgcolor: "#0ea5e9", color: "#fff" },
+ transition: "all 0.2s",
+ }}
+ >
+ <svg width="22" height="22" fill="none" viewBox="0 0 24 24">
+ <circle cx="11" cy="11" r="7" stroke="currentColor" strokeWidth="2"/>
+ <path stroke="currentColor" strokeWidth="2" strokeLinecap="round" d="M20 20l-3.5-3.5"/>
+ </svg>
+ </IconButton>
+ </InputAdornment>
+ ),
+ }}
+ />
+ {isSearchMode && (
+ <Button
+ variant="outlined"
+ color="secondary"
+ onClick={() => {
+ setIsSearchMode(false);
+ setSearchKeyword("");
+ setPage(1);
+ }}
+ sx={{
+ ml: 1,
+ color: "#fff",
+ borderColor: "#64748b",
+ borderRadius: 2,
+ fontWeight: 600,
+ letterSpacing: 1,
+ px: 2,
+ py: 0.5,
+ background: "rgba(51,65,85,0.7)",
+ "&:hover": {
+ borderColor: "#0ea5e9",
+ background: "#0ea5e9",
+ color: "#fff",
+ },
+ transition: "all 0.2s",
+ }}
+ >
+ 清除搜索
+ </Button>
+ )}
+ <Box sx={{ flex: 1 }} />
+ <Button
+ variant="contained"
+ color="primary"
+ onClick={() => window.open("/torrent-upload", "_self")}
+ sx={{
+ borderRadius: 2,
+ fontWeight: 700,
+ letterSpacing: 1,
+ px: 3,
+ py: 1,
+ boxShadow: "0 2px 8px 0 #0ea5e950",
+ background: "#0ea5e9",
+ color: "#fff",
+ "&:hover": {
+ background: "#38bdf8",
+ },
+ transition: "all 0.2s",
+ }}
+ >
+ 分享资源
+ </Button>
+ </Box>
+ <CategoryBar>
+ <Chip
+ label="全部"
+ color={selectedCat === null ? "primary" : "default"}
+ onClick={() => {
+ setSelectedCat(null);
+ setPage(1);
+ }}
+ sx={{
+ fontWeight: 600,
+ fontSize: 16,
+ color: selectedCat === null ? undefined : "#fff",
+ }}
+ />
+ {categories.map((cat) => (
+ <Chip
+ key={cat.id}
+ label={cat.name}
+ color={selectedCat === cat.id ? "primary" : "default"}
+ onClick={() => {
+ setSelectedCat(cat.id);
+ setPage(1);
+ }}
+ sx={{
+ fontWeight: 600,
+ fontSize: 16,
+ color: selectedCat === cat.id ? undefined : "#fff",
+ }}
+ />
+ ))}
+ <Box sx={{ flex: 1 }} />
+ <Select
+ size="small"
+ value={JSON.stringify(sort)}
+ onChange={(e) => {
+ setSort(JSON.parse(e.target.value));
+ setPage(1);
+ }}
+ sx={{
+ color: "#fff",
+ background: "#1e293b",
+ borderRadius: 2,
+ ".MuiOutlinedInput-notchedOutline": { border: 0 },
+ minWidth: 120,
+ }}
+ >
+ {sortOptions.map((opt) => (
+ <MenuItem key={opt.label} value={JSON.stringify(opt.value)}>
+ {opt.label}
+ </MenuItem>
+ ))}
+ </Select>
+ </CategoryBar>
+ {(loading || searching) ? (
+ <Box sx={{ display: "flex", justifyContent: "center", mt: 8 }}>
+ <CircularProgress color="info" />
+ </Box>
+ ) : (
+ <>
+ <Box
+ sx={{
+ display: "flex",
+ flexWrap: "wrap",
+ gap: 3,
+ justifyContent: { xs: "center", md: "flex-start" },
+ }}
+ >
+ {torrents.map((torrent) => (
+ <Box
+ key={torrent.id}
+ sx={{
+ flex: "1 1 320px",
+ maxWidth: { xs: "100%", sm: "48%", md: "32%" },
+ minWidth: 300,
+ mb: 3,
+ display: "flex",
+ cursor: "pointer",
+ }}
+ onClick={() => window.open(`/torrent-detail/${torrent.id}`, "_self")}
+ >
+ <TorrentCard sx={{ width: "100%" }}>
+ <CardContent>
+ <Typography
+ variant="h6"
+ sx={{
+ color: "#38bdf8",
+ fontWeight: 700,
+ mb: 1,
+ textOverflow: "ellipsis",
+ overflow: "hidden",
+ whiteSpace: "nowrap",
+ }}
+ title={torrent.title}
+ >
+ {torrent.title}
+ </Typography>
+ <Box
+ sx={{
+ display: "flex",
+ gap: 1,
+ alignItems: "center",
+ mb: 1,
+ flexWrap: "wrap",
+ }}
+ >
+ <Chip
+ size="small"
+ label={
+ categories.find((c) => c.id === torrent.category)?.name ||
+ "未知"
+ }
+ sx={{
+ background: "#0ea5e9",
+ color: "#fff",
+ fontWeight: 600,
+ }}
+ />
+ {torrent.free === "1" && (
+ <Chip
+ size="small"
+ label="促销"
+ sx={{
+ background: "#fbbf24",
+ color: "#1e293b",
+ fontWeight: 600,
+ }}
+ />
+ )}
+ {/* 状态标签 */}
+ {typeof torrent.status !== "undefined" && (
+ <Chip
+ size="small"
+ label={statusMap[torrent.status] || "未知状态"}
+ sx={{
+ background: "#64748b",
+ color: "#fff",
+ fontWeight: 600,
+ }}
+ />
+ )}
+ </Box>
+ <Typography variant="body2" sx={{ color: "#cbd5e1", mb: 1 }}>
+ 上传时间:{torrent.createTime}
+ </Typography>
+ <Box sx={{ display: "flex", gap: 2, mt: 1 }}>
+ <Typography variant="body2" sx={{ color: "#38bdf8" }}>
+ 做种:{torrent.seeders}
+ </Typography>
+ <Typography variant="body2" sx={{ color: "#f472b6" }}>
+ 下载量:{torrent.completions}
+ </Typography>
+ <Typography variant="body2" sx={{ color: "#fbbf24" }}>
+ 下载中:{torrent.leechers}
+ </Typography>
+ </Box>
+ </CardContent>
+ </TorrentCard>
+ </Box>
+ ))}
+ </Box>
+ <Box sx={{ display: "flex", justifyContent: "center", mt: 4 }}>
+ <Pagination
+ count={Math.ceil(total / PAGE_SIZE)}
+ page={page}
+ onChange={(_, v) => setPage(v)}
+ color="primary"
+ sx={{
+ ".MuiPaginationItem-root": {
+ color: "#fff",
+ background: "#1e293b",
+ border: "1px solid #334155",
+ },
+ ".Mui-selected": {
+ background: "#0ea5e9 !important",
+ },
+ }}
+ />
+ </Box>
+ </>
+ )}
+ </Container>
+ </StarBg>
+ );
+};
+
+export default TorrentListPage;
\ No newline at end of file
diff --git a/src/pages/User/Login/index.less b/src/pages/User/Login/index.less
new file mode 100644
index 0000000..cfd2f08
--- /dev/null
+++ b/src/pages/User/Login/index.less
@@ -0,0 +1,3 @@
+.ant-pro-form-login-container{
+ overflow: hidden;
+}
\ No newline at end of file
diff --git a/src/pages/User/Login/index.tsx b/src/pages/User/Login/index.tsx
index e29032e..b8a44ff 100644
--- a/src/pages/User/Login/index.tsx
+++ b/src/pages/User/Login/index.tsx
@@ -22,6 +22,8 @@
import React, { useEffect, useState } from 'react';
import { flushSync } from 'react-dom';
import { clearSessionToken, setSessionToken } from '@/access';
+import './index.less';
+import { registerUser } from '@/services/bt/index';
const ActionIcons = () => {
const langClassName = useEmotionCss(({ token }) => {
@@ -96,7 +98,7 @@
display: 'flex',
flexDirection: 'column',
height: '100vh',
- overflow: 'auto',
+ overflow: 'hidden',
backgroundImage:
"url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr')",
backgroundSize: '100% 100%',
@@ -159,6 +161,7 @@
message.error(defaultLoginFailureMessage);
}
};
+
const { code } = userLoginState;
const loginType = type;
@@ -171,7 +174,8 @@
className={containerClassName}
style={{
backgroundImage:
- "url('https://images.unsplash.com/photo-1462331940025-496dfbfc7564?auto=format&fit=crop&w=1500&q=80')",
+ "linear-gradient(120deg, #232526 0%, #414345 100%), url('https://images.unsplash.com/photo-1462331940025-496dfbfc7564?auto=format&fit=crop&w=1500&q=80')",
+ backgroundBlendMode: 'overlay',
backgroundSize: 'cover',
backgroundPosition: 'center',
minHeight: '100vh',
@@ -199,7 +203,6 @@
background: 'radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%)',
}}
>
- {/* 可以集成粒子库如 tsParticles 或者简单用 CSS 动画 */}
<svg width="100%" height="100%">
{[...Array(60)].map((_, i) => (
<circle
@@ -225,303 +228,403 @@
zIndex: 1,
}}
>
- <LoginForm
- contentStyle={{
- minWidth: 320,
- maxWidth: 400,
- background: 'rgba(25, 34, 54, 0.92)',
- borderRadius: 16,
+ <div
+ style={{
+ background: 'rgba(30, 34, 54, 0.96)',
+ borderRadius: 20,
boxShadow: '0 8px 32px 0 rgba(31, 38, 135, 0.37)',
- border: '1px solid rgba(255,255,255,0.18)',
- padding: '32px 32px 24px 32px',
+ border: '1px solid rgba(255,255,255,0.12)',
+ padding: '36px 24px 28px 24px',
+ minWidth: 340,
+ maxWidth: 420,
+ width: '100%',
color: '#fff',
- }}
- // logo={
- // <img
- // alt="logo"
- // src="/planet-logo.svg"
- // style={{
- // width: 64,
- // height: 64,
- // filter: 'drop-shadow(0 0 8px #fff8) drop-shadow(0 0 16px #6cf)',
- // marginBottom: 8,
- // }}
- // />
- // }
- title={
- <span style={{ color: '#fff', fontWeight: 700, fontSize: 28, letterSpacing: 2 }}>
- ThunderHub
- </span>
- }
- subTitle={
- <span style={{ color: '#b3c7f9', fontSize: 16 }}>
- 探索你的专属星球,畅享PT世界 JRX MSY ZYT HXQ LJB
- </span>
- }
- initialValues={{
- autoLogin: true,
- }}
- // actions={[
- // <FormattedMessage
- // key="loginWith"
- // id="pages.login.loginWith"
- // defaultMessage="其他登录方式"
- // />,
- // <ActionIcons key="icons" />,
- // ]}
- onFinish={async (values) => {
- await handleSubmit(values as API.LoginParams);
+ backdropFilter: 'blur(8px)',
+ position: 'relative',
+ transition: 'max-width 0.2s cubic-bezier(.4,2,.6,1)',
+ overflow: 'hidden',
}}
>
- <Tabs
- activeKey={type}
- onChange={setType}
- centered
- items={[
- {
- key: 'account',
- label: (
- <span style={{ color: '#fff' }}>
- {intl.formatMessage({
- id: 'pages.login.accountLogin.tab',
- defaultMessage: '账户密码登录',
- })}
- </span>
- ),
- },
- {
- key: 'mobile',
- label: (
- <span style={{ color: '#fff' }}>
- {intl.formatMessage({
- id: 'pages.login.phoneLogin.tab',
- defaultMessage: '手机号登录',
- })}
- </span>
- ),
- },
- ]}
- style={{ marginBottom: 24 }}
- />
-
- {code !== 200 && loginType === 'account' && (
- <LoginMessage
- content={intl.formatMessage({
- id: 'pages.login.accountLogin.errorMessage',
- defaultMessage: '账户或密码错误(admin/admin123)',
- })}
- />
- )}
- {type === 'account' && (
- <>
- <ProFormText
- name="username"
- initialValue="admin"
- fieldProps={{
- size: 'large',
- prefix: <UserOutlined style={{ color: '#6cf' }} />,
- style: { background: 'rgba(255,255,255,0.08)', color: '#fff' },
- }}
- placeholder={intl.formatMessage({
- id: 'pages.login.username.placeholder',
- defaultMessage: '用户名: admin',
- })}
- rules={[
- {
- required: true,
- message: (
- <FormattedMessage
- id="pages.login.username.required"
- defaultMessage="请输入用户名!"
- />
- ),
- },
- ]}
- />
- <ProFormText.Password
- name="password"
- initialValue="admin123"
- fieldProps={{
- size: 'large',
- prefix: <LockOutlined style={{ color: '#6cf' }} />,
- style: { background: 'rgba(255,255,255,0.08)', color: '#fff' },
- }}
- placeholder={intl.formatMessage({
- id: 'pages.login.password.placeholder',
- defaultMessage: '密码: admin123',
- })}
- rules={[
- {
- required: true,
- message: (
- <FormattedMessage
- id="pages.login.password.required"
- defaultMessage="请输入密码!"
- />
- ),
- },
- ]}
- />
- <Row>
- <Col flex={3}>
- <ProFormText
- style={{
- float: 'right',
- background: 'rgba(255,255,255,0.08)',
- color: '#fff',
- }}
- name="code"
- placeholder={intl.formatMessage({
- id: 'pages.login.captcha.placeholder',
- defaultMessage: '请输入验证',
- })}
- rules={[
- {
- required: true,
- message: (
- <FormattedMessage
- id="pages.searchTable.updateForm.ruleName.nameRules"
- defaultMessage="请输入验证啊"
- />
- ),
- },
- ]}
- />
- </Col>
- <Col flex={2}>
- <Image
- src={captchaCode}
- alt="验证码"
- style={{
- display: 'inline-block',
- verticalAlign: 'top',
- cursor: 'pointer',
- paddingLeft: '10px',
- width: '100px',
- borderRadius: 8,
- boxShadow: '0 0 8px #6cf8',
- background: '#fff',
- }}
- preview={false}
- onClick={() => getCaptchaCode()}
- />
- </Col>
- </Row>
- </>
- )}
-
- {code !== 200 && loginType === 'mobile' && (
- <LoginMessage content="验证码错误" />
- )}
- {type === 'mobile' && (
- <>
- <ProFormText
- fieldProps={{
- size: 'large',
- prefix: <MobileOutlined style={{ color: '#6cf' }} />,
- style: { background: 'rgba(255,255,255,0.08)', color: '#fff' },
- }}
- name="mobile"
- placeholder={intl.formatMessage({
- id: 'pages.login.phoneNumber.placeholder',
- defaultMessage: '手机号',
- })}
- rules={[
- {
- required: true,
- message: (
- <FormattedMessage
- id="pages.login.phoneNumber.required"
- defaultMessage="请输入手机号!"
- />
- ),
- },
- {
- pattern: /^1\d{10}$/,
- message: (
- <FormattedMessage
- id="pages.login.phoneNumber.invalid"
- defaultMessage="手机号格式错误!"
- />
- ),
- },
- ]}
- />
- <ProFormCaptcha
- fieldProps={{
- size: 'large',
- prefix: <LockOutlined style={{ color: '#6cf' }} />,
- style: { background: 'rgba(255,255,255,0.08)', color: '#fff' },
- }}
- captchaProps={{
- size: 'large',
- }}
- placeholder={intl.formatMessage({
- id: 'pages.login.captcha.placeholder',
- defaultMessage: '请输入验证码',
- })}
- captchaTextRender={(timing, count) => {
- if (timing) {
- return `${count} ${intl.formatMessage({
- id: 'pages.getCaptchaSecondText',
- defaultMessage: '获取验证码',
- })}`;
- }
- return intl.formatMessage({
- id: 'pages.login.phoneLogin.getVerificationCode',
- defaultMessage: '获取验证码',
- });
- }}
- name="captcha"
- rules={[
- {
- required: true,
- message: (
- <FormattedMessage
- id="pages.login.captcha.required"
- defaultMessage="请输入验证码!"
- />
- ),
- },
- ]}
- onGetCaptcha={async (phone) => {
- const result = await getFakeCaptcha({
- phone,
- });
- if (!result) {
- return;
- }
- message.success('获取验证码成功!验证码为:1234');
- }}
- />
- </>
- )}
- <div
- style={{
- marginBottom: 24,
- color: '#b3c7f9',
- }}
- >
- <ProFormCheckbox>
- <a
- style={{
- float: 'right',
- color: '#fff',
- fontSize: 14,
- }}>
- <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
- </a>
- </ProFormCheckbox>
- <a
+ <div style={{ textAlign: 'center', marginBottom: 24 }}>
+ <img
+ src="/logo.svg"
+ alt="ThunderHub"
style={{
- float: 'right',
- color: '#6cf',
+ width: 54,
+ height: 48,
+ marginBottom: 8,
+ filter: 'drop-shadow(0 0 8px #6cf8)',
+ }}
+ />
+ <div
+ style={{
+ color: '#fff',
+ fontWeight: 700,
+ fontSize: 26,
+ letterSpacing: 2,
+ marginBottom: 4,
+ fontFamily: 'Montserrat, sans-serif',
}}
>
- <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
- </a>
+ ThunderHub
+ </div>
+ <div style={{ color: '#b3c7f9', fontSize: 14, letterSpacing: 1 }}>
+ 探索你的专属星球,畅享PT世界
+ <span style={{ color: '#6cf', marginLeft: 8, fontWeight: 500, fontSize: 12 }}>
+ JRX MSY ZYT HXQ LJB
+ </span>
+ </div>
</div>
- </LoginForm>
+ <LoginForm
+ contentStyle={{
+ background: 'transparent',
+ boxShadow: 'none',
+ padding: 0,
+ color: '#fff',
+ width: '100%',
+ overflow: 'hidden',
+ }}
+ initialValues={{
+ autoLogin: true,
+ }}
+ onFinish={async (values) => {
+ if (type === 'account') {
+ await handleSubmit(values as API.LoginParams);
+ } else {
+ try {
+ const response = await registerUser(
+ values.username ?? '',
+ values.password ?? '',
+ values.inviteCode ?? ''
+ );
+ if (response.code === 0) {
+ message.success('注册成功!');
+ // 注册成功后自动跳转到登录页或自动登录
+ setType('account');
+ } else {
+ message.error(response.msg || '注册失败,请重试!');
+ clearSessionToken();
+ setUserLoginState({ ...response, type });
+ }
+ } catch (error) {
+ message.error('注册失败,请重试!');
+ }
+ }
+ }}
+ submitter={false}
+ >
+ <Tabs
+ activeKey={type}
+ onChange={setType}
+ centered
+ items={[
+ {
+ key: 'account',
+ label: (
+ <span style={{ color: '#fff', fontWeight: 500, fontSize: 16 }}>
+ {intl.formatMessage({
+ id: 'pages.login.accountLogin.tab',
+ defaultMessage: '账户密码登录',
+ })}
+ </span>
+ ),
+ },
+ {
+ key: 'mobile',
+ label: (
+ <span style={{ color: '#fff', fontWeight: 500, fontSize: 16 }}>
+ {intl.formatMessage({
+ id: 'pages.login.phoneLogin.tab',
+ defaultMessage: '注册',
+ })}
+ </span>
+ ),
+ },
+ ]}
+ style={{ marginBottom: 24 }}
+ indicatorSize={36}
+ />
+
+ {code !== 200 && loginType === 'account' && (
+ <LoginMessage
+ content={intl.formatMessage({
+ id: 'pages.login.accountLogin.errorMessage',
+ defaultMessage: '账户或密码错误',
+ })}
+ />
+ )}
+ {type === 'account' && (
+ <>
+ <ProFormText
+ name="username"
+ fieldProps={{
+ size: 'large',
+ prefix: <UserOutlined style={{ color: '#6cf' }} />,
+ style: {
+ background: 'rgba(255,255,255,0.08)',
+ color: '#fff',
+ borderRadius: 8,
+ },
+ }}
+ placeholder={intl.formatMessage({
+ id: 'pages.login.username.placeholder',
+ })}
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage
+ id="pages.login.username.required"
+ defaultMessage="请输入用户名!"
+ />
+ ),
+ },
+ ]}
+ />
+ <ProFormText.Password
+ name="password"
+ fieldProps={{
+ size: 'large',
+ prefix: <LockOutlined style={{ color: '#6cf' }} />,
+ style: {
+ background: 'rgba(255,255,255,0.08)',
+ color: '#fff',
+ borderRadius: 8,
+ },
+ }}
+ placeholder={intl.formatMessage({
+ id: 'pages.login.password.placeholder',
+ })}
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage
+ id="pages.login.password.required"
+ defaultMessage="请输入密码!"
+ />
+ ),
+ },
+ ]}
+ />
+ <Row>
+ <Col flex={3}>
+ <ProFormText
+ style={{
+ float: 'right',
+ background: 'rgba(255,255,255,0.08)',
+ color: '#fff',
+ borderRadius: 8,
+ }}
+ name="code"
+ placeholder={intl.formatMessage({
+ id: 'pages.login.captcha.placeholder',
+ defaultMessage: '请输入验证',
+ })}
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage
+ id="pages.searchTable.updateForm.ruleName.nameRules"
+ defaultMessage="请输入验证啊"
+ />
+ ),
+ },
+ ]}
+ />
+ </Col>
+ <Col flex={2}>
+ <Image
+ src={captchaCode}
+ alt="验证码"
+ style={{
+ display: 'inline-block',
+ verticalAlign: 'top',
+ cursor: 'pointer',
+ paddingLeft: '10px',
+ width: '90px',
+ borderRadius: 8,
+ boxShadow: '0 0 8px #6cf8',
+ background: '#fff',
+ }}
+ preview={false}
+ onClick={() => getCaptchaCode()}
+ />
+ </Col>
+ </Row>
+ </>
+ )}
+
+ {code !== 200 && loginType === 'mobile' && (
+ <LoginMessage content="验证码错误" />
+ )}
+ {type === 'mobile' && (
+ <>
+ <ProFormText
+ fieldProps={{
+ size: 'large',
+ prefix: <LockOutlined style={{ color: '#6cf' }} />, // 换成钥匙图标
+ style: {
+ background: 'rgba(255,255,255,0.08)',
+ color: '#fff',
+ borderRadius: 8,
+ },
+ }}
+ name="inviteCode"
+ placeholder={intl.formatMessage({
+ id: 'pages.login.inviteCode.placeholder',
+ defaultMessage: '请输入邀请码',
+ })}
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage
+ id="pages.login.inviteCode.required"
+ defaultMessage="请输入邀请码!"
+ />
+ ),
+ },
+ ]}
+ />
+ <ProFormText
+ fieldProps={{
+ size: 'large',
+ prefix: <UserOutlined style={{ color: '#6cf' }} />,
+ style: {
+ background: 'rgba(255,255,255,0.08)',
+ color: '#fff',
+ borderRadius: 8,
+ },
+ }}
+ name="username"
+ placeholder={intl.formatMessage({
+ id: 'pages.login.username.placeholder',
+ defaultMessage: '请输入用户名',
+ })}
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage
+ id="pages.login.username.required"
+ defaultMessage="请输入用户名!"
+ />
+ ),
+ },
+ ]}
+ />
+ <ProFormText.Password
+ fieldProps={{
+ size: 'large',
+ prefix: <LockOutlined style={{ color: '#6cf' }} />,
+ style: {
+ background: 'rgba(255,255,255,0.08)',
+ color: '#fff',
+ borderRadius: 8,
+ },
+ }}
+ name="password"
+ placeholder={intl.formatMessage({
+ id: 'pages.login.password.placeholder',
+ defaultMessage: '请输入密码',
+ })}
+ rules={[
+ {
+ required: true,
+ message: (
+ <FormattedMessage
+ id="pages.login.password.required"
+ defaultMessage="请输入密码!"
+ />
+ ),
+ },
+ ]}
+ />
+ </>
+ )}
+ {type === 'account' && (
+ <div
+ style={{
+ marginBottom: 24,
+ color: '#b3c7f9',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ fontSize: 13,
+ }}
+ >
+ <ProFormCheckbox>
+ <span style={{ color: '#fff' }}>
+ <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
+ </span>
+ </ProFormCheckbox>
+ <a
+ style={{
+ color: '#6cf',
+ }}
+ >
+ <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
+ </a>
+ </div>
+ )}
+ {/* 登录/注册按钮 */}
+ <div style={{ marginTop: 24, display: 'flex', gap: 16 }}>
+ {type === 'account' && (
+ <button
+ type="submit"
+ style={{
+ width: '100%',
+ background: 'linear-gradient(90deg, #6cf 0%, #3E71FF 100%)',
+ color: '#fff',
+ border: 'none',
+ borderRadius: 8,
+ padding: '12px 0',
+ fontSize: 16,
+ fontWeight: 600,
+ cursor: 'pointer',
+ boxShadow: '0 2px 8px #6cf4',
+ letterSpacing: 2,
+ }}
+ onClick={() => {
+ // 触发表单提交,登录
+ document.querySelector('form')?.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
+ }}
+ >
+ 登录
+ </button>
+ )}
+ {type === 'mobile' && (
+ <button
+ type="button"
+ style={{
+ width: '100%',
+ background: 'linear-gradient(90deg, #6cf 0%, #3E71FF 100%)',
+ color: '#fff',
+ border: 'none',
+ borderRadius: 8,
+ padding: '12px 0',
+ fontSize: 16,
+ fontWeight: 600,
+ cursor: 'pointer',
+ boxShadow: '0 2px 8px #6cf4',
+ letterSpacing: 2,
+ }}
+ onClick={async () => {
+ // 触发表单校验并注册
+ const form = document.querySelector('form');
+ if (form) {
+ form.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
+ }
+ }}
+ >
+ 注册
+ </button>
+ )}
+ </div>
+ </LoginForm>
+ </div>
</div>
<Footer />
</div>
diff --git a/src/services/ant-design-pro/typings.d.ts b/src/services/ant-design-pro/typings.d.ts
index 4ee1264..18b398d 100644
--- a/src/services/ant-design-pro/typings.d.ts
+++ b/src/services/ant-design-pro/typings.d.ts
@@ -41,6 +41,7 @@
uuid?: string;
autoLogin?: boolean;
type?: string;
+ inviteCode?: string;
};
type LoginResult = {
diff --git a/src/services/bt/index.tsx b/src/services/bt/index.tsx
index 5d9a25c..4511b8a 100644
--- a/src/services/bt/index.tsx
+++ b/src/services/bt/index.tsx
@@ -19,6 +19,12 @@
});
}
+export async function getMyTorrentList() {
+ return request('/api/torrent/myList', {
+ method: 'POST',
+ });
+}
+
export async function getTorrentInfo(params: {
id: string | number;
}) {
@@ -261,4 +267,22 @@
method: 'POST',
data: params,
});
+}
+
+export async function generatePassKey() {
+ return request('/api/torrent/generatePassKey', {
+ method: 'GET',
+ });
+}
+
+// 用户注册
+export async function registerUser(userName: string, password: string, passKey: string) {
+ return request('/api/api/register', {
+ method: 'POST',
+ data: {
+ userName,
+ password,
+ passKey,
+ },
+ });
}
\ No newline at end of file