Compare commits
2 Commits
35c29f1442
...
8258167f7f
| Author | SHA1 | Date | |
|---|---|---|---|
| 8258167f7f | |||
| 9a08140623 |
@@ -22,7 +22,8 @@
|
|||||||
"Bash(cd \"D:/360MoveData/Users/HEIHAHA/Documents/WeChat Files/wxid_k0iaj5miuryq22/FileStorage/File/2026-02/超级大网站2月23号\" && py -3 -c \"\nimport sys, os\nsys.path.insert\\(0, os.path.dirname\\(os.path.abspath\\('app.py'\\)\\)\\)\nfrom app import app\nfrom models import db, User\nwith app.app_context\\(\\):\n users = User.query.all\\(\\)\n for u in users:\n print\\(f'id={u.id}, name={u.name}, email={u.email}, phone={u.phone}, role={u.role}'\\)\n if not users:\n print\\('No users in database'\\)\n\" 2>&1)",
|
"Bash(cd \"D:/360MoveData/Users/HEIHAHA/Documents/WeChat Files/wxid_k0iaj5miuryq22/FileStorage/File/2026-02/超级大网站2月23号\" && py -3 -c \"\nimport sys, os\nsys.path.insert\\(0, os.path.dirname\\(os.path.abspath\\('app.py'\\)\\)\\)\nfrom app import app\nfrom models import db, User\nwith app.app_context\\(\\):\n users = User.query.all\\(\\)\n for u in users:\n print\\(f'id={u.id}, name={u.name}, email={u.email}, phone={u.phone}, role={u.role}'\\)\n if not users:\n print\\('No users in database'\\)\n\" 2>&1)",
|
||||||
"Bash(cd \"D:/360MoveData/Users/HEIHAHA/Documents/WeChat Files/wxid_k0iaj5miuryq22/FileStorage/File/2026-02/超级大网站2月23号\" && py -3 -c \"\nimport os, glob\ntemplates = glob.glob\\('templates/*.html'\\)\ncount = 0\nfor f in templates:\n with open\\(f, 'r', encoding='utf-8'\\) as fh:\n content = fh.read\\(\\)\n if '联考平台' in content:\n new_content = content.replace\\('联考平台', '智联青云'\\)\n with open\\(f, 'w', encoding='utf-8'\\) as fh:\n fh.write\\(new_content\\)\n count += 1\n print\\(f' replaced in {f}'\\)\nprint\\(f'Done: {count} files updated'\\)\n\" 2>&1)",
|
"Bash(cd \"D:/360MoveData/Users/HEIHAHA/Documents/WeChat Files/wxid_k0iaj5miuryq22/FileStorage/File/2026-02/超级大网站2月23号\" && py -3 -c \"\nimport os, glob\ntemplates = glob.glob\\('templates/*.html'\\)\ncount = 0\nfor f in templates:\n with open\\(f, 'r', encoding='utf-8'\\) as fh:\n content = fh.read\\(\\)\n if '联考平台' in content:\n new_content = content.replace\\('联考平台', '智联青云'\\)\n with open\\(f, 'w', encoding='utf-8'\\) as fh:\n fh.write\\(new_content\\)\n count += 1\n print\\(f' replaced in {f}'\\)\nprint\\(f'Done: {count} files updated'\\)\n\" 2>&1)",
|
||||||
"Bash(git stash:*)",
|
"Bash(git stash:*)",
|
||||||
"Bash(git pull:*)"
|
"Bash(git pull:*)",
|
||||||
|
"Bash(iconv:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
add_visibility_column.py
Normal file
40
add_visibility_column.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
添加 exam.visibility 字段的数据库迁移脚本
|
||||||
|
"""
|
||||||
|
|
||||||
|
from app import app, db
|
||||||
|
from models import Exam
|
||||||
|
|
||||||
|
def add_visibility_column():
|
||||||
|
"""为 Exam 表添加 visibility 字段"""
|
||||||
|
with app.app_context():
|
||||||
|
try:
|
||||||
|
# 检查字段是否已存在
|
||||||
|
inspector = db.inspect(db.engine)
|
||||||
|
columns = [col['name'] for col in inspector.get_columns('exam')]
|
||||||
|
|
||||||
|
if 'visibility' in columns:
|
||||||
|
print("visibility 字段已存在,无需添加")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 添加字段
|
||||||
|
with db.engine.connect() as conn:
|
||||||
|
conn.execute(db.text("ALTER TABLE exam ADD COLUMN visibility VARCHAR(20) DEFAULT 'private'"))
|
||||||
|
conn.commit()
|
||||||
|
print("成功添加 visibility 字段")
|
||||||
|
|
||||||
|
# 将所有现有考试设置为公开(保持向后兼容)
|
||||||
|
conn.execute(db.text("UPDATE exam SET visibility = 'public' WHERE visibility IS NULL"))
|
||||||
|
conn.commit()
|
||||||
|
print("已将现有考试设置为公开")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"添加字段时出错: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("开始添加 visibility 字段...")
|
||||||
|
add_visibility_column()
|
||||||
|
print("完成!")
|
||||||
85
app.py
85
app.py
@@ -554,6 +554,16 @@ def exam_list():
|
|||||||
subject_filter = request.args.get('subject', '').strip()
|
subject_filter = request.args.get('subject', '').strip()
|
||||||
|
|
||||||
query = Exam.query
|
query = Exam.query
|
||||||
|
|
||||||
|
# 只显示公开考试或用户自己创建的私有考试
|
||||||
|
from sqlalchemy import or_
|
||||||
|
query = query.filter(
|
||||||
|
or_(
|
||||||
|
Exam.visibility == 'public',
|
||||||
|
Exam.creator_id == user.get('id')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if subject_filter:
|
if subject_filter:
|
||||||
query = query.filter_by(subject=subject_filter)
|
query = query.filter_by(subject=subject_filter)
|
||||||
if search_query:
|
if search_query:
|
||||||
@@ -1059,6 +1069,23 @@ def apply_contest():
|
|||||||
flash('满分分数必须大于0')
|
flash('满分分数必须大于0')
|
||||||
return redirect(url_for('apply_contest'))
|
return redirect(url_for('apply_contest'))
|
||||||
|
|
||||||
|
# 处理图片上传
|
||||||
|
image_url = None
|
||||||
|
if 'image' in request.files:
|
||||||
|
file = request.files['image']
|
||||||
|
if file and file.filename:
|
||||||
|
if allowed_file(file.filename):
|
||||||
|
filename = secure_filename(file.filename)
|
||||||
|
# 生成唯一文件名
|
||||||
|
ext = filename.rsplit('.', 1)[1].lower()
|
||||||
|
unique_filename = f"contest_{user_id}_{int(time.time())}.{ext}"
|
||||||
|
filepath = os.path.join(UPLOAD_FOLDER, unique_filename)
|
||||||
|
file.save(filepath)
|
||||||
|
image_url = f'/static/uploads/{unique_filename}'
|
||||||
|
else:
|
||||||
|
flash('不支持的图片格式,请上传 JPG、PNG 或 GIF 格式的图片')
|
||||||
|
return redirect(url_for('apply_contest'))
|
||||||
|
|
||||||
# 如果存在申请,更新它;否则创建新申请
|
# 如果存在申请,更新它;否则创建新申请
|
||||||
if existing:
|
if existing:
|
||||||
# 更新现有申请
|
# 更新现有申请
|
||||||
@@ -1073,6 +1100,8 @@ def apply_contest():
|
|||||||
existing.responsible_phone = responsible_phone
|
existing.responsible_phone = responsible_phone
|
||||||
existing.responsible_email = responsible_email
|
existing.responsible_email = responsible_email
|
||||||
existing.organization = organization
|
existing.organization = organization
|
||||||
|
if image_url:
|
||||||
|
existing.image = image_url
|
||||||
existing.status = 'pending'
|
existing.status = 'pending'
|
||||||
existing.applied_at = datetime.utcnow()
|
existing.applied_at = datetime.utcnow()
|
||||||
existing.reviewed_at = None
|
existing.reviewed_at = None
|
||||||
@@ -1092,7 +1121,8 @@ def apply_contest():
|
|||||||
responsible_person=responsible_person,
|
responsible_person=responsible_person,
|
||||||
responsible_phone=responsible_phone,
|
responsible_phone=responsible_phone,
|
||||||
responsible_email=responsible_email,
|
responsible_email=responsible_email,
|
||||||
organization=organization
|
organization=organization,
|
||||||
|
image=image_url
|
||||||
)
|
)
|
||||||
db.session.add(app)
|
db.session.add(app)
|
||||||
flash_msg = '申请已提交,请等待管理员审核'
|
flash_msg = '申请已提交,请等待管理员审核'
|
||||||
@@ -2501,20 +2531,26 @@ def api_create_exam():
|
|||||||
return jsonify({'success': False, 'message': '请先登录'}), 401
|
return jsonify({'success': False, 'message': '请先登录'}), 401
|
||||||
data = request.get_json(force=True, silent=True)
|
data = request.get_json(force=True, silent=True)
|
||||||
contest_id = data.get('contest_id')
|
contest_id = data.get('contest_id')
|
||||||
|
visibility = data.get('visibility', 'private') # 默认私有
|
||||||
|
|
||||||
# 杯赛考试:只有杯赛负责人或系统管理员可以创建
|
# 杯赛考试:只有杯赛负责人、老师或系统管理员可以创建
|
||||||
if contest_id:
|
if contest_id:
|
||||||
contest = Contest.query.get(contest_id)
|
contest = Contest.query.get(contest_id)
|
||||||
if not contest or contest.status == 'abolished':
|
if not contest or contest.status == 'abolished':
|
||||||
return jsonify({'success': False, 'message': '杯赛不存在或已废止'}), 400
|
return jsonify({'success': False, 'message': '杯赛不存在或已废止'}), 400
|
||||||
membership = ContestMembership.query.filter_by(
|
membership = ContestMembership.query.filter_by(
|
||||||
user_id=user['id'], contest_id=contest_id, role='owner').first()
|
user_id=user['id'], contest_id=contest_id).first()
|
||||||
|
# 检查是否是负责人、老师或管理员
|
||||||
if not membership and user.get('role') != 'admin':
|
if not membership and user.get('role') != 'admin':
|
||||||
return jsonify({'success': False, 'message': '只有杯赛负责人才能组织考试'}), 403
|
return jsonify({'success': False, 'message': '只有杯赛成员才能创建杯赛考试'}), 403
|
||||||
|
# 杯赛考试默认为公开(杯赛内可见)
|
||||||
|
if visibility == 'private':
|
||||||
|
visibility = 'public'
|
||||||
else:
|
else:
|
||||||
# 普通考试:需要 teacher 或 admin 角色
|
# 普通考试:所有登录用户都可以创建私有考试
|
||||||
if user.get('role') not in ('teacher', 'admin'):
|
# 只有 teacher 或 admin 可以创建公开考试
|
||||||
return jsonify({'success': False, 'message': '无权限'}), 403
|
if visibility == 'public' and user.get('role') not in ('teacher', 'admin'):
|
||||||
|
return jsonify({'success': False, 'message': '只有教师和管理员可以创建公开考试'}), 403
|
||||||
|
|
||||||
title = data.get('title', '')
|
title = data.get('title', '')
|
||||||
subject = data.get('subject', '')
|
subject = data.get('subject', '')
|
||||||
@@ -2532,7 +2568,8 @@ def api_create_exam():
|
|||||||
duration=duration,
|
duration=duration,
|
||||||
total_score=total_score,
|
total_score=total_score,
|
||||||
creator_id=user.get('id'),
|
creator_id=user.get('id'),
|
||||||
contest_id=contest_id
|
contest_id=contest_id,
|
||||||
|
visibility=visibility
|
||||||
)
|
)
|
||||||
# 解析预定时间
|
# 解析预定时间
|
||||||
if scheduled_start:
|
if scheduled_start:
|
||||||
@@ -2708,6 +2745,30 @@ def api_update_exam_status(exam_id):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
return jsonify({'success': True, 'message': f'试卷已{"发布" if new_status == "available" else "关闭"}'})
|
return jsonify({'success': True, 'message': f'试卷已{"发布" if new_status == "available" else "关闭"}'})
|
||||||
|
|
||||||
|
@app.route('/api/exams/<int:exam_id>/visibility', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def api_update_exam_visibility(exam_id):
|
||||||
|
user = session.get('user')
|
||||||
|
exam = Exam.query.get(exam_id)
|
||||||
|
if not exam:
|
||||||
|
return jsonify({'success': False, 'message': '试卷不存在'}), 404
|
||||||
|
if exam.creator_id != user.get('id'):
|
||||||
|
return jsonify({'success': False, 'message': '只能修改自己创建的试卷'}), 403
|
||||||
|
|
||||||
|
data = request.get_json(force=True, silent=True)
|
||||||
|
new_visibility = data.get('visibility', '')
|
||||||
|
|
||||||
|
if new_visibility not in ('private', 'public'):
|
||||||
|
return jsonify({'success': False, 'message': '无效的可见性设置'}), 400
|
||||||
|
|
||||||
|
# 检查权限:只有教师和管理员可以设置为公开
|
||||||
|
if new_visibility == 'public' and user.get('role') not in ('teacher', 'admin'):
|
||||||
|
return jsonify({'success': False, 'message': '只有教师和管理员可以创建公开考试'}), 403
|
||||||
|
|
||||||
|
exam.visibility = new_visibility
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify({'success': True, 'message': f'试卷已设置为{"公开" if new_visibility == "public" else "私有"}'})
|
||||||
|
|
||||||
@app.route('/api/exams/<int:exam_id>', methods=['DELETE'])
|
@app.route('/api/exams/<int:exam_id>', methods=['DELETE'])
|
||||||
def api_delete_exam(exam_id):
|
def api_delete_exam(exam_id):
|
||||||
user = session.get('user')
|
user = session.get('user')
|
||||||
@@ -3229,12 +3290,20 @@ def api_search_posts():
|
|||||||
uid = user.get('id') if user else None
|
uid = user.get('id') if user else None
|
||||||
data = []
|
data = []
|
||||||
for p in posts:
|
for p in posts:
|
||||||
|
# 计算作者等级
|
||||||
|
author_post_count = Post.query.filter_by(author_id=p.author_id).count()
|
||||||
|
author_reply_count = Reply.query.filter_by(author_id=p.author_id).count()
|
||||||
|
author_likes = db.session.query(db.func.sum(Post.likes)).filter_by(author_id=p.author_id).scalar() or 0
|
||||||
|
author_points = author_post_count * 10 + author_reply_count * 3 + author_likes * 2
|
||||||
|
author_level = calc_level(author_points)
|
||||||
|
|
||||||
p_dict = {
|
p_dict = {
|
||||||
'id': p.id,
|
'id': p.id,
|
||||||
'title': p.title,
|
'title': p.title,
|
||||||
'content': p.content[:200] + ('...' if len(p.content) > 200 else ''),
|
'content': p.content[:200] + ('...' if len(p.content) > 200 else ''),
|
||||||
'author': p.author.name,
|
'author': p.author.name,
|
||||||
'author_id': p.author_id,
|
'author_id': p.author_id,
|
||||||
|
'author_level': author_level,
|
||||||
'tag': p.tag,
|
'tag': p.tag,
|
||||||
'is_official': p.is_official,
|
'is_official': p.is_official,
|
||||||
'pinned': p.pinned,
|
'pinned': p.pinned,
|
||||||
|
|||||||
45
create_admin.py
Normal file
45
create_admin.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""创建管理员账号脚本"""
|
||||||
|
|
||||||
|
from app import app, db
|
||||||
|
from models import User
|
||||||
|
|
||||||
|
def create_admin():
|
||||||
|
with app.app_context():
|
||||||
|
# 检查邮箱是否已存在
|
||||||
|
existing_user = User.query.filter_by(email='1512344589@qq.com').first()
|
||||||
|
if existing_user:
|
||||||
|
print(f'邮箱 1512344589@qq.com 已存在')
|
||||||
|
print(f'用户名: {existing_user.name}')
|
||||||
|
print(f'角色: {existing_user.role}')
|
||||||
|
|
||||||
|
# 更新为管理员
|
||||||
|
if existing_user.role != 'admin':
|
||||||
|
existing_user.role = 'admin'
|
||||||
|
existing_user.password = '1'
|
||||||
|
db.session.commit()
|
||||||
|
print('已将该用户更新为管理员')
|
||||||
|
else:
|
||||||
|
print('该用户已经是管理员')
|
||||||
|
return
|
||||||
|
|
||||||
|
# 创建新管理员
|
||||||
|
admin = User(
|
||||||
|
name='管理员',
|
||||||
|
email='1512344589@qq.com',
|
||||||
|
password='1',
|
||||||
|
role='admin'
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(admin)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
print('管理员账号创建成功!')
|
||||||
|
print(f'邮箱: {admin.email}')
|
||||||
|
print(f'密码: 1')
|
||||||
|
print(f'角色: {admin.role}')
|
||||||
|
print(f'用户ID: {admin.id}')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
create_admin()
|
||||||
0
database.db
Normal file
0
database.db
Normal file
66
fix_question_ids.py
Normal file
66
fix_question_ids.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
修复考试题目ID不连续的问题
|
||||||
|
重新为所有考试的题目分配连续的ID
|
||||||
|
"""
|
||||||
|
|
||||||
|
from app import app, db
|
||||||
|
from models import Exam
|
||||||
|
import json
|
||||||
|
|
||||||
|
def fix_question_ids():
|
||||||
|
"""修复所有考试的题目ID,使其连续"""
|
||||||
|
with app.app_context():
|
||||||
|
exams = Exam.query.all()
|
||||||
|
fixed_count = 0
|
||||||
|
|
||||||
|
for exam in exams:
|
||||||
|
try:
|
||||||
|
# 获取题目列表
|
||||||
|
if exam.is_encrypted and exam.encrypted_questions:
|
||||||
|
from app import decrypt_questions
|
||||||
|
questions = json.loads(decrypt_questions(exam.encrypted_questions))
|
||||||
|
else:
|
||||||
|
questions = exam.get_questions()
|
||||||
|
|
||||||
|
if not questions:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查ID是否连续
|
||||||
|
needs_fix = False
|
||||||
|
for idx, q in enumerate(questions, 1):
|
||||||
|
if q.get('id') != idx:
|
||||||
|
needs_fix = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if needs_fix:
|
||||||
|
# 重新分配连续的ID
|
||||||
|
for idx, q in enumerate(questions, 1):
|
||||||
|
q['id'] = idx
|
||||||
|
|
||||||
|
# 保存修复后的题目
|
||||||
|
questions_json = json.dumps(questions)
|
||||||
|
exam.set_questions(questions)
|
||||||
|
|
||||||
|
if exam.is_encrypted:
|
||||||
|
from app import encrypt_questions
|
||||||
|
exam.encrypted_questions = encrypt_questions(questions_json)
|
||||||
|
|
||||||
|
fixed_count += 1
|
||||||
|
print(f"修复考试 #{exam.id}: {exam.title}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"处理考试 #{exam.id} 时出错: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if fixed_count > 0:
|
||||||
|
db.session.commit()
|
||||||
|
print(f"\n总共修复了 {fixed_count} 份考试")
|
||||||
|
else:
|
||||||
|
print("\n没有需要修复的考试")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("开始修复考试题目ID...")
|
||||||
|
fix_question_ids()
|
||||||
|
print("修复完成!")
|
||||||
Binary file not shown.
@@ -68,6 +68,8 @@ class ContestApplication(db.Model):
|
|||||||
# 拒绝次数控制
|
# 拒绝次数控制
|
||||||
rejection_count = db.Column(db.Integer, default=0) # 被拒绝次数
|
rejection_count = db.Column(db.Integer, default=0) # 被拒绝次数
|
||||||
last_rejected_at = db.Column(db.DateTime) # 最后一次被拒绝的时间
|
last_rejected_at = db.Column(db.DateTime) # 最后一次被拒绝的时间
|
||||||
|
# 杯赛图片
|
||||||
|
image = db.Column(db.String(255)) # 杯赛图片URL
|
||||||
|
|
||||||
user = db.relationship('User', backref='contest_applications')
|
user = db.relationship('User', backref='contest_applications')
|
||||||
|
|
||||||
@@ -146,6 +148,7 @@ class Exam(db.Model):
|
|||||||
score_release_time = db.Column(db.DateTime, nullable=True) # 成绩公布时间
|
score_release_time = db.Column(db.DateTime, nullable=True) # 成绩公布时间
|
||||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
status = db.Column(db.String(20), default='available')
|
status = db.Column(db.String(20), default='available')
|
||||||
|
visibility = db.Column(db.String(20), default='private') # 'private' 或 'public'
|
||||||
|
|
||||||
creator = db.relationship('User', back_populates='exams_created')
|
creator = db.relationship('User', back_populates='exams_created')
|
||||||
submissions = db.relationship('Submission', back_populates='exam', lazy=True, cascade='all, delete-orphan')
|
submissions = db.relationship('Submission', back_populates='exam', lazy=True, cascade='all, delete-orphan')
|
||||||
|
|||||||
@@ -1,6 +1,54 @@
|
|||||||
/* 自定义样式 */
|
/* 自定义样式 */
|
||||||
body {
|
body {
|
||||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 科幻背景动画 */
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% { transform: translateY(0px); }
|
||||||
|
50% { transform: translateY(-20px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glow-pulse {
|
||||||
|
0%, 100% { box-shadow: 0 0 20px rgba(59, 130, 246, 0.4); }
|
||||||
|
50% { box-shadow: 0 0 40px rgba(59, 130, 246, 0.8); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% { background-position: -1000px 0; }
|
||||||
|
100% { background-position: 1000px 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 高级卡片样式 */
|
||||||
|
.futuristic-card {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 24px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.1) inset;
|
||||||
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.futuristic-card:hover {
|
||||||
|
transform: translateY(-8px) scale(1.02);
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.2) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.futuristic-card-dark {
|
||||||
|
background: rgba(15, 23, 42, 0.85);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 24px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||||
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.futuristic-card-dark:hover {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||||||
|
border-color: rgba(59, 130, 246, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 自定义滚动条 */
|
/* 自定义滚动条 */
|
||||||
@@ -182,3 +230,246 @@ button[onclick^="quickScore"]:active {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 高级按钮样式 */
|
||||||
|
.btn-futuristic {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border: none;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 14px 32px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-futuristic:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-futuristic::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||||
|
transition: left 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-futuristic:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-futuristic {
|
||||||
|
background: transparent;
|
||||||
|
border: 2px solid rgba(102, 126, 234, 0.5);
|
||||||
|
color: #667eea;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 12px 30px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-futuristic:hover {
|
||||||
|
background: rgba(102, 126, 234, 0.1);
|
||||||
|
border-color: #667eea;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入框样式 */
|
||||||
|
.input-futuristic {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border: 2px solid rgba(102, 126, 234, 0.2);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-futuristic:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导航栏样式 */
|
||||||
|
.navbar-futuristic {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-bottom: 1px solid rgba(102, 126, 234, 0.1);
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标签样式 */
|
||||||
|
.badge-futuristic {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 6px 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
box-shadow: 0 2px 10px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-outline-futuristic {
|
||||||
|
background: transparent;
|
||||||
|
border: 2px solid rgba(102, 126, 234, 0.5);
|
||||||
|
color: #667eea;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分隔线 */
|
||||||
|
.divider-futuristic {
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.5), transparent);
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载动画 */
|
||||||
|
.loading-spinner {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 4px solid rgba(102, 126, 234, 0.2);
|
||||||
|
border-top-color: #667eea;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通知提示 */
|
||||||
|
.notification-futuristic {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
animation: slideInRight 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格样式 */
|
||||||
|
.table-futuristic {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-futuristic thead {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-futuristic tbody tr {
|
||||||
|
border-bottom: 1px solid rgba(102, 126, 234, 0.1);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-futuristic tbody tr:hover {
|
||||||
|
background: rgba(102, 126, 234, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模态框样式 */
|
||||||
|
.modal-futuristic {
|
||||||
|
background: rgba(255, 255, 255, 0.98);
|
||||||
|
backdrop-filter: blur(30px);
|
||||||
|
border-radius: 24px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 进度条 */
|
||||||
|
.progress-futuristic {
|
||||||
|
height: 8px;
|
||||||
|
background: rgba(102, 126, 234, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-futuristic {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
box-shadow: 0 0 10px rgba(102, 126, 234, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 工具提示 */
|
||||||
|
.tooltip-futuristic {
|
||||||
|
background: rgba(15, 23, 42, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
color: white;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 头像样式 */
|
||||||
|
.avatar-futuristic {
|
||||||
|
border: 3px solid rgba(102, 126, 234, 0.5);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-futuristic:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 侧边栏 */
|
||||||
|
.sidebar-futuristic {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-right: 1px solid rgba(102, 126, 234, 0.1);
|
||||||
|
box-shadow: 4px 0 20px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页脚 */
|
||||||
|
.footer-futuristic {
|
||||||
|
background: rgba(15, 23, 42, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 粒子背景容器 */
|
||||||
|
.particles-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
@@ -4,24 +4,26 @@
|
|||||||
|
|
||||||
{% block admin_content %}
|
{% block admin_content %}
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<h1 class="text-2xl font-bold text-slate-900">杯赛管理</h1>
|
<h1 class="text-3xl font-extrabold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">杯赛管理</h1>
|
||||||
|
|
||||||
<div class="bg-white shadow-sm rounded-lg border border-slate-200 overflow-hidden">
|
<div class="futuristic-card-dark overflow-hidden">
|
||||||
<table class="min-w-full divide-y divide-slate-200">
|
<div class="overflow-x-auto">
|
||||||
<thead class="bg-slate-50">
|
<table class="table-futuristic">
|
||||||
<tr>
|
<thead>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">ID</th>
|
<tr>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">名称</th>
|
<th>ID</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">主办方</th>
|
<th>名称</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">状态</th>
|
<th>主办方</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">开始日期</th>
|
<th>状态</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">操作</th>
|
<th>开始日期</th>
|
||||||
</tr>
|
<th>操作</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody id="contests-tbody" class="bg-white divide-y divide-slate-200">
|
</thead>
|
||||||
</tbody>
|
<tbody id="contests-tbody">
|
||||||
</table>
|
</tbody>
|
||||||
<div id="empty-msg" class="text-center py-12 text-slate-400 hidden">暂无杯赛</div>
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="empty-msg" class="text-center py-12 text-slate-500 hidden">暂无杯赛</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -29,11 +31,11 @@
|
|||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'upcoming': ['即将开始', 'bg-blue-100 text-blue-800'],
|
'upcoming': ['即将开始', 'bg-blue-500/20 text-blue-400 border-blue-500/30'],
|
||||||
'registering': ['正在报名', 'bg-green-100 text-green-800'],
|
'registering': ['正在报名', 'bg-green-500/20 text-green-400 border-green-500/30'],
|
||||||
'ongoing': ['进行中', 'bg-yellow-100 text-yellow-800'],
|
'ongoing': ['进行中', 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30'],
|
||||||
'ended': ['已结束', 'bg-slate-100 text-slate-800'],
|
'ended': ['已结束', 'bg-slate-500/20 text-slate-400 border-slate-500/30'],
|
||||||
'abolished': ['已废止', 'bg-red-100 text-red-800']
|
'abolished': ['已废止', 'bg-red-500/20 text-red-400 border-red-500/30']
|
||||||
};
|
};
|
||||||
|
|
||||||
async function loadContests() {
|
async function loadContests() {
|
||||||
@@ -48,18 +50,18 @@ async function loadContests() {
|
|||||||
}
|
}
|
||||||
let html = '';
|
let html = '';
|
||||||
data.contests.forEach(c => {
|
data.contests.forEach(c => {
|
||||||
const [statusText, statusClass] = statusMap[c.status] || ['未知', 'bg-slate-100 text-slate-800'];
|
const [statusText, statusClass] = statusMap[c.status] || ['未知', 'bg-slate-500/20 text-slate-400 border-slate-500/30'];
|
||||||
const abolishBtn = c.status !== 'abolished'
|
const abolishBtn = c.status !== 'abolished'
|
||||||
? `<button onclick="abolishContest(${c.id}, '${c.name.replace(/'/g, "\\'")}')" class="px-2 py-1 text-xs bg-red-100 text-red-700 border border-red-300 rounded hover:bg-red-200">废止</button>`
|
? `<button onclick="abolishContest(${c.id}, '${c.name.replace(/'/g, "\\'")}')" class="btn-outline-futuristic px-3 py-1 text-xs border-red-500/30 text-red-400 hover:bg-red-500/20 hover:border-red-500/50">废止</button>`
|
||||||
: '<span class="text-xs text-red-500">已废止</span>';
|
: '<span class="text-xs text-red-500">已废止</span>';
|
||||||
html += `<tr>
|
html += `<tr>
|
||||||
<td class="px-6 py-4 text-sm text-slate-900">${c.id}</td>
|
<td>${c.id}</td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-900">${c.name}</td>
|
<td class="font-medium text-slate-200">${c.name}</td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-500">${c.organizer || '-'}</td>
|
<td class="text-slate-400">${c.organizer || '-'}</td>
|
||||||
<td class="px-6 py-4"><span class="px-2 py-1 text-xs rounded-full ${statusClass}">${statusText}</span></td>
|
<td><span class="badge-futuristic ${statusClass}">${statusText}</span></td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-500">${c.start_date || '-'}</td>
|
<td class="text-slate-400">${c.start_date || '-'}</td>
|
||||||
<td class="px-6 py-4 space-x-2">
|
<td class="space-x-2">
|
||||||
<a href="/contests/${c.id}" class="text-xs text-primary hover:underline">查看</a>
|
<a href="/contests/${c.id}" class="text-xs text-cyan-400 hover:text-cyan-300 hover:underline">查看</a>
|
||||||
${abolishBtn}
|
${abolishBtn}
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
|||||||
@@ -6,92 +6,92 @@
|
|||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-extrabold text-slate-900 tracking-tight">数据概览</h1>
|
<h1 class="text-3xl font-extrabold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent tracking-tight">数据概览</h1>
|
||||||
<p class="text-sm text-slate-500 mt-1 font-medium">查看平台运行状态与核心数据</p>
|
<p class="text-sm text-slate-400 mt-1 font-medium">查看平台运行状态与核心数据</p>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="loadDashboard()" class="w-10 h-10 rounded-xl bg-white text-slate-500 shadow-sm border border-slate-100 hover:text-indigo-600 hover:bg-indigo-50 transition-all flex items-center justify-center group" title="刷新数据">
|
<button onclick="loadDashboard()" class="w-10 h-10 rounded-xl bg-slate-800/50 text-slate-400 border border-slate-700/50 hover:text-cyan-400 hover:border-cyan-500/50 hover:shadow-lg hover:shadow-cyan-500/20 transition-all flex items-center justify-center group backdrop-blur-sm" title="刷新数据">
|
||||||
<svg class="w-5 h-5 group-hover:rotate-180 transition-transform duration-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
<svg class="w-5 h-5 group-hover:rotate-180 transition-transform duration-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 统计卡片 -->
|
<!-- 统计卡片 -->
|
||||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-6">
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-6">
|
||||||
<div class="bg-gradient-to-br from-indigo-50 to-white rounded-2xl p-6 shadow-sm border border-indigo-100/50 relative overflow-hidden group hover:-translate-y-1 transition-transform">
|
<div class="futuristic-card-dark relative overflow-hidden group hover:-translate-y-1 transition-transform">
|
||||||
<div class="absolute -right-4 -bottom-4 w-24 h-24 bg-indigo-500/5 rounded-full blur-2xl group-hover:bg-indigo-500/10 transition-colors"></div>
|
<div class="absolute -right-4 -bottom-4 w-24 h-24 bg-cyan-500/20 rounded-full blur-2xl group-hover:bg-cyan-500/30 transition-colors"></div>
|
||||||
<div class="flex items-center gap-3 mb-3 relative z-10">
|
<div class="flex items-center gap-3 mb-3 relative z-10">
|
||||||
<div class="w-10 h-10 rounded-xl bg-indigo-100 text-indigo-600 flex items-center justify-center shadow-inner">
|
<div class="w-10 h-10 rounded-xl bg-cyan-500/20 text-cyan-400 flex items-center justify-center shadow-lg shadow-cyan-500/20 border border-cyan-500/30">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm font-bold text-slate-600">总用户数</div>
|
<div class="text-sm font-bold text-slate-300">总用户数</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="stat-users" class="text-3xl font-black text-slate-900 relative z-10">-</div>
|
<div id="stat-users" class="text-3xl font-black text-white relative z-10">-</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-gradient-to-br from-purple-50 to-white rounded-2xl p-6 shadow-sm border border-purple-100/50 relative overflow-hidden group hover:-translate-y-1 transition-transform">
|
<div class="futuristic-card-dark relative overflow-hidden group hover:-translate-y-1 transition-transform">
|
||||||
<div class="absolute -right-4 -bottom-4 w-24 h-24 bg-purple-500/5 rounded-full blur-2xl group-hover:bg-purple-500/10 transition-colors"></div>
|
<div class="absolute -right-4 -bottom-4 w-24 h-24 bg-purple-500/20 rounded-full blur-2xl group-hover:bg-purple-500/30 transition-colors"></div>
|
||||||
<div class="flex items-center gap-3 mb-3 relative z-10">
|
<div class="flex items-center gap-3 mb-3 relative z-10">
|
||||||
<div class="w-10 h-10 rounded-xl bg-purple-100 text-purple-600 flex items-center justify-center shadow-inner">
|
<div class="w-10 h-10 rounded-xl bg-purple-500/20 text-purple-400 flex items-center justify-center shadow-lg shadow-purple-500/20 border border-purple-500/30">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 002-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/></svg>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm font-bold text-slate-600">赛事总数</div>
|
<div class="text-sm font-bold text-slate-300">赛事总数</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="stat-contests" class="text-3xl font-black text-slate-900 relative z-10">-</div>
|
<div id="stat-contests" class="text-3xl font-black text-white relative z-10">-</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-gradient-to-br from-emerald-50 to-white rounded-2xl p-6 shadow-sm border border-emerald-100/50 relative overflow-hidden group hover:-translate-y-1 transition-transform">
|
<div class="futuristic-card-dark relative overflow-hidden group hover:-translate-y-1 transition-transform">
|
||||||
<div class="absolute -right-4 -bottom-4 w-24 h-24 bg-emerald-500/5 rounded-full blur-2xl group-hover:bg-emerald-500/10 transition-colors"></div>
|
<div class="absolute -right-4 -bottom-4 w-24 h-24 bg-emerald-500/20 rounded-full blur-2xl group-hover:bg-emerald-500/30 transition-colors"></div>
|
||||||
<div class="flex items-center gap-3 mb-3 relative z-10">
|
<div class="flex items-center gap-3 mb-3 relative z-10">
|
||||||
<div class="w-10 h-10 rounded-xl bg-emerald-100 text-emerald-600 flex items-center justify-center shadow-inner">
|
<div class="w-10 h-10 rounded-xl bg-emerald-500/20 text-emerald-400 flex items-center justify-center shadow-lg shadow-emerald-500/20 border border-emerald-500/30">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm font-bold text-slate-600">考试总数</div>
|
<div class="text-sm font-bold text-slate-300">考试总数</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="stat-exams" class="text-3xl font-black text-slate-900 relative z-10">-</div>
|
<div id="stat-exams" class="text-3xl font-black text-white relative z-10">-</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-gradient-to-br from-amber-50 to-white rounded-2xl p-6 shadow-sm border border-amber-100/50 relative overflow-hidden group hover:-translate-y-1 transition-transform">
|
<div class="futuristic-card-dark relative overflow-hidden group hover:-translate-y-1 transition-transform">
|
||||||
<div class="absolute -right-4 -bottom-4 w-24 h-24 bg-amber-500/5 rounded-full blur-2xl group-hover:bg-amber-500/10 transition-colors"></div>
|
<div class="absolute -right-4 -bottom-4 w-24 h-24 bg-pink-500/20 rounded-full blur-2xl group-hover:bg-pink-500/30 transition-colors"></div>
|
||||||
<div class="flex items-center gap-3 mb-3 relative z-10">
|
<div class="flex items-center gap-3 mb-3 relative z-10">
|
||||||
<div class="w-10 h-10 rounded-xl bg-amber-100 text-amber-600 flex items-center justify-center shadow-inner">
|
<div class="w-10 h-10 rounded-xl bg-pink-500/20 text-pink-400 flex items-center justify-center shadow-lg shadow-pink-500/20 border border-pink-500/30">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"/></svg>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm font-bold text-slate-600">社区帖子</div>
|
<div class="text-sm font-bold text-slate-300">社区帖子</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="stat-posts" class="text-3xl font-black text-slate-900 relative z-10">-</div>
|
<div id="stat-posts" class="text-3xl font-black text-white relative z-10">-</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 待处理 + 最近活动 -->
|
<!-- 待处理 + 最近活动 -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<!-- 待处理事项 -->
|
<div>
|
||||||
<div class="bg-white rounded-3xl p-6 shadow-sm border border-slate-100 flex flex-col h-full">
|
|
||||||
<div class="flex items-center gap-2 mb-6">
|
<div class="flex items-center gap-2 mb-6">
|
||||||
<div class="w-8 h-8 rounded-lg bg-rose-100 text-rose-500 flex items-center justify-center shadow-inner">
|
<div class="w-8 h-8 rounded-lg bg-orange-500/20 text-orange-400 flex items-center justify-center shadow-lg shadow-orange-500/20 border border-orange-500/30">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-lg font-bold text-slate-800">待处理事项</h2>
|
<h2 class="text-lg font-bold bg-gradient-to-r from-orange-400 to-amber-400 bg-clip-text text-transparent">待处理事项</h2>
|
||||||
</div>
|
</div>
|
||||||
<div id="pending-items" class="space-y-3 flex-1">
|
<div id="pending-items" class="futuristic-card-dark">
|
||||||
<div class="flex flex-col items-center justify-center h-48 text-slate-400 border-2 border-dashed border-slate-100 rounded-2xl">
|
<div class="flex flex-col items-center justify-center h-48 text-slate-500">
|
||||||
<svg class="animate-spin h-6 w-6 text-indigo-500 mb-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
|
<svg class="animate-spin h-6 w-6 text-cyan-400 mb-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
|
||||||
<span class="text-sm font-medium">加载中...</span>
|
<span class="text-sm font-medium">加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 最近活动 -->
|
<div>
|
||||||
<div class="bg-white rounded-3xl p-6 shadow-sm border border-slate-100 flex flex-col h-full">
|
|
||||||
<div class="flex items-center gap-2 mb-6">
|
<div class="flex items-center gap-2 mb-6">
|
||||||
<div class="w-8 h-8 rounded-lg bg-blue-100 text-blue-500 flex items-center justify-center shadow-inner">
|
<div class="w-8 h-8 rounded-lg bg-blue-500/20 text-blue-400 flex items-center justify-center shadow-lg shadow-blue-500/20 border border-blue-500/30">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-lg font-bold text-slate-800">最近活动日志</h2>
|
<h2 class="text-lg font-bold bg-gradient-to-r from-blue-400 to-cyan-400 bg-clip-text text-transparent">最近活动</h2>
|
||||||
</div>
|
</div>
|
||||||
<div id="recent-activities" class="space-y-4 flex-1 relative pl-3">
|
<div class="futuristic-card-dark relative">
|
||||||
<div class="absolute left-[19px] top-2 bottom-2 w-0.5 bg-slate-100"></div>
|
<div class="absolute left-[19px] top-2 bottom-2 w-0.5 bg-slate-700/50"></div>
|
||||||
<div class="flex flex-col items-center justify-center h-48 text-slate-400 border-2 border-dashed border-slate-100 rounded-2xl ml-4">
|
<div id="recent-activities">
|
||||||
<svg class="animate-spin h-6 w-6 text-indigo-500 mb-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
|
<div class="flex flex-col items-center justify-center h-48 text-slate-500 ml-4">
|
||||||
<span class="text-sm font-medium">加载中...</span>
|
<svg class="animate-spin h-6 w-6 text-cyan-400 mb-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
|
||||||
|
<span class="text-sm font-medium">加载中...</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,39 +100,39 @@
|
|||||||
<!-- 快捷导航 -->
|
<!-- 快捷导航 -->
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-2 mb-6">
|
<div class="flex items-center gap-2 mb-6">
|
||||||
<div class="w-8 h-8 rounded-lg bg-teal-100 text-teal-600 flex items-center justify-center shadow-inner">
|
<div class="w-8 h-8 rounded-lg bg-teal-500/20 text-teal-400 flex items-center justify-center shadow-lg shadow-teal-500/20 border border-teal-500/30">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-lg font-bold text-slate-800">快捷操作</h2>
|
<h2 class="text-lg font-bold bg-gradient-to-r from-teal-400 to-emerald-400 bg-clip-text text-transparent">快捷操作</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-4">
|
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-4">
|
||||||
<a href="/admin/contests" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-indigo-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
|
<a href="/admin/contests" class="futuristic-card-dark hover:border-indigo-500/50 hover:shadow-lg hover:shadow-indigo-500/20 hover:-translate-y-1 transition-all text-center group flex flex-col items-center p-5">
|
||||||
<div class="w-12 h-12 rounded-xl bg-indigo-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">🏆</div>
|
<div class="w-12 h-12 rounded-xl bg-indigo-500/20 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform border border-indigo-500/30">🏆</div>
|
||||||
<div class="text-sm font-bold text-slate-700">杯赛管理</div>
|
<div class="text-sm font-bold text-slate-300">杯赛管理</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/contest-applications" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-orange-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
|
<a href="/admin/contest-applications" class="futuristic-card-dark hover:border-orange-500/50 hover:shadow-lg hover:shadow-orange-500/20 hover:-translate-y-1 transition-all text-center group flex flex-col items-center p-5">
|
||||||
<div class="w-12 h-12 rounded-xl bg-orange-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">📋</div>
|
<div class="w-12 h-12 rounded-xl bg-orange-500/20 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform border border-orange-500/30">📋</div>
|
||||||
<div class="text-sm font-bold text-slate-700">杯赛申请</div>
|
<div class="text-sm font-bold text-slate-300">杯赛申请</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/teacher-applications" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-purple-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
|
<a href="/admin/teacher-applications" class="futuristic-card-dark hover:border-purple-500/50 hover:shadow-lg hover:shadow-purple-500/20 hover:-translate-y-1 transition-all text-center group flex flex-col items-center p-5">
|
||||||
<div class="w-12 h-12 rounded-xl bg-purple-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">👨🏫</div>
|
<div class="w-12 h-12 rounded-xl bg-purple-500/20 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform border border-purple-500/30">👨🏫</div>
|
||||||
<div class="text-sm font-bold text-slate-700">教师申请</div>
|
<div class="text-sm font-bold text-slate-300">教师申请</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/exams" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-emerald-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
|
<a href="/admin/exams" class="futuristic-card-dark hover:border-emerald-500/50 hover:shadow-lg hover:shadow-emerald-500/20 hover:-translate-y-1 transition-all text-center group flex flex-col items-center p-5">
|
||||||
<div class="w-12 h-12 rounded-xl bg-emerald-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">📝</div>
|
<div class="w-12 h-12 rounded-xl bg-emerald-500/20 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform border border-emerald-500/30">📝</div>
|
||||||
<div class="text-sm font-bold text-slate-700">考试管理</div>
|
<div class="text-sm font-bold text-slate-300">考试管理</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/users" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-blue-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
|
<a href="/admin/users" class="futuristic-card-dark hover:border-blue-500/50 hover:shadow-lg hover:shadow-blue-500/20 hover:-translate-y-1 transition-all text-center group flex flex-col items-center p-5">
|
||||||
<div class="w-12 h-12 rounded-xl bg-blue-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">👥</div>
|
<div class="w-12 h-12 rounded-xl bg-blue-500/20 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform border border-blue-500/30">👥</div>
|
||||||
<div class="text-sm font-bold text-slate-700">用户管理</div>
|
<div class="text-sm font-bold text-slate-300">用户管理</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/posts" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-amber-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
|
<a href="/admin/posts" class="futuristic-card-dark hover:border-amber-500/50 hover:shadow-lg hover:shadow-amber-500/20 hover:-translate-y-1 transition-all text-center group flex flex-col items-center p-5">
|
||||||
<div class="w-12 h-12 rounded-xl bg-amber-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">💬</div>
|
<div class="w-12 h-12 rounded-xl bg-amber-500/20 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform border border-amber-500/30">💬</div>
|
||||||
<div class="text-sm font-bold text-slate-700">帖子管理</div>
|
<div class="text-sm font-bold text-slate-300">帖子管理</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="/admin/notifications" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:border-rose-200 hover:shadow-md hover:-translate-y-1 transition-all text-center group flex flex-col items-center">
|
<a href="/admin/notifications" class="futuristic-card-dark hover:border-rose-500/50 hover:shadow-lg hover:shadow-rose-500/20 hover:-translate-y-1 transition-all text-center group flex flex-col items-center p-5">
|
||||||
<div class="w-12 h-12 rounded-xl bg-rose-50 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform">📢</div>
|
<div class="w-12 h-12 rounded-xl bg-rose-500/20 flex items-center justify-center text-2xl mb-3 group-hover:scale-110 transition-transform border border-rose-500/30">📢</div>
|
||||||
<div class="text-sm font-bold text-slate-700">通知管理</div>
|
<div class="text-sm font-bold text-slate-300">通知管理</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -157,34 +157,38 @@ async function loadDashboard() {
|
|||||||
const contestApps = data.stats.pending_contest_apps || 0;
|
const contestApps = data.stats.pending_contest_apps || 0;
|
||||||
if (teacherApps === 0 && contestApps === 0) {
|
if (teacherApps === 0 && contestApps === 0) {
|
||||||
pending.innerHTML = `
|
pending.innerHTML = `
|
||||||
<div class="flex flex-col items-center justify-center h-48 text-slate-400 border-2 border-dashed border-slate-100 rounded-2xl bg-slate-50/50">
|
<div class="flex flex-col items-center justify-center h-48 text-slate-500 border-2 border-dashed border-slate-700/50 rounded-2xl bg-slate-800/30">
|
||||||
<div class="w-12 h-12 bg-white rounded-full flex items-center justify-center text-2xl shadow-sm mb-3">☕</div>
|
<div class="w-12 h-12 bg-slate-800/50 rounded-full flex items-center justify-center text-2xl shadow-lg mb-3 border border-slate-700/50">☕</div>
|
||||||
<span class="text-sm font-bold">太棒了,所有事项都已处理完毕!</span>
|
<span class="text-sm font-bold text-slate-400">太棒了,所有事项都已处理完毕!</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
} else {
|
} else {
|
||||||
let html = '';
|
let html = '';
|
||||||
if (teacherApps > 0) {
|
if (teacherApps > 0) {
|
||||||
html += `<a href="/admin/teacher-applications" class="flex items-center justify-between p-4 bg-gradient-to-r from-orange-50 to-white border border-orange-200/60 rounded-xl hover:shadow-md hover:border-orange-300 transition-all group">
|
html += `<a href="/admin/teacher-applications" class="block p-4 bg-slate-800/30 border border-purple-500/30 rounded-xl hover:border-purple-500/50 hover:shadow-lg hover:shadow-purple-500/20 transition-all group">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center justify-between">
|
||||||
<div class="w-10 h-10 rounded-full bg-orange-100 flex items-center justify-center text-orange-600 group-hover:scale-110 transition-transform">👨🏫</div>
|
<div class="flex items-center gap-3">
|
||||||
<div>
|
<div class="w-10 h-10 rounded-lg bg-purple-500/20 text-purple-400 flex items-center justify-center text-lg border border-purple-500/30">👨🏫</div>
|
||||||
<div class="text-sm font-bold text-slate-800">待审核教师申请</div>
|
<div>
|
||||||
<div class="text-xs text-slate-500 mt-0.5">需要您的审批决定</div>
|
<div class="text-sm font-bold text-slate-300">教师申请待审核</div>
|
||||||
|
<div class="text-xs text-slate-500 mt-0.5">需要您的审批</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span class="badge-futuristic">${teacherApps}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="px-3 py-1 text-xs font-bold bg-orange-500 text-white rounded-full shadow-sm shadow-orange-200 animate-pulse">${teacherApps} 项</span>
|
|
||||||
</a>`;
|
</a>`;
|
||||||
}
|
}
|
||||||
if (contestApps > 0) {
|
if (contestApps > 0) {
|
||||||
html += `<a href="/admin/contest-applications" class="flex items-center justify-between p-4 bg-gradient-to-r from-indigo-50 to-white border border-indigo-200/60 rounded-xl hover:shadow-md hover:border-indigo-300 transition-all group mt-3">
|
html += `<a href="/admin/contest-applications" class="block p-4 bg-slate-800/30 border border-orange-500/30 rounded-xl hover:border-orange-500/50 hover:shadow-lg hover:shadow-orange-500/20 transition-all group mt-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center justify-between">
|
||||||
<div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center text-indigo-600 group-hover:scale-110 transition-transform">🏆</div>
|
<div class="flex items-center gap-3">
|
||||||
<div>
|
<div class="w-10 h-10 rounded-lg bg-orange-500/20 text-orange-400 flex items-center justify-center text-lg border border-orange-500/30">📋</div>
|
||||||
<div class="text-sm font-bold text-slate-800">待审核杯赛申请</div>
|
<div>
|
||||||
<div class="text-xs text-slate-500 mt-0.5">有新的杯赛创建请求</div>
|
<div class="text-sm font-bold text-slate-300">杯赛申请待审核</div>
|
||||||
|
<div class="text-xs text-slate-500 mt-0.5">需要您的审批</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span class="badge-futuristic">${contestApps}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="px-3 py-1 text-xs font-bold bg-indigo-500 text-white rounded-full shadow-sm shadow-indigo-200 animate-pulse">${contestApps} 项</span>
|
|
||||||
</a>`;
|
</a>`;
|
||||||
}
|
}
|
||||||
pending.innerHTML = html;
|
pending.innerHTML = html;
|
||||||
@@ -197,16 +201,17 @@ async function loadDashboard() {
|
|||||||
const res = await fetch('/api/admin/recent-activities');
|
const res = await fetch('/api/admin/recent-activities');
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const container = document.getElementById('recent-activities');
|
const container = document.getElementById('recent-activities');
|
||||||
if (data.success && data.activities.length > 0) {
|
if (data.success && data.activities && data.activities.length > 0) {
|
||||||
container.innerHTML = data.activities.map(a =>
|
container.innerHTML = data.activities.map(a =>
|
||||||
`<div class="flex items-start gap-4 relative z-10 group">
|
`<div class="flex gap-4 mb-4 relative z-10">
|
||||||
<div class="w-3 h-3 rounded-full bg-white border-2 border-indigo-400 mt-1.5 flex-shrink-0 group-hover:scale-125 transition-transform group-hover:bg-indigo-400 group-hover:border-indigo-100"></div>
|
<div class="w-10 h-10 rounded-full bg-gradient-to-br ${a.color || 'from-cyan-500 to-blue-500'} flex items-center justify-center text-white text-sm font-bold shadow-lg flex-shrink-0">${a.icon || '📌'}</div>
|
||||||
<div class="bg-slate-50 border border-slate-100 rounded-xl p-3 flex-1 group-hover:bg-white group-hover:shadow-sm transition-all group-hover:border-indigo-100">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex items-center justify-between gap-4 mb-1">
|
<div class="flex items-center gap-2 mb-1 flex-wrap">
|
||||||
<span class="text-xs font-bold text-slate-400 flex items-center gap-1.5"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>${a.time}</span>
|
<span class="text-xs font-bold text-slate-400">${a.type}</span>
|
||||||
|
<span class="text-xs text-slate-600 flex items-center gap-1"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>${a.time}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-slate-700 leading-relaxed">
|
<div class="text-sm text-slate-300 leading-relaxed">
|
||||||
<span class="font-bold text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-md border border-indigo-100 mr-1">${a.user}</span>
|
<span class="font-bold text-cyan-400 bg-cyan-500/10 px-2 py-0.5 rounded-md border border-cyan-500/30 mr-1">${a.user}</span>
|
||||||
${a.action}
|
${a.action}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -214,11 +219,10 @@ async function loadDashboard() {
|
|||||||
).join('');
|
).join('');
|
||||||
} else {
|
} else {
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="flex flex-col items-center justify-center h-48 text-slate-400 border-2 border-dashed border-slate-100 rounded-2xl bg-slate-50/50 relative z-10 ml-4">
|
<div class="flex flex-col items-center justify-center h-48 text-slate-500 border-2 border-dashed border-slate-700/50 rounded-2xl bg-slate-800/30 relative z-10 ml-4">
|
||||||
<div class="w-12 h-12 bg-white rounded-full flex items-center justify-center text-2xl shadow-sm mb-3">📭</div>
|
<div class="w-12 h-12 bg-slate-800/50 rounded-full flex items-center justify-center text-2xl shadow-lg mb-3 border border-slate-700/50">📭</div>
|
||||||
<span class="text-sm font-bold">暂无近期活动记录</span>
|
<span class="text-sm font-bold text-slate-400">暂无近期活动记录</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
// Hide the timeline line if no activities
|
|
||||||
const line = container.previousElementSibling?.classList.contains('absolute') ? container.previousElementSibling : null;
|
const line = container.previousElementSibling?.classList.contains('absolute') ? container.previousElementSibling : null;
|
||||||
if(line) line.style.display = 'none';
|
if(line) line.style.display = 'none';
|
||||||
}
|
}
|
||||||
@@ -228,3 +232,4 @@ async function loadDashboard() {
|
|||||||
document.addEventListener('DOMContentLoaded', loadDashboard);
|
document.addEventListener('DOMContentLoaded', loadDashboard);
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -4,41 +4,43 @@
|
|||||||
|
|
||||||
{% block admin_content %}
|
{% block admin_content %}
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<h1 class="text-2xl font-bold text-slate-900">考试管理</h1>
|
<h1 class="text-3xl font-extrabold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">考试管理</h1>
|
||||||
|
|
||||||
<div class="bg-white shadow-sm rounded-lg border border-slate-200 overflow-hidden">
|
<div class="futuristic-card-dark overflow-hidden">
|
||||||
<table class="min-w-full divide-y divide-slate-200">
|
<div class="overflow-x-auto">
|
||||||
<thead class="bg-slate-50">
|
<table class="table-futuristic">
|
||||||
<tr>
|
<thead>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">ID</th>
|
<tr>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">标题</th>
|
<th>ID</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">科目</th>
|
<th>标题</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">出题人</th>
|
<th>科目</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">状态</th>
|
<th>出题人</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">创建时间</th>
|
<th>状态</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">操作</th>
|
<th>创建时间</th>
|
||||||
</tr>
|
<th>操作</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody id="exams-tbody" class="bg-white divide-y divide-slate-200"></tbody>
|
</thead>
|
||||||
</table>
|
<tbody id="exams-tbody"></tbody>
|
||||||
<div id="loading-spinner" class="text-center py-8 text-slate-400 hidden">
|
</table>
|
||||||
<svg class="animate-spin h-6 w-6 mx-auto" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
</div>
|
||||||
|
<div id="loading-spinner" class="text-center py-8 text-slate-500 hidden">
|
||||||
|
<svg class="animate-spin h-6 w-6 mx-auto text-cyan-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-sm">加载中...</span>
|
<span class="text-sm">加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="empty-msg" class="text-center py-12 text-slate-400 hidden">暂无考试</div>
|
<div id="empty-msg" class="text-center py-12 text-slate-500 hidden">暂无考试</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'available': ['可用', 'bg-green-100 text-green-800'],
|
'available': ['可用', 'bg-green-500/20 text-green-400 border-green-500/30'],
|
||||||
'closed': ['已关闭', 'bg-red-100 text-red-800'],
|
'closed': ['已关闭', 'bg-red-500/20 text-red-400 border-red-500/30'],
|
||||||
'draft': ['草稿', 'bg-yellow-100 text-yellow-800'],
|
'draft': ['草稿', 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30'],
|
||||||
'scheduled': ['已排期', 'bg-blue-100 text-blue-800']
|
'scheduled': ['已排期', 'bg-blue-500/20 text-blue-400 border-blue-500/30']
|
||||||
};
|
};
|
||||||
|
|
||||||
async function loadExams() {
|
async function loadExams() {
|
||||||
@@ -62,18 +64,18 @@ async function loadExams() {
|
|||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
data.exams.forEach(e => {
|
data.exams.forEach(e => {
|
||||||
const [statusText, statusClass] = statusMap[e.status] || [e.status, 'bg-slate-100 text-slate-800'];
|
const [statusText, statusClass] = statusMap[e.status] || [e.status, 'bg-slate-500/20 text-slate-400 border-slate-500/30'];
|
||||||
const isAvailable = e.status === 'available';
|
const isAvailable = e.status === 'available';
|
||||||
html += `<tr>
|
html += `<tr>
|
||||||
<td class="px-6 py-4 text-sm text-slate-900">${e.id}</td>
|
<td>${e.id}</td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-900 max-w-xs truncate">${e.title}</td>
|
<td class="font-medium text-slate-200 max-w-xs truncate">${e.title}</td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-500">${e.subject || '-'}</td>
|
<td class="text-slate-400">${e.subject || '-'}</td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-500">${e.creator_name || '-'}</td>
|
<td class="text-slate-400">${e.creator_name || '-'}</td>
|
||||||
<td class="px-6 py-4"><span class="px-2 py-1 text-xs rounded-full ${statusClass}">${statusText}</span></td>
|
<td><span class="badge-futuristic ${statusClass}">${statusText}</span></td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-500">${e.created_at}</td>
|
<td class="text-slate-400">${e.created_at}</td>
|
||||||
<td class="px-6 py-4 space-x-2">
|
<td class="space-x-2">
|
||||||
<button onclick="toggleStatus(${e.id}, '${e.status}')" class="px-2 py-1 text-xs ${isAvailable ? 'bg-yellow-100 text-yellow-700 border-yellow-300' : 'bg-green-100 text-green-700 border-green-300'} border rounded hover:opacity-80">${isAvailable ? '停止' : '恢复'}</button>
|
<button onclick="toggleStatus(${e.id}, '${e.status}')" class="${isAvailable ? 'btn-outline-futuristic border-yellow-500/30 text-yellow-400 hover:bg-yellow-500/20 hover:border-yellow-500/50' : 'btn-futuristic'} px-3 py-1 text-xs">${isAvailable ? '停止' : '恢复'}</button>
|
||||||
<button onclick="deleteExam(${e.id}, '${e.title.replace(/'/g, "\\'")}')" class="px-2 py-1 text-xs bg-red-100 text-red-700 border border-red-300 rounded hover:bg-red-200">删除</button>
|
<button onclick="deleteExam(${e.id}, '${e.title.replace(/'/g, "\\'")}')" class="btn-outline-futuristic px-3 py-1 text-xs border-red-500/30 text-red-400 hover:bg-red-500/20 hover:border-red-500/50">删除</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,53 +4,55 @@
|
|||||||
|
|
||||||
{% block admin_content %}
|
{% block admin_content %}
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<h1 class="text-2xl font-bold text-slate-900">帖子管理</h1>
|
<h1 class="text-3xl font-extrabold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">帖子管理</h1>
|
||||||
|
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<input id="search-input" type="text" placeholder="搜索标题/内容..." class="flex-1 px-4 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
<input id="search-input" type="text" placeholder="搜索标题/内容..." class="input-futuristic flex-1">
|
||||||
<select id="tag-filter" class="px-4 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
<select id="tag-filter" class="input-futuristic">
|
||||||
<option value="">全部标签</option>
|
<option value="">全部标签</option>
|
||||||
<option value="讨论">讨论</option>
|
<option value="讨论">讨论</option>
|
||||||
<option value="求助">求助</option>
|
<option value="求助">求助</option>
|
||||||
<option value="分享">分享</option>
|
<option value="分享">分享</option>
|
||||||
<option value="公告">公告</option>
|
<option value="公告">公告</option>
|
||||||
</select>
|
</select>
|
||||||
<button onclick="loadPosts()" class="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700">搜索</button>
|
<button onclick="loadPosts()" class="btn-futuristic px-6">搜索</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white shadow-sm rounded-lg border border-slate-200 overflow-hidden">
|
<div class="futuristic-card-dark overflow-hidden">
|
||||||
<table class="min-w-full divide-y divide-slate-200">
|
<div class="overflow-x-auto">
|
||||||
<thead class="bg-slate-50">
|
<table class="table-futuristic">
|
||||||
<tr>
|
<thead>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">ID</th>
|
<tr>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">标题</th>
|
<th>ID</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">作者</th>
|
<th>标题</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">标签</th>
|
<th>作者</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">置顶</th>
|
<th>标签</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">发布时间</th>
|
<th>置顶</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">操作</th>
|
<th>发布时间</th>
|
||||||
</tr>
|
<th>操作</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody id="posts-tbody" class="bg-white divide-y divide-slate-200"></tbody>
|
</thead>
|
||||||
</table>
|
<tbody id="posts-tbody"></tbody>
|
||||||
<div id="loading-spinner" class="text-center py-8 text-slate-400 hidden">
|
</table>
|
||||||
<svg class="animate-spin h-6 w-6 mx-auto" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
</div>
|
||||||
|
<div id="loading-spinner" class="text-center py-8 text-slate-500 hidden">
|
||||||
|
<svg class="animate-spin h-6 w-6 mx-auto text-cyan-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-sm">加载中...</span>
|
<span class="text-sm">加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="empty-msg" class="text-center py-12 text-slate-400 hidden">暂无帖子</div>
|
<div id="empty-msg" class="text-center py-12 text-slate-500 hidden">暂无帖子</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
const tagClassMap = {
|
const tagClassMap = {
|
||||||
'讨论': 'bg-blue-100 text-blue-800',
|
'讨论': 'bg-blue-500/20 text-blue-400 border-blue-500/30',
|
||||||
'求助': 'bg-yellow-100 text-yellow-800',
|
'求助': 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
|
||||||
'分享': 'bg-green-100 text-green-800',
|
'分享': 'bg-green-500/20 text-green-400 border-green-500/30',
|
||||||
'公告': 'bg-red-100 text-red-800'
|
'公告': 'bg-red-500/20 text-red-400 border-red-500/30'
|
||||||
};
|
};
|
||||||
|
|
||||||
async function loadPosts() {
|
async function loadPosts() {
|
||||||
@@ -79,19 +81,19 @@ async function loadPosts() {
|
|||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
data.posts.forEach(p => {
|
data.posts.forEach(p => {
|
||||||
const tagClass = tagClassMap[p.tag] || 'bg-slate-100 text-slate-800';
|
const tagClass = tagClassMap[p.tag] || 'bg-slate-500/20 text-slate-400 border-slate-500/30';
|
||||||
html += `<tr>
|
html += `<tr>
|
||||||
<td class="px-6 py-4 text-sm text-slate-900">${p.id}</td>
|
<td>${p.id}</td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-900 max-w-xs truncate">${p.title}</td>
|
<td class="font-medium text-slate-200 max-w-xs truncate">${p.title}</td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-500">${p.author}</td>
|
<td class="text-slate-400">${p.author}</td>
|
||||||
<td class="px-6 py-4"><span class="px-2 py-1 text-xs rounded-full ${tagClass}">${p.tag || '-'}</span></td>
|
<td><span class="badge-futuristic ${tagClass}">${p.tag || '-'}</span></td>
|
||||||
<td class="px-6 py-4">
|
<td>
|
||||||
<span class="px-2 py-1 text-xs rounded-full ${p.pinned ? 'bg-orange-100 text-orange-800' : 'bg-slate-100 text-slate-500'}">${p.pinned ? '已置顶' : '未置顶'}</span>
|
<span class="badge-futuristic ${p.pinned ? 'bg-orange-500/20 text-orange-400 border-orange-500/30' : 'bg-slate-500/20 text-slate-400 border-slate-500/30'}">${p.pinned ? '已置顶' : '未置顶'}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-500">${p.created_at}</td>
|
<td class="text-slate-400">${p.created_at}</td>
|
||||||
<td class="px-6 py-4 space-x-2">
|
<td class="space-x-2">
|
||||||
<button onclick="togglePin(${p.id}, ${p.pinned})" class="px-2 py-1 text-xs ${p.pinned ? 'bg-slate-100 text-slate-700 border-slate-300' : 'bg-orange-100 text-orange-700 border-orange-300'} border rounded hover:opacity-80">${p.pinned ? '取消置顶' : '置顶'}</button>
|
<button onclick="togglePin(${p.id}, ${p.pinned})" class="${p.pinned ? 'btn-outline-futuristic' : 'btn-futuristic'} px-3 py-1 text-xs">${p.pinned ? '取消置顶' : '置顶'}</button>
|
||||||
<button onclick="deletePost(${p.id}, '${p.title.replace(/'/g, "\\'")}')" class="px-2 py-1 text-xs bg-red-100 text-red-700 border border-red-300 rounded hover:bg-red-200">删除</button>
|
<button onclick="deletePost(${p.id}, '${p.title.replace(/'/g, "\\'")}')" class="btn-outline-futuristic px-3 py-1 text-xs border-red-500/30 text-red-400 hover:bg-red-500/20 hover:border-red-500/50">删除</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,46 +29,90 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="bg-white shadow-sm rounded-lg border border-slate-200 overflow-hidden">
|
<!-- 桌面端表格视图 -->
|
||||||
<table class="min-w-full divide-y divide-slate-200">
|
<div class="hidden md:block bg-white shadow-sm rounded-lg border border-slate-200 overflow-hidden">
|
||||||
<thead class="bg-slate-50">
|
<div class="overflow-x-auto">
|
||||||
<tr>
|
<table class="min-w-full divide-y divide-slate-200">
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">ID</th>
|
<thead class="bg-slate-50">
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">申请人</th>
|
<tr>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">杯赛</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">ID</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">姓名</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">申请人</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">邮箱</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">杯赛</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">申请理由</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">姓名</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">申请时间</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">邮箱</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">操作</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">申请理由</th>
|
||||||
</tr>
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">申请时间</th>
|
||||||
</thead>
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">操作</th>
|
||||||
<tbody id="applications-table-body" class="bg-white divide-y divide-slate-200">
|
</tr>
|
||||||
{% for app in apps %}
|
</thead>
|
||||||
<tr data-id="{{ app.id }}">
|
<tbody id="applications-table-body" class="bg-white divide-y divide-slate-200">
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-900">{{ app.id }}</td>
|
{% for app in apps %}
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-900">{{ app.user.name }}</td>
|
<tr data-id="{{ app.id }}">
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">{{ app.contest.name }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-900">{{ app.id }}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">{{ app.name }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-900">{{ app.user.name }}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">{{ app.email }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">{{ app.contest.name }}</td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-500 max-w-xs truncate">{{ app.reason }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">{{ app.name }}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">{{ app.applied_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">{{ app.email }}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
<td class="px-6 py-4 text-sm text-slate-500 max-w-xs truncate">{{ app.reason }}</td>
|
||||||
<form action="{{ url_for('approve_teacher_application', app_id=app.id) }}" method="post" style="display:inline;">
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">{{ app.applied_at.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||||
<button type="submit" class="text-green-600 hover:text-green-900 mr-2">批准</button>
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||||
</form>
|
<form action="{{ url_for('approve_teacher_application', app_id=app.id) }}" method="post" style="display:inline;">
|
||||||
<form action="{{ url_for('reject_teacher_application', app_id=app.id) }}" method="post" style="display:inline;">
|
<button type="submit" class="text-green-600 hover:text-green-900 mr-2">批准</button>
|
||||||
<button type="submit" class="text-red-600 hover:text-red-900">拒绝</button>
|
</form>
|
||||||
</form>
|
<form action="{{ url_for('reject_teacher_application', app_id=app.id) }}" method="post" style="display:inline;">
|
||||||
</td>
|
<button type="submit" class="text-red-600 hover:text-red-900">拒绝</button>
|
||||||
</tr>
|
</form>
|
||||||
{% else %}
|
</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td colspan="8" class="px-6 py-12 text-center text-slate-500">暂无待审核的教师申请</td>
|
{% else %}
|
||||||
</tr>
|
<tr>
|
||||||
{% endfor %}
|
<td colspan="8" class="px-6 py-12 text-center text-slate-500">暂无待审核的教师申请</td>
|
||||||
</tbody>
|
</tr>
|
||||||
</table>
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 移动端卡片视图 -->
|
||||||
|
<div class="md:hidden space-y-4">
|
||||||
|
{% for app in apps %}
|
||||||
|
<div class="bg-white shadow-sm rounded-lg border border-slate-200 p-4" data-id="{{ app.id }}">
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-slate-400 mb-1">ID: {{ app.id }}</div>
|
||||||
|
<div class="font-semibold text-slate-900">{{ app.user.name }}</div>
|
||||||
|
<div class="text-sm text-slate-600 mt-1">{{ app.contest.name }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t border-slate-100 pt-3 space-y-2 text-sm">
|
||||||
|
<div><span class="text-slate-500">姓名:</span><span class="text-slate-900">{{ app.name }}</span></div>
|
||||||
|
<div><span class="text-slate-500">邮箱:</span><span class="text-slate-900 break-all">{{ app.email }}</span></div>
|
||||||
|
<div><span class="text-slate-500">申请理由:</span><span class="text-slate-900">{{ app.reason }}</span></div>
|
||||||
|
<div><span class="text-slate-500">申请时间:</span><span class="text-slate-900">{{ app.applied_at.strftime('%Y-%m-%d %H:%M') }}</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t border-slate-100 pt-3 flex gap-3">
|
||||||
|
<form action="{{ url_for('approve_teacher_application', app_id=app.id) }}" method="post" class="flex-1">
|
||||||
|
<button type="submit" class="w-full px-4 py-2.5 bg-green-500 hover:bg-green-600 active:bg-green-700 text-white font-medium rounded-lg transition-colors touch-manipulation">
|
||||||
|
批准
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<form action="{{ url_for('reject_teacher_application', app_id=app.id) }}" method="post" class="flex-1">
|
||||||
|
<button type="submit" class="w-full px-4 py-2.5 bg-red-500 hover:bg-red-600 active:bg-red-700 text-white font-medium rounded-lg transition-colors touch-manipulation">
|
||||||
|
拒绝
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="bg-white shadow-sm rounded-lg border border-slate-200 p-8 text-center text-slate-500">
|
||||||
|
暂无待审核的教师申请
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -4,51 +4,53 @@
|
|||||||
|
|
||||||
{% block admin_content %}
|
{% block admin_content %}
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<h1 class="text-2xl font-bold text-slate-900">用户管理</h1>
|
<h1 class="text-3xl font-extrabold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">用户管理</h1>
|
||||||
|
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<input id="search-input" type="text" placeholder="搜索用户名/邮箱..." class="flex-1 px-4 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
<input id="search-input" type="text" placeholder="搜索用户名/邮箱..." class="input-futuristic flex-1">
|
||||||
<select id="role-filter" class="px-4 py-2 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
<select id="role-filter" class="input-futuristic">
|
||||||
<option value="">全部角色</option>
|
<option value="">全部角色</option>
|
||||||
<option value="admin">管理员</option>
|
<option value="admin">管理员</option>
|
||||||
<option value="teacher">教师</option>
|
<option value="teacher">教师</option>
|
||||||
<option value="student">学生</option>
|
<option value="student">学生</option>
|
||||||
</select>
|
</select>
|
||||||
<button onclick="loadUsers()" class="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700">搜索</button>
|
<button onclick="loadUsers()" class="btn-futuristic px-6">搜索</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white shadow-sm rounded-lg border border-slate-200 overflow-hidden">
|
<div class="futuristic-card-dark overflow-hidden">
|
||||||
<table class="min-w-full divide-y divide-slate-200">
|
<div class="overflow-x-auto">
|
||||||
<thead class="bg-slate-50">
|
<table class="table-futuristic">
|
||||||
<tr>
|
<thead>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">ID</th>
|
<tr>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">用户名</th>
|
<th>ID</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">邮箱</th>
|
<th>用户名</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">角色</th>
|
<th>邮箱</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">状态</th>
|
<th>角色</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">注册时间</th>
|
<th>状态</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">操作</th>
|
<th>注册时间</th>
|
||||||
</tr>
|
<th>操作</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody id="users-tbody" class="bg-white divide-y divide-slate-200"></tbody>
|
</thead>
|
||||||
</table>
|
<tbody id="users-tbody"></tbody>
|
||||||
<div id="loading-spinner" class="text-center py-8 text-slate-400 hidden">
|
</table>
|
||||||
<svg class="animate-spin h-6 w-6 mx-auto" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
</div>
|
||||||
|
<div id="loading-spinner" class="text-center py-8 text-slate-500 hidden">
|
||||||
|
<svg class="animate-spin h-6 w-6 mx-auto text-cyan-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-sm">加载中...</span>
|
<span class="text-sm">加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="empty-msg" class="text-center py-12 text-slate-400 hidden">暂无用户</div>
|
<div id="empty-msg" class="text-center py-12 text-slate-500 hidden">暂无用户</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
const roleMap = {
|
const roleMap = {
|
||||||
'admin': ['管理员', 'bg-red-100 text-red-800'],
|
'admin': ['管理员', 'bg-red-500/20 text-red-400 border-red-500/30'],
|
||||||
'teacher': ['教师', 'bg-blue-100 text-blue-800'],
|
'teacher': ['教师', 'bg-blue-500/20 text-blue-400 border-blue-500/30'],
|
||||||
'student': ['学生', 'bg-green-100 text-green-800']
|
'student': ['学生', 'bg-green-500/20 text-green-400 border-green-500/30']
|
||||||
};
|
};
|
||||||
|
|
||||||
async function loadUsers() {
|
async function loadUsers() {
|
||||||
@@ -77,22 +79,22 @@ async function loadUsers() {
|
|||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
data.users.forEach(u => {
|
data.users.forEach(u => {
|
||||||
const [roleText, roleClass] = roleMap[u.role] || ['未知', 'bg-slate-100 text-slate-800'];
|
const [roleText, roleClass] = roleMap[u.role] || ['未知', 'bg-slate-500/20 text-slate-400 border-slate-500/30'];
|
||||||
const banned = u.is_banned;
|
const banned = u.is_banned;
|
||||||
html += `<tr>
|
html += `<tr>
|
||||||
<td class="px-6 py-4 text-sm text-slate-900">${u.id}</td>
|
<td>${u.id}</td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-900">${u.name}</td>
|
<td class="font-medium text-slate-200">${u.name}</td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-500">${u.email || '-'}</td>
|
<td class="text-slate-400">${u.email || '-'}</td>
|
||||||
<td class="px-6 py-4">
|
<td>
|
||||||
<span class="px-2 py-1 text-xs rounded-full ${roleClass}">${roleText}</span>
|
<span class="badge-futuristic ${roleClass}">${roleText}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4">
|
<td>
|
||||||
<span class="px-2 py-1 text-xs rounded-full ${banned ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800'}">${banned ? '已封禁' : '正常'}</span>
|
<span class="badge-futuristic ${banned ? 'bg-red-500/20 text-red-400 border-red-500/30' : 'bg-green-500/20 text-green-400 border-green-500/30'}">${banned ? '已封禁' : '正常'}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 text-sm text-slate-500">${u.created_at}</td>
|
<td class="text-slate-400">${u.created_at}</td>
|
||||||
<td class="px-6 py-4 space-x-2">
|
<td class="space-x-2">
|
||||||
<button onclick="toggleBan(${u.id}, ${banned})" class="px-2 py-1 text-xs ${banned ? 'bg-green-100 text-green-700 border-green-300' : 'bg-yellow-100 text-yellow-700 border-yellow-300'} border rounded hover:opacity-80">${banned ? '解封' : '封禁'}</button>
|
<button onclick="toggleBan(${u.id}, ${banned})" class="${banned ? 'btn-futuristic' : 'btn-outline-futuristic'} px-3 py-1 text-xs">${banned ? '解封' : '封禁'}</button>
|
||||||
<button onclick="deleteUser(${u.id}, '${u.name.replace(/'/g, "\\'")}')" class="px-2 py-1 text-xs bg-red-100 text-red-700 border border-red-300 rounded hover:bg-red-200">删除</button>
|
<button onclick="deleteUser(${u.id}, '${u.name.replace(/'/g, "\\'")}')" class="btn-outline-futuristic px-3 py-1 text-xs border-red-500/30 text-red-400 hover:bg-red-500/20 hover:border-red-500/50">删除</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,92 +4,101 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="max-w-3xl mx-auto py-8">
|
<div class="max-w-3xl mx-auto py-8">
|
||||||
<div class="bg-white shadow-sm rounded-lg border border-slate-200 p-6">
|
<div class="futuristic-card p-6">
|
||||||
<h1 class="text-2xl font-bold text-slate-900 mb-6">申请举办杯赛</h1>
|
<h1 class="text-2xl font-bold bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent mb-6">申请举办杯赛</h1>
|
||||||
<form method="POST" action="{{ url_for('apply_contest') }}" class="space-y-6">
|
<form method="POST" action="{{ url_for('apply_contest') }}" enctype="multipart/form-data" class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label for="name" class="block text-sm font-medium text-slate-700 mb-1">杯赛名称 <span class="text-red-500">*</span></label>
|
<label for="name" class="block text-sm font-medium text-slate-300 mb-1">杯赛名称 <span class="text-red-400">*</span></label>
|
||||||
<input type="text" id="name" name="name" required
|
<input type="text" id="name" name="name" required
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
|
class="input-futuristic w-full px-3 py-2 sm:text-sm"
|
||||||
placeholder="例如:2026年星火杯">
|
placeholder="例如:2026年星火杯">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="organizer" class="block text-sm font-medium text-slate-700 mb-1">主办方 <span class="text-red-500">*</span></label>
|
<label for="organizer" class="block text-sm font-medium text-slate-300 mb-1">主办方 <span class="text-red-400">*</span></label>
|
||||||
<input type="text" id="organizer" name="organizer" required
|
<input type="text" id="organizer" name="organizer" required
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
|
class="input-futuristic w-full px-3 py-2 sm:text-sm"
|
||||||
placeholder="例如:星火杯组委会">
|
placeholder="例如:星火杯组委会">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="description" class="block text-sm font-medium text-slate-700 mb-1">描述 <span class="text-red-500">*</span></label>
|
<label for="image" class="block text-sm font-medium text-slate-300 mb-1">杯赛图片</label>
|
||||||
|
<input type="file" id="image" name="image" accept="image/*"
|
||||||
|
class="input-futuristic w-full px-3 py-2 sm:text-sm">
|
||||||
|
<p class="mt-1 text-xs text-slate-400">上传杯赛封面图片(可选,支持 JPG、PNG、GIF 格式,最大 5MB)</p>
|
||||||
|
<div id="image-preview" class="mt-3 hidden">
|
||||||
|
<img id="preview-img" src="" alt="预览" class="max-w-xs rounded-lg border border-white/10 shadow-sm">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="description" class="block text-sm font-medium text-slate-300 mb-1">描述 <span class="text-red-400">*</span></label>
|
||||||
<textarea id="description" name="description" rows="4" required
|
<textarea id="description" name="description" rows="4" required
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
|
class="input-futuristic w-full px-3 py-2 sm:text-sm"
|
||||||
placeholder="简要介绍杯赛的目的、规则等"></textarea>
|
placeholder="简要介绍杯赛的目的、规则等"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="contact" class="block text-sm font-medium text-slate-700 mb-1">联系方式 <span class="text-red-500">*</span></label>
|
<label for="contact" class="block text-sm font-medium text-slate-300 mb-1">联系方式 <span class="text-red-400">*</span></label>
|
||||||
<input type="text" id="contact" name="contact" required
|
<input type="text" id="contact" name="contact" required
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
|
class="input-futuristic w-full px-3 py-2 sm:text-sm"
|
||||||
placeholder="邮箱或手机号">
|
placeholder="邮箱或手机号">
|
||||||
<p class="mt-1 text-xs text-slate-500">用于管理员与您联系,不会公开显示</p>
|
<p class="mt-1 text-xs text-slate-400">用于管理员与您联系,不会公开显示</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="start_date" class="block text-sm font-medium text-slate-700 mb-1">开始日期 <span class="text-red-500">*</span></label>
|
<label for="start_date" class="block text-sm font-medium text-slate-300 mb-1">开始日期 <span class="text-red-400">*</span></label>
|
||||||
<input type="date" id="start_date" name="start_date" required
|
<input type="date" id="start_date" name="start_date" required
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
class="input-futuristic w-full px-3 py-2 sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="end_date" class="block text-sm font-medium text-slate-700 mb-1">结束日期 <span class="text-red-500">*</span></label>
|
<label for="end_date" class="block text-sm font-medium text-slate-300 mb-1">结束日期 <span class="text-red-400">*</span></label>
|
||||||
<input type="date" id="end_date" name="end_date" required
|
<input type="date" id="end_date" name="end_date" required
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
class="input-futuristic w-full px-3 py-2 sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="total_score" class="block text-sm font-medium text-slate-700 mb-1">杯赛满分 <span class="text-red-500">*</span></label>
|
<label for="total_score" class="block text-sm font-medium text-slate-300 mb-1">杯赛满分 <span class="text-red-400">*</span></label>
|
||||||
<input type="number" id="total_score" name="total_score" required min="1" value="150"
|
<input type="number" id="total_score" name="total_score" required min="1" value="150"
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
|
class="input-futuristic w-full px-3 py-2 sm:text-sm"
|
||||||
placeholder="例如:150">
|
placeholder="例如:150">
|
||||||
<p class="mt-1 text-xs text-slate-500">杯赛考试的默认满分分数</p>
|
<p class="mt-1 text-xs text-slate-400">杯赛考试的默认满分分数</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- 报备信息 -->
|
<!-- 报备信息 -->
|
||||||
<div class="border-t border-slate-200 pt-6 mt-2">
|
<div class="border-t border-white/10 pt-6 mt-2">
|
||||||
<h2 class="text-lg font-semibold text-slate-900 mb-4">报备信息</h2>
|
<h2 class="text-lg font-semibold bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent mb-4">报备信息</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="responsible_person" class="block text-sm font-medium text-slate-700 mb-1">责任人姓名 <span class="text-red-500">*</span></label>
|
<label for="responsible_person" class="block text-sm font-medium text-slate-300 mb-1">责任人姓名 <span class="text-red-400">*</span></label>
|
||||||
<input type="text" id="responsible_person" name="responsible_person" required
|
<input type="text" id="responsible_person" name="responsible_person" required
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
|
class="input-futuristic w-full px-3 py-2 sm:text-sm"
|
||||||
placeholder="赛事责任人真实姓名">
|
placeholder="赛事责任人真实姓名">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="responsible_phone" class="block text-sm font-medium text-slate-700 mb-1">责任人电话 <span class="text-red-500">*</span></label>
|
<label for="responsible_phone" class="block text-sm font-medium text-slate-300 mb-1">责任人电话 <span class="text-red-400">*</span></label>
|
||||||
<input type="tel" id="responsible_phone" name="responsible_phone" required
|
<input type="tel" id="responsible_phone" name="responsible_phone" required
|
||||||
pattern="1[3-9]\d{9}"
|
pattern="1[3-9]\d{9}"
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
|
class="input-futuristic w-full px-3 py-2 sm:text-sm"
|
||||||
placeholder="11位手机号码">
|
placeholder="11位手机号码">
|
||||||
<p class="mt-1 text-xs text-slate-500">请填写有效的手机号码,审核通过后将公开展示</p>
|
<p class="mt-1 text-xs text-slate-400">请填写有效的手机号码,审核通过后将公开展示</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="responsible_email" class="block text-sm font-medium text-slate-700 mb-1">责任人邮箱 <span class="text-red-500">*</span></label>
|
<label for="responsible_email" class="block text-sm font-medium text-slate-300 mb-1">责任人邮箱 <span class="text-red-400">*</span></label>
|
||||||
<input type="email" id="responsible_email" name="responsible_email" required
|
<input type="email" id="responsible_email" name="responsible_email" required
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
|
class="input-futuristic w-full px-3 py-2 sm:text-sm"
|
||||||
placeholder="example@email.com">
|
placeholder="example@email.com">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="organization" class="block text-sm font-medium text-slate-700 mb-1">所属机构/学校 <span class="text-red-500">*</span></label>
|
<label for="organization" class="block text-sm font-medium text-slate-300 mb-1">所属机构/学校 <span class="text-red-400">*</span></label>
|
||||||
<input type="text" id="organization" name="organization" required
|
<input type="text" id="organization" name="organization" required
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
|
class="input-futuristic w-full px-3 py-2 sm:text-sm"
|
||||||
placeholder="例如:XX大学、XX教育机构">
|
placeholder="例如:XX大学、XX教育机构">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-3 text-xs text-amber-600 bg-amber-50 border border-amber-200 rounded-md p-3">以上报备信息将在杯赛详情页公开展示,请确保信息真实有效。</p>
|
<p class="mt-3 text-xs text-amber-300 bg-amber-500/10 border border-amber-500/30 rounded-md p-3">以上报备信息将在杯赛详情页公开展示,请确保信息真实有效。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end gap-3">
|
<div class="flex justify-end gap-3">
|
||||||
<a href="{{ url_for('contest_list') }}" class="px-5 py-2.5 border border-slate-300 rounded-md text-sm font-medium text-slate-700 bg-white hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
|
<a href="{{ url_for('contest_list') }}" class="btn-outline-futuristic px-5 py-2.5 text-sm font-medium">
|
||||||
取消
|
取消
|
||||||
</a>
|
</a>
|
||||||
<button type="submit" id="submit-btn"
|
<button type="submit" id="submit-btn"
|
||||||
class="px-5 py-2.5 bg-primary text-white rounded-md text-sm font-medium hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
|
class="btn-futuristic px-5 py-2.5 text-sm font-medium">
|
||||||
提交申请
|
提交申请
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,6 +109,30 @@
|
|||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
|
// 图片预览功能
|
||||||
|
document.getElementById('image').addEventListener('change', function(e) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
// 检查文件大小(5MB)
|
||||||
|
if (file.size > 5 * 1024 * 1024) {
|
||||||
|
alert('图片大小不能超过 5MB');
|
||||||
|
e.target.value = '';
|
||||||
|
document.getElementById('image-preview').classList.add('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示预览
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
document.getElementById('preview-img').src = e.target.result;
|
||||||
|
document.getElementById('image-preview').classList.remove('hidden');
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
} else {
|
||||||
|
document.getElementById('image-preview').classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
document.querySelector('form').addEventListener('submit', function() {
|
document.querySelector('form').addEventListener('submit', function() {
|
||||||
const btn = document.getElementById('submit-btn');
|
const btn = document.getElementById('submit-btn');
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
|
|||||||
@@ -5,29 +5,29 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="max-w-3xl mx-auto py-8">
|
<div class="max-w-3xl mx-auto py-8">
|
||||||
<!-- 邀请码激活区域 -->
|
<!-- 邀请码激活区域 -->
|
||||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-6 mb-6">
|
<div class="futuristic-card border-l-4 border-cyan-500 p-6 mb-6">
|
||||||
<h2 class="text-lg font-semibold text-blue-800 mb-2">🎫 已有邀请码?在此激活</h2>
|
<h2 class="text-lg font-semibold text-cyan-400 mb-2">🎫 已有邀请码?在此激活</h2>
|
||||||
<p class="text-sm text-blue-600 mb-4">审核通过后,您会在私聊消息中收到邀请码。输入邀请码即可正式成为杯赛老师。</p>
|
<p class="text-sm text-slate-300 mb-4">审核通过后,您会在私聊消息中收到邀请码。输入邀请码即可正式成为杯赛老师。</p>
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<input type="text" id="invite_code" placeholder="请输入邀请码,如 TC-A3K9M2X7"
|
<input type="text" id="invite_code" placeholder="请输入邀请码,如 TC-A3K9M2X7"
|
||||||
class="flex-1 px-3 py-2 border border-blue-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm uppercase"
|
class="input-futuristic flex-1 px-3 py-2 sm:text-sm uppercase"
|
||||||
maxlength="11">
|
maxlength="11">
|
||||||
<button type="button" id="activate_btn" onclick="activateInviteCode()"
|
<button type="button" id="activate_btn" onclick="activateInviteCode()"
|
||||||
class="px-5 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
class="btn-futuristic px-5 py-2 text-sm font-medium">
|
||||||
激活
|
激活
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p id="invite_result" class="text-sm mt-2 hidden"></p>
|
<p id="invite_result" class="text-sm mt-2 hidden"></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white shadow-sm rounded-lg border border-slate-200 p-6">
|
<div class="futuristic-card p-6">
|
||||||
<h1 class="text-2xl font-bold text-slate-900 mb-6">申请成为杯赛老师</h1>
|
<h1 class="text-2xl font-bold bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent mb-6">申请成为杯赛老师</h1>
|
||||||
<p class="text-sm text-slate-500 mb-6">请选择您希望担任老师的杯赛,并填写申请理由。</p>
|
<p class="text-sm text-slate-400 mb-6">请选择您希望担任老师的杯赛,并填写申请理由。</p>
|
||||||
<form method="POST" action="{{ url_for('apply_teacher') }}" class="space-y-6">
|
<form method="POST" action="{{ url_for('apply_teacher') }}" class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label for="contest_id" class="block text-sm font-medium text-slate-700 mb-1">选择杯赛 <span class="text-red-500">*</span></label>
|
<label for="contest_id" class="block text-sm font-medium text-slate-300 mb-1">选择杯赛 <span class="text-red-400">*</span></label>
|
||||||
<select id="contest_id" name="contest_id" required
|
<select id="contest_id" name="contest_id" required
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
class="input-futuristic w-full px-3 py-2 sm:text-sm">
|
||||||
<option value="">请选择杯赛</option>
|
<option value="">请选择杯赛</option>
|
||||||
{% for contest in contests %}
|
{% for contest in contests %}
|
||||||
<option value="{{ contest.id }}" {% if selected_contest and selected_contest.id == contest.id %}selected{% endif %}>{{ contest.name }}</option>
|
<option value="{{ contest.id }}" {% if selected_contest and selected_contest.id == contest.id %}selected{% endif %}>{{ contest.name }}</option>
|
||||||
@@ -35,29 +35,29 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="name" class="block text-sm font-medium text-slate-700 mb-1">姓名 <span class="text-red-500">*</span></label>
|
<label for="name" class="block text-sm font-medium text-slate-300 mb-1">姓名 <span class="text-red-400">*</span></label>
|
||||||
<input type="text" id="name" name="name" required
|
<input type="text" id="name" name="name" required
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
|
class="input-futuristic w-full px-3 py-2 sm:text-sm"
|
||||||
placeholder="您的真实姓名">
|
placeholder="您的真实姓名">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="email" class="block text-sm font-medium text-slate-700 mb-1">邮箱 <span class="text-red-500">*</span></label>
|
<label for="email" class="block text-sm font-medium text-slate-300 mb-1">邮箱 <span class="text-red-400">*</span></label>
|
||||||
<input type="email" id="email" name="email" required
|
<input type="email" id="email" name="email" required
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
|
class="input-futuristic w-full px-3 py-2 sm:text-sm"
|
||||||
placeholder="用于接收审核结果通知">
|
placeholder="用于接收审核结果通知">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="reason" class="block text-sm font-medium text-slate-700 mb-1">申请理由 <span class="text-red-500">*</span></label>
|
<label for="reason" class="block text-sm font-medium text-slate-300 mb-1">申请理由 <span class="text-red-400">*</span></label>
|
||||||
<textarea id="reason" name="reason" rows="5" required
|
<textarea id="reason" name="reason" rows="5" required
|
||||||
class="w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm"
|
class="input-futuristic w-full px-3 py-2 sm:text-sm"
|
||||||
placeholder="请简要说明您的教学经历或申请原因"></textarea>
|
placeholder="请简要说明您的教学经历或申请原因"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end gap-3">
|
<div class="flex justify-end gap-3">
|
||||||
<a href="{{ url_for('home') }}" class="px-5 py-2.5 border border-slate-300 rounded-md text-sm font-medium text-slate-700 bg-white hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
|
<a href="{{ url_for('home') }}" class="btn-outline-futuristic px-5 py-2.5 text-sm font-medium">
|
||||||
取消
|
取消
|
||||||
</a>
|
</a>
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
class="px-5 py-2.5 bg-primary text-white rounded-md text-sm font-medium hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
|
class="btn-futuristic px-5 py-2.5 text-sm font-medium">
|
||||||
提交申请
|
提交申请
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,38 +40,42 @@
|
|||||||
onload="document.addEventListener('DOMContentLoaded',function(){renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}],throwOnError:false});})"></script>
|
onload="document.addEventListener('DOMContentLoaded',function(){renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}],throwOnError:false});})"></script>
|
||||||
<script src="/static/js/rich-editor.js"></script>
|
<script src="/static/js/rich-editor.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen bg-slate-50 font-sans text-slate-800 antialiased selection:bg-primary/30 selection:text-slate-900">
|
<body class="min-h-screen font-sans text-slate-800 antialiased selection:bg-primary/30 selection:text-slate-900">
|
||||||
|
<!-- 动态背景 -->
|
||||||
|
<div class="particles-container">
|
||||||
|
<canvas id="particles-bg" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1;"></canvas>
|
||||||
|
</div>
|
||||||
{% block navbar %}
|
{% block navbar %}
|
||||||
<nav class="sticky top-0 z-40 bg-white/80 backdrop-blur-md border-b border-slate-200/80 shadow-sm transition-all duration-300">
|
<nav class="navbar-futuristic sticky top-0 z-40 transition-all duration-300">
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="flex justify-between h-16">
|
<div class="flex justify-between h-16">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<a href="/" class="flex-shrink-0 flex items-center">
|
<a href="/" class="flex-shrink-0 flex items-center">
|
||||||
<span class="text-xl font-bold text-primary">智联青云</span>
|
<span class="text-2xl font-bold bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent">智联青云</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||||
<a href="/contests" class="border-transparent text-slate-500 hover:border-primary hover:text-slate-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
<a href="/contests" class="text-slate-600 hover:text-blue-600 inline-flex items-center px-4 py-2 rounded-xl text-sm font-medium transition-all hover:bg-blue-50">
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3l14 9-14 9V3z"/></svg>
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3l14 9-14 9V3z"/></svg>
|
||||||
杯赛专栏
|
杯赛专栏
|
||||||
</a>
|
</a>
|
||||||
<a href="/exams" class="border-transparent text-slate-500 hover:border-primary hover:text-slate-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
<a href="/exams" class="text-slate-600 hover:text-blue-600 inline-flex items-center px-4 py-2 rounded-xl text-sm font-medium transition-all hover:bg-blue-50">
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
|
||||||
考试系统
|
考试系统
|
||||||
</a>
|
</a>
|
||||||
<a href="/forum" class="border-transparent text-slate-500 hover:border-primary hover:text-slate-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
<a href="/forum" class="text-slate-600 hover:text-blue-600 inline-flex items-center px-4 py-2 rounded-xl text-sm font-medium transition-all hover:bg-blue-50">
|
||||||
<svg class="w-4 h-4 mr-2" 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>
|
<svg class="w-4 h-4 mr-2" 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>
|
||||||
社区论坛
|
社区论坛
|
||||||
</a>
|
</a>
|
||||||
<a href="/chat" class="border-transparent text-slate-500 hover:border-primary hover:text-slate-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
<a href="/chat" class="text-slate-600 hover:text-blue-600 inline-flex items-center px-4 py-2 rounded-xl text-sm font-medium transition-all hover:bg-blue-50">
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
|
||||||
消息
|
消息
|
||||||
</a>
|
</a>
|
||||||
<a href="/profile" class="border-transparent text-slate-500 hover:border-primary hover:text-slate-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
<a href="/profile" class="text-slate-600 hover:text-blue-600 inline-flex items-center px-4 py-2 rounded-xl text-sm font-medium transition-all hover:bg-blue-50">
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
||||||
个人
|
个人
|
||||||
</a>
|
</a>
|
||||||
{% if user and user.role == 'student' %}
|
{% if user and user.role == 'student' %}
|
||||||
<a href="/apply-teacher" class="border-transparent text-slate-500 hover:border-primary hover:text-slate-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
<a href="/apply-teacher" class="text-slate-600 hover:text-blue-600 inline-flex items-center px-4 py-2 rounded-xl text-sm font-medium transition-all hover:bg-blue-50">
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
|
||||||
申请老师
|
申请老师
|
||||||
</a>
|
</a>
|
||||||
@@ -102,7 +106,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if user.role == 'admin' or user.role == 'teacher' %}
|
{% if user.role == 'admin' or user.role == 'teacher' %}
|
||||||
<a href="/admin" class="text-slate-500 hover:text-slate-700" title="管理后台">
|
<a href="/admin" class="text-slate-600 hover:text-blue-600" title="管理后台">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||||
@@ -141,6 +145,10 @@
|
|||||||
<svg class="w-4 h-4 mr-3" 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>
|
<svg class="w-4 h-4 mr-3" 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>
|
||||||
社区论坛
|
社区论坛
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/notifications" class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-slate-700 hover:bg-slate-100">
|
||||||
|
<svg class="w-4 h-4 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>
|
||||||
|
通知
|
||||||
|
</a>
|
||||||
<a href="/chat" class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-slate-700 hover:bg-slate-100">
|
<a href="/chat" class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-slate-700 hover:bg-slate-100">
|
||||||
<svg class="w-4 h-4 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
|
<svg class="w-4 h-4 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
|
||||||
消息
|
消息
|
||||||
@@ -490,5 +498,81 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 粒子背景动画
|
||||||
|
(function() {
|
||||||
|
const canvas = document.getElementById('particles-bg');
|
||||||
|
if (!canvas) return;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
|
||||||
|
const particles = [];
|
||||||
|
const particleCount = 80;
|
||||||
|
|
||||||
|
for (let i = 0; i < particleCount; i++) {
|
||||||
|
particles.push({
|
||||||
|
x: Math.random() * canvas.width,
|
||||||
|
y: Math.random() * canvas.height,
|
||||||
|
vx: (Math.random() - 0.5) * 0.5,
|
||||||
|
vy: (Math.random() - 0.5) * 0.5,
|
||||||
|
radius: Math.random() * 2 + 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// 绘制渐变背景
|
||||||
|
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
|
||||||
|
gradient.addColorStop(0, '#667eea');
|
||||||
|
gradient.addColorStop(1, '#764ba2');
|
||||||
|
ctx.fillStyle = gradient;
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// 绘制粒子
|
||||||
|
particles.forEach(p => {
|
||||||
|
p.x += p.vx;
|
||||||
|
p.y += p.vy;
|
||||||
|
|
||||||
|
if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
|
||||||
|
if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
|
||||||
|
ctx.fill();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 绘制连线
|
||||||
|
particles.forEach((p1, i) => {
|
||||||
|
particles.slice(i + 1).forEach(p2 => {
|
||||||
|
const dx = p1.x - p2.x;
|
||||||
|
const dy = p1.y - p2.y;
|
||||||
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (dist < 150) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(p1.x, p1.y);
|
||||||
|
ctx.lineTo(p2.x, p2.y);
|
||||||
|
ctx.strokeStyle = `rgba(255, 255, 255, ${0.2 * (1 - dist / 150)})`;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
requestAnimationFrame(draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw();
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -2,29 +2,29 @@
|
|||||||
{% block title %}消息 - 智联青云{% endblock %}
|
{% block title %}消息 - 智联青云{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="chatApp" class="flex bg-white/80 backdrop-blur-xl rounded-3xl shadow-2xl border border-white/50 overflow-hidden" style="height:calc(100vh - 120px);min-height:500px;">
|
<div id="chatApp" class="flex futuristic-card-dark overflow-hidden" style="height:calc(100vh - 120px);min-height:500px;">
|
||||||
<!-- 左侧面板 -->
|
<!-- 左侧面板 -->
|
||||||
<div id="leftPanel" class="w-full sm:w-80 border-r border-slate-200/60 bg-white/50 flex flex-col flex-shrink-0 relative z-10">
|
<div id="leftPanel" class="w-full sm:w-80 border-r border-white/10 bg-slate-900/50 flex flex-col flex-shrink-0 relative z-10">
|
||||||
<!-- Tab 切换:聊天 / 通知 -->
|
<!-- Tab 切换:聊天 / 通知 -->
|
||||||
<div class="flex p-3 gap-2 bg-white/40 border-b border-slate-200/50 backdrop-blur-md sticky top-0 z-20">
|
<div class="flex p-3 gap-2 bg-slate-900/60 border-b border-white/10 backdrop-blur-md sticky top-0 z-20">
|
||||||
<button id="tabChat" onclick="switchTab('chat')" class="flex-1 px-4 py-2 text-sm font-bold text-white bg-gradient-to-r from-indigo-500 to-purple-500 rounded-xl shadow-md transition-all duration-300">💬 聊天</button>
|
<button id="tabChat" onclick="switchTab('chat')" class="flex-1 px-4 py-2 text-sm font-bold text-white bg-gradient-to-r from-blue-500 to-cyan-500 rounded-xl shadow-md transition-all duration-300 hover:shadow-blue-500/50">💬 聊天</button>
|
||||||
<button id="tabNotif" onclick="switchTab('notif')" class="flex-1 px-4 py-2 text-sm font-medium text-slate-600 hover:text-indigo-600 hover:bg-white bg-white/50 rounded-xl transition-all duration-300 relative border border-white/50">
|
<button id="tabNotif" onclick="switchTab('notif')" class="flex-1 px-4 py-2 text-sm font-medium text-slate-300 hover:text-cyan-400 hover:bg-slate-800/50 bg-slate-800/30 rounded-xl transition-all duration-300 relative border border-white/10">
|
||||||
🔔 通知
|
🔔 通知
|
||||||
<span id="chatNotifBadge" class="hidden absolute -top-1.5 -right-1.5 bg-rose-500 text-white text-[10px] font-bold rounded-full h-5 min-w-[20px] px-1.5 flex items-center justify-center shadow-sm border-2 border-white transform animate-bounce">0</span>
|
<span id="chatNotifBadge" class="hidden absolute -top-1.5 -right-1.5 badge-futuristic text-[10px] font-bold rounded-full h-5 min-w-[20px] px-1.5 flex items-center justify-center shadow-sm border-2 border-slate-900 transform animate-bounce">0</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- 聊天面板 -->
|
<!-- 聊天面板 -->
|
||||||
<div id="chatPanel" class="flex-1 flex flex-col overflow-hidden">
|
<div id="chatPanel" class="flex-1 flex flex-col overflow-hidden">
|
||||||
<!-- 搜索 + 创建 -->
|
<!-- 搜索 + 创建 -->
|
||||||
<div class="p-4 space-y-3 bg-white/30 backdrop-blur-sm border-b border-slate-200/50">
|
<div class="p-4 space-y-3 bg-slate-900/40 backdrop-blur-sm border-b border-white/10">
|
||||||
<div class="relative group">
|
<div class="relative group">
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
<svg class="w-4 h-4 text-slate-400 group-focus-within:text-indigo-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
<svg class="w-4 h-4 text-slate-500 group-focus-within:text-cyan-400 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<input id="roomSearch" type="text" placeholder="搜索聊天..." class="w-full pl-10 pr-4 py-2.5 text-sm bg-white border border-slate-200/80 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500 transition-all shadow-sm" oninput="filterRooms()">
|
<input id="roomSearch" type="text" placeholder="搜索聊天..." class="input-futuristic w-full pl-10 pr-4 py-2.5 text-sm" oninput="filterRooms()">
|
||||||
</div>
|
</div>
|
||||||
<button onclick="showCreateGroup()" class="w-full px-4 py-2.5 text-sm font-bold bg-white text-indigo-600 border border-indigo-100 rounded-xl hover:bg-indigo-50 hover:shadow-md transition-all flex items-center justify-center gap-2 group">
|
<button onclick="showCreateGroup()" class="btn-futuristic w-full px-4 py-2.5 text-sm font-bold flex items-center justify-center gap-2 group">
|
||||||
<div class="w-6 h-6 rounded-full bg-indigo-100 text-indigo-600 flex items-center justify-center group-hover:bg-indigo-600 group-hover:text-white transition-colors">
|
<div class="w-6 h-6 rounded-full bg-cyan-500/20 text-cyan-400 flex items-center justify-center group-hover:bg-cyan-500 group-hover:text-white transition-colors">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
||||||
</div>
|
</div>
|
||||||
发起群聊
|
发起群聊
|
||||||
@@ -37,44 +37,44 @@
|
|||||||
<div id="chatNotifPanel" class="flex-1 overflow-y-auto hide-scrollbar hidden p-2 space-y-2 bg-transparent">
|
<div id="chatNotifPanel" class="flex-1 overflow-y-auto hide-scrollbar hidden p-2 space-y-2 bg-transparent">
|
||||||
<div id="chatNotifList" class="space-y-2"></div>
|
<div id="chatNotifList" class="space-y-2"></div>
|
||||||
<div id="chatNotifEmpty" class="flex flex-col items-center justify-center h-64 text-slate-400">
|
<div id="chatNotifEmpty" class="flex flex-col items-center justify-center h-64 text-slate-400">
|
||||||
<div class="w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mb-3">📭</div>
|
<div class="w-16 h-16 bg-slate-800/50 rounded-full flex items-center justify-center mb-3 border border-white/10">📭</div>
|
||||||
<div class="text-sm font-medium">暂无新通知</div>
|
<div class="text-sm font-medium">暂无新通知</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 右侧聊天区 -->
|
<!-- 右侧聊天区 -->
|
||||||
<div id="rightPanel" class="flex-1 flex flex-col hidden sm:flex bg-[url('data:image/svg+xml,%3Csvg width=\'60\' height=\'60\' viewBox=\'0 0 60 60\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cg fill=\'none\' fill-rule=\'evenodd\'%3E%3Cg fill=\'%236366f1\' fill-opacity=\'0.03\'%3E%3Cpath d=\'M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z\'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E')] relative">
|
<div id="rightPanel" class="flex-1 flex flex-col hidden sm:flex bg-[url('data:image/svg+xml,%3Csvg width=\'60\' height=\'60\' viewBox=\'0 0 60 60\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cg fill=\'none\' fill-rule=\'evenodd\'%3E%3Cg fill=\'%2306b6d4\' fill-opacity=\'0.05\'%3E%3Cpath d=\'M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z\'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E')] relative">
|
||||||
<div class="absolute inset-0 bg-gradient-to-br from-white/90 to-white/50 backdrop-blur-[2px] z-0"></div>
|
<div class="absolute inset-0 bg-gradient-to-br from-slate-900/95 to-slate-800/90 backdrop-blur-[2px] z-0"></div>
|
||||||
<!-- 未选中状态 -->
|
<!-- 未选中状态 -->
|
||||||
<div id="emptyState" class="flex-1 flex items-center justify-center z-10">
|
<div id="emptyState" class="flex-1 flex items-center justify-center z-10">
|
||||||
<div class="text-center transform transition-all hover:scale-105 duration-500">
|
<div class="text-center transform transition-all hover:scale-105 duration-500">
|
||||||
<div class="w-32 h-32 mx-auto bg-gradient-to-tr from-indigo-100 to-purple-100 rounded-full flex items-center justify-center mb-6 shadow-inner relative">
|
<div class="w-32 h-32 mx-auto bg-gradient-to-tr from-cyan-500/20 to-blue-500/20 rounded-full flex items-center justify-center mb-6 shadow-inner relative border border-cyan-500/30">
|
||||||
<div class="absolute inset-0 bg-indigo-400 blur-2xl opacity-20 rounded-full animate-pulse"></div>
|
<div class="absolute inset-0 bg-cyan-400 blur-2xl opacity-20 rounded-full animate-pulse"></div>
|
||||||
<span class="text-6xl animate-bounce">💬</span>
|
<span class="text-6xl animate-bounce">💬</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-2xl font-extrabold text-slate-800 tracking-tight mb-2">欢迎来到联考消息中心</h3>
|
<h3 class="text-2xl font-extrabold bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent tracking-tight mb-2">欢迎来到联考消息中心</h3>
|
||||||
<p class="text-slate-500 font-medium">在左侧选择一个聊天,或发起新的对话</p>
|
<p class="text-slate-400 font-medium">在左侧选择一个聊天,或发起新的对话</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 通知详情视图 -->
|
<!-- 通知详情视图 -->
|
||||||
<div id="notifDetailView" class="flex-1 flex-col hidden z-10 bg-white/80 backdrop-blur-md">
|
<div id="notifDetailView" class="flex-1 flex-col hidden z-10 bg-slate-900/80 backdrop-blur-md">
|
||||||
<div class="px-6 py-4 border-b border-slate-200/60 flex items-center gap-4 bg-white/50 sticky top-0 backdrop-blur-xl">
|
<div class="px-6 py-4 border-b border-white/10 flex items-center gap-4 bg-slate-800/50 sticky top-0 backdrop-blur-xl">
|
||||||
<button onclick="mobileBack()" class="sm:hidden w-8 h-8 rounded-lg bg-slate-100 text-slate-600 flex items-center justify-center flex-shrink-0" aria-label="返回">
|
<button onclick="mobileBack()" class="sm:hidden w-8 h-8 rounded-lg bg-slate-700/50 text-slate-300 flex items-center justify-center flex-shrink-0 border border-white/10" aria-label="返回">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="w-12 h-12 rounded-2xl bg-gradient-to-br from-indigo-100 to-purple-100 flex items-center justify-center text-2xl shadow-sm border border-white" id="notifDetailIconContainer">
|
<div class="w-12 h-12 rounded-2xl bg-gradient-to-br from-cyan-500/20 to-blue-500/20 flex items-center justify-center text-2xl shadow-sm border border-cyan-500/30" id="notifDetailIconContainer">
|
||||||
<span id="notifDetailIcon"></span>
|
<span id="notifDetailIcon"></span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div id="notifDetailType" class="text-lg font-extrabold text-slate-900"></div>
|
<div id="notifDetailType" class="text-lg font-extrabold text-white"></div>
|
||||||
<div id="notifDetailTime" class="text-xs font-medium text-slate-500 mt-1"></div>
|
<div id="notifDetailTime" class="text-xs font-medium text-slate-400 mt-1"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 overflow-y-auto p-8 hide-scrollbar">
|
<div class="flex-1 overflow-y-auto p-8 hide-scrollbar">
|
||||||
<div class="bg-white rounded-3xl p-8 shadow-sm border border-slate-100 relative overflow-hidden">
|
<div class="futuristic-card-dark rounded-3xl p-8 shadow-sm relative overflow-hidden">
|
||||||
<div class="absolute top-0 right-0 w-32 h-32 bg-indigo-50/50 rounded-bl-full -z-10"></div>
|
<div class="absolute top-0 right-0 w-32 h-32 bg-cyan-500/10 rounded-bl-full -z-10"></div>
|
||||||
<div id="notifDetailContent" class="text-base text-slate-700 leading-relaxed font-medium"></div>
|
<div id="notifDetailContent" class="text-base text-slate-300 leading-relaxed font-medium"></div>
|
||||||
<div id="notifDetailFrom" class="text-sm text-slate-400 mt-6 pt-6 border-t border-slate-100 flex items-center gap-2"></div>
|
<div id="notifDetailFrom" class="text-sm text-slate-500 mt-6 pt-6 border-t border-white/10 flex items-center gap-2"></div>
|
||||||
<div id="notifDetailActions" class="mt-6"></div>
|
<div id="notifDetailActions" class="mt-6"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,33 +82,33 @@
|
|||||||
<!-- 聊天视图 -->
|
<!-- 聊天视图 -->
|
||||||
<div id="chatView" class="flex-1 flex-col hidden z-10 relative">
|
<div id="chatView" class="flex-1 flex-col hidden z-10 relative">
|
||||||
<!-- 顶栏 -->
|
<!-- 顶栏 -->
|
||||||
<div id="chatHeader" class="px-6 py-4 border-b border-slate-200/60 flex items-center justify-between bg-white/60 backdrop-blur-xl sticky top-0 z-20">
|
<div id="chatHeader" class="px-6 py-4 border-b border-white/10 flex items-center justify-between bg-slate-800/60 backdrop-blur-xl sticky top-0 z-20">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<button onclick="mobileBack()" class="sm:hidden w-8 h-8 rounded-lg bg-slate-100 text-slate-600 flex items-center justify-center mr-1" aria-label="返回">
|
<button onclick="mobileBack()" class="sm:hidden w-8 h-8 rounded-lg bg-slate-700/50 text-slate-300 flex items-center justify-center mr-1 border border-white/10" aria-label="返回">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<div id="chatAvatarContainer" class="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-100 to-purple-100 flex items-center justify-center text-indigo-600 font-bold shadow-sm overflow-hidden">
|
<div id="chatAvatarContainer" class="w-10 h-10 rounded-xl bg-gradient-to-br from-cyan-500/20 to-blue-500/20 flex items-center justify-center text-cyan-400 font-bold shadow-sm overflow-hidden border border-cyan-500/30">
|
||||||
<!-- 头像动态插入 -->
|
<!-- 头像动态插入 -->
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span id="chatName" class="text-lg font-bold text-slate-900 tracking-tight"></span>
|
<span id="chatName" class="text-lg font-bold text-white tracking-tight"></span>
|
||||||
<span id="chatMemberCount" class="text-xs font-bold text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-full border border-indigo-100"></span>
|
<span id="chatMemberCount" class="badge-futuristic text-xs font-bold px-2 py-0.5 rounded-full"></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="typingIndicator" class="text-[11px] font-medium text-indigo-500 hidden animate-pulse mt-0.5"></div>
|
<div id="typingIndicator" class="text-[11px] font-medium text-cyan-400 hidden animate-pulse mt-0.5"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<button onclick="showAnnouncement()" class="w-10 h-10 rounded-xl bg-white text-slate-500 hover:text-amber-600 hover:bg-amber-50 hover:shadow-sm border border-slate-100 transition-all flex items-center justify-center group" title="群公告" id="btnAnnouncement">
|
<button onclick="showAnnouncement()" class="w-10 h-10 rounded-xl bg-slate-700/50 text-slate-400 hover:text-amber-400 hover:bg-amber-500/20 hover:shadow-sm border border-white/10 transition-all flex items-center justify-center group" title="群公告" id="btnAnnouncement">
|
||||||
<svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z"/></svg>
|
<svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button onclick="showSearchPanel()" class="w-10 h-10 rounded-xl bg-white text-slate-500 hover:text-indigo-600 hover:bg-indigo-50 hover:shadow-sm border border-slate-100 transition-all flex items-center justify-center group" title="搜索聊天记录">
|
<button onclick="showSearchPanel()" class="w-10 h-10 rounded-xl bg-slate-700/50 text-slate-400 hover:text-cyan-400 hover:bg-cyan-500/20 hover:shadow-sm border border-white/10 transition-all flex items-center justify-center group" title="搜索聊天记录">
|
||||||
<svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
<svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button onclick="showFileList()" class="w-10 h-10 rounded-xl bg-white text-slate-500 hover:text-emerald-600 hover:bg-emerald-50 hover:shadow-sm border border-slate-100 transition-all flex items-center justify-center group" title="群文件">
|
<button onclick="showFileList()" class="w-10 h-10 rounded-xl bg-slate-700/50 text-slate-400 hover:text-emerald-400 hover:bg-emerald-500/20 hover:shadow-sm border border-white/10 transition-all flex items-center justify-center group" title="群文件">
|
||||||
<svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/></svg>
|
<svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button onclick="showMembers()" class="w-10 h-10 rounded-xl bg-white text-slate-500 hover:text-indigo-600 hover:bg-indigo-50 hover:shadow-sm border border-slate-100 transition-all flex items-center justify-center group" title="成员列表">
|
<button onclick="showMembers()" class="w-10 h-10 rounded-xl bg-slate-700/50 text-slate-400 hover:text-cyan-400 hover:bg-cyan-500/20 hover:shadow-sm border border-white/10 transition-all flex items-center justify-center group" title="成员列表">
|
||||||
<svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
<svg class="w-5 h-5 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,56 +116,56 @@
|
|||||||
<!-- 消息区域 -->
|
<!-- 消息区域 -->
|
||||||
<div id="messageArea" class="flex-1 overflow-y-auto hide-scrollbar px-6 py-4 space-y-5" onscroll="handleScroll()"></div>
|
<div id="messageArea" class="flex-1 overflow-y-auto hide-scrollbar px-6 py-4 space-y-5" onscroll="handleScroll()"></div>
|
||||||
<!-- 引用回复预览 -->
|
<!-- 引用回复预览 -->
|
||||||
<div id="replyPreview" class="px-4 py-3 bg-indigo-50/80 backdrop-blur-md border-t border-indigo-100 hidden flex items-center justify-between shadow-inner">
|
<div id="replyPreview" class="px-4 py-3 bg-cyan-500/10 backdrop-blur-md border-t border-cyan-500/30 hidden flex items-center justify-between shadow-inner">
|
||||||
<div class="flex items-center gap-2 overflow-hidden">
|
<div class="flex items-center gap-2 overflow-hidden">
|
||||||
<div class="w-1 h-4 bg-indigo-500 rounded-full"></div>
|
<div class="w-1 h-4 bg-cyan-400 rounded-full"></div>
|
||||||
<div class="text-sm font-medium text-indigo-900 truncate"><span id="replyToText"></span></div>
|
<div class="text-sm font-medium text-cyan-300 truncate"><span id="replyToText"></span></div>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="cancelReply()" class="w-6 h-6 rounded-full bg-white/50 text-indigo-400 hover:text-indigo-600 hover:bg-white flex items-center justify-center transition-colors shadow-sm ml-2 flex-shrink-0">
|
<button onclick="cancelReply()" class="w-6 h-6 rounded-full bg-slate-700/50 text-cyan-400 hover:text-cyan-300 hover:bg-slate-700 flex items-center justify-center transition-colors shadow-sm ml-2 flex-shrink-0 border border-white/10">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- 输入区域 -->
|
<!-- 输入区域 -->
|
||||||
<div class="p-4 bg-white/80 backdrop-blur-xl border-t border-slate-200/60 shadow-[0_-4px_20px_-15px_rgba(0,0,0,0.1)]">
|
<div class="p-4 bg-slate-800/80 backdrop-blur-xl border-t border-white/10 shadow-[0_-4px_20px_-15px_rgba(0,0,0,0.3)]">
|
||||||
<!-- Emoji 面板 -->
|
<!-- Emoji 面板 -->
|
||||||
<div id="emojiPanel" class="hidden absolute bottom-[calc(100%+10px)] left-4 p-3 bg-white/95 backdrop-blur-xl border border-slate-200/60 rounded-2xl shadow-xl max-h-48 overflow-y-auto w-64 z-50">
|
<div id="emojiPanel" class="hidden absolute bottom-[calc(100%+10px)] left-4 p-3 futuristic-card-dark backdrop-blur-xl rounded-2xl shadow-xl max-h-48 overflow-y-auto w-64 z-50">
|
||||||
<div class="flex flex-wrap gap-2 justify-center" id="emojiGrid"></div>
|
<div class="flex flex-wrap gap-2 justify-center" id="emojiGrid"></div>
|
||||||
</div>
|
</div>
|
||||||
<!-- @提及自动补全 -->
|
<!-- @提及自动补全 -->
|
||||||
<div id="mentionPanel" class="hidden absolute bottom-[calc(100%+10px)] left-20 bg-white border border-slate-200 rounded-xl shadow-xl max-h-48 overflow-y-auto w-56 z-50">
|
<div id="mentionPanel" class="hidden absolute bottom-[calc(100%+10px)] left-20 futuristic-card-dark rounded-xl shadow-xl max-h-48 overflow-y-auto w-56 z-50">
|
||||||
<div id="mentionList" class="py-1"></div>
|
<div id="mentionList" class="py-1"></div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 录音指示器 -->
|
<!-- 录音指示器 -->
|
||||||
<div id="recordingIndicator" class="hidden absolute bottom-[calc(100%+10px)] left-1/2 -translate-x-1/2 bg-rose-500 text-white px-4 py-2 rounded-xl shadow-lg flex items-center gap-2 z-50">
|
<div id="recordingIndicator" class="hidden absolute bottom-[calc(100%+10px)] left-1/2 -translate-x-1/2 badge-futuristic px-4 py-2 rounded-xl shadow-lg flex items-center gap-2 z-50">
|
||||||
<div class="w-3 h-3 bg-white rounded-full animate-pulse"></div>
|
<div class="w-3 h-3 bg-white rounded-full animate-pulse"></div>
|
||||||
<span class="text-sm font-medium">录音中... <span id="recordingTime">0s</span></span>
|
<span class="text-sm font-medium">录音中... <span id="recordingTime">0s</span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-end gap-3">
|
<div class="flex items-end gap-3">
|
||||||
<div class="flex flex-col gap-2 pb-1.5">
|
<div class="flex flex-col gap-2 pb-1.5">
|
||||||
<button onclick="toggleEmoji()" class="w-9 h-9 rounded-xl bg-slate-50 text-slate-500 hover:text-amber-500 hover:bg-amber-50 transition-colors flex items-center justify-center shadow-sm" title="表情">
|
<button onclick="toggleEmoji()" class="w-9 h-9 rounded-xl bg-slate-700/50 text-slate-400 hover:text-amber-400 hover:bg-amber-500/20 transition-colors flex items-center justify-center shadow-sm border border-white/10" title="表情">
|
||||||
😀
|
😀
|
||||||
</button>
|
</button>
|
||||||
<label class="w-9 h-9 rounded-xl bg-slate-50 text-slate-500 hover:text-emerald-500 hover:bg-emerald-50 transition-colors flex items-center justify-center shadow-sm cursor-pointer" title="发送图片">
|
<label class="w-9 h-9 rounded-xl bg-slate-700/50 text-slate-400 hover:text-emerald-400 hover:bg-emerald-500/20 transition-colors flex items-center justify-center shadow-sm cursor-pointer border border-white/10" title="发送图片">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
||||||
<input type="file" accept="image/*" class="hidden" onchange="uploadFile(this,'image')">
|
<input type="file" accept="image/*" class="hidden" onchange="uploadFile(this,'image')">
|
||||||
</label>
|
</label>
|
||||||
<label class="w-9 h-9 rounded-xl bg-slate-50 text-slate-500 hover:text-blue-500 hover:bg-blue-50 transition-colors flex items-center justify-center shadow-sm cursor-pointer" title="发送文件">
|
<label class="w-9 h-9 rounded-xl bg-slate-700/50 text-slate-400 hover:text-blue-400 hover:bg-blue-500/20 transition-colors flex items-center justify-center shadow-sm cursor-pointer border border-white/10" title="发送文件">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"/></svg>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"/></svg>
|
||||||
<input type="file" class="hidden" onchange="uploadFile(this,'file')">
|
<input type="file" class="hidden" onchange="uploadFile(this,'file')">
|
||||||
</label>
|
</label>
|
||||||
<button onclick="insertMention()" class="w-9 h-9 rounded-xl bg-slate-50 text-slate-500 hover:text-indigo-500 hover:bg-indigo-50 transition-colors flex items-center justify-center shadow-sm font-bold text-sm" title="@提及">
|
<button onclick="insertMention()" class="w-9 h-9 rounded-xl bg-slate-700/50 text-slate-400 hover:text-cyan-400 hover:bg-cyan-500/20 transition-colors flex items-center justify-center shadow-sm font-bold text-sm border border-white/10" title="@提及">
|
||||||
@
|
@
|
||||||
</button>
|
</button>
|
||||||
<button id="voiceBtn" onmousedown="startRecording()" onmouseup="stopRecording()" ontouchstart="startRecording()" ontouchend="stopRecording()" class="w-9 h-9 rounded-xl bg-slate-50 text-slate-500 hover:text-rose-500 hover:bg-rose-50 transition-colors flex items-center justify-center shadow-sm" title="按住录音">
|
<button id="voiceBtn" onmousedown="startRecording()" onmouseup="stopRecording()" ontouchstart="startRecording()" ontouchend="stopRecording()" class="w-9 h-9 rounded-xl bg-slate-700/50 text-slate-400 hover:text-rose-400 hover:bg-rose-500/20 transition-colors flex items-center justify-center shadow-sm border border-white/10" title="按住录音">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"/></svg>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1 bg-white rounded-2xl border border-slate-200/80 shadow-sm focus-within:ring-2 focus-within:ring-indigo-500/30 focus-within:border-indigo-400 transition-all overflow-hidden">
|
<div class="flex-1 futuristic-card-dark rounded-2xl shadow-sm focus-within:ring-2 focus-within:ring-cyan-500/30 focus-within:border-cyan-400/50 transition-all overflow-hidden">
|
||||||
<textarea id="msgInput" rows="1" placeholder="输入消息... (Enter发送, Shift+Enter换行)" class="flex-1 w-full px-4 py-3.5 text-sm bg-transparent border-none focus:outline-none focus:ring-0 resize-none max-h-32 min-h-[48px] hide-scrollbar" oninput="handleTyping(); this.style.height = 'auto'; this.style.height = (this.scrollHeight) + 'px';"></textarea>
|
<textarea id="msgInput" rows="1" placeholder="输入消息... (Enter发送, Shift+Enter换行)" class="flex-1 w-full px-4 py-3.5 text-sm bg-transparent text-white placeholder-slate-500 border-none focus:outline-none focus:ring-0 resize-none max-h-32 min-h-[48px] hide-scrollbar" oninput="handleTyping(); this.style.height = 'auto'; this.style.height = (this.scrollHeight) + 'px';"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onclick="sendMessage()" class="w-12 h-12 rounded-2xl bg-gradient-to-r from-indigo-500 to-purple-600 text-white flex items-center justify-center hover:shadow-lg hover:shadow-indigo-500/30 transform hover:-translate-y-0.5 transition-all flex-shrink-0 group">
|
<button onclick="sendMessage()" class="btn-futuristic w-12 h-12 rounded-2xl flex items-center justify-center hover:shadow-lg hover:shadow-cyan-500/30 transform hover:-translate-y-0.5 transition-all flex-shrink-0 group">
|
||||||
<svg class="w-5 h-5 transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/></svg>
|
<svg class="w-5 h-5 transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,35 +176,35 @@
|
|||||||
|
|
||||||
<!-- 创建群聊弹窗 -->
|
<!-- 创建群聊弹窗 -->
|
||||||
<div id="createGroupModal" class="fixed inset-0 bg-black/50 z-[9990] hidden items-center justify-center">
|
<div id="createGroupModal" class="fixed inset-0 bg-black/50 z-[9990] hidden items-center justify-center">
|
||||||
<div class="bg-white rounded-xl shadow-xl w-96 max-h-[80vh] flex flex-col">
|
<div class="futuristic-card-dark rounded-xl shadow-xl w-96 max-h-[80vh] flex flex-col">
|
||||||
<div class="px-4 py-3 border-b border-slate-200 flex justify-between items-center">
|
<div class="px-4 py-3 border-b border-white/10 flex justify-between items-center">
|
||||||
<h3 class="font-semibold">创建群聊</h3>
|
<h3 class="font-semibold text-white">创建群聊</h3>
|
||||||
<button onclick="hideCreateGroup()" class="text-slate-400 hover:text-slate-600">×</button>
|
<button onclick="hideCreateGroup()" class="text-slate-400 hover:text-slate-300">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4 space-y-3">
|
<div class="p-4 space-y-3">
|
||||||
<input id="groupName" type="text" placeholder="群聊名称" class="w-full px-3 py-2 text-sm border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50">
|
<input id="groupName" type="text" placeholder="群聊名称" class="input-futuristic w-full px-3 py-2 text-sm">
|
||||||
<p class="text-sm text-slate-500">选择好友加入群聊:</p>
|
<p class="text-sm text-slate-400">选择好友加入群聊:</p>
|
||||||
<div id="friendListForGroup" class="max-h-48 overflow-y-auto space-y-1"></div>
|
<div id="friendListForGroup" class="max-h-48 overflow-y-auto space-y-1"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 py-3 border-t border-slate-200">
|
<div class="px-4 py-3 border-t border-white/10">
|
||||||
<button onclick="createGroup()" class="w-full px-4 py-2 bg-primary text-white rounded-lg hover:bg-blue-600 text-sm">创建</button>
|
<button onclick="createGroup()" class="btn-futuristic w-full px-4 py-2 text-sm">创建</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 成员列表弹窗 -->
|
<!-- 成员列表弹窗 -->
|
||||||
<div id="membersModal" class="fixed inset-0 bg-black/50 z-[9990] hidden items-center justify-center">
|
<div id="membersModal" class="fixed inset-0 bg-black/50 z-[9990] hidden items-center justify-center">
|
||||||
<div class="bg-white rounded-xl shadow-xl w-[420px] max-h-[80vh] flex flex-col">
|
<div class="futuristic-card-dark rounded-xl shadow-xl w-[420px] max-h-[80vh] flex flex-col">
|
||||||
<div class="px-4 py-3 border-b border-slate-200 flex justify-between items-center">
|
<div class="px-4 py-3 border-b border-white/10 flex justify-between items-center">
|
||||||
<h3 class="font-semibold">群成员</h3>
|
<h3 class="font-semibold text-white">群成员</h3>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button onclick="showNicknameModal()" class="text-xs text-indigo-500 hover:text-indigo-700" title="设置群昵称">我的昵称</button>
|
<button onclick="showNicknameModal()" class="text-xs text-cyan-400 hover:text-cyan-300" title="设置群昵称">我的昵称</button>
|
||||||
<button onclick="hideMembers()" class="text-slate-400 hover:text-slate-600 text-lg">×</button>
|
<button onclick="hideMembers()" class="text-slate-400 hover:text-slate-300 text-lg">×</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="membersList" class="p-4 overflow-y-auto max-h-96 space-y-2"></div>
|
<div id="membersList" class="p-4 overflow-y-auto max-h-96 space-y-2"></div>
|
||||||
<div id="inviteSection" class="px-4 py-3 border-t border-slate-200 hidden">
|
<div id="inviteSection" class="px-4 py-3 border-t border-white/10 hidden">
|
||||||
<button onclick="showInvite()" class="w-full px-4 py-2 bg-primary text-white rounded-lg hover:bg-blue-600 text-sm">邀请好友</button>
|
<button onclick="showInvite()" class="btn-futuristic w-full px-4 py-2 text-sm">邀请好友</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,27 +3,27 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
{% if contest.status == 'abolished' %}
|
{% if contest.status == 'abolished' %}
|
||||||
<div class="bg-red-50 border border-red-300 rounded-lg p-4 text-red-700 font-medium">
|
<div class="futuristic-card p-6 border-l-4 border-red-500 p-4 text-red-300">
|
||||||
⚠️ 该杯赛已被废止,所有考试已关闭,无法报名或参加考试。
|
⚠️ 该杯赛已被废止,所有考试已关闭,无法报名或参加考试。
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if not contest.visible and is_owner %}
|
{% if not contest.visible and is_owner %}
|
||||||
<div class="bg-yellow-50 border border-yellow-300 rounded-lg p-4 flex justify-between items-center">
|
<div class="futuristic-card p-6 border-l-4 border-yellow-500 p-4 flex justify-between items-center">
|
||||||
<span class="text-yellow-800 font-medium">该杯赛尚未发布,仅负责人和管理员可见。完善资料后请点击发布。</span>
|
<span class="text-yellow-300 font-medium">该杯赛尚未发布,仅负责人和管理员可见。完善资料后请点击发布。</span>
|
||||||
<button onclick="publishContest()" id="publish-btn" class="px-4 py-2 bg-green-600 text-white rounded-md text-sm font-medium hover:bg-green-700">发布杯赛</button>
|
<button onclick="publishContest()" id="publish-btn" class="btn-futuristic px-4 py-2 text-sm font-medium">发布杯赛</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
<div class="futuristic-card p-6 p-6">
|
||||||
<div class="flex justify-between items-start">
|
<div class="flex justify-between items-start">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-slate-900 mb-2">
|
<h1 class="text-2xl font-bold bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent mb-2">
|
||||||
{{ contest.name }}
|
{{ contest.name }}
|
||||||
{% if contest.status == 'abolished' %}
|
{% if contest.status == 'abolished' %}
|
||||||
<span class="ml-2 px-2 py-0.5 text-xs rounded-full bg-red-100 text-red-800">已废止</span>
|
<span class="ml-2 badge-futuristic text-xs rounded-full">已废止</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex items-center space-x-4 text-sm text-slate-500">
|
<div class="flex items-center space-x-4 text-sm text-slate-400">
|
||||||
<span class="flex items-center">
|
<span class="flex items-center">
|
||||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
||||||
{{ contest.start_date }}
|
{{ contest.start_date }}
|
||||||
@@ -39,36 +39,36 @@
|
|||||||
{% if user %}
|
{% if user %}
|
||||||
<button id="register-btn"
|
<button id="register-btn"
|
||||||
onclick="toggleRegistration({{ contest.id }})"
|
onclick="toggleRegistration({{ contest.id }})"
|
||||||
class="px-6 py-2 {% if registered %}bg-slate-100 text-slate-700 border border-slate-300 hover:bg-slate-200{% else %}bg-primary text-white hover:bg-blue-700{% endif %} rounded-md font-medium">
|
class="{% if registered %}btn-outline-futuristic{% else %}btn-futuristic{% endif %} px-6 py-2 font-medium">
|
||||||
{% if registered %}已报名{% else %}立即报名{% endif %}
|
{% if registered %}已报名{% else %}立即报名{% endif %}
|
||||||
</button>
|
</button>
|
||||||
{% if not is_member %}
|
{% if not is_member %}
|
||||||
<a href="{{ url_for('apply_teacher', contest_id=contest.id) }}" class="px-6 py-2 bg-green-100 text-green-700 border border-green-300 rounded-md font-medium hover:bg-green-200">
|
<a href="{{ url_for('apply_teacher', contest_id=contest.id) }}" class="btn-outline-futuristic px-6 py-2 font-medium">
|
||||||
申请成为本杯赛老师
|
申请成为本杯赛老师
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_member %}
|
{% if is_member %}
|
||||||
<a href="{{ url_for('contest_question_bank', contest_id=contest.id) }}" class="px-6 py-2 bg-purple-100 text-purple-700 border border-purple-300 rounded-md font-medium hover:bg-purple-200">
|
<a href="{{ url_for('contest_question_bank', contest_id=contest.id) }}" class="btn-outline-futuristic px-6 py-2 font-medium">
|
||||||
题库管理
|
题库管理
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_owner %}
|
{% if is_owner %}
|
||||||
<a href="{{ url_for('exam_create', contest_id=contest.id) }}" class="px-6 py-2 bg-blue-100 text-blue-700 border border-blue-300 rounded-md font-medium hover:bg-blue-200">
|
<a href="{{ url_for('exam_create', contest_id=contest.id) }}" class="btn-outline-futuristic px-6 py-2 font-medium">
|
||||||
创建考试
|
创建考试
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('admin_teacher_applications') }}" class="px-6 py-2 bg-orange-100 text-orange-700 border border-orange-300 rounded-md font-medium hover:bg-orange-200">
|
<a href="{{ url_for('admin_teacher_applications') }}" class="btn-outline-futuristic px-6 py-2 font-medium">
|
||||||
审批老师申请
|
审批老师申请
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('contest_edit', contest_id=contest.id) }}#papers" class="px-6 py-2 bg-green-100 text-green-700 border border-green-300 rounded-md font-medium hover:bg-green-200">
|
<a href="{{ url_for('contest_edit', contest_id=contest.id) }}#papers" class="btn-outline-futuristic px-6 py-2 font-medium">
|
||||||
上传历年真题
|
上传历年真题
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('contest_edit', contest_id=contest.id) }}" class="px-6 py-2 bg-yellow-100 text-yellow-700 border border-yellow-300 rounded-md font-medium hover:bg-yellow-200">
|
<a href="{{ url_for('contest_edit', contest_id=contest.id) }}" class="btn-outline-futuristic px-6 py-2 font-medium">
|
||||||
编辑主页
|
编辑主页
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/login?next={{ url_for('contest_detail', contest_id=contest.id) }}"
|
<a href="/login?next={{ url_for('contest_detail', contest_id=contest.id) }}"
|
||||||
class="px-6 py-2 bg-primary text-white rounded-md hover:bg-blue-700 font-medium">
|
class="btn-futuristic px-6 py-2 font-medium">
|
||||||
登录后报名
|
登录后报名
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -84,9 +84,9 @@
|
|||||||
<!-- 比赛详情 -->
|
<!-- 比赛详情 -->
|
||||||
|
|
||||||
<!-- 历年真题 -->
|
<!-- 历年真题 -->
|
||||||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
<div class="futuristic-card p-6 p-6">
|
||||||
<h2 class="text-lg font-semibold text-slate-900 mb-4 flex items-center">
|
<h2 class="text-lg font-semibold bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent mb-4 flex items-center">
|
||||||
<svg class="w-5 h-5 mr-2 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-5 h-5 mr-2 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
||||||
</svg>
|
</svg>
|
||||||
历年真题
|
历年真题
|
||||||
@@ -95,12 +95,12 @@
|
|||||||
{% if papers %}
|
{% if papers %}
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
{% for paper in papers %}
|
{% for paper in papers %}
|
||||||
<div class="flex items-center justify-between py-2 border-b border-slate-100 last:border-0">
|
<div class="flex items-center justify-between py-2 border-b border-white/10 last:border-0">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="text-sm font-medium text-slate-700 w-16">{{ paper.year }}</span>
|
<span class="badge-futuristic text-sm font-medium w-16">{{ paper.year }}</span>
|
||||||
<span class="text-sm text-slate-600">{{ paper.title }}</span>
|
<span class="text-sm text-slate-300 ml-3">{{ paper.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ paper.file }}" target="_blank" class="inline-flex items-center px-3 py-1 text-xs font-medium text-primary border border-primary rounded hover:bg-blue-50">
|
<a href="{{ paper.file }}" target="_blank" class="btn-outline-futuristic inline-flex items-center px-3 py-1 text-xs font-medium">
|
||||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -110,19 +110,19 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center py-8 text-slate-500">暂无历年真题,敬请期待!</div>
|
<div class="text-center py-8 text-slate-400">暂无历年真题,敬请期待!</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 考试列表 -->
|
<!-- 考试列表 -->
|
||||||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
<div class="futuristic-card p-6 p-6">
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<h2 class="text-lg font-semibold text-slate-900 flex items-center">
|
<h2 class="text-lg font-semibold bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent 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="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
|
<svg class="w-5 h-5 mr-2 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
|
||||||
考试列表
|
考试列表
|
||||||
</h2>
|
</h2>
|
||||||
{% if is_owner %}
|
{% if is_owner %}
|
||||||
<button onclick="showImportModal()" class="px-3 py-1.5 bg-green-600 text-white rounded-md text-sm hover:bg-green-700">导入考试</button>
|
<button onclick="showImportModal()" class="btn-futuristic px-3 py-1.5 text-sm">导入考试</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div id="exam-list" class="space-y-3">
|
<div id="exam-list" class="space-y-3">
|
||||||
@@ -133,48 +133,48 @@
|
|||||||
|
|
||||||
<!-- 右侧一列(主办方信息) -->
|
<!-- 右侧一列(主办方信息) -->
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
<div class="futuristic-card p-6">
|
||||||
<h3 class="text-lg font-semibold text-slate-900 mb-4">主办方信息</h3>
|
<h3 class="text-lg font-semibold bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent mb-4">主办方信息</h3>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<div class="font-medium text-slate-900">{{ contest.organizer }}</div>
|
<div class="font-medium bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent">{{ contest.organizer }}</div>
|
||||||
<p class="text-sm text-slate-500 mt-1">{{ contest.description[:100] + '...' if contest.description|length > 100 else contest.description }}</p>
|
<p class="text-sm text-slate-400 mt-1">{{ contest.description[:100] + '...' if contest.description|length > 100 else contest.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% if contest.responsible_person %}
|
{% if contest.responsible_person %}
|
||||||
<div class="pt-4 border-t border-slate-100">
|
<div class="pt-4 border-t border-white/10">
|
||||||
<div class="text-sm text-slate-500 mb-1">报备信息</div>
|
<div class="text-sm text-slate-400 mb-1">报备信息</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex items-center text-sm">
|
<div class="flex items-center text-sm">
|
||||||
<span class="text-slate-500 w-16 flex-shrink-0">责任人</span>
|
<span class="text-slate-400 w-16 flex-shrink-0">责任人</span>
|
||||||
<span class="font-medium text-slate-900">{{ contest.responsible_person }}</span>
|
<span class="font-medium bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent">{{ contest.responsible_person }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center text-sm">
|
<div class="flex items-center text-sm">
|
||||||
<span class="text-slate-500 w-16 flex-shrink-0">电话</span>
|
<span class="text-slate-400 w-16 flex-shrink-0">电话</span>
|
||||||
<span class="font-medium text-slate-900">{{ contest.responsible_phone }}</span>
|
<span class="font-medium bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent">{{ contest.responsible_phone }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center text-sm">
|
<div class="flex items-center text-sm">
|
||||||
<span class="text-slate-500 w-16 flex-shrink-0">邮箱</span>
|
<span class="text-slate-400 w-16 flex-shrink-0">邮箱</span>
|
||||||
<span class="font-medium text-primary">{{ contest.responsible_email }}</span>
|
<span class="font-medium text-cyan-400">{{ contest.responsible_email }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center text-sm">
|
<div class="flex items-center text-sm">
|
||||||
<span class="text-slate-500 w-16 flex-shrink-0">机构</span>
|
<span class="text-slate-400 w-16 flex-shrink-0">机构</span>
|
||||||
<span class="font-medium text-slate-900">{{ contest.organization }}</span>
|
<span class="font-medium bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent">{{ contest.organization }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if contest.contact %}
|
{% if contest.contact %}
|
||||||
<div class="pt-4 border-t border-slate-100">
|
<div class="pt-4 border-t border-white/10">
|
||||||
<div class="text-sm text-slate-500">联系方式</div>
|
<div class="text-sm text-slate-400">联系方式</div>
|
||||||
<div class="text-sm font-medium text-primary">{{ contest.contact }}</div>
|
<div class="text-sm font-medium text-cyan-400">{{ contest.contact }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 排行榜 -->
|
<!-- 排行榜 -->
|
||||||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
<div class="futuristic-card p-6">
|
||||||
<h3 class="text-lg font-semibold text-slate-900 mb-4 flex items-center">
|
<h3 class="text-lg font-semibold bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent mb-4 flex items-center">
|
||||||
<svg class="w-5 h-5 mr-2 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"/></svg>
|
<svg class="w-5 h-5 mr-2 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"/></svg>
|
||||||
成绩排行榜
|
成绩排行榜
|
||||||
</h3>
|
</h3>
|
||||||
@@ -186,9 +186,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 讨论区(动态区域) -->
|
<!-- 讨论区(动态区域) -->
|
||||||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
<div class="futuristic-card p-6">
|
||||||
<h2 class="text-lg font-semibold text-slate-900 mb-4 flex items-center">
|
<h2 class="text-lg font-semibold bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent 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>
|
<svg class="w-5 h-5 mr-2 text-cyan-400" 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>
|
</h2>
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@
|
|||||||
|
|
||||||
<!-- 帖子列表容器 -->
|
<!-- 帖子列表容器 -->
|
||||||
<div id="post-list" class="space-y-4">
|
<div id="post-list" class="space-y-4">
|
||||||
<div class="text-center py-8 text-slate-500">加载中...</div>
|
<div class="text-center py-8 text-slate-400">加载中...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -222,9 +222,9 @@
|
|||||||
<div class="bg-white rounded-lg shadow-xl w-full max-w-lg max-h-[70vh] overflow-y-auto p-6">
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-lg max-h-[70vh] overflow-y-auto p-6">
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<h3 class="text-lg font-semibold">导入已有考试</h3>
|
<h3 class="text-lg font-semibold">导入已有考试</h3>
|
||||||
<button onclick="hideImportModal()" class="text-slate-400 hover:text-slate-600 text-xl">×</button>
|
<button onclick="hideImportModal()" class="text-slate-400 hover:text-slate-300 text-xl">×</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-slate-500 mb-4">选择您创建的未关联杯赛的考试,导入到当前杯赛。</p>
|
<p class="text-sm text-slate-400 mb-4">选择您创建的未关联杯赛的考试,导入到当前杯赛。</p>
|
||||||
<div id="available-exams-list" class="space-y-2">
|
<div id="available-exams-list" class="space-y-2">
|
||||||
<div class="text-center py-4 text-slate-400 text-sm">加载中...</div>
|
<div class="text-center py-4 text-slate-400 text-sm">加载中...</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -276,15 +276,15 @@ async function loadPosts() {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!data.success) throw new Error(data.message);
|
if (!data.success) throw new Error(data.message);
|
||||||
if (data.data.length === 0) {
|
if (data.data.length === 0) {
|
||||||
container.innerHTML = '<div class="text-center py-8 text-slate-500">暂无讨论,来抢沙发吧!</div>';
|
container.innerHTML = '<div class="text-center py-8 text-slate-400">暂无讨论,来抢沙发吧!</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let html = '';
|
let html = '';
|
||||||
data.data.forEach(p => {
|
data.data.forEach(p => {
|
||||||
html += `
|
html += `
|
||||||
<div class="border border-slate-200 rounded-lg p-4 hover:shadow-sm transition-shadow">
|
<div class="border border-slate-200 rounded-lg p-4 hover:shadow-sm transition-shadow">
|
||||||
<h3 class="text-base font-semibold text-slate-900 mb-1">${escapeHtml(p.title)}</h3>
|
<h3 class="text-base font-semibold bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent mb-1">${escapeHtml(p.title)}</h3>
|
||||||
<p class="text-sm text-slate-600 mb-2">${escapeHtml(p.content)}</p>
|
<p class="text-sm text-slate-300 mb-2">${escapeHtml(p.content)}</p>
|
||||||
<div class="flex items-center text-xs text-slate-400 space-x-3">
|
<div class="flex items-center text-xs text-slate-400 space-x-3">
|
||||||
<span>${escapeHtml(p.author)}</span>
|
<span>${escapeHtml(p.author)}</span>
|
||||||
<span>${p.created_at}</span>
|
<span>${p.created_at}</span>
|
||||||
@@ -381,14 +381,14 @@ async function loadExams() {
|
|||||||
html += `<div class="flex items-center justify-between p-3 border border-slate-200 rounded-lg hover:bg-slate-50">
|
html += `<div class="flex items-center justify-between p-3 border border-slate-200 rounded-lg hover:bg-slate-50">
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<a href="/exams/${e.id}" class="text-sm font-medium text-slate-900 hover:text-primary truncate">${escapeHtml(e.title)}</a>
|
<a href="/exams/${e.id}" class="text-sm font-medium bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent hover:text-cyan-400 truncate">${escapeHtml(e.title)}</a>
|
||||||
<span class="px-2 py-0.5 text-xs rounded-full ${statusClass}">${statusText}</span>
|
<span class="px-2 py-0.5 text-xs rounded-full ${statusClass}">${statusText}</span>
|
||||||
${subCount}
|
${subCount}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-slate-500 mt-1">${e.subject ? e.subject + ' · ' : ''}满分${e.total_score}分${e.duration ? ' · ' + e.duration + '分钟' : ''}</div>
|
<div class="text-xs text-slate-400 mt-1">${e.subject ? e.subject + ' · ' : ''}满分${e.total_score}分${e.duration ? ' · ' + e.duration + '分钟' : ''}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center ml-3 shrink-0">
|
<div class="flex items-center ml-3 shrink-0">
|
||||||
<a href="/exams/${e.id}" class="px-3 py-1 text-xs font-medium text-primary border border-primary rounded hover:bg-blue-50">进入考试</a>
|
<a href="/exams/${e.id}" class="px-3 py-1 text-xs font-medium text-cyan-400 border border-primary rounded hover:bg-blue-50">进入考试</a>
|
||||||
${removeBtn}
|
${removeBtn}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -436,8 +436,8 @@ async function loadAvailableExams() {
|
|||||||
data.exams.forEach(e => {
|
data.exams.forEach(e => {
|
||||||
html += `<div class="flex items-center justify-between p-3 border border-slate-200 rounded-lg">
|
html += `<div class="flex items-center justify-between p-3 border border-slate-200 rounded-lg">
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="text-sm font-medium text-slate-900">${escapeHtml(e.title)}</div>
|
<div class="text-sm font-medium bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent">${escapeHtml(e.title)}</div>
|
||||||
<div class="text-xs text-slate-500">${e.subject ? e.subject + ' · ' : ''}满分${e.total_score}分 · ${e.created_at}</div>
|
<div class="text-xs text-slate-400">${e.subject ? e.subject + ' · ' : ''}满分${e.total_score}分 · ${e.created_at}</div>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="importExam(${e.id})" class="ml-3 px-3 py-1 text-xs font-medium text-white bg-green-600 rounded hover:bg-green-700 shrink-0">导入</button>
|
<button onclick="importExam(${e.id})" class="ml-3 px-3 py-1 text-xs font-medium text-white bg-green-600 rounded hover:bg-green-700 shrink-0">导入</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -475,12 +475,12 @@ async function loadLeaderboard() {
|
|||||||
}
|
}
|
||||||
let html = '';
|
let html = '';
|
||||||
data.leaderboard.forEach(item => {
|
data.leaderboard.forEach(item => {
|
||||||
const rankClass = item.rank <= 3 ? 'font-bold text-yellow-600' : 'text-slate-500';
|
const rankClass = item.rank <= 3 ? 'font-bold text-yellow-600' : 'text-slate-400';
|
||||||
const medal = item.rank === 1 ? '🥇' : (item.rank === 2 ? '🥈' : (item.rank === 3 ? '🥉' : item.rank));
|
const medal = item.rank === 1 ? '🥇' : (item.rank === 2 ? '🥈' : (item.rank === 3 ? '🥉' : item.rank));
|
||||||
html += `<div class="flex items-center justify-between py-2 ${item.rank <= 3 ? '' : 'border-t border-slate-100'}">
|
html += `<div class="flex items-center justify-between py-2 ${item.rank <= 3 ? '' : 'border-t border-white/10'}">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="w-8 text-center ${rankClass}">${medal}</span>
|
<span class="w-8 text-center ${rankClass}">${medal}</span>
|
||||||
<span class="text-sm text-slate-900">${escapeHtml(item.user_name)}</span>
|
<span class="text-sm bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent">${escapeHtml(item.user_name)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm font-medium text-slate-700">${item.total_score}分 <span class="text-xs text-slate-400">(${item.exam_count}科)</span></div>
|
<div class="text-sm font-medium text-slate-700">${item.total_score}分 <span class="text-xs text-slate-400">(${item.exam_count}科)</span></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|||||||
@@ -3,21 +3,21 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<!-- 头部区域 -->
|
<!-- 头部区域 -->
|
||||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 bg-white p-6 rounded-2xl shadow-sm border border-slate-100">
|
<div class="futuristic-card flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 p-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-slate-900 flex items-center">
|
<h1 class="text-2xl font-bold flex items-center bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent">
|
||||||
<span class="w-10 h-10 bg-blue-50 text-blue-600 rounded-xl flex items-center justify-center mr-3 shadow-sm border border-blue-100">🏆</span>
|
<span class="w-10 h-10 bg-gradient-to-br from-cyan-400 to-blue-600 text-white rounded-xl flex items-center justify-center mr-3 shadow-lg border border-cyan-300/30 animate-pulse-glow">🏆</span>
|
||||||
杯赛专栏
|
杯赛专栏
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-slate-500 text-sm mt-1 ml-13">参与官方联考,检验学习成果,赢取荣誉与奖励。</p>
|
<p class="text-slate-400 text-sm mt-1 ml-13">参与官方联考,检验学习成果,赢取荣誉与奖励。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap items-center gap-3">
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
<a href="{{ url_for('apply_contest') }}" class="inline-flex items-center px-4 py-2.5 bg-green-50 text-green-600 rounded-xl hover:bg-green-100 shadow-sm border border-green-200 text-sm font-medium transition-all transform hover:-translate-y-0.5">
|
<a href="{{ url_for('apply_contest') }}" class="btn-futuristic inline-flex items-center px-4 py-2.5 text-sm font-medium">
|
||||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/></svg>
|
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/></svg>
|
||||||
申请举办杯赛
|
申请举办杯赛
|
||||||
</a>
|
</a>
|
||||||
{% if user and (user.role == 'admin' or user.role == 'teacher') %}
|
{% if user and (user.role == 'admin' or user.role == 'teacher') %}
|
||||||
<a href="/admin/contests/create" class="inline-flex items-center px-4 py-2.5 bg-primary text-white rounded-xl hover:bg-blue-600 shadow-sm text-sm font-medium transition-all transform hover:-translate-y-0.5">
|
<a href="/admin/contests/create" class="btn-futuristic inline-flex items-center px-4 py-2.5 text-sm font-medium">
|
||||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
||||||
发布新杯赛
|
发布新杯赛
|
||||||
</a>
|
</a>
|
||||||
@@ -26,29 +26,29 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 筛选和搜索 -->
|
<!-- 筛选和搜索 -->
|
||||||
<div class="bg-white p-5 rounded-2xl shadow-sm border border-slate-100 flex flex-wrap gap-4 items-center justify-between sticky top-20 z-10 glass-panel">
|
<div class="futuristic-card p-5 flex flex-wrap gap-4 items-center justify-between sticky top-20 z-10 backdrop-blur-lg">
|
||||||
<div class="flex flex-wrap gap-3 items-center w-full sm:w-auto">
|
<div class="flex flex-wrap gap-3 items-center w-full sm:w-auto">
|
||||||
<div class="relative w-full sm:w-auto">
|
<div class="relative w-full sm:w-auto">
|
||||||
<select id="statusFilter" class="w-full sm:w-40 appearance-none px-4 py-2.5 pl-10 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary bg-slate-50 hover:bg-slate-100 transition-colors cursor-pointer" onchange="searchContests()">
|
<select id="statusFilter" class="input-futuristic w-full sm:w-40 appearance-none px-4 py-2.5 pl-10 border border-cyan-500/30 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500 bg-slate-900/50 hover:bg-slate-800/50 transition-all cursor-pointer text-slate-200" onchange="searchContests()">
|
||||||
<option value="">所有状态</option>
|
<option value="">所有状态</option>
|
||||||
<option value="registering">🟢 报名中</option>
|
<option value="registering">🟢 报名中</option>
|
||||||
<option value="upcoming">🔵 进行中</option>
|
<option value="upcoming">🔵 进行中</option>
|
||||||
<option value="ended">⚪ 已结束</option>
|
<option value="ended">⚪ 已结束</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
<svg class="h-4 w-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"/></svg>
|
<svg class="h-4 w-4 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
<svg class="h-4 w-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
<svg class="h-4 w-4 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2 w-full sm:w-auto relative group">
|
<div class="flex gap-2 w-full sm:w-auto relative group">
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-primary transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
<svg class="h-4 w-4 text-cyan-400 group-focus-within:text-cyan-300 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="search-contest" placeholder="搜索杯赛名称..." class="flex-1 sm:w-72 pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary bg-slate-50 transition-all" onkeydown="if(event.key==='Enter') searchContests()">
|
<input type="text" id="search-contest" placeholder="搜索杯赛名称..." class="input-futuristic flex-1 sm:w-72 pl-10 pr-4 py-2.5 border border-cyan-500/30 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500 bg-slate-900/50 transition-all text-slate-200 placeholder-slate-500" onkeydown="if(event.key==='Enter') searchContests()">
|
||||||
<button onclick="searchContests()" class="px-5 py-2.5 bg-slate-800 text-white rounded-xl hover:bg-slate-700 text-sm font-medium transition-colors shadow-sm">
|
<button onclick="searchContests()" class="btn-futuristic px-5 py-2.5 text-sm font-medium">
|
||||||
搜索
|
搜索
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,48 +71,48 @@ let allContests = [];
|
|||||||
function renderContests(contests) {
|
function renderContests(contests) {
|
||||||
const container = document.getElementById('contest-list');
|
const container = document.getElementById('contest-list');
|
||||||
if (contests.length === 0) {
|
if (contests.length === 0) {
|
||||||
container.innerHTML = '<div class="col-span-full text-center py-12 text-slate-500">暂无杯赛</div>';
|
container.innerHTML = '<div class="col-span-full text-center py-12 text-cyan-400">暂无杯赛</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let html = '';
|
let html = '';
|
||||||
contests.forEach(c => {
|
contests.forEach(c => {
|
||||||
let statusHtml = '';
|
let statusHtml = '';
|
||||||
if (c.status === 'registering') {
|
if (c.status === 'registering') {
|
||||||
statusHtml = '<span class="absolute top-3 right-3 bg-blue-500/90 backdrop-blur text-white text-xs font-medium px-2.5 py-1 rounded-lg shadow-sm flex items-center border border-blue-400/50"><span class="w-1.5 h-1.5 rounded-full bg-white mr-1.5 animate-pulse"></span>报名中</span>';
|
statusHtml = '<span class="badge-futuristic absolute top-3 right-3 bg-gradient-to-r from-blue-500 to-cyan-500 text-white text-xs font-bold px-3 py-1.5 shadow-lg flex items-center"><span class="w-1.5 h-1.5 rounded-full bg-white mr-1.5 animate-pulse"></span>报名中</span>';
|
||||||
} else if (c.status === 'upcoming') {
|
} else if (c.status === 'upcoming') {
|
||||||
statusHtml = '<span class="absolute top-3 right-3 bg-green-500/90 backdrop-blur text-white text-xs font-medium px-2.5 py-1 rounded-lg shadow-sm flex items-center border border-green-400/50"><span class="w-1.5 h-1.5 rounded-full bg-white mr-1.5 animate-pulse"></span>即将开始</span>';
|
statusHtml = '<span class="badge-futuristic absolute top-3 right-3 bg-gradient-to-r from-green-500 to-emerald-500 text-white text-xs font-bold px-3 py-1.5 shadow-lg flex items-center"><span class="w-1.5 h-1.5 rounded-full bg-white mr-1.5 animate-pulse"></span>即将开始</span>';
|
||||||
} else if (c.status === 'abolished') {
|
} else if (c.status === 'abolished') {
|
||||||
statusHtml = '<span class="absolute top-3 right-3 bg-red-500/90 backdrop-blur text-white text-xs font-medium px-2.5 py-1 rounded-lg shadow-sm flex items-center border border-red-400/50">已废止</span>';
|
statusHtml = '<span class="badge-futuristic absolute top-3 right-3 bg-gradient-to-r from-red-500 to-pink-500 text-white text-xs font-bold px-3 py-1.5 shadow-lg flex items-center">已废止</span>';
|
||||||
} else {
|
} else {
|
||||||
statusHtml = '<span class="absolute top-3 right-3 bg-slate-600/90 backdrop-blur text-white text-xs font-medium px-2.5 py-1 rounded-lg shadow-sm flex items-center border border-slate-500/50">已结束</span>';
|
statusHtml = '<span class="badge-futuristic absolute top-3 right-3 bg-gradient-to-r from-slate-500 to-slate-600 text-white text-xs font-bold px-3 py-1.5 shadow-lg flex items-center">已结束</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
html += `
|
html += `
|
||||||
<div class="group bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden hover:shadow-soft hover:border-blue-200 transition-all duration-300 transform hover:-translate-y-1 cursor-pointer flex flex-col" onclick="location.href='/contests/${c.id}'">
|
<div class="futuristic-card-dark group overflow-hidden transition-all duration-300 transform hover:scale-105 hover:shadow-2xl hover:shadow-cyan-500/30 cursor-pointer flex flex-col" onclick="location.href='/contests/${c.id}'">
|
||||||
<div class="h-40 bg-slate-100 relative overflow-hidden">
|
<div class="h-40 bg-slate-900 relative overflow-hidden">
|
||||||
<div class="w-full h-full flex items-center justify-center bg-gradient-to-br from-indigo-100 to-purple-100 text-indigo-400 group-hover:scale-105 transition-transform duration-500">
|
<div class="w-full h-full flex items-center justify-center bg-gradient-to-br from-cyan-500/20 via-blue-500/20 to-purple-500/20 text-cyan-400 group-hover:scale-110 transition-transform duration-500">
|
||||||
<svg class="w-16 h-16 opacity-40" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
|
<svg class="w-16 h-16 opacity-60 drop-shadow-glow" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
|
||||||
</div>
|
</div>
|
||||||
${statusHtml}
|
${statusHtml}
|
||||||
<div class="absolute inset-0 bg-gradient-to-t from-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
<div class="absolute inset-0 bg-gradient-to-t from-cyan-500/30 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-5 flex-1 flex flex-col relative">
|
<div class="p-5 flex-1 flex flex-col relative">
|
||||||
<h3 class="text-lg font-bold text-slate-900 mb-2 line-clamp-2 group-hover:text-primary transition-colors">${c.name}</h3>
|
<h3 class="text-lg font-bold text-slate-100 mb-2 line-clamp-2 group-hover:text-cyan-400 transition-colors bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text group-hover:text-transparent">${c.name}</h3>
|
||||||
<p class="text-xs text-indigo-600 font-medium mb-2 bg-indigo-50 inline-block px-2 py-1 rounded-md w-max border border-indigo-100">主办方:${c.organizer || '未知'}</p>
|
<p class="badge-futuristic text-xs font-bold mb-2 bg-gradient-to-r from-cyan-500 to-blue-500 text-white inline-block px-3 py-1.5 w-max shadow-md">主办方:${c.organizer || '未知'}</p>
|
||||||
<div class="text-sm text-slate-500 mb-5 flex-1 line-clamp-2 leading-relaxed">${c.description || '暂无简介'}</div>
|
<div class="text-sm text-slate-400 mb-5 flex-1 line-clamp-2 leading-relaxed">${c.description || '暂无简介'}</div>
|
||||||
|
|
||||||
<div class="mt-auto pt-4 border-t border-slate-100 grid grid-cols-2 gap-4">
|
<div class="mt-auto pt-4 border-t border-cyan-500/30 grid grid-cols-2 gap-4">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-[10px] font-medium text-slate-400 uppercase tracking-wider mb-1">开始时间</span>
|
<span class="text-[10px] font-medium text-slate-500 uppercase tracking-wider mb-1">开始时间</span>
|
||||||
<span class="text-xs font-medium text-slate-700 flex items-center">
|
<span class="text-xs font-semibold text-slate-300 flex items-center">
|
||||||
<svg class="w-3.5 h-3.5 mr-1 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
<svg class="w-3.5 h-3.5 mr-1 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
||||||
${c.start_date ? c.start_date.split(' ')[0] : '待定'}
|
${c.start_date ? c.start_date.split(' ')[0] : '待定'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col border-l border-slate-100 pl-4">
|
<div class="flex flex-col border-l border-cyan-500/30 pl-4">
|
||||||
<span class="text-[10px] font-medium text-slate-400 uppercase tracking-wider mb-1">参与人数</span>
|
<span class="text-[10px] font-medium text-slate-500 uppercase tracking-wider mb-1">参与人数</span>
|
||||||
<span class="text-xs font-bold text-primary flex items-center">
|
<span class="text-xs font-bold text-cyan-400 flex items-center">
|
||||||
<svg class="w-3.5 h-3.5 mr-1 text-primary/70" 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>
|
<svg class="w-3.5 h-3.5 mr-1 text-cyan-400" 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>
|
||||||
${c.participants} 人
|
${c.participants} 人
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -47,10 +47,18 @@
|
|||||||
<!-- 考试密码设置 -->
|
<!-- 考试密码设置 -->
|
||||||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-slate-700">考试可见性</label>
|
||||||
|
<select id="exam-visibility" class="mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md sm:text-sm">
|
||||||
|
<option value="private">私有(仅自己可见)</option>
|
||||||
|
<option value="public">公开(所有人可见)</option>
|
||||||
|
</select>
|
||||||
|
<p class="mt-1 text-xs text-slate-400">私有考试只有您自己可以看到和管理</p>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">考试密码 <span class="text-slate-400 font-normal">(可选)</span></label>
|
<label class="block text-sm font-medium text-slate-700">考试密码 <span class="text-slate-400 font-normal">(可选)</span></label>
|
||||||
<input id="exam-password" type="text" class="mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md sm:text-sm" placeholder="留空则不设密码">
|
<input id="exam-password" type="text" class="mt-1 block w-full px-3 py-2 border border-slate-300 rounded-md sm:text-sm" placeholder="留空则不设密码">
|
||||||
<p class="mt-1 text-xs text-slate-400">设置密码后考生需输入密码才能进入考试,试卷内容将加密存储</p>
|
<p class="mt-1 text-xs text-slate-400">设置密码后考生需输入密码才能进入考试</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -291,7 +299,7 @@ function addQuestion(type, prefill) {
|
|||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<span class="drag-handle cursor-grab text-slate-400 hover:text-slate-600">⠿</span>
|
<span class="drag-handle cursor-grab text-slate-400 hover:text-slate-600">⠿</span>
|
||||||
<span class="q-number flex-shrink-0 w-8 h-8 bg-${typeColor}-100 rounded-full flex items-center justify-center text-${typeColor}-600 font-medium text-sm">${qid}</span>
|
<span class="q-number flex-shrink-0 w-8 h-8 bg-${typeColor}-100 rounded-full flex items-center justify-center text-${typeColor}-600 font-medium text-sm">1</span>
|
||||||
<span class="text-xs font-medium px-2 py-1 rounded bg-${typeColor}-50 text-${typeColor}-700">${typeLabel}</span>
|
<span class="text-xs font-medium px-2 py-1 rounded bg-${typeColor}-50 text-${typeColor}-700">${typeLabel}</span>
|
||||||
<input type="number" class="q-score w-20 px-2 py-1 border border-slate-300 rounded text-sm" placeholder="分值" value="${prefill?.score || defaultScore}" min="1">
|
<input type="number" class="q-score w-20 px-2 py-1 border border-slate-300 rounded text-sm" placeholder="分值" value="${prefill?.score || defaultScore}" min="1">
|
||||||
</div>
|
</div>
|
||||||
@@ -299,7 +307,7 @@ function addQuestion(type, prefill) {
|
|||||||
<button onclick="moveQuestion(${qid}, -1)" class="text-slate-400 hover:text-slate-600 text-sm" title="上移">↑</button>
|
<button onclick="moveQuestion(${qid}, -1)" class="text-slate-400 hover:text-slate-600 text-sm" title="上移">↑</button>
|
||||||
<button onclick="moveQuestion(${qid}, 1)" class="text-slate-400 hover:text-slate-600 text-sm" title="下移">↓</button>
|
<button onclick="moveQuestion(${qid}, 1)" class="text-slate-400 hover:text-slate-600 text-sm" title="下移">↓</button>
|
||||||
<button onclick="duplicateQuestion(${qid})" class="text-blue-400 hover:text-blue-600 text-sm" title="复制">复制</button>
|
<button onclick="duplicateQuestion(${qid})" class="text-blue-400 hover:text-blue-600 text-sm" title="复制">复制</button>
|
||||||
<button onclick="document.getElementById('q-${qid}').remove();updateStats();" class="text-red-400 hover:text-red-600 text-sm">删除</button>
|
<button onclick="document.getElementById('q-${qid}').remove();updateStats();renumberQuestions();" class="text-red-400 hover:text-red-600 text-sm">删除</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
@@ -357,6 +365,7 @@ function addQuestion(type, prefill) {
|
|||||||
// 选择题选项公式预览
|
// 选择题选项公式预览
|
||||||
if (type === 'choice' && prefill?.options) { setTimeout(function() { previewOptMath(qid); }, 50); }
|
if (type === 'choice' && prefill?.options) { setTimeout(function() { previewOptMath(qid); }, 50); }
|
||||||
updateStats();
|
updateStats();
|
||||||
|
renumberQuestions();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 数学公式实时预览
|
// 数学公式实时预览
|
||||||
@@ -575,9 +584,10 @@ function submitExam() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const access_password = document.getElementById('exam-password').value.trim();
|
const access_password = document.getElementById('exam-password').value.trim();
|
||||||
|
const visibility = document.getElementById('exam-visibility').value;
|
||||||
fetch('/api/exams', {
|
fetch('/api/exams', {
|
||||||
method: 'POST', headers: {'Content-Type':'application/json'},
|
method: 'POST', headers: {'Content-Type':'application/json'},
|
||||||
body: JSON.stringify({ title, subject, duration, questions, scheduled_start, scheduled_end, score_release_time, access_password })
|
body: JSON.stringify({ title, subject, duration, questions, scheduled_start, scheduled_end, score_release_time, access_password, visibility })
|
||||||
}).then(r => r.json()).then(data => {
|
}).then(r => r.json()).then(data => {
|
||||||
if (data.success) { alert('试卷创建成功!'); window.location.href = '/exams'; }
|
if (data.success) { alert('试卷创建成功!'); window.location.href = '/exams'; }
|
||||||
else alert(data.message);
|
else alert(data.message);
|
||||||
|
|||||||
@@ -3,18 +3,18 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="max-w-5xl mx-auto">
|
<div class="max-w-5xl mx-auto">
|
||||||
{% if need_password %}
|
{% if need_password %}
|
||||||
<div class="bg-white shadow-sm rounded-lg p-8 border border-slate-200 max-w-md mx-auto mt-12">
|
<div class="futuristic-card p-8 max-w-md mx-auto mt-12">
|
||||||
<div class="text-center mb-6">
|
<div class="text-center mb-6">
|
||||||
<svg class="w-16 h-16 mx-auto text-amber-500 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
|
<svg class="w-16 h-16 mx-auto text-amber-500 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
|
||||||
<h2 class="text-xl font-bold text-slate-900">{{ exam.title }}</h2>
|
<h2 class="text-xl font-bold text-slate-900 bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent">{{ exam.title }}</h2>
|
||||||
<p class="text-sm text-slate-500 mt-1">该考试需要输入密码才能进入</p>
|
<p class="text-sm text-slate-500 mt-1">该考试需要输入密码才能进入</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="password-form">
|
<div id="password-form">
|
||||||
<input type="password" id="exam-pwd" placeholder="请输入考试密码"
|
<input type="password" id="exam-pwd" placeholder="请输入考试密码"
|
||||||
class="w-full px-4 py-3 border border-slate-300 rounded-md text-center text-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary"
|
class="input-futuristic w-full text-center text-lg"
|
||||||
onkeydown="if(event.key==='Enter')verifyPassword()">
|
onkeydown="if(event.key==='Enter')verifyPassword()">
|
||||||
<p id="pwd-error" class="text-red-500 text-sm text-center mt-2 hidden">密码错误,请重试</p>
|
<p id="pwd-error" class="text-red-500 text-sm text-center mt-2 hidden">密码错误,请重试</p>
|
||||||
<button onclick="verifyPassword()" class="w-full mt-4 px-4 py-3 bg-primary text-white rounded-md font-medium hover:bg-blue-700">
|
<button onclick="verifyPassword()" class="btn-futuristic w-full mt-4">
|
||||||
验证并进入考试
|
验证并进入考试
|
||||||
</button>
|
</button>
|
||||||
<a href="/exams" class="block text-center mt-3 text-sm text-slate-500 hover:text-slate-700">返回列表</a>
|
<a href="/exams" class="block text-center mt-3 text-sm text-slate-500 hover:text-slate-700">返回列表</a>
|
||||||
@@ -36,27 +36,27 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% elif existing_submission %}
|
{% elif existing_submission %}
|
||||||
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-6 text-center">
|
<div class="futuristic-card p-6 text-center">
|
||||||
<p class="text-yellow-800 font-medium">您已提交过该试卷</p>
|
<p class="text-yellow-800 font-medium">您已提交过该试卷</p>
|
||||||
<a href="/exams/{{ exam.id }}/result" class="mt-3 inline-block px-4 py-2 bg-primary text-white rounded-md text-sm">查看结果</a>
|
<a href="/exams/{{ exam.id }}/result" class="mt-3 inline-block btn-futuristic text-sm">查看结果</a>
|
||||||
</div>
|
</div>
|
||||||
{% elif exam.status == 'closed' %}
|
{% elif exam.status == 'closed' %}
|
||||||
<div class="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
|
<div class="futuristic-card p-6 text-center">
|
||||||
<p class="text-red-800 font-medium">该考试已关闭</p>
|
<p class="text-red-800 font-medium">该考试已关闭</p>
|
||||||
<a href="/exams" class="mt-3 inline-block px-4 py-2 bg-slate-500 text-white rounded-md text-sm">返回列表</a>
|
<a href="/exams" class="mt-3 inline-block btn-outline-futuristic text-sm">返回列表</a>
|
||||||
</div>
|
</div>
|
||||||
{% elif schedule_status == 'not_started' %}
|
{% elif schedule_status == 'not_started' %}
|
||||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-6 text-center">
|
<div class="futuristic-card p-6 text-center">
|
||||||
<p class="text-blue-800 font-medium text-xl mb-2">⏰ 考试尚未开始</p>
|
<p class="text-blue-800 font-medium text-xl mb-2 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">⏰ 考试尚未开始</p>
|
||||||
<p class="text-blue-600">预定开始时间:{{ exam.scheduled_start.strftime('%Y-%m-%d %H:%M') }}</p>
|
<p class="text-blue-600">预定开始时间:{{ exam.scheduled_start.strftime('%Y-%m-%d %H:%M') }}</p>
|
||||||
{% if exam.scheduled_end %}
|
{% if exam.scheduled_end %}
|
||||||
<p class="text-blue-600">预定结束时间:{{ exam.scheduled_end.strftime('%Y-%m-%d %H:%M') }}</p>
|
<p class="text-blue-600">预定结束时间:{{ exam.scheduled_end.strftime('%Y-%m-%d %H:%M') }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<p class="text-sm text-blue-500 mb-2">距离开考还有:</p>
|
<p class="text-sm text-blue-500 mb-2">距离开考还有:</p>
|
||||||
<div id="countdown" class="text-3xl font-bold text-blue-700"></div>
|
<div id="countdown" class="text-3xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent"></div>
|
||||||
</div>
|
</div>
|
||||||
<a href="/exams" class="mt-4 inline-block px-4 py-2 bg-slate-500 text-white rounded-md text-sm">返回列表</a>
|
<a href="/exams" class="mt-4 inline-block btn-outline-futuristic text-sm">返回列表</a>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
@@ -79,17 +79,17 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
{% elif schedule_status == 'ended' %}
|
{% elif schedule_status == 'ended' %}
|
||||||
<div class="bg-gray-50 border border-gray-200 rounded-lg p-6 text-center">
|
<div class="futuristic-card p-6 text-center">
|
||||||
<p class="text-gray-800 font-medium">该考试已结束</p>
|
<p class="text-gray-800 font-medium">该考试已结束</p>
|
||||||
<p class="text-gray-600 text-sm mt-1">结束时间:{{ exam.scheduled_end.strftime('%Y-%m-%d %H:%M') }}</p>
|
<p class="text-gray-600 text-sm mt-1">结束时间:{{ exam.scheduled_end.strftime('%Y-%m-%d %H:%M') }}</p>
|
||||||
<a href="/exams" class="mt-3 inline-block px-4 py-2 bg-slate-500 text-white rounded-md text-sm">返回列表</a>
|
<a href="/exams" class="mt-3 inline-block btn-outline-futuristic text-sm">返回列表</a>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<!-- 顶部信息栏 -->
|
<!-- 顶部信息栏 -->
|
||||||
<div class="bg-white shadow-sm rounded-lg p-4 border border-slate-200 sticky top-0 z-20">
|
<div class="futuristic-card p-4 sticky top-0 z-20">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-lg font-bold text-slate-900">{{ exam.title }}</h1>
|
<h1 class="text-lg font-bold bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent">{{ exam.title }}</h1>
|
||||||
<div class="mt-1 text-sm text-slate-500">
|
<div class="mt-1 text-sm text-slate-500">
|
||||||
{{ exam.subject }} · {{ exam.duration }}分钟 · 满分{{ exam.total_score }}分
|
{{ exam.subject }} · {{ exam.duration }}分钟 · 满分{{ exam.total_score }}分
|
||||||
{% if exam.scheduled_end %}
|
{% if exam.scheduled_end %}
|
||||||
@@ -111,15 +111,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 进度条 -->
|
<!-- 进度条 -->
|
||||||
<div class="mt-3 w-full bg-slate-100 rounded-full h-1.5">
|
<div class="mt-3 progress-futuristic">
|
||||||
<div id="progress-bar" class="bg-primary h-1.5 rounded-full transition-all duration-300" style="width:0%"></div>
|
<div id="progress-bar" class="progress-bar-futuristic transition-all duration-300" style="width:0%"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 flex gap-4">
|
<div class="mt-4 flex gap-4">
|
||||||
<!-- 题号导航面板 -->
|
<!-- 题号导航面板 -->
|
||||||
<div class="hidden lg:block w-48 flex-shrink-0">
|
<div class="hidden lg:block w-48 flex-shrink-0">
|
||||||
<div class="bg-white shadow-sm rounded-lg p-4 border border-slate-200 sticky top-24">
|
<div class="futuristic-card p-4 sticky top-24">
|
||||||
<div class="text-sm font-medium text-slate-700 mb-3">题目导航</div>
|
<div class="text-sm font-medium text-slate-700 mb-3">题目导航</div>
|
||||||
<div class="grid grid-cols-5 gap-2" id="nav-panel">
|
<div class="grid grid-cols-5 gap-2" id="nav-panel">
|
||||||
{% for q in questions %}
|
{% for q in questions %}
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
<!-- 主答题区 -->
|
<!-- 主答题区 -->
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<!-- 移动端题号导航 -->
|
<!-- 移动端题号导航 -->
|
||||||
<div class="lg:hidden mb-4 bg-white shadow-sm rounded-lg p-3 border border-slate-200">
|
<div class="lg:hidden mb-4 futuristic-card p-3">
|
||||||
<div class="flex flex-wrap gap-1.5" id="nav-panel-mobile">
|
<div class="flex flex-wrap gap-1.5" id="nav-panel-mobile">
|
||||||
{% for q in questions %}
|
{% for q in questions %}
|
||||||
<button onclick="goToQuestion({{ loop.index0 }})" id="nav-m-{{ loop.index0 }}"
|
<button onclick="goToQuestion({{ loop.index0 }})" id="nav-m-{{ loop.index0 }}"
|
||||||
@@ -154,16 +154,14 @@
|
|||||||
|
|
||||||
<form id="exam-form" onsubmit="handleSubmit(event)">
|
<form id="exam-form" onsubmit="handleSubmit(event)">
|
||||||
{% for q in questions %}
|
{% for q in questions %}
|
||||||
<div class="question-card bg-white shadow-sm rounded-lg p-6 border border-slate-200 mb-4" data-index="{{ loop.index0 }}" style="{% if loop.index0 != 0 %}display:none{% endif %}">
|
<div class="question-card futuristic-card p-6 mb-4" data-index="{{ loop.index0 }}" style="{% if loop.index0 != 0 %}display:none{% endif %}">
|
||||||
<div class="flex items-start space-x-4">
|
<div class="flex items-start space-x-4">
|
||||||
<span class="flex-shrink-0 w-8 h-8 bg-slate-100 rounded-full flex items-center justify-center text-slate-600 font-medium">{{ loop.index }}</span>
|
<span class="flex-shrink-0 w-8 h-8 bg-gradient-to-br from-purple-500 to-blue-500 rounded-full flex items-center justify-center text-white font-medium">{{ loop.index }}</span>
|
||||||
<div class="flex-1 space-y-4">
|
<div class="flex-1 space-y-4">
|
||||||
<div class="flex justify-between items-start">
|
<div class="flex justify-between items-start">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-xs font-medium px-2 py-0.5 rounded
|
<span class="badge-futuristic text-xs
|
||||||
{% if q.type == 'choice' %}bg-blue-50 text-blue-700
|
{% if q.type == 'choice' %}{% elif q.type == 'fill' %}{% else %}{% endif %}">
|
||||||
{% elif q.type == 'fill' %}bg-green-50 text-green-700
|
|
||||||
{% else %}bg-purple-50 text-purple-700{% endif %}">
|
|
||||||
{% if q.type == 'choice' %}选择题{% elif q.type == 'fill' %}填空题{% else %}解答题{% endif %}
|
{% if q.type == 'choice' %}选择题{% elif q.type == 'fill' %}填空题{% else %}解答题{% endif %}
|
||||||
</span>
|
</span>
|
||||||
<p class="mt-2 text-lg text-slate-900">{{ q.content }}</p>
|
<p class="mt-2 text-lg text-slate-900">{{ q.content }}</p>
|
||||||
@@ -187,12 +185,12 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% elif q.type == 'fill' %}
|
{% elif q.type == 'fill' %}
|
||||||
<input type="text" name="q-{{ q.id }}" class="w-full px-3 py-2 border border-slate-300 rounded-md answer-input" placeholder="请输入答案" data-qid="{{ q.id }}" oninput="onAnswerChange({{ q.id }})">
|
<input type="text" name="q-{{ q.id }}" class="input-futuristic w-full answer-input" placeholder="请输入答案" data-qid="{{ q.id }}" oninput="onAnswerChange({{ q.id }})">
|
||||||
{% else %}
|
{% else %}
|
||||||
<textarea name="q-{{ q.id }}" rows="6" class="w-full rounded-lg border-slate-300 shadow-sm focus:border-primary focus:ring-primary border px-3 py-2 answer-input" placeholder="请输入您的答案..." data-qid="{{ q.id }}" oninput="onAnswerChange({{ q.id }})"></textarea>
|
<textarea name="q-{{ q.id }}" rows="6" class="input-futuristic w-full answer-input" placeholder="请输入您的答案..." data-qid="{{ q.id }}" oninput="onAnswerChange({{ q.id }})"></textarea>
|
||||||
<div class="flex items-center gap-2 mt-2">
|
<div class="flex items-center gap-2 mt-2">
|
||||||
<button type="button" onclick="examUpload({{ q.id }})" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs border border-slate-200 rounded-lg hover:bg-slate-50 text-slate-600">📷 上传图片</button>
|
<button type="button" onclick="examUpload({{ q.id }})" class="btn-outline-futuristic inline-flex items-center gap-1 text-xs py-1.5 px-3">📷 上传图片</button>
|
||||||
<button type="button" onclick="examCamera({{ q.id }})" class="inline-flex items-center gap-1 px-3 py-1.5 text-xs border border-slate-200 rounded-lg hover:bg-slate-50 text-slate-600">📸 拍照上传</button>
|
<button type="button" onclick="examCamera({{ q.id }})" class="btn-outline-futuristic inline-flex items-center gap-1 text-xs py-1.5 px-3">📸 拍照上传</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="img-preview-{{ q.id }}" class="flex flex-wrap gap-2 mt-2"></div>
|
<div id="img-preview-{{ q.id }}" class="flex flex-wrap gap-2 mt-2"></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -203,16 +201,16 @@
|
|||||||
|
|
||||||
<!-- 分页控制 -->
|
<!-- 分页控制 -->
|
||||||
<div class="flex justify-between items-center mt-4 mb-8">
|
<div class="flex justify-between items-center mt-4 mb-8">
|
||||||
<button type="button" id="prev-btn" onclick="prevQuestion()" class="px-5 py-2.5 bg-slate-100 text-slate-700 rounded-lg font-medium hover:bg-slate-200 disabled:opacity-40 disabled:cursor-not-allowed" disabled>
|
<button type="button" id="prev-btn" onclick="prevQuestion()" class="btn-outline-futuristic disabled:opacity-40 disabled:cursor-not-allowed" disabled>
|
||||||
← 上一题
|
← 上一题
|
||||||
</button>
|
</button>
|
||||||
<span class="text-sm text-slate-500">
|
<span class="text-sm text-slate-500">
|
||||||
第 <span id="current-num">1</span> / {{ questions|length }} 题
|
第 <span id="current-num">1</span> / {{ questions|length }} 题
|
||||||
</span>
|
</span>
|
||||||
<button type="button" id="next-btn" onclick="nextQuestion()" class="px-5 py-2.5 bg-primary text-white rounded-lg font-medium hover:bg-blue-700">
|
<button type="button" id="next-btn" onclick="nextQuestion()" class="btn-futuristic">
|
||||||
下一题 →
|
下一题 →
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" id="submit-btn" class="px-6 py-2.5 bg-red-600 text-white rounded-lg font-medium hover:bg-red-700 hidden">
|
<button type="submit" id="submit-btn" class="btn-futuristic bg-gradient-to-r from-red-500 to-pink-600 hidden">
|
||||||
提交试卷
|
提交试卷
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,17 +5,17 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<!-- 头部区域 -->
|
<!-- 头部区域 -->
|
||||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 bg-white p-6 rounded-2xl shadow-sm border border-slate-100">
|
<div class="futuristic-card flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 p-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-bold text-slate-900 flex items-center">
|
<h1 class="text-2xl font-bold flex items-center bg-gradient-to-r from-cyan-400 via-blue-500 to-purple-600 bg-clip-text text-transparent">
|
||||||
<span class="w-10 h-10 bg-indigo-50 text-indigo-600 rounded-xl flex items-center justify-center mr-3 shadow-sm border border-indigo-100">📝</span>
|
<span class="w-10 h-10 bg-gradient-to-br from-cyan-500 to-blue-600 text-white rounded-xl flex items-center justify-center mr-3 shadow-lg shadow-cyan-500/50">📝</span>
|
||||||
考试中心
|
考试中心
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-slate-500 text-sm mt-1 ml-13">海量真题与模拟卷,随时随地进行练习与自测。</p>
|
<p class="text-slate-400 text-sm mt-1 ml-13">海量真题与模拟卷,随时随地进行练习与自测。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap items-center gap-3">
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
{% if user and (user.role == 'admin' or user.role == 'teacher') %}
|
{% if user and (user.role == 'admin' or user.role == 'teacher') %}
|
||||||
<a href="/exams/create" class="inline-flex items-center px-4 py-2.5 bg-primary text-white rounded-xl hover:bg-blue-600 shadow-sm text-sm font-medium transition-all transform hover:-translate-y-0.5">
|
<a href="/exams/create" class="btn-futuristic inline-flex items-center px-4 py-2.5 text-sm font-medium">
|
||||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
||||||
创建新试卷
|
创建新试卷
|
||||||
</a>
|
</a>
|
||||||
@@ -24,54 +24,54 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 搜索区域 -->
|
<!-- 搜索区域 -->
|
||||||
<div class="bg-white p-5 rounded-2xl shadow-sm border border-slate-100 flex flex-wrap gap-4 items-center justify-between sticky top-20 z-10 glass-panel">
|
<div class="futuristic-card p-5 flex flex-wrap gap-4 items-center justify-between sticky top-20 z-10">
|
||||||
<form method="GET" action="/exams" class="flex flex-wrap items-center gap-3 w-full">
|
<form method="GET" action="/exams" class="flex flex-wrap items-center gap-3 w-full">
|
||||||
<div class="relative w-full sm:w-auto flex-1">
|
<div class="relative w-full sm:w-auto flex-1">
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
<svg class="h-4 w-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
<svg class="h-4 w-4 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" name="q" value="{{ search_query or '' }}" placeholder="搜索试卷名称..." class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary bg-slate-50 transition-all">
|
<input type="text" name="q" value="{{ search_query or '' }}" placeholder="搜索试卷名称..." class="w-full pl-10 pr-4 py-2.5 border border-cyan-500/30 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500 bg-slate-900/50 text-slate-200 placeholder-slate-500 transition-all">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative w-full sm:w-auto">
|
<div class="relative w-full sm:w-auto">
|
||||||
<select name="subject" class="w-full sm:w-32 appearance-none px-4 py-2.5 pl-10 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary bg-slate-50 hover:bg-slate-100 transition-colors cursor-pointer">
|
<select name="subject" class="w-full sm:w-32 appearance-none px-4 py-2.5 pl-10 border border-cyan-500/30 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500 bg-slate-900/50 text-slate-200 hover:bg-slate-800/50 transition-colors cursor-pointer">
|
||||||
<option value="">所有科目</option>
|
<option value="">所有科目</option>
|
||||||
{% for subject in all_subjects %}
|
{% for subject in all_subjects %}
|
||||||
<option value="{{ subject }}" {% if subject_filter == subject %}selected{% endif %}>{{ subject }}</option>
|
<option value="{{ subject }}" {% if subject_filter == subject %}selected{% endif %}>{{ subject }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
<svg class="h-4 w-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
|
<svg class="h-4 w-4 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
<svg class="h-4 w-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
<svg class="h-4 w-4 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="w-full sm:w-auto px-5 py-2.5 bg-slate-800 text-white rounded-xl hover:bg-slate-700 text-sm font-medium transition-colors shadow-sm">
|
<button type="submit" class="btn-futuristic w-full sm:w-auto px-5 py-2.5 text-sm font-medium">
|
||||||
搜索
|
搜索
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{% if search_query or subject_filter %}
|
{% if search_query or subject_filter %}
|
||||||
<a href="/exams" class="w-full sm:w-auto px-5 py-2.5 bg-slate-100 text-slate-600 rounded-xl hover:bg-slate-200 text-sm font-medium transition-colors text-center border border-slate-200">
|
<a href="/exams" class="w-full sm:w-auto px-5 py-2.5 bg-slate-800/50 text-slate-300 rounded-xl hover:bg-slate-700/50 text-sm font-medium transition-all text-center border border-cyan-500/30 hover:border-cyan-500/50">
|
||||||
重置
|
重置
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
{% if search_query or subject_filter %}
|
{% if search_query or subject_filter %}
|
||||||
<div class="mt-3 w-full text-xs text-slate-500 bg-slate-50 px-3 py-2 rounded-lg border border-slate-100 inline-block">
|
<div class="mt-3 w-full text-xs text-slate-400 bg-slate-900/30 px-3 py-2 rounded-lg border border-cyan-500/20 inline-block">
|
||||||
<span class="font-medium text-slate-700">筛选结果:</span>
|
<span class="font-medium text-cyan-400">筛选结果:</span>
|
||||||
{% if search_query %}包含 "<span class="text-primary">{{ search_query }}</span>"{% endif %}
|
{% if search_query %}包含 "<span class="text-cyan-300">{{ search_query }}</span>"{% endif %}
|
||||||
{% if subject_filter %}{% if search_query %},{% endif %}科目为 "<span class="text-primary">{{ subject_filter }}</span>"{% endif %}
|
{% if subject_filter %}{% if search_query %},{% endif %}科目为 "<span class="text-cyan-300">{{ subject_filter }}</span>"{% endif %}
|
||||||
<span class="ml-2 text-slate-400">共找到 {{ exams|length }} 份试卷</span>
|
<span class="ml-2 text-slate-500">共找到 {{ exams|length }} 份试卷</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 试卷列表 -->
|
<!-- 试卷列表 -->
|
||||||
{% if exams|length == 0 %}
|
{% if exams|length == 0 %}
|
||||||
<div class="col-span-full py-20 flex flex-col items-center justify-center text-slate-400 bg-white rounded-2xl border border-slate-100 border-dashed">
|
<div class="col-span-full py-20 flex flex-col items-center justify-center text-slate-400 futuristic-card border-dashed">
|
||||||
<svg class="w-16 h-16 mb-4 text-slate-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
<svg class="w-16 h-16 mb-4 text-cyan-500/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||||
<p>暂无符合条件的试卷</p>
|
<p>暂无符合条件的试卷</p>
|
||||||
{% if user and (user.role == 'admin' or user.role == 'teacher') %}
|
{% if user and (user.role == 'admin' or user.role == 'teacher') %}
|
||||||
<p class="text-sm mt-2">点击上方"创建新试卷"按钮开始命题吧</p>
|
<p class="text-sm mt-2">点击上方"创建新试卷"按钮开始命题吧</p>
|
||||||
@@ -81,130 +81,130 @@
|
|||||||
<div class="grid gap-6 grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
|
<div class="grid gap-6 grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{% for exam in exams %}
|
{% for exam in exams %}
|
||||||
{% set subjectColor = 'blue' %}
|
{% set subjectColor = 'blue' %}
|
||||||
{% set subjectGradient = 'from-blue-500 to-cyan-400' %}
|
{% set subjectGradient = 'from-cyan-500 to-blue-600' %}
|
||||||
{% set subjectIcon = 'M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253' %}
|
{% set subjectIcon = 'M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253' %}
|
||||||
|
|
||||||
{% if exam.subject == '数学' %}
|
{% if exam.subject == '数学' %}
|
||||||
{% set subjectColor = 'indigo' %}
|
{% set subjectColor = 'indigo' %}
|
||||||
{% set subjectGradient = 'from-indigo-500 to-purple-400' %}
|
{% set subjectGradient = 'from-purple-500 to-indigo-600' %}
|
||||||
{% set subjectIcon = 'M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z' %}
|
{% set subjectIcon = 'M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z' %}
|
||||||
{% elif exam.subject == '英语' %}
|
{% elif exam.subject == '英语' %}
|
||||||
{% set subjectColor = 'rose' %}
|
{% set subjectColor = 'rose' %}
|
||||||
{% set subjectGradient = 'from-rose-500 to-pink-400' %}
|
{% set subjectGradient = 'from-pink-500 to-rose-600' %}
|
||||||
{% set subjectIcon = 'M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129' %}
|
{% set subjectIcon = 'M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129' %}
|
||||||
{% elif exam.subject in ['物理', '化学', '生物'] %}
|
{% elif exam.subject in ['物理', '化学', '生物'] %}
|
||||||
{% set subjectColor = 'emerald' %}
|
{% set subjectColor = 'emerald' %}
|
||||||
{% set subjectGradient = 'from-emerald-500 to-teal-400' %}
|
{% set subjectGradient = 'from-emerald-500 to-teal-600' %}
|
||||||
{% set subjectIcon = 'M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z' %}
|
{% set subjectIcon = 'M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z' %}
|
||||||
{% elif exam.subject in ['历史', '地理', '政治'] %}
|
{% elif exam.subject in ['历史', '地理', '政治'] %}
|
||||||
{% set subjectColor = 'amber' %}
|
{% set subjectColor = 'amber' %}
|
||||||
{% set subjectGradient = 'from-amber-500 to-orange-400' %}
|
{% set subjectGradient = 'from-amber-500 to-orange-600' %}
|
||||||
{% set subjectIcon = 'M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z' %}
|
{% set subjectIcon = 'M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="group bg-white rounded-3xl shadow-sm border border-slate-100 overflow-hidden hover-card-up flex flex-col relative">
|
<div class="futuristic-card-dark group hover-lift flex flex-col relative overflow-hidden transition-all duration-300 transform hover:scale-105">
|
||||||
<!-- 卡片封面海报 -->
|
<!-- 卡片封面海报 -->
|
||||||
<div class="relative h-28 bg-gradient-to-r {{ subjectGradient }} overflow-hidden">
|
<div class="relative h-28 bg-gradient-to-r {{ subjectGradient }} overflow-hidden">
|
||||||
<div class="absolute inset-0 bg-grid-pattern opacity-10"></div>
|
<div class="absolute inset-0 bg-grid-pattern opacity-10"></div>
|
||||||
<div class="absolute -bottom-10 -right-10 w-32 h-32 bg-white/20 blur-2xl rounded-full"></div>
|
<div class="absolute -bottom-10 -right-10 w-32 h-32 bg-white/10 blur-2xl rounded-full"></div>
|
||||||
|
|
||||||
{% if exam.status == 'closed' %}
|
{% if exam.status == 'closed' %}
|
||||||
<span class="absolute top-4 right-4 bg-slate-900/50 backdrop-blur-md text-white text-xs font-medium px-3 py-1 rounded-full shadow-sm border border-white/10">已关闭</span>
|
<span class="badge-futuristic absolute top-4 right-4 bg-slate-900/50 backdrop-blur-md text-slate-300 border-slate-700/50">已关闭</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="absolute top-4 right-4 bg-white/90 backdrop-blur-md text-{{subjectColor}}-600 text-xs font-bold px-3 py-1 rounded-full shadow-sm flex items-center">
|
<span class="badge-futuristic absolute top-4 right-4 bg-white/90 backdrop-blur-md text-cyan-600 border-cyan-500/30">
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-{{subjectColor}}-500 mr-1.5 animate-pulse"></span>进行中
|
<span class="w-1.5 h-1.5 rounded-full bg-cyan-500 mr-1.5 animate-pulse"></span>进行中
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 悬浮图标 -->
|
<!-- 悬浮图标 -->
|
||||||
<div class="absolute top-[4.5rem] left-6">
|
<div class="absolute top-[4.5rem] left-6">
|
||||||
<div class="w-14 h-14 bg-white rounded-2xl flex items-center justify-center text-{{subjectColor}}-500 shadow-md border-4 border-white group-hover:scale-110 transition-transform duration-300">
|
<div class="w-14 h-14 bg-gradient-to-br from-slate-800 to-slate-900 rounded-2xl flex items-center justify-center text-cyan-400 shadow-lg shadow-cyan-500/30 border-2 border-cyan-500/30 group-hover:scale-110 group-hover:shadow-cyan-500/50 transition-all duration-300">
|
||||||
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="{{subjectIcon}}"/></svg>
|
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="{{subjectIcon}}"/></svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-6 pt-12 flex-1 flex flex-col">
|
<div class="p-6 pt-12 flex-1 flex flex-col">
|
||||||
<div class="flex items-start mb-3">
|
<div class="flex items-start mb-3">
|
||||||
<span class="inline-flex items-center px-2.5 py-1 rounded-lg bg-slate-100 text-slate-600 text-xs font-medium border border-slate-200">
|
<span class="badge-futuristic">
|
||||||
{{ exam.subject }}
|
{{ exam.subject }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="text-lg font-bold text-slate-900 mb-3 line-clamp-2 group-hover:text-{{subjectColor}}-600 transition-colors flex-1">{{ exam.title }}</h3>
|
<h3 class="text-lg font-bold mb-3 line-clamp-2 bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent group-hover:from-blue-300 group-hover:via-purple-300 group-hover:to-pink-300 transition-all flex-1">{{ exam.title }}</h3>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-3 mb-5 bg-slate-50 p-3 rounded-xl border border-slate-100">
|
<div class="grid grid-cols-2 gap-3 mb-5 bg-slate-900/30 p-3 rounded-xl border border-cyan-500/20">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-[10px] text-slate-400 block mb-0.5">满分 / 题目</span>
|
<span class="text-[10px] text-slate-500 block mb-0.5">满分 / 题目</span>
|
||||||
<span class="text-xs font-semibold text-slate-700">{{ exam.total_score }}分 / {{ exam.questions|fromjson|length }}题</span>
|
<span class="text-xs font-semibold text-cyan-300">{{ exam.total_score }}分 / {{ exam.questions|fromjson|length }}题</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-[10px] text-slate-400 block mb-0.5">考试时长</span>
|
<span class="text-[10px] text-slate-500 block mb-0.5">考试时长</span>
|
||||||
<span class="text-xs font-semibold text-slate-700">{{ exam.duration }} 分钟</span>
|
<span class="text-xs font-semibold text-cyan-300">{{ exam.duration }} 分钟</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if exam.scheduled_start or exam.scheduled_end %}
|
{% if exam.scheduled_start or exam.scheduled_end %}
|
||||||
<div class="mb-4 text-xs text-slate-500 flex flex-col gap-1 border-l-2 border-{{subjectColor}}-200 pl-2">
|
<div class="mb-4 text-xs text-slate-400 flex flex-col gap-1 border-l-2 border-cyan-500/50 pl-2">
|
||||||
{% if exam.scheduled_start %}
|
{% if exam.scheduled_start %}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<svg class="w-3.5 h-3.5 mr-1 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
<svg class="w-3.5 h-3.5 mr-1 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||||
开始:{{ exam.scheduled_start.strftime('%m-%d %H:%M') }}
|
开始:{{ exam.scheduled_start.strftime('%m-%d %H:%M') }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if exam.scheduled_end %}
|
{% if exam.scheduled_end %}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<svg class="w-3.5 h-3.5 mr-1 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
<svg class="w-3.5 h-3.5 mr-1 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||||
截止:{{ exam.scheduled_end.strftime('%m-%d %H:%M') }}
|
截止:{{ exam.scheduled_end.strftime('%m-%d %H:%M') }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="mt-auto pt-4 border-t border-slate-100 flex items-center justify-between gap-2 flex-wrap">
|
<div class="mt-auto pt-4 border-t border-cyan-500/20 flex items-center justify-between gap-2 flex-wrap">
|
||||||
{% set sub = user_submissions.get(exam.id) %}
|
{% set sub = user_submissions.get(exam.id) %}
|
||||||
{% if sub %}
|
{% if sub %}
|
||||||
<div class="flex items-center bg-slate-50 px-2.5 py-1.5 rounded-lg border border-slate-200">
|
<div class="flex items-center bg-slate-900/30 px-2.5 py-1.5 rounded-lg border border-cyan-500/30">
|
||||||
{% if sub.graded %}
|
{% if sub.graded %}
|
||||||
<span class="text-xs font-medium text-slate-600 mr-2 border-r border-slate-200 pr-2">已批改</span>
|
<span class="text-xs font-medium text-slate-400 mr-2 border-r border-slate-600 pr-2">已批改</span>
|
||||||
<span class="text-sm font-bold text-{{subjectColor}}-600">{{ sub.score }} <span class="text-[10px] text-slate-400 font-normal">/ {{ exam.total_score }}</span></span>
|
<span class="text-sm font-bold text-cyan-400">{{ sub.score }} <span class="text-[10px] text-slate-500 font-normal">/ {{ exam.total_score }}</span></span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-xs font-medium text-amber-600 flex items-center">
|
<span class="text-xs font-medium text-amber-400 flex items-center">
|
||||||
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||||
待批改
|
待批改
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<a href="/exams/{{ exam.id }}/result" class="ml-auto inline-flex items-center px-4 py-2 border border-slate-200 text-xs font-medium rounded-xl text-slate-700 bg-white hover:bg-slate-50 hover:border-slate-300 transition-colors shadow-sm">
|
<a href="/exams/{{ exam.id }}/result" class="btn-outline-futuristic ml-auto inline-flex items-center px-4 py-2 text-xs font-medium">
|
||||||
查看试卷
|
查看试卷
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-xs text-slate-400 flex items-center">
|
<div class="text-xs text-slate-400 flex items-center">
|
||||||
<span class="w-5 h-5 rounded-full bg-slate-100 flex items-center justify-center mr-1.5 text-[10px]">{{ exam.creator.name[0] if exam.creator else '?' }}</span>
|
<span class="w-5 h-5 rounded-full bg-slate-800 flex items-center justify-center mr-1.5 text-[10px] text-cyan-400 border border-cyan-500/30">{{ exam.creator.name[0] if exam.creator else '?' }}</span>
|
||||||
{{ exam.creator.name if exam.creator else '未知出题人' }}
|
{{ exam.creator.name if exam.creator else '未知出题人' }}
|
||||||
</div>
|
</div>
|
||||||
{% if exam.status != 'closed' %}
|
{% if exam.status != 'closed' %}
|
||||||
<a href="/exams/{{ exam.id }}" class="ml-auto inline-flex items-center px-5 py-2 border border-transparent text-sm font-medium rounded-xl shadow-sm text-white bg-{{subjectColor}}-600 hover:bg-{{subjectColor}}-700 transition-colors transform hover:-translate-y-0.5">
|
<a href="/exams/{{ exam.id }}" class="btn-futuristic ml-auto inline-flex items-center px-5 py-2 text-sm font-medium">
|
||||||
开始考试
|
开始考试
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="ml-auto text-sm text-slate-400 font-medium px-4 py-2 bg-slate-50 rounded-xl">已关闭</span>
|
<span class="ml-auto text-sm text-slate-500 font-medium px-4 py-2 bg-slate-800/50 rounded-xl border border-slate-700/50">已关闭</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if user and (user.role == 'admin' or user.role == 'teacher') %}
|
{% if user and (user.role == 'admin' or user.role == 'teacher') %}
|
||||||
<div class="mt-4 pt-3 border-t border-slate-100/50 flex flex-wrap gap-2 justify-end">
|
<div class="mt-4 pt-3 border-t border-cyan-500/20 flex flex-wrap gap-2 justify-end">
|
||||||
<a href="/exams/{{ exam.id }}/submissions" class="px-3 py-1.5 bg-slate-50 text-slate-600 hover:bg-slate-100 hover:text-slate-900 rounded-lg text-xs font-medium transition-colors border border-slate-200">提交情况</a>
|
<a href="/exams/{{ exam.id }}/submissions" class="px-3 py-1.5 bg-slate-800/50 text-slate-300 hover:bg-slate-700/50 hover:text-slate-200 rounded-lg text-xs font-medium transition-colors border border-cyan-500/30">提交情况</a>
|
||||||
<a href="/exams/{{ exam.id }}/print" class="px-3 py-1.5 bg-slate-50 text-slate-600 hover:bg-slate-100 hover:text-slate-900 rounded-lg text-xs font-medium transition-colors border border-slate-200">打印试卷</a>
|
<a href="/exams/{{ exam.id }}/print" class="px-3 py-1.5 bg-slate-800/50 text-slate-300 hover:bg-slate-700/50 hover:text-slate-200 rounded-lg text-xs font-medium transition-colors border border-cyan-500/30">打印试卷</a>
|
||||||
|
|
||||||
{% if user.role == 'admin' or exam.creator_id == user.id %}
|
{% if user.role == 'admin' or exam.creator_id == user.id %}
|
||||||
{% if exam.status == 'available' %}
|
{% if exam.status == 'available' %}
|
||||||
<button onclick="toggleExamStatus({{ exam.id }}, 'closed')" class="px-3 py-1.5 bg-orange-50 text-orange-600 hover:bg-orange-100 rounded-lg text-xs font-medium transition-colors border border-orange-200">关闭考试</button>
|
<button onclick="toggleExamStatus({{ exam.id }}, 'closed')" class="px-3 py-1.5 bg-orange-500/20 text-orange-400 hover:bg-orange-500/30 rounded-lg text-xs font-medium transition-colors border border-orange-500/30">关闭考试</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button onclick="toggleExamStatus({{ exam.id }}, 'available')" class="px-3 py-1.5 bg-green-50 text-green-600 hover:bg-green-100 rounded-lg text-xs font-medium transition-colors border border-green-200">开放考试</button>
|
<button onclick="toggleExamStatus({{ exam.id }}, 'available')" class="px-3 py-1.5 bg-green-500/20 text-green-400 hover:bg-green-500/30 rounded-lg text-xs font-medium transition-colors border border-green-500/30">开放考试</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button onclick="deleteExam({{ exam.id }})" class="px-3 py-1.5 bg-red-50 text-red-600 hover:bg-red-100 rounded-lg text-xs font-medium transition-colors border border-red-200">删除</button>
|
<button onclick="deleteExam({{ exam.id }})" class="px-3 py-1.5 bg-red-500/20 text-red-400 hover:bg-red-500/30 rounded-lg text-xs font-medium transition-colors border border-red-500/30">删除</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="max-w-4xl mx-auto space-y-6">
|
<div class="max-w-4xl mx-auto space-y-6">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<h1 class="text-2xl font-bold text-slate-900">考试结果</h1>
|
<h1 class="text-2xl font-bold bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent">考试结果</h1>
|
||||||
<a href="/exams" class="text-sm text-slate-500 hover:text-slate-700">← 返回列表</a>
|
<a href="/exams" class="text-sm text-slate-500 hover:text-slate-700">← 返回列表</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
<div class="futuristic-card p-6">
|
||||||
<h2 class="text-xl font-bold text-slate-900">{{ exam.title }}</h2>
|
<h2 class="text-xl font-bold bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent">{{ exam.title }}</h2>
|
||||||
<div class="mt-2 flex items-center text-sm text-slate-500 space-x-4">
|
<div class="mt-2 flex items-center text-sm text-slate-500 space-x-4">
|
||||||
<span>{{ exam.subject }}</span>
|
<span>{{ exam.subject }}</span>
|
||||||
<span>满分{{ exam.total_score }}分</span>
|
<span>满分{{ exam.total_score }}分</span>
|
||||||
@@ -27,9 +27,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% for q in questions %}
|
{% for q in questions %}
|
||||||
<div class="bg-white shadow-sm rounded-lg p-6 border border-slate-200">
|
<div class="futuristic-card p-6">
|
||||||
<div class="flex items-start space-x-4">
|
<div class="flex items-start space-x-4">
|
||||||
<span class="flex-shrink-0 w-8 h-8 bg-slate-100 rounded-full flex items-center justify-center text-slate-600 font-medium">{{ loop.index }}</span>
|
<span class="flex-shrink-0 w-8 h-8 bg-gradient-to-br from-purple-500 to-blue-500 rounded-full flex items-center justify-center text-white font-medium">{{ loop.index }}</span>
|
||||||
<div class="flex-1 space-y-3">
|
<div class="flex-1 space-y-3">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<p class="text-lg text-slate-900">{{ q.content }}</p>
|
<p class="text-lg text-slate-900">{{ q.content }}</p>
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
<div class="w-5 h-5"></div>
|
<div class="w-5 h-5"></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="text-slate-700">{{ letter }}. {{ opt }}</span>
|
<span class="text-slate-700">{{ letter }}. {{ opt }}</span>
|
||||||
{% if is_answer %}<span class="text-xs text-green-600 font-medium ml-2">✓ 正确答案</span>{% endif %}
|
{% if is_answer %}<span class="badge-futuristic text-xs ml-2">✓ 正确答案</span>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,47 +44,47 @@
|
|||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- 统计卡片 -->
|
<!-- 统计卡片 -->
|
||||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<div class="bg-gradient-to-br from-blue-500 to-blue-600 rounded-2xl p-5 text-white shadow-sm relative overflow-hidden group">
|
<div class="futuristic-card p-5 relative overflow-hidden group">
|
||||||
<div class="absolute -right-4 -top-4 w-24 h-24 bg-white/10 rounded-full blur-xl group-hover:bg-white/20 transition-colors"></div>
|
<div class="absolute -right-4 -top-4 w-24 h-24 bg-white/10 rounded-full blur-xl group-hover:bg-white/20 transition-colors"></div>
|
||||||
<div class="relative z-10">
|
<div class="relative z-10">
|
||||||
<div class="text-3xl font-bold mb-1" id="s-posts">0</div>
|
<div class="text-3xl font-bold mb-1 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent" id="s-posts">0</div>
|
||||||
<div class="text-sm text-blue-100 flex items-center">
|
<div class="text-sm text-slate-600 flex items-center">
|
||||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 002-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/></svg>
|
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 002-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/></svg>
|
||||||
总帖子数
|
总帖子数
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gradient-to-br from-emerald-500 to-emerald-600 rounded-2xl p-5 text-white shadow-sm relative overflow-hidden group">
|
<div class="futuristic-card p-5 relative overflow-hidden group">
|
||||||
<div class="absolute -right-4 -top-4 w-24 h-24 bg-white/10 rounded-full blur-xl group-hover:bg-white/20 transition-colors"></div>
|
<div class="absolute -right-4 -top-4 w-24 h-24 bg-white/10 rounded-full blur-xl group-hover:bg-white/20 transition-colors"></div>
|
||||||
<div class="relative z-10">
|
<div class="relative z-10">
|
||||||
<div class="text-3xl font-bold mb-1" id="s-replies">0</div>
|
<div class="text-3xl font-bold mb-1 bg-gradient-to-r from-emerald-600 to-green-600 bg-clip-text text-transparent" id="s-replies">0</div>
|
||||||
<div class="text-sm text-emerald-100 flex items-center">
|
<div class="text-sm text-slate-600 flex items-center">
|
||||||
<svg class="w-4 h-4 mr-1.5" 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>
|
<svg class="w-4 h-4 mr-1.5" 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>
|
||||||
总回复数
|
总回复数
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gradient-to-br from-orange-500 to-orange-600 rounded-2xl p-5 text-white shadow-sm relative overflow-hidden group">
|
<div class="futuristic-card p-5 relative overflow-hidden group">
|
||||||
<div class="absolute -right-4 -top-4 w-24 h-24 bg-white/10 rounded-full blur-xl group-hover:bg-white/20 transition-colors"></div>
|
<div class="absolute -right-4 -top-4 w-24 h-24 bg-white/10 rounded-full blur-xl group-hover:bg-white/20 transition-colors"></div>
|
||||||
<div class="relative z-10">
|
<div class="relative z-10">
|
||||||
<div class="text-3xl font-bold mb-1" id="s-today">0</div>
|
<div class="text-3xl font-bold mb-1 bg-gradient-to-r from-orange-600 to-amber-600 bg-clip-text text-transparent" id="s-today">0</div>
|
||||||
<div class="text-sm text-orange-100 flex items-center">
|
<div class="text-sm text-slate-600 flex items-center">
|
||||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||||
今日新帖
|
今日新帖
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gradient-to-br from-violet-500 to-violet-600 rounded-2xl p-5 text-white shadow-sm relative overflow-hidden group">
|
<div class="futuristic-card p-5 relative overflow-hidden group">
|
||||||
<div class="absolute -right-4 -top-4 w-24 h-24 bg-white/10 rounded-full blur-xl group-hover:bg-white/20 transition-colors"></div>
|
<div class="absolute -right-4 -top-4 w-24 h-24 bg-white/10 rounded-full blur-xl group-hover:bg-white/20 transition-colors"></div>
|
||||||
<div class="relative z-10">
|
<div class="relative z-10">
|
||||||
<div class="flex items-center mb-1">
|
<div class="flex items-center mb-1">
|
||||||
<span class="relative flex h-3 w-3 mr-2">
|
<span class="relative flex h-3 w-3 mr-2">
|
||||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
|
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-purple-500 opacity-75"></span>
|
||||||
<span class="relative inline-flex rounded-full h-3 w-3 bg-white"></span>
|
<span class="relative inline-flex rounded-full h-3 w-3 bg-purple-600"></span>
|
||||||
</span>
|
</span>
|
||||||
<div class="text-3xl font-bold" id="s-online">0</div>
|
<div class="text-3xl font-bold bg-gradient-to-r from-violet-600 to-purple-600 bg-clip-text text-transparent" id="s-online">0</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-violet-100 flex items-center">
|
<div class="text-sm text-slate-600 flex items-center">
|
||||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
||||||
当前在线人数
|
当前在线人数
|
||||||
</div>
|
</div>
|
||||||
@@ -93,37 +93,37 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 头部操作区: 高级毛玻璃渐变 -->
|
<!-- 头部操作区: 高级毛玻璃渐变 -->
|
||||||
<div class="relative flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 bg-gradient-to-r from-indigo-600 to-purple-600 p-8 rounded-3xl shadow-xl border border-indigo-500 overflow-hidden text-white">
|
<div class="futuristic-card relative flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 p-8 overflow-hidden">
|
||||||
<div class="absolute top-0 right-0 w-64 h-64 bg-white/10 blur-3xl rounded-full translate-x-1/2 -translate-y-1/2"></div>
|
<div class="absolute top-0 right-0 w-64 h-64 bg-purple-500/10 blur-3xl rounded-full translate-x-1/2 -translate-y-1/2"></div>
|
||||||
<div class="absolute -bottom-10 -left-10 w-40 h-40 bg-purple-500/20 blur-2xl rounded-full"></div>
|
<div class="absolute -bottom-10 -left-10 w-40 h-40 bg-blue-500/10 blur-2xl rounded-full"></div>
|
||||||
|
|
||||||
<div class="relative z-10">
|
<div class="relative z-10">
|
||||||
<h1 class="text-3xl font-extrabold flex items-center drop-shadow-md">
|
<h1 class="text-3xl font-extrabold flex items-center bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent">
|
||||||
<span class="w-12 h-12 bg-white/20 backdrop-blur-md text-white rounded-2xl flex items-center justify-center mr-4 shadow-inner border border-white/20">💬</span>
|
<span class="w-12 h-12 bg-gradient-to-br from-purple-500 to-blue-500 text-white rounded-2xl flex items-center justify-center mr-4 shadow-lg">💬</span>
|
||||||
社区论坛
|
社区论坛
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-indigo-100 text-base mt-2 ml-16 opacity-90">分享经验、交流问题、结识志同道合的学习伙伴。</p>
|
<p class="text-slate-600 text-base mt-2 ml-16">分享经验、交流问题、结识志同道合的学习伙伴。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative z-10 flex flex-wrap items-center gap-3">
|
<div class="relative z-10 flex flex-wrap items-center gap-3">
|
||||||
{% if user %}
|
{% if user %}
|
||||||
<button onclick="showLeaderboard()" class="p-3 bg-white/10 hover:bg-white/20 text-white backdrop-blur-md rounded-2xl transition-all border border-white/20 hover:border-white/40 shadow-lg transform hover:-translate-y-1" title="排行榜">
|
<button onclick="showLeaderboard()" class="btn-outline-futuristic p-3" title="排行榜">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button onclick="showBookmarks()" class="p-3 bg-white/10 hover:bg-white/20 text-yellow-300 backdrop-blur-md rounded-2xl transition-all border border-white/20 hover:border-yellow-300/50 shadow-lg transform hover:-translate-y-1" title="我的收藏">
|
<button onclick="showBookmarks()" class="btn-outline-futuristic p-3" title="我的收藏">
|
||||||
<svg class="w-5 h-5" 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>
|
<svg class="w-5 h-5" 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>
|
||||||
</button>
|
</button>
|
||||||
<button onclick="openNewPost()" class="flex items-center gap-2 px-6 py-3 bg-white text-indigo-600 rounded-2xl hover:bg-indigo-50 text-sm font-bold shadow-xl transition-all transform hover:-translate-y-1 hover:scale-105">
|
<button onclick="openNewPost()" class="btn-futuristic flex items-center gap-2 text-sm">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/></svg>
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/></svg>
|
||||||
发布新帖
|
发布新帖
|
||||||
</button>
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/login" class="px-6 py-3 bg-white text-indigo-600 rounded-2xl hover:bg-indigo-50 text-sm font-bold shadow-xl transition-all transform hover:-translate-y-1 hover:scale-105">登录后发帖</a>
|
<a href="/login" class="btn-futuristic text-sm">登录后发帖</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 搜索和分类 -->
|
<!-- 搜索和分类 -->
|
||||||
<div class="bg-white p-5 rounded-2xl shadow-sm border border-slate-100 sticky top-20 z-10 glass-panel">
|
<div class="futuristic-card p-5 sticky top-20 z-10">
|
||||||
<div class="flex flex-col lg:flex-row gap-4 justify-between items-start lg:items-center">
|
<div class="flex flex-col lg:flex-row gap-4 justify-between items-start lg:items-center">
|
||||||
<!-- 分类标签 -->
|
<!-- 分类标签 -->
|
||||||
<div class="flex flex-wrap gap-2" id="tab-nav">
|
<div class="flex flex-wrap gap-2" id="tab-nav">
|
||||||
@@ -141,10 +141,10 @@
|
|||||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
<svg class="h-4 w-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
<svg class="h-4 w-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="search-input" placeholder="搜索帖子内容..." class="w-full pl-10 pr-4 py-2 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary bg-slate-50 transition-all" onkeydown="if(event.key==='Enter')loadPosts()">
|
<input type="text" id="search-input" placeholder="搜索帖子内容..." class="input-futuristic w-full pl-10 pr-4 py-2 text-sm" onkeydown="if(event.key==='Enter')loadPosts()">
|
||||||
</div>
|
</div>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<select id="sort-select" onchange="loadPosts()" class="appearance-none pl-9 pr-8 py-2 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary bg-slate-50 hover:bg-slate-100 transition-colors cursor-pointer">
|
<select id="sort-select" onchange="loadPosts()" class="input-futuristic appearance-none pl-9 pr-8 py-2 text-sm cursor-pointer">
|
||||||
<option value="newest">最新发布</option>
|
<option value="newest">最新发布</option>
|
||||||
<option value="hottest">热度最高</option>
|
<option value="hottest">热度最高</option>
|
||||||
<option value="most_replies">最多回复</option>
|
<option value="most_replies">最多回复</option>
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
<svg class="h-4 w-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
<svg class="h-4 w-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="loadPosts()" class="px-4 py-2 bg-slate-800 text-white rounded-xl hover:bg-slate-700 text-sm font-medium transition-colors shadow-sm hidden sm:block">
|
<button onclick="loadPosts()" class="btn-futuristic text-sm hidden sm:block">
|
||||||
搜索
|
搜索
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -166,7 +166,7 @@
|
|||||||
<!-- 帖子列表 -->
|
<!-- 帖子列表 -->
|
||||||
<div id="post-list" class="space-y-4">
|
<div id="post-list" class="space-y-4">
|
||||||
<!-- 骨架屏加载状态 -->
|
<!-- 骨架屏加载状态 -->
|
||||||
<div class="bg-white p-6 rounded-2xl border border-slate-100 shadow-sm animate-pulse">
|
<div class="futuristic-card p-6 animate-pulse">
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<div class="w-10 h-10 bg-slate-200 rounded-full"></div>
|
<div class="w-10 h-10 bg-slate-200 rounded-full"></div>
|
||||||
<div class="flex-1 space-y-3">
|
<div class="flex-1 space-y-3">
|
||||||
@@ -186,7 +186,7 @@
|
|||||||
|
|
||||||
<!-- 右侧边栏 -->
|
<!-- 右侧边栏 -->
|
||||||
<div class="space-y-6 hidden lg:block">
|
<div class="space-y-6 hidden lg:block">
|
||||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden">
|
<div class="futuristic-card overflow-hidden">
|
||||||
<div class="bg-gradient-to-r from-red-50 to-orange-50 px-5 py-4 border-b border-red-100">
|
<div class="bg-gradient-to-r from-red-50 to-orange-50 px-5 py-4 border-b border-red-100">
|
||||||
<h3 class="text-sm font-bold text-red-700 flex items-center">
|
<h3 class="text-sm font-bold text-red-700 flex items-center">
|
||||||
<svg class="w-4 h-4 mr-1.5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 18.657A8 8 0 016.343 7.343S7 9 9 10c0-2 .5-5 2.986-7C14 5 16.09 5.777 17.656 7.343A7.975 7.975 0 0120 13a7.975 7.975 0 01-2.343 5.657z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.879 16.121A3 3 0 1012.015 11L11 14H9c0 .768.293 1.536.879 2.121z"/></svg>
|
<svg class="w-4 h-4 mr-1.5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 18.657A8 8 0 016.343 7.343S7 9 9 10c0-2 .5-5 2.986-7C14 5 16.09 5.777 17.656 7.343A7.975 7.975 0 0120 13a7.975 7.975 0 01-2.343 5.657z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.879 16.121A3 3 0 1012.015 11L11 14H9c0 .768.293 1.536.879 2.121z"/></svg>
|
||||||
@@ -198,7 +198,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden">
|
<div class="futuristic-card overflow-hidden">
|
||||||
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 px-5 py-4 border-b border-blue-100">
|
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 px-5 py-4 border-b border-blue-100">
|
||||||
<h3 class="text-sm font-bold text-blue-700 flex items-center">
|
<h3 class="text-sm font-bold text-blue-700 flex items-center">
|
||||||
<svg class="w-4 h-4 mr-1.5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
<svg class="w-4 h-4 mr-1.5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
|
||||||
@@ -210,7 +210,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden sticky top-20">
|
<div class="futuristic-card overflow-hidden sticky top-20">
|
||||||
<div class="bg-gradient-to-r from-slate-50 to-gray-50 px-5 py-4 border-b border-slate-100">
|
<div class="bg-gradient-to-r from-slate-50 to-gray-50 px-5 py-4 border-b border-slate-100">
|
||||||
<h3 class="text-sm font-bold text-slate-700 flex items-center">
|
<h3 class="text-sm font-bold text-slate-700 flex items-center">
|
||||||
<svg class="w-4 h-4 mr-1.5 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/></svg>
|
<svg class="w-4 h-4 mr-1.5 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/></svg>
|
||||||
@@ -452,8 +452,8 @@ function renderPosts(posts) {
|
|||||||
// 生成纯文本内容用于预览
|
// 生成纯文本内容用于预览
|
||||||
let cleanContent = p.content.replace(/\[img:[^\]]+\]/g, '[图片]');
|
let cleanContent = p.content.replace(/\[img:[^\]]+\]/g, '[图片]');
|
||||||
|
|
||||||
// 生成等级
|
// 使用真实等级
|
||||||
const randomLv = (p.id % 5) + 1;
|
const authorLevel = p.author_level || 1;
|
||||||
|
|
||||||
h += `<div class="bg-white rounded-3xl p-6 border ${p.pinned ? 'border-amber-200 shadow-md bg-gradient-to-br from-amber-50/40 to-white' : 'border-slate-100 shadow-sm'} hover-card-up transition-all duration-300 cursor-pointer group flex flex-col sm:flex-row gap-5" onclick="openPost(${p.id})">
|
h += `<div class="bg-white rounded-3xl p-6 border ${p.pinned ? 'border-amber-200 shadow-md bg-gradient-to-br from-amber-50/40 to-white' : 'border-slate-100 shadow-sm'} hover-card-up transition-all duration-300 cursor-pointer group flex flex-col sm:flex-row gap-5" onclick="openPost(${p.id})">
|
||||||
|
|
||||||
@@ -463,7 +463,7 @@ function renderPosts(posts) {
|
|||||||
<span class="text-xl">${esc(p.author.charAt(0))}</span>
|
<span class="text-xl">${esc(p.author.charAt(0))}</span>
|
||||||
${p.is_official ?
|
${p.is_official ?
|
||||||
'<div class="absolute -bottom-2 -right-2 bg-gradient-to-r from-red-500 to-rose-600 text-white text-[9px] font-black px-1.5 py-0.5 rounded-md shadow-sm border border-white">官方</div>' :
|
'<div class="absolute -bottom-2 -right-2 bg-gradient-to-r from-red-500 to-rose-600 text-white text-[9px] font-black px-1.5 py-0.5 rounded-md shadow-sm border border-white">官方</div>' :
|
||||||
'<div class="absolute -bottom-1.5 -right-1.5 bg-slate-800 text-white text-[9px] font-bold px-1.5 py-0.5 rounded-full shadow-sm border border-white">Lv.' + randomLv + '</div>'}
|
'<div class="absolute -bottom-1.5 -right-1.5 bg-slate-800 text-white text-[9px] font-bold px-1.5 py-0.5 rounded-full shadow-sm border border-white">Lv.' + authorLevel + '</div>'}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onclick="toggleLike(${p.id},this)" class="group/btn w-12 h-12 flex flex-col items-center justify-center rounded-2xl border ${p.liked?'border-rose-200 bg-rose-50 text-rose-500':'border-slate-100 bg-slate-50 text-slate-400 hover:bg-rose-50 hover:text-rose-500 hover:border-rose-200'} transition-all transform hover:-translate-y-1">
|
<button onclick="toggleLike(${p.id},this)" class="group/btn w-12 h-12 flex flex-col items-center justify-center rounded-2xl border ${p.liked?'border-rose-200 bg-rose-50 text-rose-500':'border-slate-100 bg-slate-50 text-slate-400 hover:bg-rose-50 hover:text-rose-500 hover:border-rose-200'} transition-all transform hover:-translate-y-1">
|
||||||
|
|||||||
@@ -4,31 +4,31 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="min-h-screen bg-slate-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
<div class="min-h-screen bg-slate-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||||
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
<h2 class="mt-6 text-center text-3xl font-extrabold text-slate-900">登录您的账户</h2>
|
<h2 class="mt-6 text-center text-3xl font-extrabold bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent animate-pulse">登录您的账户</h2>
|
||||||
<p class="mt-2 text-center text-sm text-slate-600">
|
<p class="mt-2 text-center text-sm text-slate-600">
|
||||||
或者 <a href="/register" class="font-medium text-primary hover:text-blue-500">注册新账户</a>
|
或者 <a href="/register" class="font-medium text-primary hover:text-blue-500 transition-all duration-300 hover:scale-110 inline-block">注册新账户</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
<div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
<div class="futuristic-card py-8 px-4 sm:px-10">
|
||||||
<div class="flex border-b border-slate-200 mb-6">
|
<div class="flex border-b border-slate-200 mb-6">
|
||||||
<button id="tab-phone" onclick="switchTab('phone')" class="flex-1 pb-4 text-sm font-medium text-center text-primary border-b-2 border-primary">手机验证码登录</button>
|
<button id="tab-phone" onclick="switchTab('phone')" class="flex-1 pb-4 text-sm font-medium text-center text-primary border-b-2 border-primary transition-all duration-300 hover:scale-105">手机验证码登录</button>
|
||||||
<button id="tab-email" onclick="switchTab('email')" class="flex-1 pb-4 text-sm font-medium text-center text-slate-500 hover:text-slate-700">邮箱密码登录</button>
|
<button id="tab-email" onclick="switchTab('email')" class="flex-1 pb-4 text-sm font-medium text-center text-slate-500 hover:text-slate-700 transition-all duration-300 hover:scale-105">邮箱密码登录</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- 手机登录表单 -->
|
<!-- 手机登录表单 -->
|
||||||
<form id="form-phone" class="space-y-6" onsubmit="handlePhoneLogin(event)">
|
<form id="form-phone" class="space-y-6" onsubmit="handlePhoneLogin(event)">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">手机号码</label>
|
<label class="block text-sm font-medium text-slate-700">手机号码</label>
|
||||||
<div class="mt-1 relative rounded-md shadow-sm">
|
<div class="mt-1 relative rounded-md shadow-sm">
|
||||||
<input id="phone" type="tel" required class="focus:ring-primary focus:border-primary block w-full pl-3 sm:text-sm border-slate-300 rounded-md py-2 border" placeholder="请输入11位手机号">
|
<input id="phone" type="tel" required class="input-futuristic focus:ring-primary focus:border-primary block w-full pl-3 sm:text-sm rounded-md py-2" placeholder="请输入11位手机号">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">图形验证码</label>
|
<label class="block text-sm font-medium text-slate-700">图形验证码</label>
|
||||||
<div class="mt-1 flex items-center space-x-2">
|
<div class="mt-1 flex items-center space-x-2">
|
||||||
<input id="captcha-text-phone" type="text" class="focus:ring-primary focus:border-primary block flex-1 sm:text-sm border-slate-300 rounded-md py-2 border px-3" placeholder="请输入图形验证码">
|
<input id="captcha-text-phone" type="text" class="input-futuristic focus:ring-primary focus:border-primary block flex-1 sm:text-sm rounded-md py-2 px-3" placeholder="请输入图形验证码">
|
||||||
<img id="captcha-img-phone" class="cursor-pointer h-10" onclick="fetchCaptcha('phone')" alt="验证码">
|
<img id="captcha-img-phone" class="cursor-pointer h-10 transition-transform duration-300 hover:scale-110" onclick="fetchCaptcha('phone')" alt="验证码">
|
||||||
<button type="button" onclick="fetchCaptcha('phone')" class="p-2 text-slate-400 hover:text-slate-600" title="刷新验证码">
|
<button type="button" onclick="fetchCaptcha('phone')" class="p-2 text-slate-400 hover:text-slate-600 transition-all duration-300 hover:rotate-180" title="刷新验证码">
|
||||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,31 +36,31 @@
|
|||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">短信验证码</label>
|
<label class="block text-sm font-medium text-slate-700">短信验证码</label>
|
||||||
<div class="mt-1 flex rounded-md shadow-sm">
|
<div class="mt-1 flex rounded-md shadow-sm">
|
||||||
<input id="sms-code" type="text" required class="focus:ring-primary focus:border-primary block w-full rounded-none rounded-l-md sm:text-sm border-slate-300 py-2 border px-3" placeholder="请输入短信验证码">
|
<input id="sms-code" type="text" required class="input-futuristic focus:ring-primary focus:border-primary block w-full rounded-none rounded-l-md sm:text-sm py-2 px-3" placeholder="请输入短信验证码">
|
||||||
<button type="button" id="send-sms-btn" onclick="handleSendSms()" class="relative inline-flex items-center px-4 py-2 border border-slate-300 text-sm font-medium rounded-r-md text-slate-700 bg-slate-50 hover:bg-slate-100">获取验证码</button>
|
<button type="button" id="send-sms-btn" onclick="handleSendSms()" class="btn-futuristic relative inline-flex items-center px-4 py-2 text-sm font-medium rounded-r-md">获取验证码</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<input id="remember-phone" type="checkbox" class="h-4 w-4 text-primary focus:ring-primary border-slate-300 rounded">
|
<input id="remember-phone" type="checkbox" class="h-4 w-4 text-primary focus:ring-primary border-slate-300 rounded">
|
||||||
<label for="remember-phone" class="ml-2 block text-sm text-slate-700">保持登录10天</label>
|
<label for="remember-phone" class="ml-2 block text-sm text-slate-700">保持登录10天</label>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary hover:bg-blue-700">登录</button>
|
<button type="submit" class="btn-futuristic w-full flex justify-center py-2 px-4 rounded-md text-sm font-medium text-white">登录</button>
|
||||||
</form>
|
</form>
|
||||||
<!-- 邮箱登录表单 -->
|
<!-- 邮箱登录表单 -->
|
||||||
<form id="form-email" class="space-y-6 hidden" onsubmit="handleEmailLogin(event)">
|
<form id="form-email" class="space-y-6 hidden" onsubmit="handleEmailLogin(event)">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">邮箱地址</label>
|
<label class="block text-sm font-medium text-slate-700">邮箱地址</label>
|
||||||
<input id="login-email" type="email" required class="mt-1 focus:ring-primary focus:border-primary block w-full pl-3 sm:text-sm border-slate-300 rounded-md py-2 border" placeholder="请输入邮箱">
|
<input id="login-email" type="email" required class="input-futuristic mt-1 focus:ring-primary focus:border-primary block w-full pl-3 sm:text-sm rounded-md py-2" placeholder="请输入邮箱">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">密码</label>
|
<label class="block text-sm font-medium text-slate-700">密码</label>
|
||||||
<input id="login-password" type="password" required class="mt-1 focus:ring-primary focus:border-primary block w-full pl-3 sm:text-sm border-slate-300 rounded-md py-2 border" placeholder="请输入密码">
|
<input id="login-password" type="password" required class="input-futuristic mt-1 focus:ring-primary focus:border-primary block w-full pl-3 sm:text-sm rounded-md py-2" placeholder="请输入密码">
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<input id="remember-email" type="checkbox" class="h-4 w-4 text-primary focus:ring-primary border-slate-300 rounded">
|
<input id="remember-email" type="checkbox" class="h-4 w-4 text-primary focus:ring-primary border-slate-300 rounded">
|
||||||
<label for="remember-email" class="ml-2 block text-sm text-slate-700">保持登录10天</label>
|
<label for="remember-email" class="ml-2 block text-sm text-slate-700">保持登录10天</label>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary hover:bg-blue-700">登录</button>
|
<button type="submit" class="btn-futuristic w-full flex justify-center py-2 px-4 rounded-md text-sm font-medium text-white">登录</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="mt-6 relative">
|
<div class="mt-6 relative">
|
||||||
<div class="absolute inset-0 flex items-center"><div class="w-full border-t border-slate-300"></div></div>
|
<div class="absolute inset-0 flex items-center"><div class="w-full border-t border-slate-300"></div></div>
|
||||||
|
|||||||
@@ -4,31 +4,31 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="max-w-4xl mx-auto py-8 px-4 sm:px-6">
|
<div class="max-w-4xl mx-auto py-8 px-4 sm:px-6">
|
||||||
<!-- 头部区域 -->
|
<!-- 头部区域 -->
|
||||||
<div class="bg-white rounded-3xl p-6 shadow-sm border border-slate-100 mb-6 flex flex-col sm:flex-row sm:items-center justify-between gap-4 relative overflow-hidden group">
|
<div class="futuristic-card p-6 mb-6 flex flex-col sm:flex-row sm:items-center justify-between gap-4 relative overflow-hidden group">
|
||||||
<div class="absolute top-0 right-0 w-32 h-32 bg-indigo-50/50 rounded-bl-full -z-10 group-hover:scale-110 transition-transform duration-500"></div>
|
<div class="absolute top-0 right-0 w-32 h-32 bg-cyan-500/10 rounded-bl-full -z-10 group-hover:scale-110 transition-transform duration-500"></div>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div class="w-12 h-12 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-2xl flex items-center justify-center text-white shadow-lg shadow-indigo-200 transform group-hover:rotate-12 transition-transform duration-300">
|
<div class="w-12 h-12 bg-gradient-to-br from-cyan-500 to-blue-600 rounded-2xl flex items-center justify-center text-white shadow-lg shadow-cyan-500/30 transform group-hover:rotate-12 transition-transform duration-300">
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-extrabold text-slate-900 tracking-tight">通知中心</h1>
|
<h1 class="text-2xl font-extrabold bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent tracking-tight">通知中心</h1>
|
||||||
<p class="text-sm text-slate-500 font-medium mt-0.5">查看系统通知、审核结果及最新公告</p>
|
<p class="text-sm text-slate-400 font-medium mt-0.5">查看系统通知、审核结果及最新公告</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="markAllRead()" class="px-5 py-2.5 text-sm font-bold text-slate-600 bg-slate-50 border border-slate-200 rounded-xl hover:bg-white hover:text-indigo-600 hover:border-indigo-200 hover:shadow-sm transition-all flex items-center gap-2 group/btn">
|
<button onclick="markAllRead()" class="btn-outline-futuristic px-5 py-2.5 text-sm font-bold flex items-center gap-2 group/btn">
|
||||||
<svg class="w-4 h-4 text-slate-400 group-hover/btn:text-indigo-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
|
<svg class="w-4 h-4 text-slate-400 group-hover/btn:text-cyan-400 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
|
||||||
全部标为已读
|
全部标为已读
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 标签切换 (玻璃拟物化) -->
|
<!-- 标签切换 (玻璃拟物化) -->
|
||||||
<div class="bg-white/80 backdrop-blur-md rounded-2xl p-1.5 shadow-sm border border-slate-100 mb-6 flex overflow-x-auto hide-scrollbar sticky top-4 z-10">
|
<div class="futuristic-card-dark backdrop-blur-md rounded-2xl p-1.5 mb-6 flex overflow-x-auto hide-scrollbar sticky top-4 z-10">
|
||||||
<button onclick="switchTab('all')" id="tab-all" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-bold bg-white text-indigo-600 shadow-sm rounded-xl transition-all duration-300">全部</button>
|
<button onclick="switchTab('all')" id="tab-all" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-bold bg-gradient-to-r from-cyan-500 to-blue-500 text-white shadow-sm rounded-xl transition-all duration-300">全部</button>
|
||||||
<button onclick="switchTab('system')" id="tab-system" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 rounded-xl transition-all duration-300">📢 系统公告</button>
|
<button onclick="switchTab('system')" id="tab-system" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-400 hover:text-slate-200 hover:bg-slate-700/50 rounded-xl transition-all duration-300">📢 系统公告</button>
|
||||||
<button onclick="switchTab('teacher')" id="tab-teacher" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 rounded-xl transition-all duration-300">👨🏫 教师相关</button>
|
<button onclick="switchTab('teacher')" id="tab-teacher" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-400 hover:text-slate-200 hover:bg-slate-700/50 rounded-xl transition-all duration-300">👨🏫 教师相关</button>
|
||||||
<button onclick="switchTab('contest')" id="tab-contest" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 rounded-xl transition-all duration-300">🏆 杯赛相关</button>
|
<button onclick="switchTab('contest')" id="tab-contest" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-400 hover:text-slate-200 hover:bg-slate-700/50 rounded-xl transition-all duration-300">🏆 杯赛相关</button>
|
||||||
<button onclick="switchTab('friend')" id="tab-friend" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 rounded-xl transition-all duration-300">👤 好友</button>
|
<button onclick="switchTab('friend')" id="tab-friend" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-400 hover:text-slate-200 hover:bg-slate-700/50 rounded-xl transition-all duration-300">👤 好友</button>
|
||||||
<button onclick="switchTab('exam')" id="tab-exam" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 rounded-xl transition-all duration-300">📝 考试相关</button>
|
<button onclick="switchTab('exam')" id="tab-exam" class="flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-400 hover:text-slate-200 hover:bg-slate-700/50 rounded-xl transition-all duration-300">📝 考试相关</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 列表内容区 -->
|
<!-- 列表内容区 -->
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<div id="announcements-section" class="hidden">
|
<div id="announcements-section" class="hidden">
|
||||||
<div class="flex items-center gap-2 mb-4 px-2">
|
<div class="flex items-center gap-2 mb-4 px-2">
|
||||||
<span class="w-8 h-8 rounded-lg bg-amber-100 text-amber-600 flex items-center justify-center font-bold">📢</span>
|
<span class="w-8 h-8 rounded-lg bg-amber-100 text-amber-600 flex items-center justify-center font-bold">📢</span>
|
||||||
<h2 class="text-lg font-bold text-slate-800">系统公告</h2>
|
<h2 class="text-lg font-bold text-slate-300">系统公告</h2>
|
||||||
</div>
|
</div>
|
||||||
<div id="announcements-list" class="space-y-4"></div>
|
<div id="announcements-list" class="space-y-4"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,10 +70,10 @@ function esc(s) { if(!s)return''; const d=document.createElement('div'); d.textC
|
|||||||
function switchTab(tab) {
|
function switchTab(tab) {
|
||||||
currentTab = tab;
|
currentTab = tab;
|
||||||
document.querySelectorAll('[id^="tab-"]').forEach(el => {
|
document.querySelectorAll('[id^="tab-"]').forEach(el => {
|
||||||
el.className = 'flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-slate-50 rounded-xl transition-all duration-300';
|
el.className = 'flex-1 min-w-[100px] px-4 py-2.5 text-sm font-medium text-slate-400 hover:text-slate-200 hover:bg-slate-700/50 rounded-xl transition-all duration-300';
|
||||||
});
|
});
|
||||||
const activeBtn = document.getElementById('tab-' + tab);
|
const activeBtn = document.getElementById('tab-' + tab);
|
||||||
activeBtn.className = 'flex-1 min-w-[100px] px-4 py-2.5 text-sm font-bold bg-white text-indigo-600 shadow-sm rounded-xl transition-all duration-300';
|
activeBtn.className = 'flex-1 min-w-[100px] px-4 py-2.5 text-sm font-bold bg-gradient-to-r from-cyan-500 to-blue-500 text-white shadow-sm rounded-xl transition-all duration-300';
|
||||||
renderNotifications();
|
renderNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,21 +116,21 @@ function renderNotifications() {
|
|||||||
if ((currentTab === 'all' || currentTab === 'system') && announcements.length > 0) {
|
if ((currentTab === 'all' || currentTab === 'system') && announcements.length > 0) {
|
||||||
annSection.style.display = '';
|
annSection.style.display = '';
|
||||||
annList.innerHTML = announcements.map(a => `
|
annList.innerHTML = announcements.map(a => `
|
||||||
<div class="bg-white rounded-2xl p-5 shadow-sm border ${a.pinned ? 'border-amber-200 shadow-amber-100/50' : 'border-indigo-100 shadow-indigo-100/50'} relative overflow-hidden group hover:shadow-md transition-all duration-300">
|
<div class="futuristic-card p-5 border ${a.pinned ? 'border-amber-500/30' : 'border-cyan-500/30'} relative overflow-hidden group hover:shadow-lg transition-all duration-300">
|
||||||
<div class="absolute top-0 right-0 w-24 h-24 ${a.pinned ? 'bg-amber-50' : 'bg-indigo-50'} rounded-bl-full -z-10 group-hover:scale-110 transition-transform duration-500"></div>
|
<div class="absolute top-0 right-0 w-24 h-24 ${a.pinned ? 'bg-amber-500/10' : 'bg-cyan-500/10'} rounded-bl-full -z-10 group-hover:scale-110 transition-transform duration-500"></div>
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
${a.pinned ? '<span class="text-[10px] bg-gradient-to-r from-amber-400 to-orange-500 text-white px-2 py-0.5 rounded-full font-bold shadow-sm flex items-center gap-1"><svg class="w-3 h-3" 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> 置顶</span>' : ''}
|
${a.pinned ? '<span class="badge-futuristic text-[10px] px-2 py-0.5 rounded-full font-bold shadow-sm flex items-center gap-1"><svg class="w-3 h-3" 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> 置顶</span>' : ''}
|
||||||
<h3 class="text-lg font-bold text-slate-900 group-hover:text-indigo-600 transition-colors">${esc(a.title)}</h3>
|
<h3 class="text-lg font-bold text-white group-hover:text-cyan-400 transition-colors">${esc(a.title)}</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-slate-600 mt-3 whitespace-pre-wrap leading-relaxed">${esc(a.content)}</p>
|
<p class="text-sm text-slate-300 mt-3 whitespace-pre-wrap leading-relaxed">${esc(a.content)}</p>
|
||||||
<div class="flex items-center gap-3 mt-4 pt-4 border-t border-slate-100/60">
|
<div class="flex items-center gap-3 mt-4 pt-4 border-t border-slate-900/10">
|
||||||
<span class="flex items-center gap-1.5 text-xs font-medium text-slate-500 bg-slate-50 px-2.5 py-1 rounded-lg">
|
<span class="flex items-center gap-1.5 text-xs font-medium text-slate-400 bg-slate-800/50 px-2.5 py-1 rounded-lg">
|
||||||
<span class="w-4 h-4 rounded-full bg-slate-200 flex items-center justify-center text-[8px]">${esc(a.author_name)[0]}</span>
|
<span class="w-4 h-4 rounded-full bg-slate-700 flex items-center justify-center text-[8px] text-white">${esc(a.author_name)[0]}</span>
|
||||||
${esc(a.author_name)}
|
${esc(a.author_name)}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-xs font-medium text-slate-400 flex items-center gap-1">
|
<span class="text-xs font-medium text-slate-500 flex items-center gap-1">
|
||||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||||
${a.created_at}
|
${a.created_at}
|
||||||
</span>
|
</span>
|
||||||
@@ -156,8 +156,8 @@ function renderNotifications() {
|
|||||||
|
|
||||||
if (currentTab === 'system') {
|
if (currentTab === 'system') {
|
||||||
container.innerHTML = announcements.length === 0 ? `
|
container.innerHTML = announcements.length === 0 ? `
|
||||||
<div class="flex flex-col items-center justify-center py-16 text-slate-400 bg-white rounded-3xl border border-slate-100 border-dashed">
|
<div class="flex flex-col items-center justify-center py-16 text-slate-400 futuristic-card border-dashed">
|
||||||
<div class="w-16 h-16 bg-slate-50 rounded-full flex items-center justify-center text-3xl mb-4">📭</div>
|
<div class="w-16 h-16 bg-slate-800/50 rounded-full flex items-center justify-center text-3xl mb-4">📭</div>
|
||||||
<div class="font-medium">暂无系统公告</div>
|
<div class="font-medium">暂无系统公告</div>
|
||||||
</div>` : '';
|
</div>` : '';
|
||||||
return;
|
return;
|
||||||
@@ -165,8 +165,8 @@ function renderNotifications() {
|
|||||||
|
|
||||||
if (filtered.length === 0) {
|
if (filtered.length === 0) {
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="flex flex-col items-center justify-center py-16 text-slate-400 bg-white rounded-3xl border border-slate-100 border-dashed">
|
<div class="flex flex-col items-center justify-center py-16 text-slate-400 futuristic-card border-dashed">
|
||||||
<div class="w-16 h-16 bg-slate-50 rounded-full flex items-center justify-center text-3xl mb-4">📭</div>
|
<div class="w-16 h-16 bg-slate-800/50 rounded-full flex items-center justify-center text-3xl mb-4">📭</div>
|
||||||
<div class="font-medium">暂无通知</div>
|
<div class="font-medium">暂无通知</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
return;
|
return;
|
||||||
@@ -177,12 +177,12 @@ function renderNotifications() {
|
|||||||
const iconBgColor = {
|
const iconBgColor = {
|
||||||
'teacher_application': 'bg-purple-100 text-purple-600', 'teacher_result': 'bg-fuchsia-100 text-fuchsia-600',
|
'teacher_application': 'bg-purple-100 text-purple-600', 'teacher_result': 'bg-fuchsia-100 text-fuchsia-600',
|
||||||
'contest_application': 'bg-orange-100 text-orange-600', 'contest_result': 'bg-amber-100 text-amber-600',
|
'contest_application': 'bg-orange-100 text-orange-600', 'contest_result': 'bg-amber-100 text-amber-600',
|
||||||
'contest_new_exam': 'bg-indigo-100 text-indigo-600', 'exam_graded': 'bg-emerald-100 text-emerald-600',
|
'contest_new_exam': 'bg-indigo-100 text-cyan-400', 'exam_graded': 'bg-emerald-100 text-emerald-600',
|
||||||
'system_announcement': 'bg-blue-100 text-blue-600', 'friend_request': 'bg-cyan-100 text-cyan-600'
|
'system_announcement': 'bg-blue-100 text-blue-600', 'friend_request': 'bg-cyan-100 text-cyan-600'
|
||||||
}[n.type] || 'bg-slate-100 text-slate-600';
|
}[n.type] || 'bg-slate-100 text-slate-600';
|
||||||
|
|
||||||
return `<div class="bg-white border ${n.read ? 'border-slate-100' : 'border-indigo-200 shadow-md shadow-indigo-100/50 relative'} rounded-2xl p-4 sm:p-5 hover:border-indigo-300 hover:shadow-lg transition-all duration-300 cursor-pointer group" onclick="markSingleRead(${n.id}, this, ${JSON.stringify(n).replace(/"/g, '"')})">
|
return `<div class="futuristic-card ${n.read ? 'border-slate-900/10' : 'border-cyan-500/30 shadow-md shadow-indigo-100/50 relative'} rounded-2xl p-4 sm:p-5 hover:border-cyan-500/50 hover:shadow-lg transition-all duration-300 cursor-pointer group" onclick="markSingleRead(${n.id}, this, ${JSON.stringify(n).replace(/"/g, '"')})">
|
||||||
${!n.read ? '<div class="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full border-2 border-white animate-pulse"></div>' : ''}
|
${!n.read ? '<div class="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full border-2 border-slate-900 animate-pulse"></div>' : ''}
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<div class="w-12 h-12 rounded-2xl ${iconBgColor} flex items-center justify-center text-2xl flex-shrink-0 shadow-inner group-hover:scale-110 group-hover:rotate-6 transition-transform">
|
<div class="w-12 h-12 rounded-2xl ${iconBgColor} flex items-center justify-center text-2xl flex-shrink-0 shadow-inner group-hover:scale-110 group-hover:rotate-6 transition-transform">
|
||||||
${getTypeIcon(n.type)}
|
${getTypeIcon(n.type)}
|
||||||
@@ -196,7 +196,7 @@ function renderNotifications() {
|
|||||||
${n.created_at}
|
${n.created_at}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm font-medium text-slate-800 leading-relaxed ${!n.read ? 'font-bold' : ''}">${esc(n.content)}</div>
|
<div class="text-sm font-medium text-slate-300 leading-relaxed ${!n.read ? 'font-bold' : ''}">${esc(n.content)}</div>
|
||||||
${actions}
|
${actions}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -208,31 +208,31 @@ function buildActions(n) {
|
|||||||
if (n.type === 'teacher_application' && n.application_status === 'pending' && n.application_id) {
|
if (n.type === 'teacher_application' && n.application_status === 'pending' && n.application_id) {
|
||||||
// 管理员显示快捷操作按钮
|
// 管理员显示快捷操作按钮
|
||||||
if (currentUser.role === 'admin') {
|
if (currentUser.role === 'admin') {
|
||||||
return `<div class="flex gap-3 mt-4 pt-3 border-t border-slate-100">
|
return `<div class="flex gap-3 mt-4 pt-3 border-t border-slate-900/10">
|
||||||
<button onclick="event.stopPropagation();approveTeacherN(${n.application_id})" class="px-4 py-1.5 text-xs font-bold bg-emerald-50 text-emerald-600 border border-emerald-200 rounded-lg hover:bg-emerald-500 hover:text-white hover:border-emerald-500 transition-colors shadow-sm">✅ 同意申请</button>
|
<button onclick="event.stopPropagation();approveTeacherN(${n.application_id})" class="px-4 py-1.5 text-xs font-bold bg-emerald-50 text-emerald-600 border border-emerald-200 rounded-lg hover:bg-emerald-500 hover:text-white hover:border-emerald-500 transition-colors shadow-sm">✅ 同意申请</button>
|
||||||
<button onclick="event.stopPropagation();rejectTeacherN(${n.application_id})" class="px-4 py-1.5 text-xs font-bold bg-rose-50 text-rose-600 border border-rose-200 rounded-lg hover:bg-rose-500 hover:text-white hover:border-rose-500 transition-colors shadow-sm">❌ 拒绝申请</button>
|
<button onclick="event.stopPropagation();rejectTeacherN(${n.application_id})" class="px-4 py-1.5 text-xs font-bold bg-rose-50 text-rose-600 border border-rose-200 rounded-lg hover:bg-rose-500 hover:text-white hover:border-rose-500 transition-colors shadow-sm">❌ 拒绝申请</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
// 杯赛负责人显示查看按钮
|
// 杯赛负责人显示查看按钮
|
||||||
return `<div class="flex gap-3 mt-4 pt-3 border-t border-slate-100">
|
return `<div class="flex gap-3 mt-4 pt-3 border-t border-slate-900/10">
|
||||||
<a href="/admin/teacher-applications" onclick="event.stopPropagation()" class="px-4 py-1.5 text-xs font-bold bg-indigo-50 text-indigo-600 border border-indigo-200 rounded-lg hover:bg-indigo-500 hover:text-white hover:border-indigo-500 transition-colors shadow-sm">👁️ 查看申请</a>
|
<a href="/admin/teacher-applications" onclick="event.stopPropagation()" class="px-4 py-1.5 text-xs font-bold bg-indigo-50 text-cyan-400 border border-cyan-500/30 rounded-lg hover:bg-indigo-500 hover:text-white hover:border-indigo-500 transition-colors shadow-sm">👁️ 查看申请</a>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
if (n.type === 'teacher_application' && n.application_status === 'approved') return '<div class="mt-3 flex items-center gap-2"><span class="inline-flex items-center gap-1.5 px-3 py-1 bg-emerald-50 border border-emerald-100 text-emerald-600 text-xs font-bold rounded-lg"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg> 已同意</span>' + (currentUser.role === 'admin' ? `<button onclick="event.stopPropagation();deleteNotif(${n.id})" class="px-3 py-1 text-xs font-bold bg-slate-50 text-slate-500 border border-slate-200 rounded-lg hover:bg-red-50 hover:text-red-600 hover:border-red-200 transition-colors">删除</button>` : '') + '</div>';
|
if (n.type === 'teacher_application' && n.application_status === 'approved') return '<div class="mt-3 flex items-center gap-2"><span class="inline-flex items-center gap-1.5 px-3 py-1 bg-emerald-50 border border-emerald-100 text-emerald-600 text-xs font-bold rounded-lg"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg> 已同意</span>' + (currentUser.role === 'admin' ? `<button onclick="event.stopPropagation();deleteNotif(${n.id})" class="px-3 py-1 text-xs font-bold bg-slate-800/50 text-slate-500 border border-slate-200 rounded-lg hover:bg-red-50 hover:text-red-600 hover:border-red-200 transition-colors">删除</button>` : '') + '</div>';
|
||||||
if (n.type === 'teacher_application' && n.application_status === 'rejected') return '<div class="mt-3 flex items-center gap-2"><span class="inline-flex items-center gap-1.5 px-3 py-1 bg-rose-50 border border-rose-100 text-rose-600 text-xs font-bold rounded-lg"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg> 已拒绝</span>' + (currentUser.role === 'admin' ? `<button onclick="event.stopPropagation();deleteNotif(${n.id})" class="px-3 py-1 text-xs font-bold bg-slate-50 text-slate-500 border border-slate-200 rounded-lg hover:bg-red-50 hover:text-red-600 hover:border-red-200 transition-colors">删除</button>` : '') + '</div>';
|
if (n.type === 'teacher_application' && n.application_status === 'rejected') return '<div class="mt-3 flex items-center gap-2"><span class="inline-flex items-center gap-1.5 px-3 py-1 bg-rose-50 border border-rose-100 text-rose-600 text-xs font-bold rounded-lg"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg> 已拒绝</span>' + (currentUser.role === 'admin' ? `<button onclick="event.stopPropagation();deleteNotif(${n.id})" class="px-3 py-1 text-xs font-bold bg-slate-800/50 text-slate-500 border border-slate-200 rounded-lg hover:bg-red-50 hover:text-red-600 hover:border-red-200 transition-colors">删除</button>` : '') + '</div>';
|
||||||
|
|
||||||
if (n.type === 'contest_application' && n.application_status === 'pending' && n.post_id && currentUser.role === 'admin') {
|
if (n.type === 'contest_application' && n.application_status === 'pending' && n.post_id && currentUser.role === 'admin') {
|
||||||
return `<div class="flex gap-3 mt-4 pt-3 border-t border-slate-100">
|
return `<div class="flex gap-3 mt-4 pt-3 border-t border-slate-900/10">
|
||||||
<button onclick="event.stopPropagation();approveContestN(${n.post_id})" class="px-4 py-1.5 text-xs font-bold bg-emerald-50 text-emerald-600 border border-emerald-200 rounded-lg hover:bg-emerald-500 hover:text-white hover:border-emerald-500 transition-colors shadow-sm">✅ 同意申请</button>
|
<button onclick="event.stopPropagation();approveContestN(${n.post_id})" class="px-4 py-1.5 text-xs font-bold bg-emerald-50 text-emerald-600 border border-emerald-200 rounded-lg hover:bg-emerald-500 hover:text-white hover:border-emerald-500 transition-colors shadow-sm">✅ 同意申请</button>
|
||||||
<button onclick="event.stopPropagation();rejectContestN(${n.post_id})" class="px-4 py-1.5 text-xs font-bold bg-rose-50 text-rose-600 border border-rose-200 rounded-lg hover:bg-rose-500 hover:text-white hover:border-rose-500 transition-colors shadow-sm">❌ 拒绝申请</button>
|
<button onclick="event.stopPropagation();rejectContestN(${n.post_id})" class="px-4 py-1.5 text-xs font-bold bg-rose-50 text-rose-600 border border-rose-200 rounded-lg hover:bg-rose-500 hover:text-white hover:border-rose-500 transition-colors shadow-sm">❌ 拒绝申请</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
if (n.type === 'contest_application' && n.application_status === 'approved') return '<div class="mt-3 flex items-center gap-2"><span class="inline-flex items-center gap-1.5 px-3 py-1 bg-emerald-50 border border-emerald-100 text-emerald-600 text-xs font-bold rounded-lg"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg> 已同意</span>' + (currentUser.role === 'admin' ? `<button onclick="event.stopPropagation();deleteNotif(${n.id})" class="px-3 py-1 text-xs font-bold bg-slate-50 text-slate-500 border border-slate-200 rounded-lg hover:bg-red-50 hover:text-red-600 hover:border-red-200 transition-colors">删除</button>` : '') + '</div>';
|
if (n.type === 'contest_application' && n.application_status === 'approved') return '<div class="mt-3 flex items-center gap-2"><span class="inline-flex items-center gap-1.5 px-3 py-1 bg-emerald-50 border border-emerald-100 text-emerald-600 text-xs font-bold rounded-lg"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg> 已同意</span>' + (currentUser.role === 'admin' ? `<button onclick="event.stopPropagation();deleteNotif(${n.id})" class="px-3 py-1 text-xs font-bold bg-slate-800/50 text-slate-500 border border-slate-200 rounded-lg hover:bg-red-50 hover:text-red-600 hover:border-red-200 transition-colors">删除</button>` : '') + '</div>';
|
||||||
if (n.type === 'contest_application' && n.application_status === 'rejected') return '<div class="mt-3 flex items-center gap-2"><span class="inline-flex items-center gap-1.5 px-3 py-1 bg-rose-50 border border-rose-100 text-rose-600 text-xs font-bold rounded-lg"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg> 已拒绝</span>' + (currentUser.role === 'admin' ? `<button onclick="event.stopPropagation();deleteNotif(${n.id})" class="px-3 py-1 text-xs font-bold bg-slate-50 text-slate-500 border border-slate-200 rounded-lg hover:bg-red-50 hover:text-red-600 hover:border-red-200 transition-colors">删除</button>` : '') + '</div>';
|
if (n.type === 'contest_application' && n.application_status === 'rejected') return '<div class="mt-3 flex items-center gap-2"><span class="inline-flex items-center gap-1.5 px-3 py-1 bg-rose-50 border border-rose-100 text-rose-600 text-xs font-bold rounded-lg"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg> 已拒绝</span>' + (currentUser.role === 'admin' ? `<button onclick="event.stopPropagation();deleteNotif(${n.id})" class="px-3 py-1 text-xs font-bold bg-slate-800/50 text-slate-500 border border-slate-200 rounded-lg hover:bg-red-50 hover:text-red-600 hover:border-red-200 transition-colors">删除</button>` : '') + '</div>';
|
||||||
|
|
||||||
// 好友申请处理
|
// 好友申请处理
|
||||||
if (n.type === 'friend_request' && n.application_status === 'pending' && n.friend_request_id) {
|
if (n.type === 'friend_request' && n.application_status === 'pending' && n.friend_request_id) {
|
||||||
return `<div class="flex gap-3 mt-4 pt-3 border-t border-slate-100">
|
return `<div class="flex gap-3 mt-4 pt-3 border-t border-slate-900/10">
|
||||||
<button onclick="event.stopPropagation();acceptFriendN(${n.friend_request_id})" class="px-4 py-1.5 text-xs font-bold bg-emerald-50 text-emerald-600 border border-emerald-200 rounded-lg hover:bg-emerald-500 hover:text-white hover:border-emerald-500 transition-colors shadow-sm">✅ 同意</button>
|
<button onclick="event.stopPropagation();acceptFriendN(${n.friend_request_id})" class="px-4 py-1.5 text-xs font-bold bg-emerald-50 text-emerald-600 border border-emerald-200 rounded-lg hover:bg-emerald-500 hover:text-white hover:border-emerald-500 transition-colors shadow-sm">✅ 同意</button>
|
||||||
<button onclick="event.stopPropagation();rejectFriendN(${n.friend_request_id})" class="px-4 py-1.5 text-xs font-bold bg-rose-50 text-rose-600 border border-rose-200 rounded-lg hover:bg-rose-500 hover:text-white hover:border-rose-500 transition-colors shadow-sm">❌ 拒绝</button>
|
<button onclick="event.stopPropagation();rejectFriendN(${n.friend_request_id})" class="px-4 py-1.5 text-xs font-bold bg-rose-50 text-rose-600 border border-rose-200 rounded-lg hover:bg-rose-500 hover:text-white hover:border-rose-500 transition-colors shadow-sm">❌ 拒绝</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -242,7 +242,7 @@ function buildActions(n) {
|
|||||||
// 如果有考试关联的通知,添加快捷入口
|
// 如果有考试关联的通知,添加快捷入口
|
||||||
if (n.type === 'contest_new_exam' || n.type === 'exam_graded') {
|
if (n.type === 'contest_new_exam' || n.type === 'exam_graded') {
|
||||||
if(n.post_id) {
|
if(n.post_id) {
|
||||||
return `<div class="mt-4 pt-3 border-t border-slate-100"><a href="/exams/${n.post_id}" class="inline-flex items-center gap-1.5 px-4 py-1.5 text-xs font-bold bg-indigo-50 text-indigo-600 border border-indigo-200 rounded-lg hover:bg-indigo-600 hover:text-white hover:border-indigo-600 transition-colors shadow-sm"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"/></svg> 查看考试</a></div>`;
|
return `<div class="mt-4 pt-3 border-t border-slate-900/10"><a href="/exams/${n.post_id}" class="inline-flex items-center gap-1.5 px-4 py-1.5 text-xs font-bold bg-indigo-50 text-cyan-400 border border-cyan-500/30 rounded-lg hover:bg-indigo-600 hover:text-white hover:border-indigo-600 transition-colors shadow-sm"><svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"/></svg> 查看考试</a></div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
@@ -304,8 +304,8 @@ async function rejectFriendN(reqId) {
|
|||||||
function markSingleRead(nid, el, notif) {
|
function markSingleRead(nid, el, notif) {
|
||||||
fetch(`/api/notifications/${nid}/read`, {method:'POST'});
|
fetch(`/api/notifications/${nid}/read`, {method:'POST'});
|
||||||
// 移除未读的特定样式
|
// 移除未读的特定样式
|
||||||
el.classList.remove('border-indigo-200', 'shadow-md', 'shadow-indigo-100/50');
|
el.classList.remove('border-cyan-500/30', 'shadow-md', 'shadow-indigo-100/50');
|
||||||
el.classList.add('border-slate-100');
|
el.classList.add('border-slate-900/10');
|
||||||
const content = el.querySelector('.font-bold.leading-relaxed');
|
const content = el.querySelector('.font-bold.leading-relaxed');
|
||||||
if(content) content.classList.remove('font-bold');
|
if(content) content.classList.remove('font-bold');
|
||||||
const dot = el.querySelector('.bg-red-500.animate-pulse');
|
const dot = el.querySelector('.bg-red-500.animate-pulse');
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
{% block title %}个人中心 - 智联青云{% endblock %}
|
{% block title %}个人中心 - 智联青云{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="max-w-6xl mx-auto space-y-6">
|
<div class="max-w-6xl mx-auto space-y-6">
|
||||||
<!-- 顶部横幅与头像:高级游戏化设计 -->
|
<!-- 顶部横幅与头像:科幻未来风格 -->
|
||||||
<div class="bg-white rounded-3xl shadow-xl border border-slate-100 overflow-hidden relative group">
|
<div class="futuristic-card-dark overflow-hidden relative group transition-all duration-300 hover:shadow-2xl hover:shadow-cyan-500/20">
|
||||||
<div class="h-48 bg-gradient-to-r from-indigo-600 via-purple-600 to-blue-600 relative overflow-hidden">
|
<div class="h-48 bg-gradient-to-r from-indigo-600 via-purple-600 to-blue-600 relative overflow-hidden">
|
||||||
<!-- 高级光影动画背景 -->
|
<!-- 高级光影动画背景 -->
|
||||||
<div class="absolute inset-0 bg-[url('data:image/svg+xml,%3Csvg width=\'40\' height=\'40\' viewBox=\'0 0 40 40\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cpath d=\'M20 20.5V18H0v-2h20v-2H0v-2h20v-2H0V8h20V6H0V4h20V2H0V0h22v20h2V0h2v20h2V0h2v20h2V0h2v20h2V0h2v20h2v2H20v-1.5zM0 20h2v20H0V20zm4 0h2v20H4V20zm4 0h2v20H8V20zm4 0h2v20h-2V20zm4 0h2v20h-2V20zm4 4h20v2H20v-2zm0 4h20v2H20v-2zm0 4h20v2H20v-2zm0 4h20v2H20v-2z\' fill=\'%23ffffff\' fill-opacity=\'0.05\' fill-rule=\'evenodd\'/%3E%3C/svg%3E')] opacity-50"></div>
|
<div class="absolute inset-0 bg-[url('data:image/svg+xml,%3Csvg width=\'40\' height=\'40\' viewBox=\'0 0 40 40\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cpath d=\'M20 20.5V18H0v-2h20v-2H0v-2h20v-2H0V8h20V6H0V4h20V2H0V0h22v20h2V0h2v20h2V0h2v20h2V0h2v20h2V0h2v20h2v2H20v-1.5zM0 20h2v20H0V20zm4 0h2v20H4V20zm4 0h2v20H8V20zm4 0h2v20h-2V20zm4 0h2v20h-2V20zm4 4h20v2H20v-2zm0 4h20v2H20v-2zm0 4h20v2H20v-2zm0 4h20v2H20v-2z\' fill=\'%23ffffff\' fill-opacity=\'0.05\' fill-rule=\'evenodd\'/%3E%3C/svg%3E')] opacity-50"></div>
|
||||||
@@ -13,10 +13,10 @@
|
|||||||
|
|
||||||
<div class="px-6 pb-8 sm:px-12 relative">
|
<div class="px-6 pb-8 sm:px-12 relative">
|
||||||
<div class="flex flex-col sm:flex-row items-center sm:items-end -mt-20 sm:-mt-24 sm:space-x-8">
|
<div class="flex flex-col sm:flex-row items-center sm:items-end -mt-20 sm:-mt-24 sm:space-x-8">
|
||||||
<!-- 头像区域(呼吸发光效果) -->
|
<!-- 头像区域(科幻未来风格) -->
|
||||||
<div class="relative group/avatar">
|
<div class="relative group/avatar">
|
||||||
<div class="absolute inset-0 bg-gradient-to-br from-indigo-500 to-purple-500 rounded-full blur-md opacity-40 group-hover/avatar:opacity-60 animate-pulse transition-opacity"></div>
|
<div class="absolute inset-0 bg-gradient-to-br from-cyan-500 to-blue-500 rounded-full blur-md opacity-40 group-hover/avatar:opacity-60 animate-pulse transition-opacity"></div>
|
||||||
<div id="avatar-display" class="relative w-36 h-36 rounded-full overflow-hidden border-4 border-white shadow-xl flex items-center justify-center bg-gradient-to-br from-indigo-50 to-blue-50 text-indigo-600 text-5xl font-black cursor-pointer transform group-hover/avatar:scale-105 group-hover/avatar:rotate-3 transition-all duration-300 z-10" onclick="uploadAvatar()">
|
<div id="avatar-display" class="avatar-futuristic relative w-36 h-36 rounded-full overflow-hidden border-4 border-cyan-400/30 shadow-xl flex items-center justify-center bg-gradient-to-br from-slate-900 to-slate-800 text-cyan-400 text-5xl font-black cursor-pointer transform group-hover/avatar:scale-105 group-hover/avatar:rotate-3 transition-all duration-300 z-10" onclick="uploadAvatar()">
|
||||||
{% if profile_user.avatar %}
|
{% if profile_user.avatar %}
|
||||||
<img src="{{ profile_user.avatar }}" class="w-full h-full object-cover" id="avatar-img">
|
<img src="{{ profile_user.avatar }}" class="w-full h-full object-cover" id="avatar-img">
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -24,38 +24,38 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if profile_user.id == user.id %}
|
{% if profile_user.id == user.id %}
|
||||||
<div class="absolute bottom-1 right-1 bg-white p-2.5 rounded-full shadow-lg border-2 border-slate-100 cursor-pointer text-slate-500 hover:text-indigo-600 hover:border-indigo-200 transition-all z-20 transform hover:scale-110" onclick="uploadAvatar()">
|
<div class="absolute bottom-1 right-1 bg-slate-900/90 p-2.5 rounded-full shadow-lg border-2 border-cyan-400/30 cursor-pointer text-cyan-400 hover:text-cyan-300 hover:border-cyan-300 transition-all z-20 transform hover:scale-110" onclick="uploadAvatar()">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- 浮动等级徽章 -->
|
<!-- 浮动等级徽章 -->
|
||||||
<div class="absolute -top-2 -right-2 bg-slate-900 text-white text-xs font-black px-3 py-1.5 rounded-full shadow-lg border-2 border-white z-20 transform -rotate-12 group-hover/avatar:rotate-0 transition-transform">
|
<div class="badge-futuristic absolute -top-2 -right-2 text-xs font-black px-3 py-1.5 rounded-full shadow-lg border-2 border-cyan-400/50 z-20 transform -rotate-12 group-hover/avatar:rotate-0 transition-transform">
|
||||||
Lv.{{ level }}
|
Lv.{{ level }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 个人信息 -->
|
<!-- 个人信息 -->
|
||||||
<div class="mt-5 sm:mt-0 text-center sm:text-left flex-1 pb-2">
|
<div class="mt-5 sm:mt-0 text-center sm:text-left flex-1 pb-2">
|
||||||
<h1 class="text-4xl font-extrabold text-slate-900 flex items-center justify-center sm:justify-start gap-4 tracking-tight drop-shadow-sm">
|
<h1 class="text-4xl font-extrabold flex items-center justify-center sm:justify-start gap-4 tracking-tight drop-shadow-sm bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent">
|
||||||
{{ get_display_name(profile_user.id, profile_user.name) if profile_user.id == user.id else profile_user.name }}
|
{{ get_display_name(profile_user.id, profile_user.name) if profile_user.id == user.id else profile_user.name }}
|
||||||
{% if profile_user.role == 'admin' %}
|
{% if profile_user.role == 'admin' %}
|
||||||
<span class="px-3 py-1 rounded-xl text-xs font-bold bg-gradient-to-r from-red-500 to-rose-600 text-white shadow-sm shadow-red-200 transform hover:scale-105 transition-transform">管理员</span>
|
<span class="badge-futuristic px-3 py-1 rounded-xl text-xs font-bold bg-gradient-to-r from-red-500 to-rose-600 text-white shadow-sm shadow-red-200 transform hover:scale-105 transition-transform">管理员</span>
|
||||||
{% elif profile_user.role == 'teacher' %}
|
{% elif profile_user.role == 'teacher' %}
|
||||||
<span class="px-3 py-1 rounded-xl text-xs font-bold bg-gradient-to-r from-blue-500 to-indigo-600 text-white shadow-sm shadow-blue-200 transform hover:scale-105 transition-transform flex items-center gap-1">👨🏫 认证教师</span>
|
<span class="badge-futuristic px-3 py-1 rounded-xl text-xs font-bold bg-gradient-to-r from-blue-500 to-indigo-600 text-white shadow-sm shadow-blue-200 transform hover:scale-105 transition-transform flex items-center gap-1">👨🏫 认证教师</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="px-3 py-1 rounded-xl text-xs font-bold bg-gradient-to-r from-emerald-400 to-teal-500 text-white shadow-sm shadow-emerald-200 transform hover:scale-105 transition-transform flex items-center gap-1">🎓 学生</span>
|
<span class="badge-futuristic px-3 py-1 rounded-xl text-xs font-bold bg-gradient-to-r from-emerald-400 to-teal-500 text-white shadow-sm shadow-emerald-200 transform hover:scale-105 transition-transform flex items-center gap-1">🎓 学生</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center justify-center sm:justify-start gap-5 mt-4 text-sm font-medium text-slate-500">
|
<div class="flex flex-wrap items-center justify-center sm:justify-start gap-5 mt-4 text-sm font-medium text-slate-400">
|
||||||
{% if profile_user.email %}
|
{% if profile_user.email %}
|
||||||
<span class="flex items-center gap-1.5 bg-slate-50 px-3 py-1.5 rounded-lg border border-slate-100 hover:bg-slate-100 transition-colors"><svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>{{ profile_user.email }}</span>
|
<span class="flex items-center gap-1.5 bg-slate-800/50 px-3 py-1.5 rounded-lg border border-cyan-500/30 hover:bg-slate-700/50 transition-colors"><svg class="w-4 h-4 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>{{ profile_user.email }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if profile_user.phone %}
|
{% if profile_user.phone %}
|
||||||
<span class="flex items-center gap-1.5 bg-slate-50 px-3 py-1.5 rounded-lg border border-slate-100 hover:bg-slate-100 transition-colors"><svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/></svg>{{ profile_user.phone }}</span>
|
<span class="flex items-center gap-1.5 bg-slate-800/50 px-3 py-1.5 rounded-lg border border-cyan-500/30 hover:bg-slate-700/50 transition-colors"><svg class="w-4 h-4 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/></svg>{{ profile_user.phone }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="flex items-center gap-1.5 bg-indigo-50 text-indigo-600 px-3 py-1.5 rounded-lg border border-indigo-100"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>加入于 {{ profile_user.created_at.strftime('%Y-%m-%d') }}</span>
|
<span class="flex items-center gap-1.5 bg-indigo-500/20 text-indigo-400 px-3 py-1.5 rounded-lg border border-indigo-500/30"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>加入于 {{ profile_user.created_at.strftime('%Y-%m-%d') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,21 +65,21 @@
|
|||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<!-- 左侧:统计与信息 -->
|
<!-- 左侧:统计与信息 -->
|
||||||
<div class="space-y-6 lg:col-span-1">
|
<div class="space-y-6 lg:col-span-1">
|
||||||
<!-- 游戏化数据统计(Bento 风格) -->
|
<!-- 游戏化数据统计(科幻未来风格) -->
|
||||||
<div class="bg-white rounded-3xl p-6 shadow-sm border border-slate-100 relative overflow-hidden group">
|
<div class="futuristic-card-dark relative overflow-hidden group transition-all duration-300 hover:scale-105">
|
||||||
<div class="absolute top-0 right-0 w-32 h-32 bg-indigo-50 rounded-bl-full -z-10 group-hover:scale-110 transition-transform duration-500"></div>
|
<div class="absolute top-0 right-0 w-32 h-32 bg-indigo-500/10 rounded-bl-full -z-10 group-hover:scale-110 transition-transform duration-500"></div>
|
||||||
<h3 class="text-xl font-extrabold text-slate-900 mb-5 flex items-center">
|
<h3 class="text-xl font-extrabold mb-5 flex items-center bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent">
|
||||||
<span class="w-8 h-8 bg-indigo-100 text-indigo-600 rounded-xl flex items-center justify-center mr-3 shadow-sm">📊</span>
|
<span class="w-8 h-8 bg-indigo-500/20 text-indigo-400 rounded-xl flex items-center justify-center mr-3 shadow-sm border border-indigo-500/30">📊</span>
|
||||||
活跃数据
|
活跃数据
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<!-- 积分进度条 -->
|
<!-- 积分进度条 -->
|
||||||
<div class="mb-6 bg-slate-50 rounded-2xl p-4 border border-slate-100">
|
<div class="mb-6 bg-slate-900/50 rounded-2xl p-4 border border-cyan-500/30">
|
||||||
<div class="flex justify-between text-xs font-bold text-slate-600 mb-2">
|
<div class="flex justify-between text-xs font-bold text-slate-400 mb-2">
|
||||||
<span>当前等级进度</span>
|
<span>当前等级进度</span>
|
||||||
<span class="text-indigo-600">{{ points }} / {{ (level * 100) }} XP</span>
|
<span class="text-indigo-400">{{ points }} / {{ (level * 100) }} XP</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-3 w-full bg-slate-200 rounded-full overflow-hidden shadow-inner relative">
|
<div class="h-3 w-full bg-slate-800/50 rounded-full overflow-hidden shadow-inner relative border border-cyan-500/30">
|
||||||
{% set progress = (points / (level * 100) * 100) | int %}
|
{% set progress = (points / (level * 100) * 100) | int %}
|
||||||
{% set p_width = progress if progress <= 100 else 100 %}
|
{% set p_width = progress if progress <= 100 else 100 %}
|
||||||
<div class="absolute top-0 left-0 h-full bg-gradient-to-r from-indigo-500 to-purple-500 rounded-full transition-all duration-1000 ease-out overflow-hidden" style="width: {{ p_width }}%;">
|
<div class="absolute top-0 left-0 h-full bg-gradient-to-r from-indigo-500 to-purple-500 rounded-full transition-all duration-1000 ease-out overflow-hidden" style="width: {{ p_width }}%;">
|
||||||
@@ -89,56 +89,56 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div class="bg-gradient-to-br from-indigo-50 to-blue-50 rounded-2xl p-4 text-center border border-indigo-100/50 hover:shadow-md hover:-translate-y-1 transition-all duration-300">
|
<div class="bg-gradient-to-br from-indigo-500/20 to-blue-500/20 rounded-2xl p-4 text-center border border-indigo-500/30 hover:shadow-md hover:shadow-indigo-500/20 hover:-translate-y-1 transition-all duration-300">
|
||||||
<div class="text-3xl font-black text-indigo-600 drop-shadow-sm">{{ points }}</div>
|
<div class="text-3xl font-black text-indigo-400 drop-shadow-sm">{{ points }}</div>
|
||||||
<div class="text-[11px] font-bold text-indigo-400 uppercase tracking-widest mt-1">总积分</div>
|
<div class="text-[11px] font-bold text-indigo-300 uppercase tracking-widest mt-1">总积分</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gradient-to-br from-emerald-50 to-teal-50 rounded-2xl p-4 text-center border border-emerald-100/50 hover:shadow-md hover:-translate-y-1 transition-all duration-300">
|
<div class="bg-gradient-to-br from-emerald-500/20 to-teal-500/20 rounded-2xl p-4 text-center border border-emerald-500/30 hover:shadow-md hover:shadow-emerald-500/20 hover:-translate-y-1 transition-all duration-300">
|
||||||
<div class="text-3xl font-black text-emerald-600 drop-shadow-sm">{{ post_count }}</div>
|
<div class="text-3xl font-black text-emerald-400 drop-shadow-sm">{{ post_count }}</div>
|
||||||
<div class="text-[11px] font-bold text-emerald-500 uppercase tracking-widest mt-1">发帖数</div>
|
<div class="text-[11px] font-bold text-emerald-300 uppercase tracking-widest mt-1">发帖数</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gradient-to-br from-amber-50 to-orange-50 rounded-2xl p-4 text-center border border-amber-100/50 hover:shadow-md hover:-translate-y-1 transition-all duration-300">
|
<div class="bg-gradient-to-br from-amber-500/20 to-orange-500/20 rounded-2xl p-4 text-center border border-amber-500/30 hover:shadow-md hover:shadow-amber-500/20 hover:-translate-y-1 transition-all duration-300">
|
||||||
<div class="text-3xl font-black text-amber-600 drop-shadow-sm">{{ reply_count }}</div>
|
<div class="text-3xl font-black text-amber-400 drop-shadow-sm">{{ reply_count }}</div>
|
||||||
<div class="text-[11px] font-bold text-amber-500 uppercase tracking-widest mt-1">回复数</div>
|
<div class="text-[11px] font-bold text-amber-300 uppercase tracking-widest mt-1">回复数</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gradient-to-br from-rose-50 to-pink-50 rounded-2xl p-4 text-center border border-rose-100/50 hover:shadow-md hover:-translate-y-1 transition-all duration-300">
|
<div class="bg-gradient-to-br from-rose-500/20 to-pink-500/20 rounded-2xl p-4 text-center border border-rose-500/30 hover:shadow-md hover:shadow-rose-500/20 hover:-translate-y-1 transition-all duration-300">
|
||||||
<div class="text-3xl font-black text-rose-500 drop-shadow-sm">{{ likes_received }}</div>
|
<div class="text-3xl font-black text-rose-400 drop-shadow-sm">{{ likes_received }}</div>
|
||||||
<div class="text-[11px] font-bold text-rose-400 uppercase tracking-widest mt-1">获赞数</div>
|
<div class="text-[11px] font-bold text-rose-300 uppercase tracking-widest mt-1">获赞数</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 账号设置 -->
|
<!-- 账号设置 -->
|
||||||
{% if profile_user.id == user.id %}
|
{% if profile_user.id == user.id %}
|
||||||
<div class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
<div class="futuristic-card-dark transition-all duration-300 hover:scale-105">
|
||||||
<h3 class="text-lg font-bold text-slate-900 mb-4 flex items-center">
|
<h3 class="text-lg font-bold mb-4 flex items-center bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent">
|
||||||
<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="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
<svg class="w-5 h-5 mr-2 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
||||||
账号设置
|
账号设置
|
||||||
</h3>
|
</h3>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex items-center justify-between p-3 bg-slate-50 rounded-xl">
|
<div class="flex items-center justify-between p-3 bg-slate-900/50 rounded-xl border border-cyan-500/30">
|
||||||
<div>
|
<div>
|
||||||
<div class="text-sm font-medium text-slate-900">用户名</div>
|
<div class="text-sm font-medium text-slate-200">用户名</div>
|
||||||
<div class="text-xs text-slate-500 mt-0.5" id="display-username">{{ profile_user.name }}</div>
|
<div class="text-xs text-slate-400 mt-0.5" id="display-username">{{ profile_user.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="changeName()" class="px-3 py-1.5 text-xs font-medium text-primary bg-blue-50 hover:bg-blue-100 rounded-lg transition-colors">修改</button>
|
<button onclick="changeName()" class="btn-futuristic px-3 py-1.5 text-xs font-medium rounded-lg transition-colors">修改</button>
|
||||||
</div>
|
</div>
|
||||||
{% if user.role == 'student' %}
|
{% if user.role == 'student' %}
|
||||||
<a href="/apply-teacher" class="flex items-center justify-between p-3 bg-slate-50 rounded-xl hover:bg-slate-100 transition-colors group">
|
<a href="/apply-teacher" class="flex items-center justify-between p-3 bg-slate-900/50 rounded-xl hover:bg-slate-800/50 border border-blue-500/30 hover:border-blue-500/50 transition-all group">
|
||||||
<div>
|
<div>
|
||||||
<div class="text-sm font-medium text-slate-900 group-hover:text-primary transition-colors">申请成为老师</div>
|
<div class="text-sm font-medium text-slate-200 group-hover:text-cyan-400 transition-colors">申请成为老师</div>
|
||||||
<div class="text-xs text-slate-500 mt-0.5">获取发布赛事和考试的权限</div>
|
<div class="text-xs text-slate-400 mt-0.5">获取发布赛事和考试的权限</div>
|
||||||
</div>
|
</div>
|
||||||
<svg class="w-5 h-5 text-slate-400 group-hover:text-primary transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
<svg class="w-5 h-5 text-slate-400 group-hover:text-cyan-400 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.role == 'admin' or user.role == 'teacher' %}
|
{% if user.role == 'admin' or user.role == 'teacher' %}
|
||||||
<a href="/admin" class="flex items-center justify-between p-3 bg-slate-50 rounded-xl hover:bg-slate-100 transition-colors group">
|
<a href="/admin" class="flex items-center justify-between p-3 bg-slate-900/50 rounded-xl hover:bg-slate-800/50 border border-purple-500/30 hover:border-purple-500/50 transition-all group">
|
||||||
<div>
|
<div>
|
||||||
<div class="text-sm font-medium text-slate-900 group-hover:text-primary transition-colors">进入管理后台</div>
|
<div class="text-sm font-medium text-slate-200 group-hover:text-cyan-400 transition-colors">进入管理后台</div>
|
||||||
<div class="text-xs text-slate-500 mt-0.5">管理系统各项数据</div>
|
<div class="text-xs text-slate-400 mt-0.5">管理系统各项数据</div>
|
||||||
</div>
|
</div>
|
||||||
<svg class="w-5 h-5 text-slate-400 group-hover:text-primary transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
<svg class="w-5 h-5 text-slate-400 group-hover:text-cyan-400 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -148,45 +148,45 @@
|
|||||||
|
|
||||||
<!-- 右侧:主要内容 -->
|
<!-- 右侧:主要内容 -->
|
||||||
<div class="space-y-6 lg:col-span-2">
|
<div class="space-y-6 lg:col-span-2">
|
||||||
<!-- 快捷入口(玻璃拟物化卡片) -->
|
<!-- 快捷入口(科幻未来风格) -->
|
||||||
{% if profile_user.id == user.id %}
|
{% if profile_user.id == user.id %}
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
||||||
<a href="/notifications" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:shadow-lg hover:border-blue-200 hover:-translate-y-1 transition-all duration-300 text-center group">
|
<a href="/notifications" class="futuristic-card-dark text-center group transition-all duration-300 hover:scale-105">
|
||||||
<div class="w-12 h-12 mx-auto bg-gradient-to-br from-blue-100 to-indigo-100 rounded-2xl flex items-center justify-center text-blue-600 shadow-inner group-hover:scale-110 group-hover:rotate-6 transition-transform mb-3">
|
<div class="w-12 h-12 mx-auto bg-gradient-to-br from-cyan-500/20 to-blue-500/20 rounded-2xl flex items-center justify-center text-cyan-400 shadow-inner group-hover:scale-110 group-hover:rotate-6 transition-transform mb-3 border border-cyan-500/30">
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm font-bold text-slate-700 group-hover:text-blue-600 transition-colors">通知中心</div>
|
<div class="text-sm font-bold text-slate-300 group-hover:text-cyan-400 transition-colors">通知中心</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="/chat" class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:shadow-lg hover:border-emerald-200 hover:-translate-y-1 transition-all duration-300 text-center group">
|
<a href="/chat" class="futuristic-card-dark text-center group transition-all duration-300 hover:scale-105">
|
||||||
<div class="w-12 h-12 mx-auto bg-gradient-to-br from-emerald-100 to-teal-100 rounded-2xl flex items-center justify-center text-emerald-600 shadow-inner group-hover:scale-110 group-hover:-rotate-6 transition-transform mb-3">
|
<div class="w-12 h-12 mx-auto bg-gradient-to-br from-emerald-500/20 to-teal-500/20 rounded-2xl flex items-center justify-center text-emerald-400 shadow-inner group-hover:scale-110 group-hover:-rotate-6 transition-transform mb-3 border border-emerald-500/30">
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/></svg>
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm font-bold text-slate-700 group-hover:text-emerald-600 transition-colors">我的消息</div>
|
<div class="text-sm font-bold text-slate-300 group-hover:text-emerald-400 transition-colors">我的消息</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:shadow-lg hover:border-purple-200 hover:-translate-y-1 transition-all duration-300 text-center group cursor-pointer" onclick="document.getElementById('exam-history-tab').scrollIntoView({behavior:'smooth'})">
|
<div class="futuristic-card-dark text-center group cursor-pointer transition-all duration-300 hover:scale-105" onclick="document.getElementById('exam-history-tab').scrollIntoView({behavior:'smooth'})">
|
||||||
<div class="w-12 h-12 mx-auto bg-gradient-to-br from-purple-100 to-fuchsia-100 rounded-2xl flex items-center justify-center text-purple-600 shadow-inner group-hover:scale-110 group-hover:rotate-6 transition-transform mb-3">
|
<div class="w-12 h-12 mx-auto bg-gradient-to-br from-purple-500/20 to-fuchsia-500/20 rounded-2xl flex items-center justify-center text-purple-400 shadow-inner group-hover:scale-110 group-hover:rotate-6 transition-transform mb-3 border border-purple-500/30">
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/></svg>
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm font-bold text-slate-700 group-hover:text-purple-600 transition-colors">考试记录</div>
|
<div class="text-sm font-bold text-slate-300 group-hover:text-purple-400 transition-colors">考试记录</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-white rounded-2xl p-5 shadow-sm border border-slate-100 hover:shadow-lg hover:border-amber-200 hover:-translate-y-1 transition-all duration-300 text-center group cursor-pointer" onclick="document.getElementById('bookmarks-tab').scrollIntoView({behavior:'smooth'})">
|
<div class="futuristic-card-dark text-center group cursor-pointer transition-all duration-300 hover:scale-105" onclick="document.getElementById('bookmarks-tab').scrollIntoView({behavior:'smooth'})">
|
||||||
<div class="w-12 h-12 mx-auto bg-gradient-to-br from-amber-100 to-orange-100 rounded-2xl flex items-center justify-center text-amber-600 shadow-inner group-hover:scale-110 group-hover:-rotate-6 transition-transform mb-3">
|
<div class="w-12 h-12 mx-auto bg-gradient-to-br from-amber-500/20 to-orange-500/20 rounded-2xl flex items-center justify-center text-amber-400 shadow-inner group-hover:scale-110 group-hover:-rotate-6 transition-transform mb-3 border border-amber-500/30">
|
||||||
<svg class="w-6 h-6" 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>
|
<svg class="w-6 h-6" 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>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm font-bold text-slate-700 group-hover:text-amber-600 transition-colors">我的收藏</div>
|
<div class="text-sm font-bold text-slate-300 group-hover:text-amber-400 transition-colors">我的收藏</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- 高级选项卡内容区 -->
|
<!-- 高级选项卡内容区 -->
|
||||||
<div class="bg-white shadow-sm rounded-3xl border border-slate-100 overflow-hidden">
|
<div class="futuristic-card-dark overflow-hidden">
|
||||||
<!-- 游戏化标签栏 -->
|
<!-- 科幻未来标签栏 -->
|
||||||
<div class="p-2 bg-slate-50/80 border-b border-slate-100">
|
<div class="p-2 bg-slate-900/50 border-b border-cyan-500/20">
|
||||||
<div class="flex gap-2 overflow-x-auto hide-scrollbar">
|
<div class="flex gap-2 overflow-x-auto hide-scrollbar">
|
||||||
<button class="flex-1 min-w-[120px] px-6 py-3.5 text-sm font-bold text-indigo-600 bg-white rounded-xl shadow-sm transition-all duration-300 whitespace-nowrap" id="tab-btn-history" onclick="switchTab('history')">📜 考试经历</button>
|
<button class="btn-futuristic flex-1 min-w-[120px] px-6 py-3.5 text-sm font-bold rounded-xl shadow-sm transition-all duration-300 whitespace-nowrap" id="tab-btn-history" onclick="switchTab('history')">📜 考试经历</button>
|
||||||
<button class="flex-1 min-w-[120px] px-6 py-3.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-white/60 bg-transparent rounded-xl transition-all duration-300 whitespace-nowrap" id="tab-btn-posts" onclick="switchTab('posts')">📝 我的帖子</button>
|
<button class="btn-outline-futuristic flex-1 min-w-[120px] px-6 py-3.5 text-sm font-medium rounded-xl transition-all duration-300 whitespace-nowrap" id="tab-btn-posts" onclick="switchTab('posts')">📝 我的帖子</button>
|
||||||
<button class="flex-1 min-w-[120px] px-6 py-3.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-white/60 bg-transparent rounded-xl transition-all duration-300 whitespace-nowrap" id="tab-btn-bookmarks" onclick="switchTab('bookmarks')">⭐ 收藏试卷</button>
|
<button class="btn-outline-futuristic flex-1 min-w-[120px] px-6 py-3.5 text-sm font-medium rounded-xl transition-all duration-300 whitespace-nowrap" id="tab-btn-bookmarks" onclick="switchTab('bookmarks')">⭐ 收藏试卷</button>
|
||||||
<button class="flex-1 min-w-[120px] px-6 py-3.5 text-sm font-medium text-slate-500 hover:text-slate-800 hover:bg-white/60 bg-transparent rounded-xl transition-all duration-300 whitespace-nowrap" id="tab-btn-friends" onclick="switchTab('friends')">👥 好友列表</button>
|
<button class="btn-outline-futuristic flex-1 min-w-[120px] px-6 py-3.5 text-sm font-medium rounded-xl transition-all duration-300 whitespace-nowrap" id="tab-btn-friends" onclick="switchTab('friends')">👥 好友列表</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
<h4 class="text-sm font-bold text-slate-700">搜索用户</h4>
|
<h4 class="text-sm font-bold text-slate-700">搜索用户</h4>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<input id="friend-search-input" type="text" placeholder="输入用户名搜索..." class="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary">
|
<input id="friend-search-input" type="text" placeholder="输入用户名搜索..." class="flex-1 px-3 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary">
|
||||||
<button onclick="searchUsers()" class="px-4 py-2 bg-primary text-white text-sm rounded-lg hover:bg-blue-600 transition-colors">搜索</button>
|
<button onclick="searchUsers()" class="btn-futuristic px-4 py-2 text-sm rounded-lg transition-colors">搜索</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="search-results"></div>
|
<div id="search-results"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -290,7 +290,7 @@ async function loadFriends() {
|
|||||||
<div class="text-sm font-medium text-slate-900 truncate">${f.name}</div>
|
<div class="text-sm font-medium text-slate-900 truncate">${f.name}</div>
|
||||||
<div class="text-xs text-slate-400">好友 · ${f.created_at}</div>
|
<div class="text-xs text-slate-400">好友 · ${f.created_at}</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="/chat?dm=${f.id}" class="px-3 py-1.5 text-xs bg-primary/10 text-primary rounded-lg hover:bg-primary/20 transition-colors font-medium">私聊</a>
|
<a href="/chat?dm=${f.id}" class="btn-futuristic px-3 py-1.5 text-xs rounded-lg transition-colors font-medium">私聊</a>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
container.innerHTML = html;
|
container.innerHTML = html;
|
||||||
@@ -319,7 +319,7 @@ async function searchUsers() {
|
|||||||
} else if (u.friend_status === 'pending') {
|
} else if (u.friend_status === 'pending') {
|
||||||
actionBtn = '<span class="text-xs text-amber-600 font-medium">已申请</span>';
|
actionBtn = '<span class="text-xs text-amber-600 font-medium">已申请</span>';
|
||||||
} else {
|
} else {
|
||||||
actionBtn = `<button onclick="addFriend(${u.id}, this)" class="px-3 py-1 text-xs bg-primary text-white rounded-lg hover:bg-blue-600 transition-colors">加好友</button>`;
|
actionBtn = `<button onclick="addFriend(${u.id}, this)" class="btn-futuristic px-3 py-1 text-xs rounded-lg transition-colors">加好友</button>`;
|
||||||
}
|
}
|
||||||
html += `
|
html += `
|
||||||
<div class="flex items-center space-x-3 p-2 rounded-lg hover:bg-white transition-colors">
|
<div class="flex items-center space-x-3 p-2 rounded-lg hover:bg-white transition-colors">
|
||||||
@@ -362,8 +362,8 @@ async function loadFriendRequests() {
|
|||||||
${r.avatar ? `<img src="${r.avatar}" class="w-full h-full object-cover">` : r.name.charAt(0).toUpperCase()}
|
${r.avatar ? `<img src="${r.avatar}" class="w-full h-full object-cover">` : r.name.charAt(0).toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 min-w-0"><div class="text-sm font-medium text-slate-800 truncate">${r.name}</div></div>
|
<div class="flex-1 min-w-0"><div class="text-sm font-medium text-slate-800 truncate">${r.name}</div></div>
|
||||||
<button onclick="acceptFriend(${r.id})" class="px-2.5 py-1 text-xs bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors">同意</button>
|
<button onclick="acceptFriend(${r.id})" class="btn-futuristic px-2.5 py-1 text-xs rounded-lg transition-colors">同意</button>
|
||||||
<button onclick="rejectFriend(${r.id})" class="px-2.5 py-1 text-xs bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors">拒绝</button>
|
<button onclick="rejectFriend(${r.id})" class="btn-outline-futuristic px-2.5 py-1 text-xs rounded-lg transition-colors">拒绝</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
container.innerHTML = html;
|
container.innerHTML = html;
|
||||||
@@ -503,8 +503,8 @@ function switchTab(tabName) {
|
|||||||
|
|
||||||
const btn = document.getElementById(`tab-btn-${t}`);
|
const btn = document.getElementById(`tab-btn-${t}`);
|
||||||
if(btn) {
|
if(btn) {
|
||||||
btn.classList.remove('text-indigo-600', 'bg-white', 'shadow-sm', 'font-bold');
|
btn.classList.remove('btn-futuristic');
|
||||||
btn.classList.add('text-slate-500', 'bg-transparent', 'hover:bg-white/60', 'font-medium');
|
btn.classList.add('btn-outline-futuristic');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -519,8 +519,8 @@ function switchTab(tabName) {
|
|||||||
// 激活按钮样式
|
// 激活按钮样式
|
||||||
const activeBtn = document.getElementById(`tab-btn-${tabName}`);
|
const activeBtn = document.getElementById(`tab-btn-${tabName}`);
|
||||||
if(activeBtn) {
|
if(activeBtn) {
|
||||||
activeBtn.classList.remove('text-slate-500', 'bg-transparent', 'hover:bg-white/60', 'font-medium');
|
activeBtn.classList.remove('btn-outline-futuristic');
|
||||||
activeBtn.classList.add('text-indigo-600', 'bg-white', 'shadow-sm', 'font-bold');
|
activeBtn.classList.add('btn-futuristic');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,35 +4,35 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="min-h-screen bg-slate-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
<div class="min-h-screen bg-slate-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||||
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
<h2 class="mt-6 text-center text-3xl font-extrabold text-slate-900">注册新账户</h2>
|
<h2 class="mt-6 text-center text-3xl font-extrabold bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 bg-clip-text text-transparent animate-pulse">注册新账户</h2>
|
||||||
<p class="mt-2 text-center text-sm text-slate-600">
|
<p class="mt-2 text-center text-sm text-slate-600">
|
||||||
已有账户? <a href="/login" class="font-medium text-primary hover:text-blue-500">立即登录</a>
|
已有账户? <a href="/login" class="font-medium text-primary hover:text-blue-500 transition-all duration-300 hover:scale-110 inline-block">立即登录</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
<div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
<div class="futuristic-card py-8 px-4 sm:px-10">
|
||||||
<!-- 选项卡 -->
|
<!-- 选项卡 -->
|
||||||
<div class="flex border-b border-slate-200 mb-6">
|
<div class="flex border-b border-slate-200 mb-6">
|
||||||
<button id="tab-phone" onclick="switchTab('phone')" class="flex-1 pb-4 text-sm font-medium text-center text-primary border-b-2 border-primary">手机号注册</button>
|
<button id="tab-phone" onclick="switchTab('phone')" class="flex-1 pb-4 text-sm font-medium text-center text-primary border-b-2 border-primary transition-all duration-300 hover:scale-105">手机号注册</button>
|
||||||
<button id="tab-email" onclick="switchTab('email')" class="flex-1 pb-4 text-sm font-medium text-center text-slate-500 hover:text-slate-700">邮箱注册</button>
|
<button id="tab-email" onclick="switchTab('email')" class="flex-1 pb-4 text-sm font-medium text-center text-slate-500 hover:text-slate-700 transition-all duration-300 hover:scale-105">邮箱注册</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 手机注册表单 -->
|
<!-- 手机注册表单 -->
|
||||||
<form id="form-phone" class="space-y-6" onsubmit="handlePhoneRegister(event)">
|
<form id="form-phone" class="space-y-6" onsubmit="handlePhoneRegister(event)">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">姓名</label>
|
<label class="block text-sm font-medium text-slate-700">姓名</label>
|
||||||
<input id="phone-name" type="text" required class="mt-1 appearance-none block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
<input id="phone-name" type="text" required class="input-futuristic mt-1 appearance-none block w-full px-3 py-2 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">手机号码</label>
|
<label class="block text-sm font-medium text-slate-700">手机号码</label>
|
||||||
<input id="phone-number" type="tel" required class="mt-1 appearance-none block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
<input id="phone-number" type="tel" required class="input-futuristic mt-1 appearance-none block w-full px-3 py-2 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">图形验证码</label>
|
<label class="block text-sm font-medium text-slate-700">图形验证码</label>
|
||||||
<div class="mt-1 flex items-center space-x-2">
|
<div class="mt-1 flex items-center space-x-2">
|
||||||
<input id="captcha-text-phone" type="text" class="focus:ring-primary focus:border-primary block flex-1 sm:text-sm border-slate-300 rounded-md py-2 border px-3" placeholder="请输入图形验证码">
|
<input id="captcha-text-phone" type="text" class="input-futuristic focus:ring-primary focus:border-primary block flex-1 sm:text-sm rounded-md py-2 px-3" placeholder="请输入图形验证码">
|
||||||
<img id="captcha-img-phone" class="cursor-pointer h-10" onclick="fetchCaptcha('phone')" alt="验证码">
|
<img id="captcha-img-phone" class="cursor-pointer h-10 transition-transform duration-300 hover:scale-110" onclick="fetchCaptcha('phone')" alt="验证码">
|
||||||
<button type="button" onclick="fetchCaptcha('phone')" class="p-2 text-slate-400 hover:text-slate-600" title="刷新验证码">
|
<button type="button" onclick="fetchCaptcha('phone')" class="p-2 text-slate-400 hover:text-slate-600 transition-all duration-300 hover:rotate-180" title="刷新验证码">
|
||||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,41 +40,41 @@
|
|||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">短信验证码</label>
|
<label class="block text-sm font-medium text-slate-700">短信验证码</label>
|
||||||
<div class="mt-1 flex rounded-md shadow-sm">
|
<div class="mt-1 flex rounded-md shadow-sm">
|
||||||
<input id="sms-code-phone" type="text" required class="focus:ring-primary focus:border-primary block w-full rounded-none rounded-l-md sm:text-sm border-slate-300 py-2 border px-3" placeholder="请输入短信验证码">
|
<input id="sms-code-phone" type="text" required class="input-futuristic focus:ring-primary focus:border-primary block w-full rounded-none rounded-l-md sm:text-sm py-2 px-3" placeholder="请输入短信验证码">
|
||||||
<button type="button" id="send-sms-btn-phone" onclick="handleSendSmsPhone()" class="relative inline-flex items-center px-4 py-2 border border-slate-300 text-sm font-medium rounded-r-md text-slate-700 bg-slate-50 hover:bg-slate-100">获取验证码</button>
|
<button type="button" id="send-sms-btn-phone" onclick="handleSendSmsPhone()" class="btn-futuristic relative inline-flex items-center px-4 py-2 text-sm font-medium rounded-r-md">获取验证码</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">密码</label>
|
<label class="block text-sm font-medium text-slate-700">密码</label>
|
||||||
<input id="phone-password" type="password" required class="mt-1 appearance-none block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
<input id="phone-password" type="password" required class="input-futuristic mt-1 appearance-none block w-full px-3 py-2 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">确认密码</label>
|
<label class="block text-sm font-medium text-slate-700">确认密码</label>
|
||||||
<input id="phone-confirm" type="password" required class="mt-1 appearance-none block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
<input id="phone-confirm" type="password" required class="input-futuristic mt-1 appearance-none block w-full px-3 py-2 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary hover:bg-blue-700">注册</button>
|
<button type="submit" class="btn-futuristic w-full flex justify-center py-2 px-4 rounded-md text-sm font-medium text-white">注册</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- 邮箱注册表单(已添加手机号绑定) -->
|
<!-- 邮箱注册表单(已添加手机号绑定) -->
|
||||||
<form id="form-email" class="space-y-6 hidden" onsubmit="handleEmailRegister(event)">
|
<form id="form-email" class="space-y-6 hidden" onsubmit="handleEmailRegister(event)">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">姓名</label>
|
<label class="block text-sm font-medium text-slate-700">姓名</label>
|
||||||
<input id="reg-name" type="text" required class="mt-1 appearance-none block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
<input id="reg-name" type="text" required class="input-futuristic mt-1 appearance-none block w-full px-3 py-2 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">邮箱地址</label>
|
<label class="block text-sm font-medium text-slate-700">邮箱地址</label>
|
||||||
<input id="reg-email" type="email" required class="mt-1 appearance-none block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
<input id="reg-email" type="email" required class="input-futuristic mt-1 appearance-none block w-full px-3 py-2 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">手机号码(用于绑定)</label>
|
<label class="block text-sm font-medium text-slate-700">手机号码(用于绑定)</label>
|
||||||
<input id="reg-phone" type="tel" required class="mt-1 appearance-none block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
<input id="reg-phone" type="tel" required class="input-futuristic mt-1 appearance-none block w-full px-3 py-2 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">图形验证码</label>
|
<label class="block text-sm font-medium text-slate-700">图形验证码</label>
|
||||||
<div class="mt-1 flex items-center space-x-2">
|
<div class="mt-1 flex items-center space-x-2">
|
||||||
<input id="captcha-text-reg" type="text" class="focus:ring-primary focus:border-primary block flex-1 sm:text-sm border-slate-300 rounded-md py-2 border px-3" placeholder="请输入图形验证码">
|
<input id="captcha-text-reg" type="text" class="input-futuristic focus:ring-primary focus:border-primary block flex-1 sm:text-sm rounded-md py-2 px-3" placeholder="请输入图形验证码">
|
||||||
<img id="captcha-img-reg" class="cursor-pointer h-10" onclick="fetchCaptcha('reg')" alt="验证码">
|
<img id="captcha-img-reg" class="cursor-pointer h-10 transition-transform duration-300 hover:scale-110" onclick="fetchCaptcha('reg')" alt="验证码">
|
||||||
<button type="button" onclick="fetchCaptcha('reg')" class="p-2 text-slate-400 hover:text-slate-600" title="刷新验证码">
|
<button type="button" onclick="fetchCaptcha('reg')" class="p-2 text-slate-400 hover:text-slate-600 transition-all duration-300 hover:rotate-180" title="刷新验证码">
|
||||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,19 +82,19 @@
|
|||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">邮箱验证码</label>
|
<label class="block text-sm font-medium text-slate-700">邮箱验证码</label>
|
||||||
<div class="mt-1 flex rounded-md shadow-sm">
|
<div class="mt-1 flex rounded-md shadow-sm">
|
||||||
<input id="reg-email-code" type="text" required class="focus:ring-primary focus:border-primary block w-full rounded-none rounded-l-md sm:text-sm border-slate-300 py-2 border px-3" placeholder="请输入邮箱验证码">
|
<input id="reg-email-code" type="text" required class="input-futuristic focus:ring-primary focus:border-primary block w-full rounded-none rounded-l-md sm:text-sm py-2 px-3" placeholder="请输入邮箱验证码">
|
||||||
<button type="button" id="send-email-btn" onclick="handleSendEmailCode()" class="relative inline-flex items-center px-4 py-2 border border-slate-300 text-sm font-medium rounded-r-md text-slate-700 bg-slate-50 hover:bg-slate-100">获取验证码</button>
|
<button type="button" id="send-email-btn" onclick="handleSendEmailCode()" class="btn-futuristic relative inline-flex items-center px-4 py-2 text-sm font-medium rounded-r-md">获取验证码</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">密码</label>
|
<label class="block text-sm font-medium text-slate-700">密码</label>
|
||||||
<input id="reg-password" type="password" required class="mt-1 appearance-none block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
<input id="reg-password" type="password" required class="input-futuristic mt-1 appearance-none block w-full px-3 py-2 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-700">确认密码</label>
|
<label class="block text-sm font-medium text-slate-700">确认密码</label>
|
||||||
<input id="reg-confirm" type="password" required class="mt-1 appearance-none block w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
<input id="reg-confirm" type="password" required class="input-futuristic mt-1 appearance-none block w-full px-3 py-2 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary hover:bg-blue-700">注册</button>
|
<button type="submit" class="btn-futuristic w-full flex justify-center py-2 px-4 rounded-md text-sm font-medium text-white">注册</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,8 +110,8 @@ let countdown = 0;
|
|||||||
function switchTab(tab) {
|
function switchTab(tab) {
|
||||||
document.getElementById('form-phone').classList.toggle('hidden', tab !== 'phone');
|
document.getElementById('form-phone').classList.toggle('hidden', tab !== 'phone');
|
||||||
document.getElementById('form-email').classList.toggle('hidden', tab !== 'email');
|
document.getElementById('form-email').classList.toggle('hidden', tab !== 'email');
|
||||||
document.getElementById('tab-phone').className = 'flex-1 pb-4 text-sm font-medium text-center ' + (tab === 'phone' ? 'text-primary border-b-2 border-primary' : 'text-slate-500 hover:text-slate-700');
|
document.getElementById('tab-phone').className = 'flex-1 pb-4 text-sm font-medium text-center transition-all duration-300 hover:scale-105 ' + (tab === 'phone' ? 'text-primary border-b-2 border-primary' : 'text-slate-500 hover:text-slate-700');
|
||||||
document.getElementById('tab-email').className = 'flex-1 pb-4 text-sm font-medium text-center ' + (tab === 'email' ? 'text-primary border-b-2 border-primary' : 'text-slate-500 hover:text-slate-700');
|
document.getElementById('tab-email').className = 'flex-1 pb-4 text-sm font-medium text-center transition-all duration-300 hover:scale-105 ' + (tab === 'email' ? 'text-primary border-b-2 border-primary' : 'text-slate-500 hover:text-slate-700');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取图形验证码
|
// 获取图形验证码
|
||||||
|
|||||||
Reference in New Issue
Block a user