first commit
This commit is contained in:
2067
dontshushme/app(4).py
Normal file
2067
dontshushme/app(4).py
Normal file
File diff suppressed because it is too large
Load Diff
288
dontshushme/models(1).py
Normal file
288
dontshushme/models(1).py
Normal file
@@ -0,0 +1,288 @@
|
||||
# models.py
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
class User(db.Model):
|
||||
__tablename__ = 'user'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(80), nullable=False)
|
||||
email = db.Column(db.String(120), unique=True, nullable=True)
|
||||
phone = db.Column(db.String(20), unique=True, nullable=True)
|
||||
password = db.Column(db.String(128), nullable=False)
|
||||
role = db.Column(db.String(20), default='student') # student, teacher, admin
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
avatar = db.Column(db.String(200), nullable=True)
|
||||
|
||||
exams_created = db.relationship('Exam', backref='creator', lazy=True)
|
||||
submissions = db.relationship('Submission', backref='user', lazy=True)
|
||||
drafts = db.relationship('Draft', backref='user', lazy=True)
|
||||
posts = db.relationship('Post', backref='author', lazy=True)
|
||||
replies = db.relationship('Reply', backref='author', lazy=True)
|
||||
reactions = db.relationship('Reaction', backref='user', lazy=True)
|
||||
bookmarks = db.relationship('Bookmark', backref='user', lazy=True)
|
||||
notifications = db.relationship('Notification', backref='user', lazy=True)
|
||||
contest_registrations = db.relationship('ContestRegistration', backref='user', lazy=True)
|
||||
teacher_applications = db.relationship('TeacherApplication', backref='user', lazy=True)
|
||||
# exam_bookmarks 关系将由 ExamBookmark 中的 backref 自动创建,此处不再定义
|
||||
|
||||
|
||||
class Exam(db.Model):
|
||||
__tablename__ = 'exam'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String(200), nullable=False)
|
||||
subject = db.Column(db.String(50))
|
||||
duration = db.Column(db.Integer, default=120)
|
||||
total_score = db.Column(db.Integer, default=100)
|
||||
status = db.Column(db.String(20), default='available')
|
||||
creator_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
_questions = db.Column(db.Text, default='[]')
|
||||
|
||||
submissions = db.relationship('Submission', backref='exam', lazy=True, cascade='all, delete-orphan')
|
||||
drafts = db.relationship('Draft', backref='exam', lazy=True, cascade='all, delete-orphan')
|
||||
# bookmarked_by 关系将由 ExamBookmark 中的 backref 自动创建,此处不再定义
|
||||
|
||||
def set_questions(self, questions):
|
||||
self._questions = json.dumps(questions, ensure_ascii=False)
|
||||
|
||||
def get_questions(self):
|
||||
return json.loads(self._questions) if self._questions else []
|
||||
|
||||
|
||||
class Submission(db.Model):
|
||||
__tablename__ = 'submission'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
exam_id = db.Column(db.Integer, db.ForeignKey('exam.id'), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
_answers = db.Column(db.Text, default='{}')
|
||||
_question_scores = db.Column(db.Text, default='{}')
|
||||
score = db.Column(db.Integer, default=0)
|
||||
graded = db.Column(db.Boolean, default=False)
|
||||
graded_by = db.Column(db.String(80), default='')
|
||||
submitted_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
def set_answers(self, answers):
|
||||
self._answers = json.dumps(answers, ensure_ascii=False)
|
||||
|
||||
def get_answers(self):
|
||||
return json.loads(self._answers) if self._answers else {}
|
||||
|
||||
def set_question_scores(self, scores):
|
||||
self._question_scores = json.dumps(scores, ensure_ascii=False)
|
||||
|
||||
def get_question_scores(self):
|
||||
return json.loads(self._question_scores) if self._question_scores else {}
|
||||
|
||||
@property
|
||||
def user_name(self):
|
||||
return self.user.name if self.user else ''
|
||||
|
||||
|
||||
class Draft(db.Model):
|
||||
__tablename__ = 'draft'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
exam_id = db.Column(db.Integer, db.ForeignKey('exam.id'), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
_answers = db.Column(db.Text, default='{}')
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
def set_answers(self, answers):
|
||||
self._answers = json.dumps(answers, ensure_ascii=False)
|
||||
|
||||
def get_answers(self):
|
||||
return json.loads(self._answers) if self._answers else {}
|
||||
|
||||
|
||||
class Contest(db.Model):
|
||||
__tablename__ = 'contest'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(200), nullable=False)
|
||||
organizer = db.Column(db.String(100))
|
||||
description = db.Column(db.Text)
|
||||
start_date = db.Column(db.String(20))
|
||||
end_date = db.Column(db.String(20))
|
||||
status = db.Column(db.String(20), default='upcoming')
|
||||
participants = db.Column(db.Integer, default=0)
|
||||
created_by = db.Column(db.String(50))
|
||||
contact = db.Column(db.String(100))
|
||||
_past_papers = db.Column(db.Text, default='[]')
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
registrations = db.relationship('ContestRegistration', backref='contest', lazy=True)
|
||||
posts = db.relationship('Post', backref='contest', lazy=True)
|
||||
|
||||
def set_past_papers(self, papers):
|
||||
self._past_papers = json.dumps(papers, ensure_ascii=False)
|
||||
|
||||
def get_past_papers(self):
|
||||
return json.loads(self._past_papers) if self._past_papers else []
|
||||
|
||||
|
||||
class ContestRegistration(db.Model):
|
||||
__tablename__ = 'contest_registration'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
contest_id = db.Column(db.Integer, db.ForeignKey('contest.id'), nullable=False)
|
||||
registered_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class ContestMembership(db.Model):
|
||||
__tablename__ = 'contest_membership'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
contest_id = db.Column(db.Integer, db.ForeignKey('contest.id'), nullable=False)
|
||||
role = db.Column(db.String(20), default='member')
|
||||
joined_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class Post(db.Model):
|
||||
__tablename__ = 'post'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String(200), nullable=False)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
contest_id = db.Column(db.Integer, db.ForeignKey('contest.id'), nullable=True)
|
||||
tag = db.Column(db.String(50), default='全部')
|
||||
is_official = db.Column(db.Boolean, default=False)
|
||||
pinned = db.Column(db.Boolean, default=False)
|
||||
likes = db.Column(db.Integer, default=0)
|
||||
replies_count = db.Column(db.Integer, default=0)
|
||||
views = db.Column(db.Integer, default=0)
|
||||
has_poll = db.Column(db.Boolean, default=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
replies = db.relationship('Reply', backref='post', lazy=True, cascade='all, delete-orphan')
|
||||
reactions = db.relationship('Reaction', backref='post', lazy=True, cascade='all, delete-orphan')
|
||||
bookmarks = db.relationship('Bookmark', backref='post', lazy=True, cascade='all, delete-orphan')
|
||||
poll = db.relationship('Poll', backref='post', uselist=False, cascade='all, delete-orphan')
|
||||
|
||||
|
||||
class Reply(db.Model):
|
||||
__tablename__ = 'reply'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
|
||||
author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
reply_to = db.Column(db.String(80), nullable=True)
|
||||
likes = db.Column(db.Integer, default=0)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
reactions = db.relationship('Reaction', backref='reply', lazy=True, cascade='all, delete-orphan')
|
||||
|
||||
|
||||
class Poll(db.Model):
|
||||
__tablename__ = 'poll'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False, unique=True)
|
||||
question = db.Column(db.String(200), nullable=False)
|
||||
multi = db.Column(db.Boolean, default=False)
|
||||
total_votes = db.Column(db.Integer, default=0)
|
||||
_options = db.Column(db.Text, default='[]')
|
||||
_voters = db.Column(db.Text, default='{}')
|
||||
|
||||
def set_options(self, options):
|
||||
self._options = json.dumps(options, ensure_ascii=False)
|
||||
|
||||
def get_options(self):
|
||||
return json.loads(self._options) if self._options else []
|
||||
|
||||
def set_voters(self, voters):
|
||||
self._voters = json.dumps(voters, ensure_ascii=False)
|
||||
|
||||
def get_voters(self):
|
||||
return json.loads(self._voters) if self._voters else {}
|
||||
|
||||
|
||||
class Reaction(db.Model):
|
||||
__tablename__ = 'reaction'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=True)
|
||||
reply_id = db.Column(db.Integer, db.ForeignKey('reply.id'), nullable=True)
|
||||
reaction = db.Column(db.String(20), nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class Bookmark(db.Model):
|
||||
__tablename__ = 'bookmark'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class Notification(db.Model):
|
||||
__tablename__ = 'notification'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
type = db.Column(db.String(50))
|
||||
content = db.Column(db.String(200))
|
||||
from_user = db.Column(db.String(80))
|
||||
post_id = db.Column(db.Integer, nullable=True)
|
||||
read = db.Column(db.Boolean, default=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class EditHistory(db.Model):
|
||||
__tablename__ = 'edit_history'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=True)
|
||||
reply_id = db.Column(db.Integer, db.ForeignKey('reply.id'), nullable=True)
|
||||
editor_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
old_content = db.Column(db.Text)
|
||||
new_content = db.Column(db.Text)
|
||||
edited_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class Report(db.Model):
|
||||
__tablename__ = 'report'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
type = db.Column(db.String(20))
|
||||
target_id = db.Column(db.Integer, nullable=False)
|
||||
reporter_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
reason = db.Column(db.String(100))
|
||||
detail = db.Column(db.Text)
|
||||
status = db.Column(db.String(20), default='pending')
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class TeacherApplication(db.Model):
|
||||
__tablename__ = 'teacher_application'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
name = db.Column(db.String(80), nullable=False)
|
||||
email = db.Column(db.String(120), nullable=False)
|
||||
reason = db.Column(db.Text, nullable=False)
|
||||
status = db.Column(db.String(20), default='pending')
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class Friend(db.Model):
|
||||
__tablename__ = 'friend'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
friend_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
status = db.Column(db.String(20), default='pending')
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
user = db.relationship('User', foreign_keys=[user_id], backref=db.backref('friends_initiated', lazy='dynamic'))
|
||||
friend = db.relationship('User', foreign_keys=[friend_id], backref=db.backref('friends_received', lazy='dynamic'))
|
||||
|
||||
|
||||
class ExamBookmark(db.Model):
|
||||
__tablename__ = 'exam_bookmark'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
exam_id = db.Column(db.Integer, db.ForeignKey('exam.id'), nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
# 使用 backref 自动在 User 和 Exam 上创建 'exam_bookmarks' 和 'bookmarked_by' 属性
|
||||
user = db.relationship('User', backref=db.backref('exam_bookmarks', lazy='dynamic', cascade='all, delete-orphan'))
|
||||
exam = db.relationship('Exam', backref=db.backref('bookmarked_by', lazy='dynamic', cascade='all, delete-orphan'))
|
||||
|
||||
__table_args__ = (db.UniqueConstraint('user_id', 'exam_id', name='unique_exam_bookmark'),)
|
||||
161
dontshushme/profile.html
Normal file
161
dontshushme/profile.html
Normal file
@@ -0,0 +1,161 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}个人中心 - 联考平台{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-6xl mx-auto space-y-6">
|
||||
<!-- 个人信息卡片 -->
|
||||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="w-16 h-16 bg-primary rounded-full flex items-center justify-center text-white text-2xl font-bold">
|
||||
{{ user.name[0] | upper }}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h1 class="text-2xl font-bold text-slate-900">{{ user.name }}</h1>
|
||||
<p class="text-sm text-slate-500">{{ user.email }} · {{ user.role | capitalize }}</p>
|
||||
<p class="text-sm text-slate-400 mt-1">注册时间:{{ user.created_at if user.created_at else '2025-01-01' }}</p> <!-- 实际可从数据库获取真实时间 -->
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<a href="/profile/edit" class="px-4 py-2 border border-slate-300 rounded-md text-sm font-medium text-slate-700 hover:bg-slate-50">编辑资料</a>
|
||||
<a href="{{ url_for('logout') }}" class="px-4 py-2 bg-red-500 text-white rounded-md text-sm font-medium hover:bg-red-600">退出登录</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 切换账户提示 -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 flex items-center justify-between">
|
||||
<span class="text-sm text-blue-700">如需切换账户,请先退出当前登录,然后重新登录其他账号。</span>
|
||||
<a href="{{ url_for('logout') }}" class="px-4 py-2 bg-blue-500 text-white rounded-md text-sm font-medium hover:bg-blue-600">切换账户</a>
|
||||
</div>
|
||||
|
||||
<!-- 三列布局:好友、帖子、收藏 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<!-- 好友列表 -->
|
||||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
||||
<h2 class="text-lg font-semibold text-slate-900 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/></svg>
|
||||
好友列表
|
||||
</h2>
|
||||
<div id="friends-list" class="space-y-3">
|
||||
<div class="text-center py-4 text-slate-400">加载中...</div>
|
||||
</div>
|
||||
<div class="mt-4 text-center">
|
||||
<a href="/friends" class="text-sm text-primary hover:text-blue-700">查看全部好友</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 我的帖子 -->
|
||||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
||||
<h2 class="text-lg font-semibold text-slate-900 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>
|
||||
我的帖子
|
||||
</h2>
|
||||
<div id="my-posts" class="space-y-3">
|
||||
<div class="text-center py-4 text-slate-400">加载中...</div>
|
||||
</div>
|
||||
<div class="mt-4 text-center">
|
||||
<a href="/forum?author=me" class="text-sm text-primary hover:text-blue-700">查看更多帖子</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 收藏的试卷 -->
|
||||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
||||
<h2 class="text-lg font-semibold text-slate-900 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"/></svg>
|
||||
收藏的试卷
|
||||
</h2>
|
||||
<div id="bookmarked-exams" class="space-y-3">
|
||||
<div class="text-center py-4 text-slate-400">加载中...</div>
|
||||
</div>
|
||||
<div class="mt-4 text-center">
|
||||
<a href="/exams?bookmarked=true" class="text-sm text-primary hover:text-blue-700">查看全部收藏</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
async function loadFriends() {
|
||||
const container = document.getElementById('friends-list');
|
||||
try {
|
||||
const res = await fetch('/api/user/friends');
|
||||
const data = await res.json();
|
||||
if (!data.success) throw new Error(data.message);
|
||||
if (data.friends.length === 0) {
|
||||
container.innerHTML = '<div class="text-center py-4 text-slate-400">暂无好友</div>';
|
||||
return;
|
||||
}
|
||||
let html = '';
|
||||
data.friends.slice(0, 5).forEach(f => {
|
||||
html += `
|
||||
<div class="flex items-center space-x-3 p-2 border border-slate-100 rounded-lg">
|
||||
<div class="w-8 h-8 bg-slate-200 rounded-full flex items-center justify-center text-slate-600 text-xs font-bold">
|
||||
${f.name.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-slate-900">${f.name}</div>
|
||||
<div class="text-xs text-slate-400">成为好友于 ${f.created_at}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
container.innerHTML = html;
|
||||
} catch (e) {
|
||||
container.innerHTML = '<div class="text-center py-4 text-red-500">加载失败</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPosts() {
|
||||
const container = document.getElementById('my-posts');
|
||||
try {
|
||||
const res = await fetch('/api/user/posts');
|
||||
const data = await res.json();
|
||||
if (!data.success) throw new Error(data.message);
|
||||
if (data.posts.length === 0) {
|
||||
container.innerHTML = '<div class="text-center py-4 text-slate-400">暂无帖子</div>';
|
||||
return;
|
||||
}
|
||||
let html = '';
|
||||
data.posts.slice(0, 5).forEach(p => {
|
||||
html += `
|
||||
<div class="p-3 border border-slate-100 rounded-lg hover:bg-slate-50 cursor-pointer" onclick="location.href='/forum#post-${p.id}'">
|
||||
<div class="text-sm font-medium text-slate-900 truncate">${p.title}</div>
|
||||
<div class="text-xs text-slate-500 mt-1">${p.created_at} · 💬 ${p.replies} · ❤️ ${p.likes}</div>
|
||||
</div>`;
|
||||
});
|
||||
container.innerHTML = html;
|
||||
} catch (e) {
|
||||
container.innerHTML = '<div class="text-center py-4 text-red-500">加载失败</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadBookmarks() {
|
||||
const container = document.getElementById('bookmarked-exams');
|
||||
try {
|
||||
const res = await fetch('/api/user/exam-bookmarks');
|
||||
const data = await res.json();
|
||||
if (!data.success) throw new Error(data.message);
|
||||
if (data.bookmarks.length === 0) {
|
||||
container.innerHTML = '<div class="text-center py-4 text-slate-400">暂无收藏试卷</div>';
|
||||
return;
|
||||
}
|
||||
let html = '';
|
||||
data.bookmarks.slice(0, 5).forEach(e => {
|
||||
html += `
|
||||
<div class="p-3 border border-slate-100 rounded-lg hover:bg-slate-50 cursor-pointer" onclick="location.href='/exams/${e.id}'">
|
||||
<div class="text-sm font-medium text-slate-900 truncate">${e.title}</div>
|
||||
<div class="text-xs text-slate-500 mt-1">${e.subject} · 收藏于 ${e.bookmarked_at}</div>
|
||||
</div>`;
|
||||
});
|
||||
container.innerHTML = html;
|
||||
} catch (e) {
|
||||
container.innerHTML = '<div class="text-center py-4 text-red-500">加载失败</div>';
|
||||
}
|
||||
}
|
||||
|
||||
loadFriends();
|
||||
loadPosts();
|
||||
loadBookmarks();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user