chore: stop tracking ignored local files

This commit is contained in:
yoyuzh
2026-03-18 19:55:43 +08:00
parent 81cbb68b74
commit 96079b7e5b
12 changed files with 0 additions and 5889 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
backend/.DS_Store vendored

Binary file not shown.

View File

@@ -1 +0,0 @@
^C

File diff suppressed because it is too large Load Diff

BIN
backend/src/.DS_Store vendored

Binary file not shown.

View File

@@ -1,18 +0,0 @@
# 开发测试账号
以下账号会在后端以 `dev` profile 启动时自动初始化。
## 门户账号
| 门户用户名 | 门户密码 | 教务学号 | 教务密码 | 查询学期 | 网盘示例文件 |
| --- | --- | --- | --- | --- | --- |
| `portal-demo` | `portal123456` | `2023123456` | `portal123456` | `2025-spring` | `迎新资料.txt``课程规划.md``campus-shot.png` |
| `portal-study` | `study123456` | `2022456789` | `study123456` | `2024-fall` | `实验数据.csv``论文草稿.md``data-chart.png` |
| `portal-design` | `design123456` | `2021789012` | `design123456` | `2024-spring` | `素材清单.txt``作品说明.md``ui-mockup.png` |
## 使用说明
- 先用上表中的“门户用户名 / 门户密码”登录站点。
- 登录后进入网盘页,每个用户都会看到自己的 `下载 / 文档 / 图片` 目录,以及各自不同的样例文件。
- 进入教务页后,填入对应的“教务学号 / 教务密码 / 查询学期”即可看到该用户对应的 mock 教务数据。
- 当前开发环境的教务密码字段仅用于前端占位,后端主要依据登录态、学号和学期返回该用户的 mock 数据。为避免混淆,直接填表中的教务密码即可。

View File

