Files
ClassFeedback/.claude/templates/课评汇总网页.html

1353 lines
51 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">
<!-- DYNAMIC: 页面标题 —— 格式第X周[星期时间][课程编号]班课评 -->
<title>第X周[星期时间][课程编号]班课评</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);
}
.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;
}
.feedback-content[contenteditable="true"] {
outline: none;
border: 2px dashed transparent;
border-radius: 10px;
padding: 10px;
margin: -10px;
transition: all 0.2s ease;
}
.feedback-content[contenteditable="true"]:hover {
border-color: var(--border);
background: var(--paper);
}
.feedback-content[contenteditable="true"]:focus {
border-color: var(--coral);
background: rgba(232, 93, 76, 0.03);
}
.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-rating .star.half-filled {
background: linear-gradient(90deg, var(--amber) 50%, #ddd5cc 50%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.star-rating.editable .star {
cursor: pointer;
}
.star-rating.editable .star:hover,
.star-rating.editable .star.hovered {
color: var(--amber-light);
transform: scale(1.15);
}
.star-rating.editable .star.filled:hover,
.star-rating.editable .star.filled.hovered {
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;
}
.metric-comment:empty::before {
content: '点击编辑评语...';
color: #bbb;
}
.metric-comment[contenteditable="true"] {
border-color: var(--coral-light);
background: rgba(232, 93, 76, 0.03);
outline: none;
}
.metric-comment[contenteditable="true"]:focus {
border-color: var(--coral);
}
/* ===== 侧边导航按钮 ===== */
.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);
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: rgba(250, 246, 241, 0.95);
backdrop-filter: blur(16px);
border-top: 1px solid var(--border);
padding: 14px 24px;
display: flex;
justify-content: center;
gap: 12px;
z-index: 100;
}
.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);
}
.edit-mode-badge {
display: none;
position: fixed;
top: 80px;
left: 50%;
transform: translateX(-50%);
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
color: #92400e;
padding: 8px 20px;
border-radius: 20px;
font-size: 0.8em;
font-weight: 500;
z-index: 200;
border: 1px solid #fcd34d;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.edit-mode-badge.show { display: block; }
.footer {
text-align: center;
padding: 36px 20px;
color: #bbb;
font-size: 0.78em;
font-family: 'Noto Serif SC', serif;
letter-spacing: 1px;
}
/* ===== OJ 做题数据表格CSP 课程专用) ===== */
.oj-section {
margin-top: 20px;
border-radius: 12px;
border: 1px solid var(--border);
overflow: hidden;
background: var(--paper);
}
.oj-header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background: linear-gradient(135deg, rgba(232, 93, 76, 0.06) 0%, rgba(240, 168, 74, 0.04) 100%);
border-bottom: 1px solid var(--border);
font-size: 0.85em;
font-weight: 600;
color: var(--ink);
}
.oj-table {
width: 100%;
border-collapse: collapse;
font-size: 0.82em;
}
.oj-table thead th {
padding: 10px 14px;
text-align: left;
font-weight: 600;
color: var(--ink-soft);
background: var(--cream);
border-bottom: 1px solid var(--border);
font-size: 0.9em;
}
.oj-table thead th:first-child { width: 45%; }
.oj-table thead th:nth-child(2) { width: 18%; }
.oj-table thead th:nth-child(3) { width: 18%; text-align: center; }
.oj-table thead th:last-child { width: 19%; text-align: center; }
.oj-table tbody td {
padding: 10px 14px;
border-bottom: 1px solid rgba(232, 228, 222, 0.5);
color: var(--ink);
vertical-align: middle;
}
.oj-table tbody tr:last-child td { border-bottom: none; }
.oj-table tbody tr:hover td {
background: rgba(232, 93, 76, 0.02);
}
.oj-status {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 10px;
border-radius: 20px;
font-size: 0.88em;
font-weight: 500;
}
.oj-status.ac {
background: #e8f5e9;
color: #2e7d32;
}
.oj-status.wa {
background: #fff3e0;
color: #e65100;
}
.oj-status.re {
background: #fce4ec;
color: #c2185b;
}
.oj-status.tle {
background: #e3f2fd;
color: #1565c0;
}
.oj-status.pending {
background: #f5f5f5;
color: #9e9e9e;
}
.oj-status-icon {
font-size: 1.1em;
line-height: 1;
}
.oj-submit-count {
text-align: center;
font-weight: 500;
color: var(--ink-soft);
}
.oj-submit-count.highlight {
color: var(--coral);
font-weight: 600;
}
.oj-pattern {
text-align: center;
font-size: 0.9em;
color: var(--ink-soft);
}
.oj-summary {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 16px;
background: var(--cream);
border-top: 1px solid var(--border);
font-size: 0.82em;
color: var(--ink-soft);
}
.oj-summary .oj-summary-item {
display: flex;
align-items: center;
gap: 4px;
}
.oj-summary .oj-summary-num {
font-weight: 600;
color: var(--ink);
}
@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; }
.oj-table { font-size: 0.78em; }
.oj-table tbody td, .oj-table thead th { padding: 8px 10px; }
}
</style>
</head>
<body>
<!-- 顶部导航 -->
<div class="topbar">
<div>
<!-- DYNAMIC: 顶部标题 —— 格式第X周 · [课程编号]班课评 -->
<div class="topbar-title">第X周 · [课程编号]班课评</div>
<!-- DYNAMIC: 顶部副标题 —— 格式YYYY年M月D日 · 《课程名称》 · 橙子老师 -->
<div class="topbar-meta">YYYY年M月D日 · 《课程名称》 · 橙子老师</div>
</div>
<div class="topbar-actions">
<button class="btn btn-outline" onclick="toggleEdit()" id="editBtn">✏️ 编辑</button>
<button class="btn btn-outline" onclick="exportText()">📋 导出</button>
</div>
</div>
<!-- 编辑模式提示 -->
<div class="edit-mode-badge" id="editBadge">📝 编辑模式 · 点击文字即可修改</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>
<!-- DYNAMIC: 课程大标题 —— 《课程名称》 -->
<h1 class="hero-title">《课程名称》</h1>
<!-- DYNAMIC: 课程副标题 —— 课程领域 · 第X课 -->
<p class="hero-subtitle">课程领域 · 第X课</p>
<div class="hero-divider"></div>
<div class="hero-info">
<!-- DYNAMIC: 授课日期 -->
<div class="hero-info-item">
<span class="icon">📅</span>
<span>YYYY年M月D日</span>
</div>
<!-- DYNAMIC: 上课时间 -->
<div class="hero-info-item">
<span class="icon"></span>
<span>星期X XX:XX</span>
</div>
<!-- DYNAMIC: 授课老师 -->
<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">
<!-- DYNAMIC: 知识点列表 —— 5个知识点每个一条 li -->
<li><span class="goal-num">1</span><span class="goal-text">知识点1</span></li>
<li><span class="goal-num">2</span><span class="goal-text">知识点2</span></li>
<li><span class="goal-num">3</span><span class="goal-text">知识点3</span></li>
<li><span class="goal-num">4</span><span class="goal-text">知识点4</span></li>
<li><span class="goal-num">5</span><span class="goal-text">知识点5</span></li>
</ul>
</div>
<!-- 学生卡片滑动区 -->
<div class="cards-slider-wrapper">
<div class="cards-slider" id="cardsSlider">
<!--
DYNAMIC: 学生卡片模板 —— 为每个学生生成一个 .student-card
第一个学生必须有 active 类,其余没有
状态标签根据出勤正常上课→badge-normal补课→badge-makeup请假/未到→badge-absent
-->
<!-- 示例学生卡片(实际生成时复制此结构) -->
<div class="student-card active">
<div class="card-header">
<span class="student-name">学生姓名</span>
<span class="card-badge badge-normal">正常上课</span>
</div>
<div class="card-body">
<div class="feedback-content" data-student="学生姓名">课评内容(将换行符转为 &lt;br&gt;</div>
<div class="metrics">
<!-- DYNAMIC: 指标项以好的维度为主不好的最多1个默认要有星星和评语 -->
<!-- 指标项模板:星星评分 + 文字评语 -->
<div class="metric-item" data-metric="课堂专注度">
<div class="metric-label">课堂专注度</div>
<div class="star-rating editable" data-score="4">
<span class="star" data-val="1"></span>
<span class="star" data-val="2"></span>
<span class="star" data-val="3"></span>
<span class="star" data-val="4"></span>
<span class="star" data-val="5"></span>
<span class="star-score">优秀</span>
</div>
<div class="metric-comment" data-metric-comment="专注度">上课认真听讲,全程跟着老师节奏走</div>
</div>
<div class="metric-item" data-metric="知识理解">
<div class="metric-label">知识理解</div>
<div class="star-rating editable" data-score="4">
<span class="star" data-val="1"></span>
<span class="star" data-val="2"></span>
<span class="star" data-val="3"></span>
<span class="star" data-val="4"></span>
<span class="star" data-val="5"></span>
<span class="star-score">优秀</span>
</div>
<div class="metric-comment" data-metric-comment="知识理解">对核心知识点掌握较好,能举一反三</div>
</div>
<div class="metric-item" data-metric="动手实践">
<div class="metric-label">动手实践</div>
<div class="star-rating editable" data-score="4">
<span class="star" data-val="1"></span>
<span class="star" data-val="2"></span>
<span class="star" data-val="3"></span>
<span class="star" data-val="4"></span>
<span class="star" data-val="5"></span>
<span class="star-score">优秀</span>
</div>
<div class="metric-comment" data-metric-comment="动手实践">能独立完成课堂练习,动手能力很强</div>
</div>
<div class="metric-item" data-metric="互动参与">
<div class="metric-label">互动参与</div>
<div class="star-rating editable" data-score="5">
<span class="star" data-val="1"></span>
<span class="star" data-val="2"></span>
<span class="star" data-val="3"></span>
<span class="star" data-val="4"></span>
<span class="star" data-val="5"></span>
<span class="star-score">超棒</span>
</div>
<div class="metric-comment" data-metric-comment="互动参与">积极举手发言,和老师同学互动很好</div>
</div>
<div class="metric-item" data-metric="逻辑思维">
<div class="metric-label">逻辑思维</div>
<div class="star-rating editable" data-score="4">
<span class="star" data-val="1"></span>
<span class="star" data-val="2"></span>
<span class="star" data-val="3"></span>
<span class="star" data-val="4"></span>
<span class="star" data-val="5"></span>
<span class="star-score">优秀</span>
</div>
<div class="metric-comment" data-metric-comment="逻辑思维">思路清晰,遇到问题能条理分析</div>
</div>
<div class="metric-item" data-metric="创意表达">
<div class="metric-label">创意表达</div>
<div class="star-rating editable" data-score="5">
<span class="star" data-val="1"></span>
<span class="star" data-val="2"></span>
<span class="star" data-val="3"></span>
<span class="star" data-val="4"></span>
<span class="star" data-val="5"></span>
<span class="star-score">超棒</span>
</div>
<div class="metric-comment" data-metric-comment="创意表达">作品有创意,能融入自己的想法</div>
</div>
<div class="metric-item" data-metric="任务完成度">
<div class="metric-label">任务完成度</div>
<div class="star-rating editable" data-score="5">
<span class="star" data-val="1"></span>
<span class="star" data-val="2"></span>
<span class="star" data-val="3"></span>
<span class="star" data-val="4"></span>
<span class="star" data-val="5"></span>
<span class="star-score">超棒</span>
</div>
<div class="metric-comment" data-metric-comment="任务完成度">课堂任务全部完成,效率很高</div>
</div>
<div class="metric-item" data-metric="学习态度">
<div class="metric-label">学习态度</div>
<div class="star-rating editable" data-score="5">
<span class="star" data-val="1"></span>
<span class="star" data-val="2"></span>
<span class="star" data-val="3"></span>
<span class="star" data-val="4"></span>
<span class="star" data-val="5"></span>
<span class="star-score">超棒</span>
</div>
<div class="metric-comment" data-metric-comment="学习态度">态度端正,遇到困难不放弃,主动请教</div>
</div>
</div>
<!-- DYNAMIC: CSP 课程 OJ 做题数据表格 —— 仅 CSP 课程显示 -->
<div class="oj-section" data-oj-section="学生姓名">
<div class="oj-header">
<span>📊</span>
<span>OJ 做题情况</span>
</div>
<table class="oj-table">
<thead>
<tr>
<th>题目</th>
<th>状态</th>
<th style="text-align:center">提交次数</th>
<th style="text-align:center">思考模式</th>
</tr>
</thead>
<tbody>
<!-- DYNAMIC: OJ 题目数据行 —— 为每道题生成一行 -->
<tr>
<td>题目名称示例</td>
<td><span class="oj-status ac"><span class="oj-status-icon"></span>AC</span></td>
<td class="oj-submit-count">1</td>
<td class="oj-pattern">一气呵成</td>
</tr>
<tr>
<td>题目名称示例 2</td>
<td><span class="oj-status wa"><span class="oj-status-icon"></span>WA→AC</span></td>
<td class="oj-submit-count highlight">3</td>
<td class="oj-pattern">调试改进</td>
</tr>
<tr>
<td>题目名称示例 3</td>
<td><span class="oj-status pending">未完成</span></td>
<td class="oj-submit-count">0</td>
<td class="oj-pattern">-</td>
</tr>
</tbody>
</table>
<div class="oj-summary">
<div class="oj-summary-item">
<span>完成:</span><span class="oj-summary-num">2/3</span>
</div>
<div class="oj-summary-item">
<span>AC</span><span class="oj-summary-num">2</span>
</div>
<div class="oj-summary-item">
<span>总提交:</span><span class="oj-summary-num">4</span>
</div>
</div>
</div>
<!-- /DYNAMIC OJ 表格 -->
</div>
</div>
<!-- /DYNAMIC 学生卡片 -->
</div>
</div>
<!-- 导航控制 -->
<div class="nav-controls">
<span class="page-indicator" id="pageIndicator">1 / N</span>
</div>
<!-- 进度点 -->
<div class="progress-dots" id="progressDots">
<!-- DYNAMIC: 为每个学生生成一个进度点,第一个是 active -->
<div class="progress-dot active" onclick="goToCard(0)"></div>
</div>
<div class="footer">穹狼科创 · [课程编号]班 · 橙子老师</div>
</div>
<!-- 底部操作栏 -->
<div class="bottom-bar" id="bottomBar" style="display: none;">
<button class="btn btn-outline" onclick="cancelEdit()">取消</button>
<button class="btn btn-success" onclick="saveEdit()">✓ 保存修改</button>
</div>
<!-- 提示条 -->
<div class="toast" id="toast"></div>
<script>
// ===== 卡片切换 =====
let currentCard = 0;
// DYNAMIC: 学生总数
const totalCards = 1;
const allCards = document.querySelectorAll('.student-card');
const sidePrevBtn = document.getElementById('sidePrevBtn');
const sideNextBtn = document.getElementById('sideNextBtn');
const pageIndicator = document.getElementById('pageIndicator');
const progressDots = document.querySelectorAll('.progress-dot');
function updateSlider() {
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'); }
});
pageIndicator.textContent = (currentCard + 1) + ' / ' + totalCards;
sidePrevBtn.disabled = currentCard === 0;
sideNextBtn.disabled = currentCard === totalCards - 1;
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;
var sliderEl = document.getElementById('cardsSlider');
sliderEl.addEventListener('touchstart', function(e) { touchStartX = e.changedTouches[0].screenX; });
sliderEl.addEventListener('touchend', function(e) {
var dx = e.changedTouches[0].screenX;
if (touchStartX - dx > 50) nextCard();
if (dx - touchStartX > 50) prevCard();
});
updateSlider();
initStarRatings();
// ===== 学习目标展开/收起 =====
function toggleGoals() {
var list = document.getElementById('goalsList');
var btn = document.querySelector('.goals-toggle');
list.classList.toggle('collapsed');
btn.textContent = list.classList.contains('collapsed') ? '展开' : '收起';
}
// ===== 星星评分功能 =====
function initStarRatings() {
document.querySelectorAll('.star-rating').forEach(function(rating) {
var stars = rating.querySelectorAll('.star');
var scoreDisplay = rating.querySelector('.star-score');
// 自动应用默认分数(从 data-score 读取)
var defaultScore = parseInt(rating.dataset.score) || 0;
if (defaultScore > 0) {
setStarRating(rating, defaultScore);
}
stars.forEach(function(star) {
// 鼠标悬停预览
star.addEventListener('mouseenter', function() {
if (!rating.classList.contains('editable')) return;
var val = parseInt(this.dataset.val);
stars.forEach(function(s) {
var sv = parseInt(s.dataset.val);
if (sv <= val) s.classList.add('hovered');
else s.classList.remove('hovered');
});
});
// 鼠标离开恢复
star.addEventListener('mouseleave', function() {
stars.forEach(function(s) { s.classList.remove('hovered'); });
});
// 点击评分
star.addEventListener('click', function() {
if (!rating.classList.contains('editable')) return;
var val = parseInt(this.dataset.val);
var currentScore = parseInt(rating.dataset.score);
// 再次点击同分则取消
if (currentScore === val) val = 0;
setStarRating(rating, val);
});
});
});
}
function setStarRating(ratingEl, score) {
ratingEl.dataset.score = score;
var stars = ratingEl.querySelectorAll('.star');
var scoreDisplay = ratingEl.querySelector('.star-score');
stars.forEach(function(s) {
var sv = parseInt(s.dataset.val);
s.classList.remove('filled', 'half-filled');
if (sv <= score) s.classList.add('filled');
});
var labels = ['未评', '需加强', '合格', '良好', '优秀', '超棒'];
scoreDisplay.textContent = labels[score] || '未评';
}
// ===== 编辑功能 =====
var originalContent = {};
var originalMetrics = {};
var isEditMode = false;
function toggleEdit() {
isEditMode = !isEditMode;
var feedbacks = document.querySelectorAll('.feedback-content');
var metricComments = document.querySelectorAll('.metric-comment');
var starRatings = document.querySelectorAll('.star-rating');
var editBtn = document.getElementById('editBtn');
var editBadge = document.getElementById('editBadge');
var bottomBar = document.getElementById('bottomBar');
if (isEditMode) {
// 保存原始状态
feedbacks.forEach(function(el) { originalContent[el.dataset.student] = el.innerHTML; });
metricComments.forEach(function(el) {
var key = el.closest('.student-card').querySelector('.student-name').textContent + '-' + el.dataset.metricComment;
originalMetrics[key] = { text: el.innerHTML, score: 0 };
});
starRatings.forEach(function(el) {
var key = el.closest('.student-card').querySelector('.student-name').textContent + '-' + el.closest('.metric-item').dataset.metric;
if (originalMetrics[key]) originalMetrics[key].score = el.dataset.score;
});
// 开启编辑
feedbacks.forEach(function(el) { el.contentEditable = true; });
metricComments.forEach(function(el) { el.contentEditable = true; });
starRatings.forEach(function(el) { el.classList.add('editable'); });
editBtn.textContent = '✏️ 退出编辑';
editBadge.classList.add('show');
bottomBar.style.display = 'flex';
showToast('编辑模式已开启,点击文字或星星即可修改');
} else {
feedbacks.forEach(function(el) { el.contentEditable = false; });
metricComments.forEach(function(el) { el.contentEditable = false; });
starRatings.forEach(function(el) { el.classList.remove('editable'); });
editBtn.textContent = '✏️ 编辑';
editBadge.classList.remove('show');
bottomBar.style.display = 'none';
}
}
function saveEdit() {
isEditMode = false;
document.querySelectorAll('.feedback-content').forEach(function(el) { el.contentEditable = false; });
document.querySelectorAll('.metric-comment').forEach(function(el) { el.contentEditable = false; });
document.querySelectorAll('.star-rating').forEach(function(el) { el.classList.remove('editable'); });
document.getElementById('editBtn').textContent = '✏️ 编辑';
document.getElementById('editBadge').classList.remove('show');
document.getElementById('bottomBar').style.display = 'none';
originalContent = {};
originalMetrics = {};
showToast('✓ 修改已保存');
}
function cancelEdit() {
isEditMode = false;
document.querySelectorAll('.feedback-content').forEach(function(el) {
if (originalContent[el.dataset.student]) el.innerHTML = originalContent[el.dataset.student];
el.contentEditable = false;
});
// 恢复指标评语
document.querySelectorAll('.metric-comment').forEach(function(el) {
var key = el.closest('.student-card').querySelector('.student-name').textContent + '-' + el.dataset.metricComment;
if (originalMetrics[key]) el.innerHTML = originalMetrics[key].text;
el.contentEditable = false;
});
// 恢复星星评分
document.querySelectorAll('.star-rating').forEach(function(el) {
var key = el.closest('.student-card').querySelector('.student-name').textContent + '-' + el.closest('.metric-item').dataset.metric;
if (originalMetrics[key]) setStarRating(el, parseInt(originalMetrics[key].score));
el.classList.remove('editable');
});
document.getElementById('editBtn').textContent = '✏️ 编辑';
document.getElementById('editBadge').classList.remove('show');
document.getElementById('bottomBar').style.display = 'none';
originalContent = {};
originalMetrics = {};
showToast('已恢复原始内容');
}
// ===== 导出纯文本 =====
function exportText() {
// DYNAMIC: 导出内容中的课程信息
var text = '第X周[星期时间][课程编号]班课评\n';
text += 'YYYY年M月D日 · 《课程名称》 · 橙子老师\n';
text += '═══════════════════════════════════════\n\n';
text += '🎯 本周知识点\n';
text += '─────────────────────────────────────\n';
// DYNAMIC: 列出5个知识点
text += '1. 知识点1\n2. 知识点2\n3. 知识点3\n4. 知识点4\n5. 知识点5\n\n';
text += '═══════════════════════════════════════\n\n';
document.querySelectorAll('.student-card').forEach(function(card) {
var name = card.querySelector('.student-name').textContent;
var badge = card.querySelector('.card-badge').textContent;
var feedback = card.querySelector('.feedback-content').innerText;
text += '【' + name + '】' + badge + '\n';
text += '─────────────────────────────────\n';
text += feedback + '\n\n';
// 导出星星评分和评语
card.querySelectorAll('.metric-item').forEach(function(m) {
var label = m.querySelector('.metric-label').textContent;
var rating = m.querySelector('.star-rating');
var score = parseInt(rating.dataset.score);
var scoreLabels = ['未评', '⭐', '⭐⭐', '⭐⭐⭐', '⭐⭐⭐⭐', '⭐⭐⭐⭐⭐'];
var stars = scoreLabels[score] || '未评';
var comment = m.querySelector('.metric-comment').innerText.trim();
if (comment === '点击编辑评语...') comment = '';
text += ' ' + label + '' + stars;
if (comment) text += '' + comment + '';
text += '\n';
});
text += '\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 showToast(msg) {
var toast = document.getElementById('toast');
toast.textContent = msg;
toast.classList.add('show');
setTimeout(function() { toast.classList.remove('show'); }, 2000);
}
</script>
</body>
</html>