Files
wechat_backend/index.html
王宏豪 9a7681112b feat(database): 迁移用户关联字段并优化打卡系统
- 修改数据库配置将MYSQL_DB从mys_info_v3改为mys_info_test
- 新增auth_users表wechat_openid字段并建立唯一索引
- 为plan_checkins表添加计划性能索引(idx_plan_period_student)
- 将checkin_records_old表关联方式从student_id改为user_id
- 将plan_checkins表关联方式从student_id改为user_id并更新附件路径
- 移除routes/auth.py中的uuid导入使用安全token生成函数
- 重构routes/checkin.py打卡逻辑去除course_type字段并优化返回结构
- 添加打卡时间限制验证功能防止过期打卡操作
- 实现小组统计功能支持老师查看各组打卡情况
- 更新主页index.html界面适配新的打卡和计划功能
- 调整计划提交时间规则为周五至周四周期
- 添加月度和年度计划提交状态验证机制
2026-09-10 14:11:59 +08:00

259 lines
13 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="expires" content="0">
<title>明缘进阶场 · 薪火相传</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{--gold:#C67B30;--gold-light:#E8C99B;--warm-white:#FDFBF7;--ink:#2C1810;--ink-l:#5A4A3A;--fs:"Inter","PingFang SC","Noto Sans SC",sans-serif;--fser:"Noto Serif SC",serif}
html{scroll-behavior:smooth}
body{font-family:var(--fs);background:var(--warm-white);color:var(--ink-l);line-height:1.6;min-height:100vh;display:flex;justify-content:center;background-image:radial-gradient(circle at 10% 20%, rgba(198,123,48,0.06) 0%, transparent 50%),radial-gradient(circle at 90% 80%, rgba(192,57,43,0.04) 0%, transparent 50%)}
.wrap{max-width:960px;width:100%;margin:0 auto;padding:28px 24px 20px;display:flex;flex-direction:column;min-height:100vh}
header{text-align:center;margin-bottom:32px;padding:20px 0 16px;position:relative}
header::after{content:'';position:absolute;bottom:0;left:50%;transform:translateX(-50%);width:80px;height:2px;background:linear-gradient(90deg,transparent,var(--gold),transparent)}
header h1{font-family:var(--fser);font-size:40px;font-weight:900;color:var(--ink);margin-bottom:6px;letter-spacing:8px;text-shadow:0 2px 4px rgba(0,0,0,0.02)}
header .greeting-wrap{display:flex;align-items:center;justify-content:center;gap:12px;margin-top:10px;flex-wrap:wrap}
header .greeting-wrap .name{font-size:20px;font-weight:700;color:var(--gold)}
header .greeting-wrap .msg{font-size:14px;color:#888;background:rgba(198,123,48,0.08);padding:2px 14px;border-radius:20px;border:1px solid rgba(198,123,48,0.12)}
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:20px;align-content:start}
.choice{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;padding:20px 22px;border-radius:16px;background:rgba(255,255,255,0.75);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);text-decoration:none;transition:all 0.35s cubic-bezier(0.25,0.46,0.45,0.94);gap:8px;box-shadow:0 4px 20px rgba(0,0,0,0.04),0 1px 3px rgba(0,0,0,0.02);border:1px solid rgba(255,255,255,0.9);min-height:120px;position:relative;overflow:hidden}
.choice::before{content:'';position:absolute;top:0;left:0;right:0;bottom:0;background:linear-gradient(135deg,transparent 60%,rgba(198,123,48,0.03));pointer-events:none;border-radius:16px}
.choice:hover{transform:translateY(-4px);box-shadow:0 20px 40px rgba(44,24,16,0.08),0 4px 12px rgba(198,123,48,0.10);border-color:rgba(198,123,48,0.3);background:rgba(255,255,255,0.92)}
.choice .row{display:flex;align-items:center;gap:14px;width:100%}
.choice .icon{font-size:34px;flex-shrink:0;line-height:1;filter:drop-shadow(0 2px 4px rgba(0,0,0,0.04));margin-bottom:2px}
.choice .label{font-size:18px;font-weight:700;color:var(--ink);letter-spacing:1px;transition:color .2s}
.choice:hover .label{color:var(--gold)}
.choice .desc{font-size:13px;color:#999;line-height:1.4;letter-spacing:0.3px}
.choice .badge{display:inline-block;background:rgba(192,57,43,0.06);color:#c0392b;font-size:11px;padding:0 10px;border-radius:12px;font-weight:500;border:1px solid rgba(192,57,43,0.08);margin-left:8px;line-height:20px}
.choice.virtual{background:linear-gradient(135deg,#fdfcf9,#f7f0e6);border:1px dashed rgba(198,123,48,0.3)}
.choice.virtual:hover{border-color:var(--gold);background:#fcf8f0}
.choice.hidden{display:none}
.bottom-links{text-align:center;padding:18px 0 4px;font-size:13px;color:#bbb;border-top:1px solid rgba(0,0,0,0.04);margin-top:auto}
.bottom-links a{color:#8B6B4A;text-decoration:none;margin:0 8px;transition:color .2s;font-weight:450}
.bottom-links a:hover{color:var(--gold);text-decoration:none}
.bottom-links .sep{color:#ddd;margin:0 2px}
.bottom-links .ver{color:#ccc;font-size:12px;margin-left:8px}
@media(max-width:800px){.grid{grid-template-columns:repeat(2,1fr)}}
@media(max-width:550px){.grid{grid-template-columns:1fr}.choice{min-height:auto;padding:16px 18px}.choice .icon{font-size:28px}.choice .label{font-size:16px}.wrap{padding:16px 14px}header h1{font-size:30px;letter-spacing:4px}}
</style>
</head>
<body>
<div class="wrap">
<header>
<h1>明缘进阶场</h1>
<div class="greeting-wrap">
<span class="name" id="welcome">加载中...</span>
<span class="msg" id="greeting"></span>
<span class="enc-line" id="encLine" style="font-size:13px;color:#8B6B4A;margin-top:4px;font-style:italic;opacity:0.85;display:block"></span><p class="enc-line" id="encLine" style="font-size:13px;color:#8B6B4A;margin-top:6px;font-style:italic;opacity:0.85"></p>
</div>
</header>
<div class="grid" id="grid">
<a href="/xinhuo/" class="choice" id="btn_xh">
<div class="row">
<div class="icon">🔥</div>
<div>
<div class="label">薪火相传</div>
<div class="desc">年课 / 终身学习小组</div>
</div>
</div>
</a>
<a href="/19qi/" class="choice hidden" id="btn_19">
<div class="row">
<div class="icon">📖</div>
<div>
<div class="label">19期研修</div>
<div class="desc">小班课 · 第19期</div>
</div>
</div>
</a>
<a href="/18qi/" class="choice hidden" id="btn_18">
<div class="row">
<div class="icon">📖</div>
<div>
<div class="label">18期研修</div>
<div class="desc">小班课 · 第18期</div>
</div>
</div>
</a>
<a href="/20qi/" class="choice hidden" id="btn_20">
<div class="row">
<div class="icon">📖</div>
<div>
<div class="label">20期小班课</div>
<div class="desc">小班课 · 第20期</div>
</div>
</div>
</a>
<a href="/xinhuo/upgrade-info.html" class="choice" id="btn_teacher">
<div class="row">
<div class="icon">🎓</div>
<div>
<div class="label">老师大班授权</div>
<div class="desc">年课 / 长期学员入口</div>
</div>
</div>
</a>
</a>
<a href="/xinhuo360/" class="choice" id="btn_360"><div class="row"><div class="icon">🧬</div><div><div class="label">学员360</div><div class="desc">薪火相传 · 个人成长档案</div></div></div></a><a href="/quant-courses.html" class="choice" id="btn_quant"><div class="row"><div class="icon">📊</div><div><div class="label">量化课程</div><div class="desc">量化课程内容 · 随课程方上传更新</div></div></div></a><a href="/reading-club.html" class="choice" id="btn_reading"><div class="row"><div class="icon">📚</div><div><div class="label">读书会</div><div class="desc">经典原著 · 共读课程</div></div></div></a><a href="/course-assistant.html" class="choice hidden" id="btn_assistant"><div class="row"><div class="icon">🎓</div><div><div class="label">课程助理</div><div class="desc">老师专属 · 逐页讲解</div></div></div></a>
<a href="/xinhuo/virtual-group.html" class="choice virtual" id="btn_demo">
<div class="row">
<div class="icon">🧭</div>
<div>
<div class="label">功能演示</div>
<div class="desc">先看看讨论和量化报告的样子</div>
</div>
</div>
</a>
<a href="yijing/" class="choice" id="btn_yijing"><div class="row"><div class="icon">☯</div><div><div class="label">易经学习</div><div class="desc">周易六十四卦 · 经文详解</div></div></div></a>
<a href="homework/" class="choice hidden" id="btn_hw"><div class="row"><div class="icon">📝</div><div><div class="label">20期作业评价</div><div class="desc">回嗔作喜 · 观过思仁 · 温故知新</div></div></div></a>
<a href="checkin/index.html" class="choice" id="btn_checkin">
<div class="row">
<div class="icon">&#128203;</div>
<div>
<div class="label">上课打卡</div>
<div class="desc">会员课程 · 小组讨论考勤</div>
</div>
</div>
</a>
<a href="plan/index.html" class="choice" id="btn_plan">
<div class="row">
<div class="icon">&#9989;</div>
<div>
<div class="label">计划打卡</div>
<div class="desc">周计划 · 月计划 · 年计划</div>
</div>
</div>
</a>
</div>
<div class="bottom-links">
<a href="/feedback.html">问题反馈</a><span class="sep">|</span>
<a href="/changelog.html">更新日志</a><span class="sep">|</span>
<a href="/help.html">使用帮助</a><span class="sep">|</span>
<a href="#" id="logoutBtn">退出登录</a>
<span class="ver" id="versionInfo">V3.1</span>
</div>
</div>
<script>
(async function(){
try{
var vr=await fetch("/api/auth/verify");var vd=await vr.json();
if(!vd.success){window.location.href="/auth/login.html";return;}
var myName=vd.name||"";var displayName=vd.display_name||myName;var myRole=vd.role||"student";var isTeacher=(myRole==="teacher");var sid=vd.student_id||"";
document.getElementById("welcome").textContent=displayName||"欢迎回来";
var h=new Date().getHours();var greet="";
if(h<6)greet="夜深了,还在用功";
else if(h<9)greet="早上好,今天又起了一个大早";
else if(h<12)greet="上午好";
else if(h<14)greet="中午好,记得吃饭";
else if(h<18)greet="下午好";
else if(h<21)greet="晚上好";
else greet="夜深了,早点休息";
document.getElementById("greeting").textContent=greet;
var ps=[{n:"薪火相传",b:document.getElementById("btn_xh"),d:"xinhuo"},{n:"19期",b:document.getElementById("btn_19"),d:"19qi"},{n:"18期",b:document.getElementById("btn_18"),d:"18qi"},{n:"20期",b:document.getElementById("btn_20"),d:"20qi"}];
ps.forEach(function(p){
var mg=null;
if(vd.class_groups){for(var j=0;j<vd.class_groups.length;j++){if(vd.class_groups[j].period===p.n){mg=vd.class_groups[j];break;}}}
if(!mg&&p.n==="薪火相传"&&vd.group){mg={group:vd.group,period:"薪火相传",role:"组员"};}
if(isTeacher){p.b.classList.remove("hidden");}
else if(p.n==="薪火相传"&&(sid.indexOf("N")===0||sid.indexOf("0")===0)){p.b.classList.remove("hidden");p.b.href="/xinhuo/index.html";}
else if(mg){p.b.classList.remove("hidden");var g=(mg.group||"").replace("组","").trim();if(g.length===1)g="0"+g;if(/^\d+$/.test(g)){p.b.href="/"+p.d+"/"+"No"+parseInt(g,10)+".html";}else{p.b.href="/"+p.d+"/free-group.html";}}
else{p.b.classList.add("hidden");}
});
var hwBtn=document.getElementById("btn_hw");
if(hwBtn){
var is20=false;
if(vd.class_groups){for(var j=0;j<vd.class_groups.length;j++){if(vd.class_groups[j].period==="20期"){is20=true;break;}}}
if(isTeacher||is20){hwBtn.classList.remove("hidden");}
}
var assistBtn=document.getElementById("btn_assistant");
if(assistBtn){
if(isTeacher){assistBtn.classList.remove("hidden");}
else{assistBtn.classList.add("hidden");}
}
var bt=document.getElementById("btn_teacher");
if(bt){
if(isTeacher||sid.startsWith("N")||sid.startsWith("0")){bt.href="/teacher/";}
else{bt.href="/xinhuo/upgrade-info.html";}
}
// 功能演示:仅自由学习组/周计划组成员可见(自由学习组含薪火相传与小班课)
var demoBtn=document.getElementById("btn_demo");
if(demoBtn){
var isFreeOrPlan=false;
if(vd.class_groups){for(var j=0;j<vd.class_groups.length;j++){
var gg=(vd.class_groups[j].group||"");
if(gg==="自由学习组"||gg==="自由"||gg==="周计划"){isFreeOrPlan=true;break;}
}}
if(isTeacher||isFreeOrPlan){demoBtn.classList.remove("hidden");}
else{demoBtn.classList.add("hidden");}
}
// 学员360:老师进索引,学生直接进自己的报告/吸引力页
var b360=document.getElementById("btn_360");
if(b360){
if(isTeacher){b360.href="/xinhuo360/index.html";}
else{
fetch("/xinhuo360/_user360_versions.json?t="+Date.now(),{cache:"no-store"})
.then(function(r){return r.json();})
.then(function(d){
var ks=Object.keys(d.versions||{});
for(var i=0;i<ks.length;i++){if(ks[i].indexOf(sid+"-")===0){b360.href="/xinhuo360/"+ks[i]+"?t="+Date.now();return;}}
}).catch(function(){});
}
}
// Random encouragement
var periods = ["xinhuo","19qi","18qi","20qi"];
var rp = periods[Math.floor(Math.random()*periods.length)];
var encUrl = "";
var grp = vd.group || "";
if (grp === "自由学习组" || grp === "周计划") {
encUrl = "/" + rp + "/_encouragements_free.json?t=" + Date.now();
} else if (isTeacher) {
var rg = ("0"+Math.floor(Math.random()*33+1)).slice(-2);
encUrl = "/xinhuo/reports/" + rg + "组/_encouragements.json?t=" + Date.now();
} else {
var ownG = (grp||"").replace("组","").trim();
if (ownG && /^\d+$/.test(ownG)) {
if (ownG.length===1) ownG="0"+ownG;
encUrl = "/" + rp + "/reports/" + ownG + "组/_encouragements.json?t=" + Date.now();
}
}
if (encUrl) {
fetch(encUrl).then(function(r){return r.ok?r.json():null}).then(function(d){
if(!d) return;
var pool = (d.team||[]).slice();
if (d.intro) pool = pool.concat(d.intro);
var myMsgs = (d.members||{})[myName] || (d.members||{})[sid] || [];
pool = pool.concat(myMsgs);
if (pool.length) {
var msg = pool[Math.floor(Math.random()*pool.length)];
var memberNames = Object.keys(d.members || {});
for (var ni = 0; ni < memberNames.length; ni++) {
if (msg.indexOf(memberNames[ni]) >= 0) { msg = '有同学说:' + msg; break; }
}
document.getElementById("encLine").textContent = msg;
}
}).catch(function(){});
}
document.getElementById("logoutBtn").onclick=function(e){e.preventDefault();window.location.href="/api/auth/logout?redirect=/auth/login.html";};
}catch(e){window.location.href="/auth/login.html";}
})();
</script>
</body>
</html>