@@ -1,261 +0,0 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { motion, AnimatePresence } from 'motion/react';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/src/components/ui/card';
import { Button } from '@/src/components/ui/button';
import { Input } from '@/src/components/ui/input';
import { LogIn, User, Lock, UserPlus, Mail, ArrowLeft } from 'lucide-react';
import { cn } from '@/src/lib/utils';
export default function Login() {
const navigate = useNavigate();
const [isLogin, setIsLogin] = useState(true);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
// Simulate auth
setTimeout(() => {
setLoading(false);
if (isLogin) {
navigate('/overview');
} else {
setIsLogin(true); // Switch back to login after "registering"
}
}, 1000);
};
return (
<div className="min-h-screen flex items-center justify-center bg-[#07101D] relative overflow-hidden">
{/* Background Glow */}
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-[#336EFF] rounded-full mix-blend-screen filter blur-[128px] opacity-20 animate-pulse" />
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-purple-600 rounded-full mix-blend-screen filter blur-[128px] opacity-20" />
<div className="container mx-auto px-4 relative z-10 flex items-center w-full min-h-[600px]">
{/* Left Side: Brand Info (Only visible in Login mode) */}
<AnimatePresence>
{isLogin && (
<motion.div
key="brand-info"
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -50 }}
transition={{ duration: 0.5, ease: 'easeOut' }}
className="absolute left-4 lg:left-8 xl:left-12 w-1/2 max-w-lg hidden lg:flex flex-col space-y-6"
>
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full glass-panel border-white/10 w-fit">
<span className="w-2 h-2 rounded-full bg-[#336EFF] animate-pulse" />
<span className="text-sm text-slate-300 font-medium tracking-wide uppercase">Access Portal</span>
</div>
<div className="space-y-2">
<h2 className="text-xl text-[#336EFF] font-bold tracking-widest uppercase">YOYUZH.XYZ</h2>
<h1 className="text-5xl md:text-6xl font-bold text-white leading-tight">
<br />
</h1>
</div>
<p className="text-lg text-slate-400 leading-relaxed">
YOYUZH
</p>
</motion.div>
)}
</AnimatePresence>
{/* Form Container */}
<motion.div
layout
transition={{ type: 'spring', stiffness: 200, damping: 25 }}
className={cn(
"w-full max-w-md z-10",
isLogin ? "ml-auto lg:mr-8 xl:mr-12" : "mx-auto"
)}
>
<Card className="border-white/10 backdrop-blur-2xl bg-white/5 shadow-2xl overflow-hidden">
<AnimatePresence mode="wait">
{isLogin ? (
<motion.div
key="login-form"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3 }}
>
<CardHeader className="space-y-1 pb-8">
<CardTitle className="text-2xl font-bold text-white flex items-center gap-2">
<LogIn className="w-6 h-6 text-[#336EFF]" />
</CardTitle>
<CardDescription className="text-slate-400">
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-300 ml-1"></label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500" />
<Input
type="text"
placeholder="账号 / 用户名 / 学号"
className="pl-10 bg-black/20 border-white/10 focus-visible:ring-[#336EFF]"
required
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-300 ml-1"></label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500" />
<Input
type="password"
placeholder="••••••••"
className="pl-10 bg-black/20 border-white/10 focus-visible:ring-[#336EFF]"
required
/>
</div>
</div>
</div>
{error && (
<div className="p-3 rounded-xl bg-red-500/10 border border-red-500/20 text-red-400 text-sm">
{error}
</div>
)}
<div className="space-y-4">
<Button
type="submit"
className="w-full h-12 text-base font-semibold"
disabled={loading}
>
{loading ? (
<span className="flex items-center gap-2">
<span className="w-4 h-4 border-2 border-white/20 border-t-white rounded-full animate-spin" />
...
</span>
) : (
'进入系统'
)}
</Button>
<div className="text-center">
<button
type="button"
onClick={() => setIsLogin(false)}
className="text-sm text-slate-400 hover:text-[#336EFF] transition-colors"
>
</button>
</div>
</div>
</form>
</CardContent>
</motion.div>
) : (
<motion.div
key="register-form"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3 }}
>
<CardHeader className="space-y-1 pb-8">
<div className="flex items-center justify-between">
<CardTitle className="text-2xl font-bold text-white flex items-center gap-2">
<UserPlus className="w-6 h-6 text-[#336EFF]" />
</CardTitle>
<button
onClick={() => setIsLogin(true)}
className="p-2 rounded-full hover:bg-white/5 text-slate-400 transition-colors"
>
<ArrowLeft className="w-5 h-5" />
</button>
</div>
<CardDescription className="text-slate-400">
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-300 ml-1"></label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500" />
<Input
type="text"
placeholder="设置您的用户名"
className="pl-10 bg-black/20 border-white/10 focus-visible:ring-[#336EFF]"
required
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-300 ml-1"></label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500" />
<Input
type="email"
placeholder="your@email.com"
className="pl-10 bg-black/20 border-white/10 focus-visible:ring-[#336EFF]"
required
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-300 ml-1"></label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-500" />
<Input
type="password"
placeholder="设置您的密码"
className="pl-10 bg-black/20 border-white/10 focus-visible:ring-[#336EFF]"
required
/>
</div>
</div>
</div>
<div className="space-y-4">
<Button
type="submit"
className="w-full h-12 text-base font-semibold"
disabled={loading}
>
{loading ? (
<span className="flex items-center gap-2">
<span className="w-4 h-4 border-2 border-white/20 border-t-white rounded-full animate-spin" />
...
</span>
) : (
'创建账号'
)}
</Button>
<div className="text-center">
<button
type="button"
onClick={() => setIsLogin(true)}
className="text-sm text-slate-400 hover:text-[#336EFF] transition-colors"
>
</button>
</div>
</div>
</form>
</CardContent>
</motion.div>
)}
</AnimatePresence>
</Card>
</motion.div>
</div>
</div>
);
}

View File

@@ -1,54 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>html+css实现漂亮的透明动态漂浮登录页面</title>
</head>
<body>
<section>
<!-- 背景颜色 -->
<div class="color"></div>
<div class="color"></div>
<div class="color"></div>
<div class="box">
<!-- 背景圆 -->
<div class="circle" style="--x:0"></div>
<div class="circle" style="--x:1"></div>
<div class="circle" style="--x:2"></div>
<div class="circle" style="--x:3"></div>
<div class="circle" style="--x:4"></div>
<!-- 登录框 -->
<div class="container">
<div class="form">
<h2>登录</h2>
<form>
<div class="inputBox">
<input type="text" placeholder="姓名">
</div>
<div class="inputBox">
<input type="password" placeholder="密码">
</div>
<div class="inputBox">
<input type="submit" value="登录">
</div>
<p class="forget">忘记密码?<a href="#">
点击这里
</a></p>
<p class="forget">没有账户?<a href="#">
注册
</a></p>
</form>
</div>
</div>
</div>
</section>
</body>
</html>

View File

