Files
ClassFeedback/.claude/templates/课评网页模板.html

470 lines
27 KiB
HTML
Raw 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">
<title>{{第N周}} · {{班级简称}}课评</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700&family=Noto+Sans+SC:wght@300;400;500;600&display=swap');
:root {
--coral: #e85d4c;
--coral-light: #f5a99e;
--coral-dark: #c44738;
--amber: #f0a84a;
--amber-light: #f5d08a;
--ink: #2c2420;
--ink-soft: #5a524e;
--paper: #faf6f1;
--paper-warm: #f5efe8;
--cream: #fffdf9;
--border: #e8e0d8;
--shadow-soft: rgba(44, 36, 32, 0.06);
--shadow-medium: rgba(44, 36, 32, 0.10);
--shadow-card: rgba(44, 36, 32, 0.08);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--paper);
min-height: 100vh;
color: var(--ink);
background-image:
radial-gradient(circle at 20% 50%, rgba(240, 168, 74, 0.03) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(232, 93, 76, 0.02) 0%, transparent 50%),
radial-gradient(circle at 50% 80%, rgba(240, 168, 74, 0.02) 0%, transparent 40%);
}
.topbar {
position: sticky; top: 0; z-index: 100;
background: rgba(250, 246, 241, 0.92); backdrop-filter: blur(16px);
border-bottom: 1px solid var(--border);
padding: 14px 28px; display: flex; justify-content: space-between; align-items: center;
}
.topbar-title { font-family: 'Noto Serif SC', serif; font-size: 1.05em; font-weight: 600; color: var(--ink); letter-spacing: 0.5px; }
.topbar-meta { font-size: 0.8em; color: var(--ink-soft); margin-top: 2px; }
.topbar-actions { display: flex; gap: 10px; }
.btn { padding: 8px 18px; border-radius: 10px; font-size: 0.85em; font-weight: 500; cursor: pointer; transition: all 0.25s ease; border: none; font-family: inherit; }
.btn-outline { background: transparent; border: 1.5px solid var(--border); color: var(--ink-soft); }
.btn-outline:hover { border-color: var(--coral); color: var(--coral); background: rgba(232, 93, 76, 0.05); }
.btn-success { background: var(--coral); color: white; }
.btn-success:hover { background: var(--coral-dark); }
.btn-success:disabled { opacity: 0.5; cursor: not-allowed; }
.main { max-width: 680px; margin: 0 auto; padding: 28px 20px 160px; position: relative; }
.course-hero {
background: linear-gradient(135deg, var(--cream) 0%, var(--paper-warm) 100%);
border-radius: 20px; padding: 36px 32px 32px; margin-bottom: 24px;
border: 1px solid var(--border); position: relative; overflow: hidden;
box-shadow: 0 2px 8px var(--shadow-soft), 0 8px 32px var(--shadow-card);
}
.course-hero::before { content: ''; position: absolute; top: -60px; right: -40px; width: 180px; height: 180px; border-radius: 50%; background: radial-gradient(circle, rgba(232, 93, 76, 0.08) 0%, transparent 70%); }
.course-hero::after { content: ''; position: absolute; bottom: -40px; left: -30px; width: 140px; height: 140px; border-radius: 50%; background: radial-gradient(circle, rgba(240, 168, 74, 0.06) 0%, transparent 70%); }
.hero-badge { display: inline-flex; align-items: center; gap: 6px; background: linear-gradient(135deg, var(--coral) 0%, var(--amber) 100%); color: white; font-size: 0.75em; font-weight: 600; padding: 6px 16px; border-radius: 20px; margin-bottom: 16px; position: relative; z-index: 1; letter-spacing: 0.5px; }
.hero-title { font-family: 'Noto Serif SC', serif; font-size: 2.2em; font-weight: 700; color: var(--ink); line-height: 1.3; margin-bottom: 8px; position: relative; z-index: 1; letter-spacing: 2px; }
.hero-subtitle { font-size: 0.95em; color: var(--ink-soft); margin-bottom: 20px; position: relative; z-index: 1; }
.hero-divider { width: 60px; height: 3px; background: linear-gradient(90deg, var(--coral), var(--amber)); border-radius: 2px; margin-bottom: 20px; position: relative; z-index: 1; }
.hero-info { display: flex; flex-wrap: wrap; gap: 16px; position: relative; z-index: 1; }
.hero-info-item { display: flex; align-items: center; gap: 6px; font-size: 0.85em; color: var(--ink-soft); }
.hero-info-item .icon { font-size: 1.1em; }
.goals-section { background: var(--cream); border-radius: 16px; padding: 24px; margin-bottom: 28px; border: 1px solid var(--border); box-shadow: 0 2px 12px var(--shadow-soft); }
.goals-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px; }
.goals-title { font-family: 'Noto Serif SC', serif; font-size: 1em; font-weight: 600; color: var(--ink); display: flex; align-items: center; gap: 8px; }
.goals-title .title-icon { font-size: 1.2em; }
.goals-toggle { font-size: 0.78em; color: var(--ink-soft); cursor: pointer; background: var(--paper-warm); border: 1px solid var(--border); font-family: inherit; padding: 5px 12px; border-radius: 8px; transition: all 0.2s ease; }
.goals-toggle:hover { background: var(--coral); color: white; border-color: var(--coral); }
.goals-list { list-style: none; display: block; }
.goals-list.collapsed { display: none; }
.goals-list li { padding: 12px 16px; background: var(--paper); border-radius: 10px; margin-bottom: 10px; font-size: 0.9em; color: var(--ink-soft); display: flex; align-items: flex-start; gap: 12px; border-left: 3px solid transparent; transition: all 0.2s ease; }
.goals-list li:hover { border-left-color: var(--coral); background: var(--cream); transform: translateX(4px); }
.goals-list li:last-child { margin-bottom: 0; }
.goal-num { background: linear-gradient(135deg, var(--coral) 0%, var(--amber) 100%); color: white; width: 26px; height: 26px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 0.78em; font-weight: 600; flex-shrink: 0; margin-top: 1px; }
.goal-text { flex: 1; line-height: 1.6; }
.cards-slider-wrapper { position: relative; border-radius: 16px; margin-bottom: 20px; overflow: hidden; }
.cards-slider { position: relative; min-height: 300px; }
.student-card {
background: var(--cream); border-radius: 16px; border: 1px solid var(--border);
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
position: absolute; width: 100%; top: 0; left: 0;
opacity: 0; transform: translateX(60px) scale(0.96); pointer-events: none;
box-shadow: 0 4px 20px var(--shadow-card);
}
.student-card.active { opacity: 1; transform: translateX(0) scale(1); pointer-events: auto; position: relative; }
.student-card.prev { transform: translateX(-60px) scale(0.96); opacity: 0; }
.student-card:hover { box-shadow: 0 8px 32px var(--shadow-medium); }
.card-header { padding: 18px 24px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); background: linear-gradient(135deg, rgba(250, 246, 241, 0.5) 0%, rgba(245, 239, 232, 0.5) 100%); border-radius: 16px 16px 0 0; }
.student-name { font-family: 'Noto Serif SC', serif; font-size: 1.15em; font-weight: 600; color: var(--ink); letter-spacing: 1px; }
.card-badge { font-size: 0.75em; padding: 5px 12px; border-radius: 20px; font-weight: 500; }
.badge-normal { background: #e8f5e9; color: #2e7d32; }
.badge-makeup { background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); color: #e65100; }
.badge-absent { background: #fce4ec; color: #c2185b; }
.card-body { padding: 24px; }
.feedback-content { font-size: 0.92em; line-height: 2; color: var(--ink-soft); margin-bottom: 20px; }
.metrics { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.metric-item { padding: 14px; background: var(--paper); border-radius: 10px; border: 1px solid transparent; transition: all 0.2s ease; }
.metric-item:hover { border-color: var(--border); background: var(--cream); }
.metric-label { font-size: 0.72em; color: #999; margin-bottom: 5px; text-transform: uppercase; letter-spacing: 0.5px; }
.metric-value { font-size: 0.88em; font-weight: 500; color: var(--ink); }
.metric-stars { color: var(--amber); letter-spacing: 1px; }
.star-rating { display: flex; align-items: center; gap: 2px; margin-top: 6px; }
.star-rating .star { font-size: 1.3em; color: #ddd5cc; cursor: default; transition: all 0.15s ease; user-select: none; line-height: 1; }
.star-rating .star.filled { color: var(--amber); }
.star-score { font-size: 0.78em; color: var(--ink-soft); margin-left: 6px; font-weight: 500; min-width: 28px; }
.metric-comment { margin-top: 8px; padding: 8px 10px; background: var(--paper); border-radius: 8px; font-size: 0.82em; color: var(--ink-soft); line-height: 1.5; border: 1px solid transparent; min-height: 32px; }
.side-nav-btn {
position: fixed; top: 50%; transform: translateY(-50%); z-index: 90;
width: 52px; height: 80px; border: none;
background: rgba(250, 246, 241, 0.85); backdrop-filter: blur(12px);
cursor: pointer; display: flex; align-items: center; justify-content: center;
font-size: 1.4em; color: var(--ink-soft); transition: all 0.3s ease;
box-shadow: 0 4px 16px var(--shadow-medium);
}
.side-nav-btn:hover:not(:disabled) { background: var(--cream); color: var(--coral); width: 60px; box-shadow: 0 6px 24px rgba(232, 93, 76, 0.15); }
.side-nav-btn:disabled { opacity: 0.25; cursor: not-allowed; }
.side-nav-btn.prev-btn { left: 0; border-radius: 0 12px 12px 0; border-right: 1px solid var(--border); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); padding-right: 4px; }
.side-nav-btn.next-btn { right: 0; border-radius: 12px 0 0 12px; border-left: 1px solid var(--border); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); padding-left: 4px; }
.side-nav-btn .arrow { transition: transform 0.3s ease; }
.side-nav-btn:hover .arrow.prev-arrow { transform: translateX(-3px); }
.side-nav-btn:hover .arrow.next-arrow { transform: translateX(3px); }
.nav-controls { display: flex; justify-content: center; align-items: center; gap: 20px; margin-bottom: 16px; }
.page-indicator { font-family: 'Noto Serif SC', serif; font-size: 0.95em; color: var(--ink-soft); font-weight: 500; min-width: 70px; text-align: center; letter-spacing: 1px; }
.progress-dots { display: flex; justify-content: center; gap: 10px; margin-top: 4px; }
.progress-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--border); cursor: pointer; transition: all 0.3s ease; position: relative; }
.progress-dot.active { background: var(--coral); width: 28px; border-radius: 4px; }
.progress-dot:hover:not(.active) { background: var(--coral-light); transform: scale(1.3); }
.toast { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%) translateY(20px); background: var(--ink); color: white; padding: 12px 28px; border-radius: 10px; font-size: 0.85em; opacity: 0; transition: all 0.3s ease; pointer-events: none; z-index: 200; box-shadow: 0 4px 16px rgba(0,0,0,0.15); }
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.footer { text-align: center; padding: 36px 20px; color: #bbb; font-size: 0.78em; font-family: 'Noto Serif SC', serif; letter-spacing: 1px; }
@media (max-width: 768px) {
.hero-title { font-size: 1.7em; }
.side-nav-btn { width: 40px; height: 60px; font-size: 1.1em; }
.main { padding: 20px 16px 160px; }
.course-hero { padding: 28px 20px 24px; }
}
</style>
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
</head>
<body>
<!-- ========== 顶栏 ========== -->
<div class="topbar">
<div>
<div class="topbar-title">{{第N周}} · {{班级简称}}班课评</div>
<div class="topbar-meta">{{日期中文}} · 《{{课程名称}}》 · {{老师}}老师</div>
</div>
<div class="topbar-actions">
<button class="btn btn-outline" onclick="exportText()">📋 导出</button>
<button class="btn btn-success" id="screenshotBtn" onclick="downloadAllScreenshots()">📸 全部下载</button>
</div>
</div>
<!-- ========== 左右切换按钮 ========== -->
<button class="side-nav-btn prev-btn" id="sidePrevBtn" onclick="prevCard()" title="上一张"><span class="arrow prev-arrow"></span></button>
<button class="side-nav-btn next-btn" id="sideNextBtn" onclick="nextCard()" title="下一张"><span class="arrow next-arrow"></span></button>
<!-- ========== 主内容区 ========== -->
<div class="main">
<!-- 课程头部 -->
<div class="course-hero">
<div class="hero-badge">📚 本周课程</div>
<h1 class="hero-title">《{{课程名称}}》</h1>
<p class="hero-subtitle">{{课程副标题}} · 第{{课次}}课</p>
<div class="hero-divider"></div>
<div class="hero-info">
<div class="hero-info-item"><span class="icon">📅</span><span>{{日期中文}}</span></div>
<div class="hero-info-item"><span class="icon"></span><span>{{上课时间}}</span></div>
<div class="hero-info-item"><span class="icon">👩‍🏫</span><span>{{老师}}老师</span></div>
</div>
</div>
<!-- 知识点 -->
<div class="goals-section">
<div class="goals-header">
<span class="goals-title"><span class="title-icon">🎯</span>本周知识点</span>
<button class="goals-toggle" onclick="toggleGoals()">收起</button>
</div>
<ul class="goals-list" id="goalsList">
{{知识点列表}}
<!-- 示例格式:
<li><span class="goal-num">1</span><span class="goal-text">知识点内容</span></li>
-->
</ul>
</div>
<!-- 学生卡片区(由 JS 动态生成) -->
<div class="cards-slider-wrapper">
<div class="cards-slider" id="cardsSlider"></div>
</div>
<!-- 页码指示器 -->
<div class="nav-controls"><span class="page-indicator" id="pageIndicator"></span></div>
<div class="progress-dots" id="progressDots"></div>
<div class="footer">{{底部文案}}</div>
</div>
<div class="toast" id="toast"></div>
<script>
// ============================================================
// 📝 配置区:填写以下数据即可生成课评网页
// ============================================================
// 页面元信息
var PAGE = {
weekLabel: '{{第N周}}', // 如 "第17周"
classShort: '{{班级简称}}', // 如 "K4"
courseName: '{{课程名称}}', // 如 "成语接龙"
courseSubtitle: '{{课程副标题}}', // 如 "列表 · 插入与查找元素"
lessonNum: '{{课次}}', // 如 "17"
dateCN: '{{日期中文}}', // 如 "2026年6月26日"
dateISO: '{{日期ISO}}', // 如 "2026-06-26"
timeSlot: '{{上课时间}}', // 如 "周五 19:00-20:30"
teacher: '{{老师}}', // 如 "橙子"
footerText: '{{底部文案}}' // 如 "穹狼科创 · K4班 · 橙子老师"
};
// 知识点列表(数组,每项一条)
var GOALS = [
// 示例:
// '理解词语接龙的游戏规则:下一个词的首字必须等于上一个词的末字',
// '学习使用变量"旧词语"和列表"词语表"存储和管理接龙数据',
];
// 学生数据(数组,每项一个学生)
// badge: 'normal' | 'makeup' | 'absent'
// metrics: [{ label, score(1-5), comment }, ...]
var STUDENTS = [
// 示例:
// {
// name: '陈圆振',
// badge: 'normal',
// badgeText: '正常上课',
// feedback: '圆振这节课表现很积极~...',
// metrics: [
// { label: '课堂互动', score: 5, comment: '积极回答问题' },
// { label: '知识理解', score: 5, comment: '理解透彻' },
// ]
// },
];
// 截图下载的文件名格式:{姓名}-第{课次}课{课程名称}.png
function getScreenshotFilename(studentName) {
return studentName + '-第' + PAGE.lessonNum + '课' + PAGE.courseName + '.png';
}
// ============================================================
// 🔧 以下为渲染引擎,一般无需修改
// ============================================================
var totalCards = STUDENTS.length;
var currentCard = 0;
// 渲染知识点
(function renderGoals() {
var html = '';
GOALS.forEach(function(g, i) {
html += '<li><span class="goal-num">' + (i + 1) + '</span><span class="goal-text">' + g + '</span></li>';
});
document.getElementById('goalsList').innerHTML = html;
})();
// 渲染学生卡片
(function renderCards() {
var badgeClassMap = { normal: 'badge-normal', makeup: 'badge-makeup', absent: 'badge-absent' };
var scoreLabels = ['未评', '需加强', '合格', '良好', '优秀', '超棒'];
var cardsHtml = '';
STUDENTS.forEach(function(s, idx) {
var isActive = idx === 0 ? ' active' : '';
cardsHtml += '<div class="student-card' + isActive + '" data-index="' + idx + '">';
cardsHtml += '<div class="card-header"><span class="student-name">' + s.name + '</span><span class="card-badge ' + (badgeClassMap[s.badge] || 'badge-normal') + '">' + s.badgeText + '</span></div>';
cardsHtml += '<div class="card-body">';
cardsHtml += '<div class="feedback-content" data-student="' + s.name + '">' + s.feedback + '</div>';
cardsHtml += '<div class="metrics">';
s.metrics.forEach(function(m) {
var starsHtml = '';
for (var si = 1; si <= 5; si++) {
starsHtml += '<span class="star' + (si <= m.score ? ' filled' : '') + '" data-val="' + si + '">★</span>';
}
cardsHtml += '<div class="metric-item" data-metric="' + m.label + '">';
cardsHtml += '<div class="metric-label">' + m.label + '</div>';
cardsHtml += '<div class="star-rating" data-score="' + m.score + '">' + starsHtml + '<span class="star-score">' + (scoreLabels[m.score] || '未评') + '</span></div>';
cardsHtml += '<div class="metric-comment" data-metric-comment="' + m.label + '">' + m.comment + '</div>';
cardsHtml += '</div>';
});
cardsHtml += '</div></div></div>';
});
document.getElementById('cardsSlider').innerHTML = cardsHtml;
})();
// 渲染进度点
(function renderDots() {
var dotsHtml = '';
for (var i = 0; i < totalCards; i++) {
dotsHtml += '<div class="progress-dot' + (i === 0 ? ' active' : '') + '" onclick="goToCard(' + i + ')"></div>';
}
document.getElementById('progressDots').innerHTML = dotsHtml;
document.getElementById('pageIndicator').textContent = '1 / ' + totalCards;
})();
// 卡片切换
var allCards, sidePrevBtn, sideNextBtn, pageIndicatorEl, progressDots;
function initSliderRefs() {
allCards = document.querySelectorAll('.student-card');
sidePrevBtn = document.getElementById('sidePrevBtn');
sideNextBtn = document.getElementById('sideNextBtn');
pageIndicatorEl = document.getElementById('pageIndicator');
progressDots = document.querySelectorAll('.progress-dot');
updateSlider();
}
function updateSlider() {
if (!allCards) return;
allCards.forEach(function(card, i) {
card.classList.remove('active', 'prev');
if (i === currentCard) card.classList.add('active');
else if (i < currentCard) card.classList.add('prev');
});
if (pageIndicatorEl) pageIndicatorEl.textContent = (currentCard + 1) + ' / ' + totalCards;
if (sidePrevBtn) sidePrevBtn.disabled = currentCard === 0;
if (sideNextBtn) sideNextBtn.disabled = currentCard === totalCards - 1;
if (progressDots) progressDots.forEach(function(dot, i) { dot.classList.toggle('active', i === currentCard); });
}
function nextCard() { if (currentCard < totalCards - 1) { currentCard++; updateSlider(); } }
function prevCard() { if (currentCard > 0) { currentCard--; updateSlider(); } }
function goToCard(i) { currentCard = i; updateSlider(); }
document.addEventListener('keydown', function(e) {
if (e.key === 'ArrowLeft') prevCard();
if (e.key === 'ArrowRight') nextCard();
});
var touchStartX = 0;
document.getElementById('cardsSlider').addEventListener('touchstart', function(e) { touchStartX = e.changedTouches[0].screenX; });
document.getElementById('cardsSlider').addEventListener('touchend', function(e) {
var dx = e.changedTouches[0].screenX;
if (touchStartX - dx > 50) nextCard();
if (dx - touchStartX > 50) prevCard();
});
initSliderRefs();
// 知识点展开/收起
function toggleGoals() {
var list = document.getElementById('goalsList');
var btn = document.querySelector('.goals-toggle');
list.classList.toggle('collapsed');
btn.textContent = list.classList.contains('collapsed') ? '展开' : '收起';
}
// Toast 提示
function showToast(msg) {
var toast = document.getElementById('toast');
toast.textContent = msg;
toast.classList.add('show');
setTimeout(function() { toast.classList.remove('show'); }, 2000);
}
// 导出文本
function exportText() {
var sep = '═══════════════════════════════════════';
var text = PAGE.weekLabel + PAGE.classShort + '班课评\n';
text += PAGE.dateCN + ' · 《' + PAGE.courseName + '》 · ' + PAGE.teacher + '老师\n';
text += sep + '\n\n';
text += '🎯 本周知识点\n';
text += '─────────────────────────────────────\n';
GOALS.forEach(function(g, i) { text += (i + 1) + '. ' + g + '\n'; });
text += '\n' + sep + '\n\n';
STUDENTS.forEach(function(s) {
text += '【' + s.name + '】' + s.badgeText + '\n';
text += '─────────────────────────────────\n';
text += s.feedback + '\n\n';
});
navigator.clipboard.writeText(text).then(function() {
showToast('✓ 已复制到剪贴板,可直接粘贴发送');
}).catch(function() {
var ta = document.createElement('textarea');
ta.value = text; document.body.appendChild(ta); ta.select();
document.execCommand('copy'); document.body.removeChild(ta);
showToast('✓ 已复制到剪贴板');
});
}
// 📸 批量截图下载
function downloadAllScreenshots() {
var btn = document.getElementById('screenshotBtn');
var originalText = btn.textContent;
btn.disabled = true;
btn.textContent = '⏳ 截图准备中...';
var savedCard = currentCard;
var goalsEl = document.getElementById('goalsList');
var goalsCollapsed = goalsEl.classList.contains('collapsed');
var sidePrev = document.getElementById('sidePrevBtn');
var sideNext = document.getElementById('sideNextBtn');
var mainEl = document.querySelector('.main');
if (goalsCollapsed) goalsEl.classList.remove('collapsed');
function captureOne(i) {
if (i >= totalCards) {
goToCard(savedCard);
if (goalsCollapsed) goalsEl.classList.add('collapsed');
btn.disabled = false;
btn.textContent = originalText;
sidePrev.style.display = '';
sideNext.style.display = '';
mainEl.style.paddingBottom = '';
showToast('✓ ' + totalCards + '张截图已下载');
return;
}
btn.textContent = '⏳ 截图 ' + (i + 1) + '/' + totalCards;
goToCard(i);
sidePrev.style.display = 'none';
sideNext.style.display = 'none';
mainEl.style.paddingBottom = '40px';
setTimeout(function() {
html2canvas(mainEl, {
backgroundColor: '#faf6f1',
scale: 2,
useCORS: true
}).then(function(canvas) {
var link = document.createElement('a');
link.download = getScreenshotFilename(STUDENTS[i].name);
link.href = canvas.toDataURL('image/png');
link.click();
captureOne(i + 1);
}).catch(function() {
captureOne(i + 1);
});
}, 450);
}
captureOne(0);
}
</script>
</body>
</html>