@@ -1,236 +0,0 @@
/* 清除浏览器默认边距,
使边框和内边距的值包含在元素的width和height内 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 使用flex布局让内容垂直和水平居中 */
section {
/* 相对定位 */
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
/* linear-gradient() 函数用于创建一个表示两种或多种颜色线性渐变的图片 */
background: linear-gradient(to bottom, #f1f4f9, #dff1ff);
}
/* 背景颜色 */
section .color {
/* 绝对定位 */
position: absolute;
/* 使用filter(滤镜) 属性,给图像设置高斯模糊*/
filter: blur(200px);
}
/* :nth-child(n) 选择器匹配父元素中的第 n 个子元素 */
section .color:nth-child(1) {
top: -350px;
width: 600px;
height: 600px;
background: #ff359b;
}
section .color:nth-child(2) {
bottom: -150px;
left: 100px;
width: 500px;
height: 500px;
background: #fffd87;
}
section .color:nth-child(3) {
bottom: 50px;
right: 100px;
width: 500px;
height: 500px;
background: #00d2ff;
}
.box {
position: relative;
}
/* 背景圆样式 */
.box .circle {
position: absolute;
background: rgba(255, 255, 255, 0.1);
/* backdrop-filter属性为一个元素后面区域添加模糊效果 */
backdrop-filter: blur(5px);
box-shadow: 0 25px 45px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.5);
border-right: 1px solid rgba(255, 255, 255, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 50%;
/* 使用filter(滤镜) 属性,改变颜色。
hue-rotate(deg) 给图像应用色相旋转
calc() 函数用于动态计算长度值
var() 函数调用自定义的CSS属性值x*/
filter: hue-rotate(calc(var(--x) * 70deg));
/* 调用动画animate需要10s完成动画
linear表示动画从头到尾的速度是相同的
infinite指定动画应该循环播放无限次*/
animation: animate 10s linear infinite;
/* 动态计算动画延迟几秒播放 */
animation-delay: calc(var(--x) * -1s);
}
/* 背景圆动画 */
@keyframes animate {
0%, 100%, {
transform: translateY(-50px);
}
50% {
transform: translateY(50px);
}
}
.box .circle:nth-child(1) {
top: -50px;
right: -60px;
width: 100px;
height: 100px;
}
.box .circle:nth-child(2) {
top: 150px;
left: -100px;
width: 120px;
height: 120px;
z-index: 2;
}
.box .circle:nth-child(3) {
bottom: 50px;
right: -60px;
width: 80px;
height: 80px;
z-index: 2;
}
.box .circle:nth-child(4) {
bottom: -80px;
left: 100px;
width: 60px;
height: 60px;
}
.box .circle:nth-child(5) {
top: -80px;
left: 140px;
width: 60px;
height: 60px;
}
/* 登录框样式 */
.container {
position: relative;
width: 400px;
min-height: 400px;
background: rgba(255, 255, 255, 0.1);
display: flex;
justify-content: center;
align-items: center;
backdrop-filter: blur(5px);
box-shadow: 0 25px 45px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.5);
border-right: 1px solid rgba(255, 255, 255, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.form {
position: relative;
width: 100%;
height: 100%;
padding: 50px;
}
/* 登录标题样式 */
.form h2 {
position: relative;
color: #fff;
font-size: 24px;
font-weight: 600;
letter-spacing: 5px;
margin-bottom: 30px;
cursor: pointer;
}
/* 登录标题的下划线样式 */
.form h2::before {
content: "";
position: absolute;
left: 0;
bottom: -10px;
width: 0px;
height: 3px;
background: #fff;
transition: 0.5s;
}
.form h2:hover:before {
width: 53px;
}
.form .inputBox {
width: 100%;
margin-top: 20px;
}
/* 输入框样式 */
.form .inputBox input {
width: 100%;
padding: 10px 20px;
background: rgba(255, 255, 255, 0.2);
outline: none;
border: none;
border-radius: 30px;
border: 1px solid rgba(255, 255, 255, 0.5);
border-right: 1px solid rgba(255, 255, 255, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
font-size: 16px;
letter-spacing: 1px;
color: #fff;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
}
.form .inputBox input::placeholder {
color: #fff;
}
/* 登录按钮样式 */
.form .inputBox input[type="submit"] {
background: #fff;
color: #666;
max-width: 100px;
margin-bottom: 20px;
font-weight: 600;
cursor: pointer;
}
.forget {
margin-top: 6px;
color: #fff;
letter-spacing: 1px;
}
.forget a {
color: #fff;
font-weight: 600;
text-decoration: none;
}

View File

@@ -1,668 +0,0 @@
# Personal Portal 前端页面需求文档
## 1. 文档说明
- 设计来源:`草图/pencil-new.pen`
- 目标:将设计稿整理为可执行的前端页面需求,供 UI 开发、接口联调、测试验收使用
- 适用范围PC Web 端个人门户站点
- 设计基准尺寸:`1440 x 1024`
- 说明:文档中未在设计稿中直接标注但对实现必需的内容,已按常规门户产品逻辑做合理补充,并明确写为“建议”或“推断”
## 2. 产品定位
这是一个统一个人门户站点,用于集中承载以下能力:
- 账号登录
- 首页总览
- 网盘文件管理
- 教务数据查询
- 游戏入口聚合
站点风格偏深色、毛玻璃、卡片式信息展示,强调“统一入口”和“个人数据聚合”。
## 3. 站点信息架构
建议前端按以下一级路由组织:
| 页面 | 建议路由 | 页面目标 |
| --- | --- | --- |
| 登录页 | `/login` | 完成身份认证并进入门户 |
| 总览页 | `/overview` | 汇总展示用户、文件、课程、成绩等核心信息 |
| 网盘页 | `/files` | 浏览、筛选、查看和上传个人文件 |
| 教务页 | `/school` | 查询课表、成绩并展示学期数据 |
| 游戏页 | `/games` | 提供小游戏入口和分类切换 |
## 4. 全局设计与交互要求
### 4.1 全局视觉规范
- 页面主背景为深蓝黑色系,建议主背景色接近 `#07101D`
- 卡片、导航、输入框采用半透明深色背景叠加模糊效果,形成毛玻璃风格
- 主强调色为亮蓝色,建议主色接近 `#336EFF`
- 文字颜色分三级:
- 一级文字:高亮白色,用于标题、主按钮文字、关键数据
- 二级文字:浅蓝灰色,用于说明文案、次要导航
- 三级文字:更浅的透明文字,用于分隔说明和弱提示
- 整站圆角统一偏大,按钮、卡片、胶囊标签均使用较高圆角
### 4.2 全局布局要求
- 所有页面采用顶部导航 + 主内容区结构
- 顶部导航固定在页面顶部,默认包含站点品牌和一级导航入口
- 主内容区使用卡片化分区布局,不同页面根据业务类型使用多列或多卡片排布
- 所有卡片之间需要保留清晰的间距层级,避免信息拥挤
### 4.3 全局导航要求
- 顶部左侧固定展示站点品牌:
- 主标题:`YOYUZH.XYZ`
- 副标题:`Personal Portal`
- 顶部右侧固定展示一级导航:
- 总览
- 网盘
- 教务
- 游戏
- 当前所在页面的导航项为激活态:
- 高亮蓝色背景
- 白色文字
- 带轻微发光或阴影
- 非当前页面为普通态:
- 半透明深色背景
- 浅色文字
- 点击一级导航后执行页面切换,建议保留当前登录态
### 4.4 全局交互要求
- 所有按钮、胶囊标签、切换项、列表行都应具备 hover、focus、active 状态
- 键盘 Tab 顺序必须与视觉顺序一致
- 图标型按钮若后续补充图标,必须提供 `aria-label`
- 卡片内部若有按钮组,主操作需突出显示,次操作保持弱化视觉
- 页面切换过程中应有最基本的加载保护,避免空白闪烁
### 4.5 全局状态要求
每个业务页面至少支持以下状态:
- 加载中:显示骨架屏或占位态
- 加载成功:展示设计稿对应内容
- 空状态:当列表、课表、成绩无数据时显示空态说明
- 错误状态:接口失败时显示错误提示与重试入口
- 未登录状态:除登录页外,若无有效登录态应跳回登录页
### 4.6 响应式要求
设计稿是桌面端稿件,前端实现时建议补齐以下断点:
- `>= 1280px`:按设计稿布局展示
- `768px - 1279px`:多列卡片可改为 2 列,右侧预览或抽屉可下移
- `< 768px`:顶部导航压缩为可滚动标签或抽屉菜单;卡片全部改为单列堆叠
移动端适配要求:
- 不允许出现横向滚动条
- 卡片内文字不可溢出遮挡
- 按钮点击热区不小于 `44 x 44px`
- 文件表格在小屏下可切换为列表卡片模式
### 4.7 可访问性要求
- 文本与背景对比度满足基本可读性
- 表单字段必须带标签
- 所有可点击控件需支持键盘操作
- 当前导航项、当前分段切换项需要有明确的选中态
- 动画需兼容 `prefers-reduced-motion`
## 5. 登录页需求
### 5.1 页面目标
为用户提供统一入口,完成账号认证并进入门户首页。
### 5.2 页面结构
页面分为左右两区:
- 左侧品牌介绍区
- 右侧登录表单区
### 5.3 左侧品牌介绍区
应包含以下元素:
- 品牌标题:`YOYUZH.XYZ`
- 品牌副标题:`Personal Portal`
- 胶囊标签:`Access Portal`
- 主标题:`个人网站统一入口`
- 一段站点简介文案
实现要求:
- 左侧内容垂直排列,整体偏页面左上区域
- 背景需要有大面积柔和发光装饰,营造氛围感
- 简介文案允许后续替换为真实站点介绍
### 5.4 右侧登录表单区
表单卡片应包含以下字段和元素:
- 表单标题:`登录`
- 用户名输入框
- 密码输入框
- 主按钮:`进入系统`
字段要求:
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| 用户名 | 文本输入 | 是 | 支持账号、用户名或学号 |
| 密码 | 密码输入 | 是 | 采用掩码显示 |
交互要求:
- 用户名、密码均为空时点击按钮,给出必填校验
- 支持按 Enter 提交表单
- 登录请求发起后按钮进入 loading 状态并禁用重复点击
- 登录成功后跳转至 `/overview`
- 登录失败时在表单区域显示错误提示
建议补充能力:
- 记住登录状态
- 登录态过期自动跳转登录页
- 允许后续增加验证码或二次验证
### 5.5 登录页验收标准
- 页面首屏能清晰表达“统一入口”的产品定位
- 输入框、按钮在视觉上与整体毛玻璃风格统一
- 表单校验、提交、错误反馈完整
- 登录成功跳转逻辑正常
## 6. 总览页需求
### 6.1 页面目标
集中展示用户最常用的跨模块摘要信息,让用户在一个页面看到文件、课程、成绩、账号等核心状态。
### 6.2 页面结构
总览页包含以下模块:
- 顶部导航栏
- 欢迎 Hero 区
- 4 张数据指标卡片
- 最近文件卡片
- 今日 / 本周课程卡片
- 快捷操作卡片
- 存储空间卡片
- 账号信息卡片
### 6.3 Hero 欢迎区
需展示以下内容:
- 欢迎语:如 `欢迎回来tester5595`
- 当前时间与问候语:如 `现在时间 20:17 · 晚上好`
- 简短页面说明
实现要求:
- 欢迎语作为页面最强视觉标题
- 当前时间应来自前端运行时或服务端返回,不应硬编码
- 问候语应根据时间段变化,建议分为凌晨、早上、下午、晚上
### 6.4 指标卡片区
共 4 个指标模块:
| 模块 | 示例值 | 说明 |
| --- | --- | --- |
| 网盘文件总数 | `128` | 展示文件总量及分类说明 |
| 最近 7 天上传 | `6` | 展示近 7 日上传数量和最近更新时间 |
| 本周课程 | `18` | 展示本周课程总量及今日课节数 |
| 已录入成绩 | `42` | 展示成绩总数及最近学期 |
实现要求:
- 数字需突出显示
- 标题、数字、补充描述分层明显
- 支持接口数据为空时展示 `0` 和空说明
### 6.5 最近文件模块
需展示:
- 模块标题:`最近文件`
- 最近文件列表,建议至少显示 3 条
- 每条信息至少包含:
- 文件名
- 文件大小
- 最近更新时间
交互要求:
- 点击文件项可跳转 `/files`,并定位到该文件
- 文件名超长时需截断并保留 tooltip 或完整展示方案
### 6.6 课程模块
模块标题:`今日 / 本周课程`
模块内包含:
- 分段切换:`今日` / `本周`
- 课程列表,建议显示:
- 时间段
- 课程名称
- 教室或地点
交互要求:
- 默认选中 `今日`
- 点击 `本周` 后刷新为本周课表摘要
- 切换过程不可造成卡片高度明显抖动
### 6.7 快捷操作模块
需提供 4 个快捷入口:
- 上传文件
- 新建文件夹
- 进入网盘
- 查询成绩
交互要求:
- 上传文件:打开上传弹窗或跳转文件页并打开上传面板
- 新建文件夹:打开新建文件夹表单
- 进入网盘:跳转 `/files`
- 查询成绩:跳转 `/school` 并聚焦成绩查询区域
### 6.8 存储空间模块
需展示:
- 模块标题:`存储空间`
- 已使用容量 / 总容量,如 `12.6 GB / 50 GB`
- 使用进度条
- 百分比数值,如 `25%`
实现要求:
- 进度条宽度与百分比联动
- 数值格式统一保留 1 位小数或按产品约定显示
### 6.9 账号信息模块
需展示:
- 模块标题:`账号信息`
- 用户名
- 邮箱
建议补充:
- 后续预留头像、绑定状态、退出登录入口
### 6.10 总览页验收标准
- 各卡片数据层级清晰,首屏即可理解
- 快捷操作能通往对应模块
- 时间、课表、文件、容量信息均支持动态数据渲染
- 页面在 1280px 以上布局稳定,在平板和移动端能正常堆叠
## 7. 网盘页需求
### 7.1 页面目标
提供个人网盘文件浏览、路径导航、详情预览和基础操作入口。
### 7.2 页面结构
网盘页采用三栏布局:
- 左侧目录导航栏
- 中间文件内容区
- 右侧详细信息预览栏
### 7.3 左侧目录导航栏
建议包括两组内容:
- 快速访问
- 桌面
- 下载
- 文档
- 图片
- 网盘目录
- 我的文件
- 课程资料
- 项目归档
- 收藏夹
交互要求:
- 点击目录项刷新中间内容区
- 当前目录项应有激活态
- 若目录层级增加,建议支持树形展开
### 7.4 中间文件内容区
应包含以下元素:
- 路径面包屑栏
- 视图切换控件:`列表` / `分栏` / `预览`
- 文件列表表头
- 文件列表内容
- 底部操作按钮:`新建``上传`
面包屑要求:
- 当前示例路径为:`网盘 > 我的文件 > 课程资料 > 软件工程`
- 每一级路径应可点击返回
文件列表字段要求:
| 列名 | 说明 |
| --- | --- |
| 名称 | 文件或文件夹名称 |
| 修改日期 | 最近更新时间 |
| 类型 | 文件类型或目录类型 |
| 大小 | 文件大小;文件夹可显示 `—` |
列表交互要求:
- 支持单行选中
- 支持文件夹进入下一级目录
- 支持当前选中项同步到右侧详情栏
- 当前选中行需有明显高亮
- 文件名过长时截断处理
建议补充能力:
- 双击打开文件
- 右键菜单
- 多选
- 排序
- 搜索
### 7.5 右侧详细信息预览栏
需展示当前选中文件的详细信息:
- 当前选择的文件名
- 文件路径
- 文件大小
- 上传时间
建议扩展字段:
- 文件类型图标
- 最近编辑人
- 下载次数
- 预览缩略图
### 7.6 网盘页操作要求
主操作:
- 上传
- 新建
交互说明:
- 上传:支持本地文件选择,建议支持拖拽上传
- 新建:默认创建文件夹,也可为后续扩展新建文档保留能力
- 操作成功后,文件列表与统计信息应刷新
### 7.7 网盘页状态要求
- 空目录:显示空状态文案和“上传文件”入口
- 加载中:文件表格骨架屏
- 上传中:显示上传进度
- 上传失败:显示失败原因并支持重试
### 7.8 网盘页验收标准
- 三栏布局信息职责明确
- 面包屑、目录树、列表选中、右侧详情联动正常
- 上传与新建入口位置明显,交互闭环完整
- 小屏情况下可退化为“目录 / 列表 / 详情”分步展示
## 8. 教务页需求
### 8.1 页面目标
提供统一教务查询入口,用于查看课表、成绩及历史学期数据摘要。
### 8.2 页面结构
页面包含以下主要区域:
- 教务查询卡片
- 数据摘要卡片
- 课表抽屉卡片
- 成绩热力图区域
### 8.3 教务查询卡片
应包含以下字段:
| 字段 | 示例 | 说明 |
| --- | --- | --- |
| 学号 | `2023123456` | 查询账号 |
| 密码 | `••••••••` | 教务系统密码 |
| 学期 | `2025 秋` | 当前选择学期 |
按钮包括:
- 查询课表
- 查询成绩
交互要求:
- 学号、密码、学期为必填项
- 点击 `查询课表` 后刷新课表抽屉和摘要区对应信息
- 点击 `查询成绩` 后刷新成绩区域和摘要区对应信息
- 建议保留上次成功查询的账号与学期缓存
### 8.4 数据摘要卡片
需展示缓存或最近一次查询结果摘要:
- 当前缓存账号
- 已保存课表学期
- 已保存成绩学期列表
实现要求:
- 未查询时显示空态说明
- 查询成功后即时更新
### 8.5 课表抽屉卡片
模块标题:`课表抽屉`
展示形式:
- 按星期分组展示课程
- 每条课程至少包含:
- 上课时间
- 课程名称
- 教室
交互要求:
- 右侧有明显的抽屉把手视觉提示
- 建议支持展开 / 收起或横向滑出效果
- 抽屉在平板和移动端可切为底部弹层
### 8.6 成绩视图切换
查询卡片内存在分段切换:
- 课表
- 成绩
要求:
- 默认激活 `课表`
- 切换到 `成绩` 时,页面焦点应转向成绩区域
- 当前激活项需具备明显视觉反馈
### 8.7 成绩热力图区域
模块标题:`成绩热力图`
需展示多个学期的成绩分布示意,设计稿中包含:
- `2024 秋`
- `2025 春`
- `2025 秋`
实现要求:
- 每个学期形成独立列
- 每列包含多个课程成绩条
- 颜色深浅或透明度表示成绩高低
- 条形右侧或内部显示分数
建议补充能力:
- 悬浮提示课程名、学分、绩点
- 学期筛选
- 按成绩排序
### 8.8 教务页状态要求
- 未查询:显示引导说明
- 查询中:按钮 loading结果区骨架屏
- 查询成功:更新课表、摘要、成绩
- 查询失败:展示错误原因和重试入口
### 8.9 教务页验收标准
- 查询表单、结果摘要、课表和成绩之间联动清晰
- 抽屉与热力图区域在不同状态下布局稳定
- 历史学期成绩能正确映射为视觉强弱差异
## 9. 游戏页需求
### 9.1 页面目标
作为轻量娱乐入口页,承载站内小游戏展示和启动入口。
### 9.2 页面结构
页面包含:
- 顶部导航
- 页面说明 Hero 区
- 分类切换
- 游戏卡片列表
### 9.3 Hero 区
需展示:
- 模块标题:`游戏入口`
- 一段说明文案,表达“保留轻量试玩与静态资源检查入口,维持与整站一致的毛玻璃语言”
### 9.4 分类切换
设计稿中包含:
- 精选
- 全部
交互要求:
- 默认选中 `精选`
- 点击后刷新卡片列表
- 若当前仅有少量游戏,`全部` 可与 `精选` 数据一致
### 9.5 游戏卡片
设计稿中至少包含两个游戏入口:
| 名称 | 描述 | 按钮文案 |
| --- | --- | --- |
| CAT | 简单的小猫升级游戏 | Launch |
| RACE | 赛车休闲小游戏 | Launch |
交互要求:
- 点击 `Launch` 后进入游戏详情页、游戏运行页或新窗口
- 游戏卡片需支持封面图扩展位
- 后续支持增加更多游戏时,卡片布局可扩展
### 9.6 游戏页验收标准
- 卡片入口清晰,用户能快速理解每个游戏内容
- 分类切换有效
- 启动按钮行为明确,不产生死链
## 10. 数据与接口需求建议
### 10.1 登录相关
- 登录接口:提交用户名和密码,返回用户信息与 token
- 获取当前登录用户接口:用于页面初始化恢复登录态
### 10.2 总览相关
- 获取总览统计接口
- 获取最近文件接口
- 获取今日 / 本周课表摘要接口
- 获取账号信息接口
- 获取存储容量接口
### 10.3 网盘相关
- 获取目录树接口
- 获取当前路径文件列表接口
- 获取文件详情接口
- 上传文件接口
- 新建文件夹接口
### 10.4 教务相关
- 查询课表接口
- 查询成绩接口
- 获取历史查询缓存接口
### 10.5 游戏相关
- 获取游戏列表接口
- 获取游戏分类接口
## 11. 前端实现建议
- 建议将顶部导航抽为全局公共组件
- 建议将卡片、按钮、分段切换、信息列表抽为复用组件
- 建议为每个页面单独维护数据请求与状态管理
- 建议所有动态时间、容量、数量、成绩统一做格式化封装
- 建议页面首屏背景光斑与毛玻璃效果使用纯 CSS 或轻量方案实现,避免过重图像资源
## 12. 测试验收清单
- 登录页表单校验、错误提示、提交跳转正常
- 顶部导航在 5 个页面间切换正常,当前态正确
- 总览页卡片数据渲染与快捷跳转正常
- 网盘页目录、路径、列表、详情联动正常
- 教务页查询、切换、热力图展示正常
- 游戏页分类切换和启动行为正常
- 页面在桌面、平板、移动端无明显布局错乱
- 页面无横向滚动,文本无明显遮挡
- 键盘可访问性与 focus 状态正常
## 13. 交付物建议
前端开发阶段建议同步产出以下内容:
- 页面路由结构
- 公共布局组件
- 各页面接口类型定义
- 空状态、错误状态、加载状态组件
- 基础 UI 规范说明文档

File diff suppressed because it is too large Load Diff

View File

@@ -1,182 +0,0 @@
---
# 文件 1Yoyuzh_Portal_需求文档.md
# Yoyuzh Personal Portal 后端需求文档
## 项目概述
该项目为 **yoyuzh.xyz** 的后端系统,提供用户身份认证、个人网盘功能以及重庆大学校园信息接口整合,供前端 Vue3 页面调用。
主要功能:
1. 用户身份认证与管理注册、登录、JWT、密码加密
2. 个人网盘(文件上传/下载/删除/目录管理/分页列表)
3. 重庆大学校园信息接口(课表、成绩)
4. RESTful API 风格,安全、可扩展
---
## 技术选型
| 模块 | 技术/框架 | 说明 |
|------------|---------------------------|--------------------------------|
| 后端框架 | Spring Boot 3.x | Java RESTful 后端快速开发 |
| 数据库 | MySQL / openGauss | 用户、文件、课程信息存储 |
| 安全 | Spring Security + JWT | 身份认证、接口权限控制 |
| 文件存储 | 本地文件系统 / 阿里云 OSS | 网盘文件存储 |
| 构建工具 | Maven | 依赖管理 |
| API 文档 | Swagger / Springdoc | 自动生成接口文档 |
| 日志 | SLF4J + Logback | 日志管理 |
---
## 功能模块
### 1. 用户身份认证
- 注册:`POST /api/auth/register`,加密密码存储,防止重复用户名/邮箱
- 登录:`POST /api/auth/login`,验证密码并生成 JWT
- JWT 验证:拦截需要权限接口,验证 Token有效则注入用户信息
- 获取用户信息:`GET /api/user/profile`
### 2. 个人网盘
- 文件上传:`POST /api/files/upload`,文件流 + 路径
- 文件下载:`GET /api/files/download/{fileId}`
- 文件删除:`DELETE /api/files/{fileId}`
- 文件列表:`GET /api/files/list?path=&page=&size=`
- 目录管理:`POST /api/files/mkdir`
### 3. 重庆大学校园信息接口
- 课表查询:`GET /api/cqu/schedule`,学期/学号参数
- 成绩查询:`GET /api/cqu/grades`,学期/学号参数
- 实现:调用现有 API转换统一 JSON 格式
### 4. 安全与权限
- 网盘接口需登录授权
- 学校信息接口可设置登录访问
- 文件操作需校验用户身份
### 5. 日志与异常
- SLF4J + Logback 日志
- 全局异常处理,统一 JSON 错误返回
- 错误码示例:
- 1000未知错误
- 1001用户未登录
- 1002权限不足
- 1003文件不存在
---
## 数据库设计
### 用户表 (user)
```
id PK
username VARCHAR
email VARCHAR
password_hash VARCHAR
created_at DATETIME
```
### 文件表 (file)
```
id PK
user_id FK -> user.id
filename VARCHAR
path VARCHAR
size BIGINT
created_at DATETIME
```
### 课程表 (course)
```
id PK
user_id FK -> user.id
course_name VARCHAR
teacher VARCHAR
classroom VARCHAR
day_of_week INT
start_time INT
end_time INT
```
### 成绩表 (grade)
```
id PK
user_id FK -> user.id
course_name VARCHAR
grade FLOAT
semester VARCHAR
```
---
## 项目结构
```
yoyuzh-portal-backend
├─ src/main/java/com/yoyuzh
│ ├─ auth # 用户注册/登录/JWT
│ ├─ files # 网盘管理
│ ├─ cqu # 校园信息 API
│ ├─ common # 全局异常处理/工具类
│ └─ config # Spring Security / JWT / Swagger 配置
├─ src/main/resources
│ ├─ application.yml # 数据库/OSS/JWT配置
│ └─ logback.xml # 日志配置
```
---
# 文件 2Yoyuzh_Portal_实现细则.md
# Yoyuzh Personal Portal 后端实现细则
## 1. 用户认证细则
- 密码使用 BCrypt 加密存储
- JWT 有效期 1 天,可刷新
- 注册接口验证邮箱和用户名唯一性
- 登录接口返回 Token + 用户基础信息
- 所有需要权限接口通过 JWT Filter 校验
- 异常统一抛出JSON 返回
## 2. 网盘实现细则
- 上传文件保存路径:`/storage/{userId}/{目录路径}/文件名`
- 文件大小限制:默认 50MB可在配置文件调整
- 文件列表分页:前端请求 `page``size`
- 删除文件时先校验用户身份,确保只能删除自己上传的文件
- 支持创建多级目录,目录路径在数据库中记录
## 3. 重庆大学接口细则
- 调用现有 API 获取课表和成绩
- 统一 JSON 输出:
```json
{
"code": 0,
"msg": "success",
"data": [...]
}
```
- 若用户未登录或 Token 无效,可返回 1001 错误码
- 前端可通过学期参数过滤数据
## 4. 安全与权限细则
- Spring Security 配置:
- `/api/auth/**` 和 Swagger 接口无需认证
- `/api/files/**` 需要认证
- `/api/cqu/**` 可选认证
- JWT Token 通过 Authorization Header 传递:`Bearer {token}`
## 5. 日志与异常处理
- 所有接口调用记录 info 日志
- 异常统一在 `@ControllerAdvice` 中处理
- 返回 JSON 格式:
```json
{
"code": 1000,
"msg": "error message"
}
```
## 6. 数据库细则
- 用户表:唯一索引 username、email
- 文件表user_id + path + filename 组合唯一
- 课程表、成绩表与用户表关联,保证查询方便
- 建议为文件表、课程表添加创建时间索引,加快分页查询