1412 lines
61 KiB
HTML
1412 lines
61 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>小Q老师 - 课评生成系统 v6.0</title>
|
||
<style>
|
||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap');
|
||
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
||
:root {
|
||
--primary: #2d5a3d;
|
||
--primary-dark: #1a3a2a;
|
||
--accent: #4a8c5c;
|
||
--bg: #fdf6ee;
|
||
--card: #ffffff;
|
||
--text: #1a3a2a;
|
||
--muted: #666;
|
||
--border: #e8ddd0;
|
||
--danger: #e74c3c;
|
||
--warning: #f39c12;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Noto Sans SC', sans-serif;
|
||
background: linear-gradient(135deg, var(--bg) 0%, #f5e6d3 100%);
|
||
min-height: 100vh;
|
||
padding: 12px;
|
||
color: var(--text);
|
||
}
|
||
|
||
.container { max-width: 1300px; margin: 0 auto; }
|
||
|
||
.header {
|
||
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
|
||
border-radius: 16px;
|
||
padding: 20px 24px;
|
||
color: white;
|
||
margin-bottom: 12px;
|
||
box-shadow: 0 6px 24px rgba(26,58,42,0.25);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.header h1 { font-size: 22px; font-weight: 900; }
|
||
.header .sub { font-size: 13px; opacity: 0.85; margin-left: auto; }
|
||
|
||
/* ==================== AI 配置面板 ==================== */
|
||
.ai-config-bar {
|
||
background: #1a1a2e;
|
||
border-radius: 12px;
|
||
padding: 12px 16px;
|
||
margin-bottom: 12px;
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||
}
|
||
|
||
.ai-config-bar label { color: #94a3b8; font-size: 12px; font-weight: 600; white-space: nowrap; }
|
||
.ai-config-bar select, .ai-config-bar input {
|
||
padding: 7px 10px;
|
||
border-radius: 8px;
|
||
border: 1px solid #333;
|
||
background: #16213e;
|
||
color: #e2e8f0;
|
||
font-size: 13px;
|
||
font-family: inherit;
|
||
}
|
||
.ai-config-bar select:focus, .ai-config-bar input:focus {
|
||
outline: none;
|
||
border-color: #6366f1;
|
||
}
|
||
.ai-config-bar input { min-width: 200px; }
|
||
.ai-config-bar input[type="password"] { min-width: 150px; }
|
||
.ai-config-bar .model-input { min-width: 140px; }
|
||
.ai-config-bar .toggle-btn {
|
||
padding: 7px 12px;
|
||
border-radius: 8px;
|
||
border: 1px solid #6366f1;
|
||
background: transparent;
|
||
color: #6366f1;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
font-family: inherit;
|
||
white-space: nowrap;
|
||
}
|
||
.ai-config-bar .toggle-btn:hover { background: #6366f120; }
|
||
.ai-config-bar .test-btn {
|
||
padding: 7px 12px;
|
||
border-radius: 8px;
|
||
border: none;
|
||
background: #6366f1;
|
||
color: white;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
font-family: inherit;
|
||
white-space: nowrap;
|
||
}
|
||
.ai-config-bar .test-btn:hover { background: #5558e6; }
|
||
.ai-config-bar .status-dot {
|
||
width: 8px; height: 8px;
|
||
border-radius: 50%;
|
||
background: #666;
|
||
flex-shrink: 0;
|
||
}
|
||
.ai-config-bar .status-dot.ok { background: #10b981; }
|
||
.ai-config-bar .status-dot.err { background: #ef4444; }
|
||
|
||
/* ==================== 快速跳转 ==================== */
|
||
.quick-jump {
|
||
background: var(--card);
|
||
border-radius: 14px;
|
||
padding: 12px 16px;
|
||
margin-bottom: 12px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
}
|
||
|
||
.quick-jump input {
|
||
flex: 1;
|
||
padding: 10px 14px;
|
||
border: 2px solid var(--border);
|
||
border-radius: 10px;
|
||
font-size: 14px;
|
||
font-family: inherit;
|
||
background: #faf8f5;
|
||
}
|
||
|
||
.quick-jump input:focus { outline: none; border-color: var(--primary); background: white; }
|
||
.quick-jump button {
|
||
padding: 10px 20px;
|
||
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
|
||
color: white;
|
||
border: none;
|
||
border-radius: 10px;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
transition: all 0.2s;
|
||
}
|
||
.quick-jump button:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(45,90,61,0.3); }
|
||
|
||
/* ==================== 主布局 ==================== */
|
||
.main-layout {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
/* ==================== 选择器面板 ==================== */
|
||
.selector-panel {
|
||
background: var(--card);
|
||
border-radius: 14px;
|
||
padding: 14px 16px;
|
||
margin-bottom: 12px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.selector-group { flex: 1; min-width: 140px; }
|
||
|
||
.selector-label {
|
||
font-size: 11px; font-weight: 700; color: var(--muted);
|
||
margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.5px;
|
||
}
|
||
|
||
.selector-select {
|
||
width: 100%;
|
||
padding: 8px 12px;
|
||
border: 2px solid var(--border);
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-family: inherit;
|
||
background: #faf8f5;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.selector-select:focus { outline: none; border-color: var(--primary); background: white; }
|
||
|
||
.date-display { font-size: 13px; color: var(--primary); font-weight: 600; padding: 8px 0; }
|
||
|
||
/* ==================== 课程面板 ==================== */
|
||
.course-panel {
|
||
background: var(--card);
|
||
border-radius: 14px;
|
||
padding: 14px 16px;
|
||
margin-bottom: 12px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||
display: none;
|
||
}
|
||
.course-panel.show { display: block; }
|
||
.course-info { display: flex; gap: 16px; flex-wrap: wrap; align-items: center; }
|
||
.course-item { flex: 1; min-width: 150px; }
|
||
.course-item-label { font-size: 10px; font-weight: 700; color: var(--muted); margin-bottom: 1px; }
|
||
.course-item-value { font-size: 15px; font-weight: 700; color: var(--primary); }
|
||
|
||
.custom-course {
|
||
border-top: 1px dashed var(--border);
|
||
margin-top: 10px; padding-top: 10px; display: none;
|
||
}
|
||
.custom-course.show { display: block; }
|
||
.custom-input {
|
||
width: 100%; padding: 8px 12px; border: 2px solid var(--border);
|
||
border-radius: 8px; font-size: 14px; font-family: inherit;
|
||
margin-bottom: 6px; background: #faf8f5;
|
||
}
|
||
.custom-input:focus { outline: none; border-color: var(--primary); background: white; }
|
||
|
||
/* ==================== 进度条 ==================== */
|
||
.progress-wrap {
|
||
background: var(--card);
|
||
border-radius: 14px;
|
||
padding: 10px 14px;
|
||
margin-bottom: 12px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.progress-track { flex:1; min-width:100px; height:6px; background:#eee; border-radius:3px; overflow:hidden; }
|
||
.progress-fill { height:100%; background:linear-gradient(90deg,var(--primary),var(--accent)); border-radius:3px; transition:width 0.3s; width:0%; }
|
||
.progress-label { font-size:13px; color:var(--muted); white-space:nowrap; }
|
||
.progress-label strong { color:var(--primary); }
|
||
|
||
/* ==================== 学生卡片 ==================== */
|
||
.student-card {
|
||
background: var(--card);
|
||
border-radius: 14px;
|
||
padding: 14px 16px;
|
||
margin-bottom: 10px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||
transition: box-shadow 0.2s;
|
||
}
|
||
.student-card.absent { opacity: 0.55; }
|
||
.student-top { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
|
||
.student-avatar {
|
||
width: 38px; height: 38px; border-radius: 50%;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 18px; flex-shrink: 0;
|
||
}
|
||
.student-name { font-size: 15px; font-weight: 800; }
|
||
.student-trait { font-size: 11px; color: var(--muted); margin-left: auto; max-width: 200px; text-align: right; }
|
||
.status-buttons { display: flex; gap: 6px; margin-bottom: 8px; }
|
||
.status-btn {
|
||
padding: 4px 12px; border-radius: 16px; font-size: 12px; font-weight: 600;
|
||
border: 2px solid var(--border); background: #f5f5f5;
|
||
cursor: pointer; transition: all 0.2s; font-family: inherit;
|
||
}
|
||
.status-btn:hover { transform: translateY(-1px); }
|
||
.status-btn.active-present { background: var(--primary); border-color: var(--primary); color: white; }
|
||
.status-btn.active-leave { background: var(--danger); border-color: var(--danger); color: white; }
|
||
|
||
.textarea {
|
||
width: 100%; min-height: 60px;
|
||
padding: 10px 12px; border-radius: 10px;
|
||
border: 2px solid var(--border);
|
||
font-size: 14px; font-family: inherit;
|
||
resize: vertical; transition: border-color 0.2s;
|
||
background: #faf8f5; line-height: 1.6;
|
||
}
|
||
.textarea:focus { outline: none; border-color: var(--primary); background: white; }
|
||
.textarea.filled { border-color: var(--accent); background: #f0faf2; }
|
||
.textarea::placeholder { color: #bbb; }
|
||
|
||
/* ==================== 速记参考 ==================== */
|
||
.shorthand {
|
||
background: var(--card); border-radius: 14px; padding: 10px 14px;
|
||
margin-bottom: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||
font-size: 11px; color: var(--muted); line-height: 1.7;
|
||
}
|
||
.shorthand code { background:#f0ece6; padding:1px 6px; border-radius:3px; font-size:11px; color:#444; }
|
||
|
||
/* ==================== 补课学生区 ==================== */
|
||
.makeup-card {
|
||
background: #fff5f5; border-radius: 14px; padding: 14px 16px;
|
||
margin-bottom: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||
border: 2px dashed var(--danger);
|
||
}
|
||
.makeup-header { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
|
||
.makeup-icon { font-size: 20px; }
|
||
.makeup-title { font-weight: 800; color: var(--danger); }
|
||
.makeup-entry { margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #ffd0d0; }
|
||
.makeup-entry:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }
|
||
.makeup-row { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
|
||
.makeup-input {
|
||
flex: 1; min-width: 100px; padding: 8px 12px;
|
||
border-radius: 8px; border: 2px solid #ffd0d0;
|
||
font-size: 13px; font-family: inherit; background: #fff8f8;
|
||
}
|
||
.makeup-input:focus { outline: none; border-color: var(--danger); background: white; }
|
||
.makeup-status-btns { display: flex; gap: 6px; }
|
||
.btn-small {
|
||
padding: 4px 10px; border-radius: 14px; font-size: 12px; font-weight: 600;
|
||
border: 2px solid var(--border); background: #f5f5f5;
|
||
cursor: pointer; font-family: inherit;
|
||
}
|
||
.btn-small.makeup { background: var(--danger); border-color: var(--danger); color: white; }
|
||
.btn-small.trial { background: var(--warning); border-color: var(--warning); color: white; }
|
||
.btn-remove { background: transparent; border-color: #ddd; color: #999; font-size: 16px; padding: 2px 8px; }
|
||
.btn-add {
|
||
width: 100%; padding: 8px;
|
||
background: white; border: 2px dashed var(--danger); color: var(--danger);
|
||
border-radius: 10px; cursor: pointer; font-family: inherit; font-weight: 700; transition: all 0.2s;
|
||
}
|
||
.btn-add:hover { background: #fff5f5; }
|
||
|
||
/* ==================== 标签库面板 ==================== */
|
||
.tag-sidebar {
|
||
flex: 0 0 300px;
|
||
background: var(--card);
|
||
border-radius: 14px;
|
||
padding: 16px 14px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||
position: sticky;
|
||
top: 12px;
|
||
max-height: calc(100vh - 24px);
|
||
overflow-y: auto;
|
||
}
|
||
.tag-sidebar h3 {
|
||
font-size: 15px; font-weight: 800; margin-bottom: 10px;
|
||
color: var(--primary); display: flex; align-items: center; gap: 6px;
|
||
}
|
||
.tag-category {
|
||
margin-bottom: 14px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px solid #f0ece6;
|
||
}
|
||
.tag-category:last-child { border-bottom: none; }
|
||
.tag-cat-header {
|
||
font-size: 13px; font-weight: 700; margin-bottom: 8px;
|
||
padding: 6px 8px; border-radius: 8px;
|
||
display: flex; align-items: center; gap: 6px;
|
||
cursor: pointer; user-select: none;
|
||
transition: background 0.15s;
|
||
}
|
||
.tag-cat-header:hover { background: #f5f5f5; }
|
||
.tag-cat-header .arrow { transition: transform 0.2s; font-size: 10px; margin-right: 2px; }
|
||
.tag-cat-header.collapsed .arrow { transform: rotate(-90deg); }
|
||
.tag-items { display: flex; flex-wrap: wrap; gap: 6px; padding-left: 4px; }
|
||
.tag-cat-header.collapsed + .tag-items { display: none; }
|
||
.tag-chip {
|
||
display: inline-block; padding: 5px 12px; border-radius: 14px;
|
||
font-size: 13px; cursor: pointer; transition: all 0.15s;
|
||
border: 2px solid transparent; white-space: nowrap;
|
||
user-select: none; line-height: 1.4;
|
||
}
|
||
.tag-chip:hover { transform: translateY(-2px); box-shadow: 0 2px 8px rgba(0,0,0,0.12); }
|
||
.tag-chip:active { transform: scale(0.95); }
|
||
.tag-chip.selected { border-color: currentColor; box-shadow: 0 2px 8px rgba(0,0,0,0.18); font-weight: 600; }
|
||
.tag-cat-creative .tag-chip { background: #ebf5ff; color: #2563eb; }
|
||
.tag-cat-creative .tag-chip.selected { background: #dbeafe; }
|
||
.tag-cat-skill .tag-chip { background: #ecfdf5; color: #059669; }
|
||
.tag-cat-skill .tag-chip.selected { background: #d1fae5; }
|
||
.tag-cat-attitude .tag-chip { background: #fefce8; color: #ca8a04; }
|
||
.tag-cat-attitude .tag-chip.selected { background: #fef08a; }
|
||
.tag-cat-thinking .tag-chip { background: #f0f9ff; color: #0891b2; }
|
||
.tag-cat-thinking .tag-chip.selected { background: #cffafe; }
|
||
.tag-cat-state .tag-chip { background: #fff7ed; color: #ea580c; }
|
||
.tag-cat-state .tag-chip.selected { background: #fed7aa; }
|
||
.tag-cat-resilience .tag-chip { background: #fdf2f8; color: #db2777; }
|
||
.tag-cat-resilience .tag-chip.selected { background: #fce7f3; }
|
||
.tag-cat-social .tag-chip { background: #eef2ff; color: #4f46e5; }
|
||
.tag-cat-social .tag-chip.selected { background: #e0e7ff; }
|
||
.tag-cat-issue .tag-chip { background: #fef2f2; color: #dc2626; }
|
||
.tag-cat-issue .tag-chip.selected { background: #fee2e2; }
|
||
.tag-cat-suggest .tag-chip { background: #faf5ff; color: #7c3aed; }
|
||
.tag-cat-suggest .tag-chip.selected { background: #ede9fe; }
|
||
|
||
.tag-reset { font-size: 11px; color: var(--muted); cursor: pointer; float: right; background: none; border: none; }
|
||
.tag-reset:hover { color: var(--danger); }
|
||
|
||
/* 标签目标提示 */
|
||
.tag-target-hint {
|
||
font-size: 11px; color: var(--muted); margin-top: 4px;
|
||
display: flex; align-items: center; gap: 4px;
|
||
}
|
||
.tag-target-indicator {
|
||
font-size: 10px; padding: 1px 6px; border-radius: 8px;
|
||
background: #f0ece6; cursor: pointer; font-weight: 600;
|
||
}
|
||
.tag-target-indicator.active { background: var(--primary); color: white; }
|
||
|
||
/* ==================== 右侧表单 ==================== */
|
||
.form-panel { flex: 1; min-width: 0; }
|
||
|
||
/* ==================== 操作按钮 ==================== */
|
||
.actions {
|
||
display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap;
|
||
}
|
||
.btn-primary {
|
||
flex: 1; min-width: 160px; padding: 14px 24px;
|
||
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
|
||
color: white; border: none; border-radius: 12px;
|
||
font-size: 15px; font-weight: 800; cursor: pointer;
|
||
transition: all 0.2s; font-family: inherit; letter-spacing: 0.5px;
|
||
box-shadow: 0 4px 14px rgba(45,90,61,0.35);
|
||
}
|
||
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(45,90,61,0.45); }
|
||
.btn-primary:disabled { opacity: 0.6; cursor: wait; }
|
||
.btn-ai {
|
||
background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
|
||
box-shadow: 0 4px 14px rgba(99,102,241,0.4);
|
||
}
|
||
.btn-ai:hover { box-shadow: 0 6px 20px rgba(99,102,241,0.5); }
|
||
.btn-secondary {
|
||
padding: 14px 20px;
|
||
background: var(--card); color: var(--primary);
|
||
border: 2px solid var(--border); border-radius: 12px;
|
||
font-size: 14px; font-weight: 600; cursor: pointer;
|
||
font-family: inherit; transition: all 0.2s;
|
||
}
|
||
.btn-secondary:hover { border-color: var(--primary); background: #faf8f5; }
|
||
|
||
/* ==================== 输出区域 ==================== */
|
||
.output {
|
||
display: none; margin-top: 16px;
|
||
background: var(--primary-dark); border-radius: 14px; padding: 18px;
|
||
}
|
||
.output.show { display: block; }
|
||
.output h3 { color: white; font-size: 14px; font-weight: 600; margin-bottom: 8px; opacity: 0.9; }
|
||
.output-content {
|
||
background: #0f2519; border-radius: 8px; padding: 14px;
|
||
color: #b8ffcc; font-family: 'Fira Code','Cascadia Code',Consolas,monospace;
|
||
font-size: 13px; line-height: 1.7; white-space: pre-wrap;
|
||
word-break: break-all; max-height: 350px; overflow-y: auto;
|
||
}
|
||
.copy-btn {
|
||
margin-top: 10px; padding: 8px 20px;
|
||
background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2);
|
||
border-radius: 8px; color: white; font-size: 13px;
|
||
cursor: pointer; font-family: inherit; transition: all 0.2s;
|
||
}
|
||
.copy-btn:hover { background: rgba(255,255,255,0.2); }
|
||
.copy-btn.copied { background: var(--accent); border-color: var(--accent); }
|
||
.save-hint { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 6px; }
|
||
|
||
/* ==================== 提示消息 ==================== */
|
||
.toast {
|
||
position: fixed; top: 20px; left: 50%; transform: translateX(-50%);
|
||
padding: 14px 28px; border-radius: 12px; color: white;
|
||
font-weight: 600; z-index: 1000; display: none;
|
||
animation: fadeIn 0.3s; font-size: 14px;
|
||
box-shadow: 0 4px 16px rgba(0,0,0,0.25);
|
||
}
|
||
.toast.success { background: var(--accent); }
|
||
.toast.error { background: var(--danger); }
|
||
.toast.info { background: #6366f1; }
|
||
@keyframes fadeIn { from{opacity:0;transform:translateX(-50%) translateY(-10px)} to{opacity:1;transform:translateX(-50%) translateY(0)} }
|
||
|
||
.empty {
|
||
background: var(--card); border-radius: 14px; padding: 32px 16px;
|
||
text-align: center; color: var(--muted);
|
||
}
|
||
|
||
/* ==================== 响应式 ==================== */
|
||
@media (max-width: 920px) {
|
||
.main-layout { flex-direction: column; }
|
||
.tag-sidebar {
|
||
flex: none; position: static; max-height: none;
|
||
width: 100%; max-height: 300px;
|
||
}
|
||
.header { padding: 16px; }
|
||
.header h1 { font-size: 18px; }
|
||
.ai-config-bar { gap: 6px; }
|
||
.ai-config-bar input { min-width: 120px; }
|
||
.student-trait { display: none; }
|
||
.actions { flex-direction: column; }
|
||
}
|
||
|
||
@media (max-width: 600px) {
|
||
body { padding: 8px; }
|
||
.header { padding: 14px 12px; border-radius: 12px; }
|
||
.student-card { padding: 10px 12px; border-radius: 10px; }
|
||
}
|
||
|
||
/* 滚动条美化 */
|
||
.tag-sidebar::-webkit-scrollbar { width: 4px; }
|
||
.tag-sidebar::-webkit-scrollbar-thumb { background: #ddd; border-radius: 2px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="toast" id="toast"></div>
|
||
|
||
<div class="container">
|
||
<!-- ========== 头部 ========== -->
|
||
<div class="header">
|
||
<h1>小Q老师 · 课评系统</h1>
|
||
<div class="sub">2026春季学期 | Form-First模式 | v6.0</div>
|
||
</div>
|
||
|
||
<!-- ========== AI 提供商配置面板 ========== -->
|
||
<div class="ai-config-bar" id="aiConfigBar">
|
||
<span class="status-dot" id="aiStatusDot" title="未配置"></span>
|
||
<label>提供商</label>
|
||
<select id="aiProvider" onchange="onAiProviderChange()" style="min-width:100px;">
|
||
<option value="deepseek">DeepSeek</option>
|
||
<option value="openai">OpenAI</option>
|
||
<option value="kimi">Kimi (月之暗面)</option>
|
||
<option value="custom">自定义</option>
|
||
</select>
|
||
<label>API地址</label>
|
||
<input id="aiApiUrl" placeholder="https://api.deepseek.com/v1" onchange="saveAiConfig()" />
|
||
<label>Key</label>
|
||
<input id="aiApiKey" type="password" placeholder="输入API Key..." onchange="saveAiConfig()" />
|
||
<label>模型</label>
|
||
<input id="aiModel" class="model-input" placeholder="deepseek-chat" onchange="saveAiConfig()" />
|
||
<button class="toggle-btn" id="aiConfigToggle" onclick="toggleAiConfig()">🔒 隐藏Key</button>
|
||
<button class="test-btn" onclick="testAiConnection()">测试连接</button>
|
||
</div>
|
||
|
||
<!-- ========== 快速跳转 ========== -->
|
||
<div class="quick-jump">
|
||
<input type="text" id="quickInput" placeholder="输入 /周六 16点 快速跳转..." onkeydown="handleQuickJump(event)" />
|
||
<button onclick="doQuickJump()">跳转</button>
|
||
</div>
|
||
|
||
<!-- ========== 主布局:标签库 + 表单 ========== -->
|
||
<div class="main-layout">
|
||
<!-- ===== 左侧:标签库 ===== -->
|
||
<div class="tag-sidebar" id="tagSidebar">
|
||
<h3>🏷️ 课堂观察标签</h3>
|
||
<div class="tag-target-hint">
|
||
标签目标:
|
||
<span class="tag-target-indicator" id="tagTargetLabel" onclick="cycleTagTarget()">当前学生</span>
|
||
<span style="font-size:10px;color:#aaa;">(点击切换)</span>
|
||
</div>
|
||
<div id="tagLibrary"></div>
|
||
<button class="tag-reset" onclick="resetAllTags()" style="margin-top:8px;">🔄 重置所有标签</button>
|
||
</div>
|
||
|
||
<!-- ===== 右侧:表单面板 ===== -->
|
||
<div class="form-panel">
|
||
<!-- 选择器 -->
|
||
<div class="selector-panel">
|
||
<div class="selector-group">
|
||
<div class="selector-label">1. 选择周数</div>
|
||
<select class="selector-select" id="weekSelect" onchange="onWeekChange()">
|
||
<option value="">-- 请选择 --</option>
|
||
</select>
|
||
</div>
|
||
<div class="selector-group">
|
||
<div class="selector-label">2. 选择周几</div>
|
||
<select class="selector-select" id="weekdaySelect" disabled onchange="onWeekdayChange()">
|
||
<option value="">-- 先选周数 --</option>
|
||
</select>
|
||
</div>
|
||
<div class="selector-group">
|
||
<div class="selector-label">3. 选择班级</div>
|
||
<select class="selector-select" id="classSelect" disabled onchange="onClassChange()">
|
||
<option value="">-- 先选周几 --</option>
|
||
</select>
|
||
</div>
|
||
<div class="selector-group" id="customClassGroup" style="display:none;">
|
||
<div class="selector-label">输入班级名称</div>
|
||
<input type="text" class="selector-select" id="customClassInput" placeholder="如:体验班-张三" style="background:#faf8f5;" />
|
||
</div>
|
||
<div class="selector-group" id="customClassTypeGroup" style="display:none;">
|
||
<div class="selector-label">班级类型</div>
|
||
<select class="selector-select" id="customClassType">
|
||
<option value="DISC">发现世界 (DISC)</option>
|
||
<option value="CREATE">Wedo创造世界 (CREATE)</option>
|
||
<option value="SPIKE">SPIKE (SPIKE)</option>
|
||
<option value="AICODE03">AICODE03 (AICODE03)</option>
|
||
<option value="CUSTOM">自定义课程</option>
|
||
</select>
|
||
</div>
|
||
<div class="date-display" id="dateDisplay">📅</div>
|
||
</div>
|
||
|
||
<!-- 课程信息 -->
|
||
<div class="course-panel" id="coursePanel">
|
||
<div class="course-info">
|
||
<div class="course-item">
|
||
<div class="course-item-label">课程主题</div>
|
||
<div class="course-item-value" id="courseTheme">-</div>
|
||
</div>
|
||
<div class="course-item">
|
||
<div class="course-item-label">课程代码</div>
|
||
<div class="course-item-value" id="courseCode">-</div>
|
||
</div>
|
||
<div class="course-item">
|
||
<div class="course-item-label">核心知识点</div>
|
||
<div class="course-item-value" id="courseKnowledge">-</div>
|
||
</div>
|
||
</div>
|
||
<div class="custom-course" id="customCourseArea">
|
||
<input type="text" class="custom-input" id="customTheme" placeholder="输入课程主题(如"蜥蜴")" />
|
||
<input type="text" class="custom-input" id="customKnowledge" placeholder="输入核心知识点(可选)" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 学生卡片容器 -->
|
||
<div id="cardsContainer"></div>
|
||
|
||
<!-- 速记参考 -->
|
||
<div class="shorthand">
|
||
<strong>📌 速记符号参考:</strong><br />
|
||
<code>gj#</code> 观望#分钟 ·
|
||
<code>zd#</code> 主动搭#层 ·
|
||
<code>zt</code> 自己调整 ·
|
||
<code>zz#</code> 专注#分钟 ·
|
||
<code>bz</code> 帮助同学 ·
|
||
<code>tw</code> 提问 ·
|
||
<code>wc</code> 完成 ·
|
||
<code>cx</code> 创新 ·
|
||
<code>↑</code> 开心 ·
|
||
<code>→</code> 稳定 ·
|
||
<code>↓</code> 低落 ·
|
||
<code>++</code> 比上周进步
|
||
</div>
|
||
|
||
<!-- 补课/体验学生 -->
|
||
<div class="makeup-card" id="makeupCard">
|
||
<div class="makeup-header">
|
||
<span class="makeup-icon">🔄</span>
|
||
<span class="makeup-title">补课/体验学生</span>
|
||
</div>
|
||
<div id="makeupContainer">
|
||
<div class="makeup-entry" id="makeup-0">
|
||
<div class="makeup-row">
|
||
<input class="makeup-input" id="mname-0" placeholder="输入补课学生姓名" />
|
||
<div class="makeup-status-btns">
|
||
<button class="btn-small makeup" id="mstatus-makeup-0" onclick="setMakeupStatus(0,'makeup')">补课</button>
|
||
<button class="btn-small trial" id="mstatus-trial-0" onclick="setMakeupStatus(0,'trial')" style="opacity:0.5;">体验</button>
|
||
<button class="btn-small btn-remove" onclick="removeMakeup(0)">✕</button>
|
||
</div>
|
||
</div>
|
||
<textarea class="textarea" id="mdesc-0" placeholder="输入补课/体验学生的表现..." oninput="updateProgress()" onclick="setMakeupTagTarget(0)"></textarea>
|
||
</div>
|
||
</div>
|
||
<button class="btn-add" onclick="addMakeup()">➕ 添加补课/体验学生</button>
|
||
</div>
|
||
|
||
<!-- 底部按钮 -->
|
||
<div class="actions">
|
||
<button class="btn-primary" id="genFormBtn" onclick="generate()">📋 生成表单输出</button>
|
||
<button class="btn-primary btn-ai" id="genAiBtn" onclick="generateWithAI()">🤖 AI一键生成课评</button>
|
||
<button class="btn-secondary" onclick="clearAll()">🗑️ 清空</button>
|
||
</div>
|
||
|
||
<!-- 输出区域 -->
|
||
<div class="output" id="output">
|
||
<h3 id="outputTitle">⬇️ 输出结果(复制保存,或交给Claude生成课评)</h3>
|
||
<div class="output-content" id="outputContent"></div>
|
||
<button class="copy-btn" id="copyBtn" onclick="copyOutput()">📋 复制到剪贴板</button>
|
||
<div class="save-hint">💡 将内容粘贴给Claude,使用keping-advanced技能生成并自动保存课评</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src=".claude/memory/config/class-data.js?v=20260527"></script>
|
||
<script>
|
||
// ===============================================
|
||
// 状态管理
|
||
// ===============================================
|
||
let selectedWeek = '';
|
||
let currentWeekday = '';
|
||
let currentClass = null;
|
||
let currentWeek = 1;
|
||
let students = [];
|
||
let statuses = [];
|
||
let makeupCounter = 1;
|
||
let makeupData = [];
|
||
let tagTargetIndex = 0; // 标签填充到哪个学生(0=第1个学生)
|
||
let selectedTags = []; // 当前选中标签数组
|
||
let tagTargetMode = 'student'; // 'student' 或 'makeup' — 标签填充目标模式
|
||
let tagTargetMakeupIdx = 0; // 补课/体验学生的索引
|
||
|
||
// ===============================================
|
||
// 周数选项填充
|
||
// ===============================================
|
||
function fillWeekOptions() {
|
||
const sel = document.getElementById('weekSelect');
|
||
for (let w=1; w<=21; w++) {
|
||
const opt = document.createElement('option');
|
||
opt.value = String(w); opt.textContent = `第${w}周`;
|
||
sel.appendChild(opt);
|
||
}
|
||
}
|
||
|
||
// ===============================================
|
||
// 日期计算
|
||
// ===============================================
|
||
function getDateForWeekAndWeekday(weekNum, weekday) {
|
||
const start = new Date('2026-03-02');
|
||
const weekMap = {'周一':1,'周二':2,'周三':3,'周四':4,'周五':5,'周六':6,'周日':0};
|
||
let targetDay = weekMap[weekday];
|
||
const weekStart = new Date(start);
|
||
weekStart.setDate(start.getDate()+(weekNum-1)*7);
|
||
const diff = (targetDay-weekStart.getDay()+7)%7;
|
||
const result = new Date(weekStart);
|
||
result.setDate(weekStart.getDate()+diff);
|
||
return result;
|
||
}
|
||
|
||
function formatDate(date) {
|
||
const y=date.getFullYear(), m=String(date.getMonth()+1).padStart(2,'0'), d=String(date.getDate()).padStart(2,'0');
|
||
return `${y}-${m}-${d}`;
|
||
}
|
||
|
||
function formatDateShort(date) {
|
||
return `${date.getMonth()+1}月${date.getDate()}日`;
|
||
}
|
||
|
||
// ===============================================
|
||
// 选择器事件
|
||
// ===============================================
|
||
function onWeekChange() {
|
||
const weekNum = document.getElementById('weekSelect').value;
|
||
selectedWeek = weekNum;
|
||
const ws = document.getElementById('weekdaySelect');
|
||
ws.innerHTML = '<option value="">-- 请选择 --</option>';
|
||
ws.disabled = true;
|
||
const cs = document.getElementById('classSelect');
|
||
cs.innerHTML = '<option value="">-- 先选周几 --</option>';
|
||
cs.disabled = true;
|
||
document.getElementById('dateDisplay').textContent = '📅';
|
||
document.getElementById('coursePanel').classList.remove('show');
|
||
document.getElementById('cardsContainer').innerHTML = '';
|
||
if (!weekNum) return;
|
||
['周一','周二','周三','周四','周五','周六','周日'].forEach(wd => {
|
||
const opt = document.createElement('option'); opt.value=wd; opt.textContent=wd; ws.appendChild(opt);
|
||
});
|
||
ws.disabled = false;
|
||
}
|
||
|
||
function onWeekdayChange() {
|
||
const weekday = document.getElementById('weekdaySelect').value;
|
||
currentWeekday = weekday;
|
||
const cs = document.getElementById('classSelect');
|
||
cs.innerHTML = '<option value="">-- 请选择班级 --</option>';
|
||
document.getElementById('customClassGroup').style.display='none';
|
||
document.getElementById('customClassTypeGroup').style.display='none';
|
||
if (!weekday) { cs.disabled=true; resetDisplay(); return; }
|
||
const date = getDateForWeekAndWeekday(parseInt(selectedWeek), weekday);
|
||
currentWeek = parseInt(selectedWeek);
|
||
document.getElementById('dateDisplay').textContent = `📅 ${formatDateShort(date)} · 第${selectedWeek}周`;
|
||
const filtered = CONFIG.classes.filter(c=>c.weekday===weekday);
|
||
filtered.forEach(c=>{const o=document.createElement('option');o.value=c.id;o.textContent=c.name;cs.appendChild(o);});
|
||
['───────────','🌟 体验班级','🔄 补课班级','➕ 自定义新班级'].forEach((label,i)=>{
|
||
const o=document.createElement('option');
|
||
if(i===0){o.disabled=true;o.textContent=label;}
|
||
else{o.value=['__TRIAL__','__MAKEUP__','__CUSTOM__'][i-1];o.textContent=label;}
|
||
cs.appendChild(o);
|
||
});
|
||
cs.disabled=false;
|
||
resetDisplay();
|
||
}
|
||
|
||
function resetDisplay() {
|
||
document.getElementById('coursePanel').classList.remove('show');
|
||
document.getElementById('cardsContainer').innerHTML='';
|
||
document.getElementById('output').classList.remove('show');
|
||
selectedTags = [];
|
||
renderTagLibrary();
|
||
}
|
||
|
||
function onClassChange() {
|
||
const classId = document.getElementById('classSelect').value;
|
||
if (classId==='__TRIAL__'||classId==='__MAKEUP__'||classId==='__CUSTOM__') {
|
||
document.getElementById('customClassGroup').style.display='block';
|
||
document.getElementById('customClassTypeGroup').style.display='block';
|
||
const ci=document.getElementById('customClassInput');
|
||
const labels={__TRIAL__:'体验班',__MAKEUP__:'补课班',__CUSTOM__:'自定义班'};
|
||
ci.placeholder=`如:${labels[classId]}-张三`;
|
||
ci.value=''; document.getElementById('customTheme').value=''; document.getElementById('customKnowledge').value='';
|
||
document.getElementById('coursePanel').classList.remove('show');
|
||
document.getElementById('cardsContainer').innerHTML='';
|
||
ci.oninput=handleCustomClass;
|
||
document.getElementById('customClassType').onchange=handleCustomClass;
|
||
setTimeout(handleCustomClass,0);
|
||
return;
|
||
}
|
||
document.getElementById('customClassGroup').style.display='none';
|
||
document.getElementById('customClassTypeGroup').style.display='none';
|
||
currentClass = CONFIG.classes.find(c=>c.id===classId);
|
||
if(!currentClass){document.getElementById('coursePanel').classList.remove('show');document.getElementById('cardsContainer').innerHTML='';return;}
|
||
const courseType=currentClass.courseType;
|
||
const cd=CONFIG.courses[courseType]?.[currentWeek];
|
||
const theme=cd?.theme||'待定';
|
||
const code=cd?.code||`${currentClass.coursePrefix}-${String(currentWeek).padStart(2,'0')}`;
|
||
const knowledge=cd?.knowledge||'';
|
||
document.getElementById('courseTheme').textContent=theme;
|
||
document.getElementById('courseCode').textContent=code;
|
||
document.getElementById('courseKnowledge').textContent=knowledge;
|
||
const ca=document.getElementById('customCourseArea');
|
||
ca.classList.toggle('show',!cd||theme==='待定');
|
||
document.getElementById('coursePanel').classList.add('show');
|
||
selectedTags=[];
|
||
renderStudents();
|
||
renderTagLibrary();
|
||
}
|
||
|
||
function handleCustomClass() {
|
||
const ci=document.getElementById('customClassInput');
|
||
let cn=ci.value.trim();
|
||
const ct=document.getElementById('customClassType').value;
|
||
const clv=document.getElementById('classSelect').value;
|
||
let tp='新班级';
|
||
if(clv==='__TRIAL__')tp='体验班';if(clv==='__MAKEUP__')tp='补课班';if(clv==='__CUSTOM__')tp='自定义班';
|
||
if(!cn)cn=tp;
|
||
currentClass={id:`${tp}-${cn}`,name:`${tp}-${cn}`,courseType:ct,coursePrefix:ct==='CUSTOM'?'CUSTOM':ct,weekday:currentWeekday,time:'',students:[],isCustom:true};
|
||
const cd=ct==='CUSTOM'?null:CONFIG.courses[ct]?.[currentWeek];
|
||
document.getElementById('courseTheme').textContent=cd?.theme||'待定';
|
||
document.getElementById('courseCode').textContent=cd?.code||`${currentClass.coursePrefix}-${String(currentWeek).padStart(2,'0')}`;
|
||
document.getElementById('courseKnowledge').textContent=cd?.knowledge||'';
|
||
document.getElementById('customCourseArea').classList.add('show');
|
||
const cti=document.getElementById('customTheme'), cki=document.getElementById('customKnowledge');
|
||
if(ct==='CUSTOM'){cti.value='';cki.value='';}
|
||
else if(!cti.value&&cd&&cd.theme&&cd.theme!=='待定'){cti.value=cd.theme;cki.value=cd.knowledge||'';}
|
||
document.getElementById('coursePanel').classList.add('show');
|
||
selectedTags=[];
|
||
renderCustomStudents();
|
||
renderTagLibrary();
|
||
}
|
||
|
||
// ===============================================
|
||
// 学生渲染
|
||
// ===============================================
|
||
function renderStudents() {
|
||
students=[...currentClass.students];
|
||
statuses=students.map(()=>'present');
|
||
tagTargetIndex=0;
|
||
const c=document.getElementById('cardsContainer');
|
||
c.innerHTML=students.map((s,i)=>`
|
||
<div class="student-card" id="card-${i}">
|
||
<div class="student-top">
|
||
<div class="student-avatar" style="background:${s.color}">${s.emoji}</div>
|
||
<div class="student-name">${s.name}</div>
|
||
<div class="student-trait">${s.trait}</div>
|
||
</div>
|
||
<div class="status-buttons">
|
||
<button class="status-btn active-present" id="present-${i}" onclick="setStatus(${i},'present');setTagTarget(${i})">✅ 出勤</button>
|
||
<button class="status-btn" id="leave-${i}" onclick="setStatus(${i},'leave')">❌ 请假</button>
|
||
</div>
|
||
<textarea class="textarea" id="input-${i}" placeholder="输入速记,如 gj3→zd2→zt ↑++ \n或输入详细描述...\n💡 也可点击左侧标签快速填充" oninput="onInput(${i})" onclick="setTagTarget(${i})"></textarea>
|
||
</div>
|
||
`).join('');
|
||
updateProgress();
|
||
renderTagLibrary();
|
||
}
|
||
|
||
function renderCustomStudents() {
|
||
const c=document.getElementById('cardsContainer');
|
||
c.innerHTML=`
|
||
<div class="student-card" id="custom-students-card">
|
||
<div class="student-top">
|
||
<div class="student-avatar" style="background:#e8f5e9">👥</div>
|
||
<div class="student-name">自定义学生列表</div>
|
||
<div class="student-trait">请输入学生姓名,每行一个</div>
|
||
</div>
|
||
<textarea class="textarea" id="customStudentsInput" placeholder="输入学生姓名,每行一个,如:\n张三\n李四\n王五" oninput="updateCustomStudents()"></textarea>
|
||
</div>
|
||
<div id="customStudentCards"></div>
|
||
`;
|
||
updateProgress();
|
||
}
|
||
|
||
function updateCustomStudents() {
|
||
const inp=document.getElementById('customStudentsInput');
|
||
if(!inp)return;
|
||
const names=inp.value.trim().split('\n').filter(n=>n.trim());
|
||
students=names.map((n,i)=>({name:n.trim(),emoji:i%2===0?'👧':'🧒',color:['#e8f5e9','#e3f2fd','#fff3e0','#fce4ec','#f3e5f5'][i%5],trait:'待观察'}));
|
||
statuses=students.map(()=>'present');
|
||
tagTargetIndex=0;
|
||
const cc=document.getElementById('customStudentCards');
|
||
if(cc)cc.innerHTML=students.map((s,i)=>`
|
||
<div class="student-card" id="card-${i}">
|
||
<div class="student-top">
|
||
<div class="student-avatar" style="background:${s.color}">${s.emoji}</div>
|
||
<div class="student-name">${s.name}</div>
|
||
<div class="student-trait">${s.trait}</div>
|
||
</div>
|
||
<div class="status-buttons">
|
||
<button class="status-btn active-present" id="present-${i}" onclick="setStatus(${i},'present');setTagTarget(${i})">✅ 出勤</button>
|
||
<button class="status-btn" id="leave-${i}" onclick="setStatus(${i},'leave')">❌ 请假</button>
|
||
</div>
|
||
<textarea class="textarea" id="input-${i}" placeholder="输入速记或描述...\n💡 也可点击左侧标签快速填充" oninput="onInput(${i})" onclick="setTagTarget(${i})"></textarea>
|
||
</div>
|
||
`).join('');
|
||
updateProgress();
|
||
renderTagLibrary();
|
||
}
|
||
|
||
function setStatus(i,status) {
|
||
statuses[i]=status;
|
||
const pre=document.getElementById(`present-${i}`),lea=document.getElementById(`leave-${i}`),card=document.getElementById(`card-${i}`);
|
||
pre.classList.toggle('active-present',status==='present');
|
||
lea.classList.toggle('active-leave',status==='leave');
|
||
card.classList.toggle('absent',status==='leave');
|
||
updateProgress();
|
||
}
|
||
|
||
function onInput(i) {
|
||
const ta=document.getElementById(`input-${i}`);
|
||
ta.classList.toggle('filled',ta.value.trim().length>0);
|
||
updateProgress();
|
||
}
|
||
|
||
// ===============================================
|
||
// 补课学生
|
||
// ===============================================
|
||
function addMakeup() {
|
||
const idx=makeupCounter++;
|
||
const mc=document.getElementById('makeupContainer');
|
||
const div=document.createElement('div');
|
||
div.className='makeup-entry'; div.id=`makeup-${idx}`;
|
||
div.innerHTML=`
|
||
<div class="makeup-row">
|
||
<input class="makeup-input" id="mname-${idx}" placeholder="输入补课学生姓名" />
|
||
<div class="makeup-status-btns">
|
||
<button class="btn-small makeup" id="mstatus-makeup-${idx}" onclick="setMakeupStatus(${idx},'makeup')">补课</button>
|
||
<button class="btn-small trial" id="mstatus-trial-${idx}" onclick="setMakeupStatus(${idx},'trial')" style="opacity:0.5;">体验</button>
|
||
<button class="btn-small btn-remove" onclick="removeMakeup(${idx})">✕</button>
|
||
</div>
|
||
</div>
|
||
<textarea class="textarea" id="mdesc-${idx}" placeholder="输入补课/体验学生的表现..." oninput="updateProgress()" onclick="setMakeupTagTarget(${idx})"></textarea>
|
||
`;
|
||
mc.appendChild(div);
|
||
updateProgress();
|
||
}
|
||
|
||
function removeMakeup(idx) {
|
||
const el=document.getElementById(`makeup-${idx}`);
|
||
if(el){el.remove();updateProgress();}
|
||
}
|
||
|
||
function setMakeupStatus(idx,type) {
|
||
const mb=document.getElementById(`mstatus-makeup-${idx}`),tb=document.getElementById(`mstatus-trial-${idx}`);
|
||
if(!mb||!tb)return;
|
||
mb.style.opacity=type==='makeup'?'1':'0.5';
|
||
tb.style.opacity=type==='makeup'?'0.5':'1';
|
||
}
|
||
|
||
function collectMakeupData() {
|
||
const entries=document.querySelectorAll('.makeup-entry');
|
||
const data=[];
|
||
entries.forEach(entry=>{
|
||
const idx=entry.id.replace('makeup-','');
|
||
const ni=document.getElementById(`mname-${idx}`), di=document.getElementById(`mdesc-${idx}`);
|
||
const mb=document.getElementById(`mstatus-makeup-${idx}`);
|
||
if(!ni||!di)return;
|
||
const name=ni.value.trim(),desc=di.value.trim();
|
||
const isMakeup=mb&&mb.style.opacity==='1';
|
||
if(name)data.push({name,desc,type:isMakeup?'补课':'体验'});
|
||
});
|
||
return data;
|
||
}
|
||
|
||
function updateProgress() {
|
||
let filled=0,total=students.length;
|
||
students.forEach((_,i)=>{
|
||
const inp=document.getElementById(`input-${i}`);
|
||
if(inp){const v=inp.value.trim();if(v.length>0||statuses[i]==='leave')filled++;}
|
||
});
|
||
const makeup=collectMakeupData();
|
||
makeup.forEach(m=>{if(m.name&&m.desc){filled++;total++;}else if(m.name){total++;}});
|
||
document.getElementById('filledCount').textContent=filled;
|
||
document.getElementById('totalCount').textContent=total;
|
||
const pct=total>0?(filled/total)*100:0;
|
||
document.getElementById('progressFill').style.width=pct+'%';
|
||
const ph=document.getElementById('pendingHint'),p=total-filled;
|
||
ph.textContent=p>0?`⚠️ ${p} 人待填写`:'✅ 全部完成';
|
||
ph.style.color=p>0?'#e74c3c':'#4a8c5c';
|
||
}
|
||
|
||
// ===============================================
|
||
// AI 配置管理
|
||
// ===============================================
|
||
const AI_PROVIDERS = {
|
||
deepseek: { url: 'https://api.deepseek.com/v1/chat/completions', model: 'deepseek-chat' },
|
||
openai: { url: 'https://api.openai.com/v1/chat/completions', model: 'gpt-4o' },
|
||
kimi: { url: 'https://api.moonshot.cn/v1/chat/completions', model: 'moonshot-v1-8k' },
|
||
custom: { url: '', model: '' }
|
||
};
|
||
|
||
function loadAiConfig() {
|
||
const cfg=JSON.parse(localStorage.getItem('keping_ai_config')||'{}');
|
||
document.getElementById('aiProvider').value=cfg.provider||'deepseek';
|
||
document.getElementById('aiApiUrl').value=cfg.apiUrl||AI_PROVIDERS.deepseek.url;
|
||
document.getElementById('aiApiKey').value=cfg.apiKey||'';
|
||
document.getElementById('aiModel').value=cfg.model||AI_PROVIDERS.deepseek.model;
|
||
updateAiStatusDot();
|
||
}
|
||
|
||
function saveAiConfig() {
|
||
const cfg={provider:document.getElementById('aiProvider').value,apiUrl:document.getElementById('aiApiUrl').value,apiKey:document.getElementById('aiApiKey').value,model:document.getElementById('aiModel').value};
|
||
localStorage.setItem('keping_ai_config',JSON.stringify(cfg));
|
||
updateAiStatusDot();
|
||
}
|
||
|
||
function onAiProviderChange() {
|
||
const p=document.getElementById('aiProvider').value;
|
||
const prov=AI_PROVIDERS[p];
|
||
document.getElementById('aiApiUrl').value=prov.url||'';
|
||
document.getElementById('aiModel').value=prov.model||'';
|
||
if(p==='custom'){document.getElementById('aiApiUrl').placeholder='输入自定义API地址';document.getElementById('aiModel').placeholder='输入模型名称';}
|
||
saveAiConfig();
|
||
}
|
||
|
||
function updateAiStatusDot() {
|
||
const key=document.getElementById('aiApiKey').value;
|
||
const dot=document.getElementById('aiStatusDot');
|
||
dot.className='status-dot '+(key?'ok':'err');
|
||
dot.title=key?'API Key 已配置':'未配置 API Key';
|
||
}
|
||
|
||
function toggleAiConfig() {
|
||
const keyInput=document.getElementById('aiApiKey');
|
||
const btn=document.getElementById('aiConfigToggle');
|
||
if(keyInput.type==='password'){keyInput.type='text';btn.textContent='🔒 隐藏Key';}
|
||
else{keyInput.type='password';btn.textContent='👁️ 显示Key';}
|
||
}
|
||
|
||
async function testAiConnection() {
|
||
const key=document.getElementById('aiApiKey').value.trim();
|
||
const url=document.getElementById('aiApiUrl').value.trim();
|
||
const model=document.getElementById('aiModel').value.trim();
|
||
if(!key){showToast('请先填写 API Key','error');return;}
|
||
if(!url){showToast('请先填写 API 地址','error');return;}
|
||
showToast('正在测试连接...','info');
|
||
try {
|
||
const resp=await fetch(url,{method:'POST',headers:{'Content-Type':'application/json','Authorization':`Bearer ${key}`},body:JSON.stringify({model:model||'deepseek-chat',messages:[{role:'user',content:'测试'}],max_tokens:10})});
|
||
if(resp.ok){showToast('✅ 连接成功!','success');document.getElementById('aiStatusDot').className='status-dot ok';}
|
||
else{const err=await resp.json();showToast('❌ 连接失败: '+(err.error?.message||resp.status),'error');document.getElementById('aiStatusDot').className='status-dot err';}
|
||
}catch(e){showToast('❌ 网络错误: '+e.message,'error');document.getElementById('aiStatusDot').className='status-dot err';}
|
||
}
|
||
|
||
// ===============================================
|
||
// Toast 提示
|
||
// ===============================================
|
||
function showToast(msg, type) {
|
||
const t=document.getElementById('toast');
|
||
t.textContent=msg; t.className='toast '+type; t.style.display='block';
|
||
setTimeout(()=>{t.style.display='none';},3000);
|
||
}
|
||
|
||
// ===============================================
|
||
// 标签库
|
||
// ===============================================
|
||
const TAG_LIBRARY = [
|
||
{name:'💡 创意设计', colorClass:'tag-cat-creative',tags:['结构有创意','功能设计新颖','造型独特','构思巧妙','有想象力','不拘泥于示范','加入个性化装饰','功能性创新突出','整体美感强','用AI激发创意','AI辅助构思新颖']},
|
||
{name:'🛠️ 搭建技能', colorClass:'tag-cat-skill',tags:['结构稳固','零件搭配合理','传动精准','齿轮啮合良好','底盘设计合理','电机安装规范','传感器使用恰当','连接件使用熟练','整体结构紧凑','机械原理理解透彻','互锁结构掌握熟练','管道连接稳固','螺丝刀使用灵活','杠杆原理应用恰当','重心调整合理','结构对称性好','力传导理解清晰','结构加固方法得当','直角传动掌握良好','惰轮使用正确','轴固定牢固不松动','滑轮应用恰当','减速结构理解透彻','加速结构运用熟练','连贯搭建动作流畅','作品布局合理美观','手眼协调能力强','空间建构能力好','颜色搭配美观','尺寸比例协调','造型还原度高','齿轮变速理解到位','履带传动运用灵活','铰链结构运用巧妙','复式搭建技巧熟练']},
|
||
{name:'📋 学习态度', colorClass:'tag-cat-attitude',tags:['认真专注','积极主动','乐于尝试','精益求精','遵守课堂规则','高效率完成任务','主动整理零件','有责任心','对AI学习热情高','主动探索新功能','粗心大意','敷衍了事','注意力不集中']},
|
||
{name:'🧠 思维特点', colorClass:'tag-cat-thinking',tags:['逻辑清晰','独立思考','善于分析','举一反三','空间想象力强','善于规划步骤','编程思维较好','计算思维突出','能拆解复杂问题','善于发现规律','思路混乱','需要引导','喜欢模仿']},
|
||
{name:'🔥 课堂状态', colorClass:'tag-cat-state',tags:['专注度高','沉浸搭建','踊跃发言','高效完成任务','积极参与讨论','动手速度快','课堂纪律好','积极体验AI工具','乐于分享AI发现','小动作较多','容易分心','需要提醒']},
|
||
{name:'💪 情绪韧性', colorClass:'tag-cat-resilience',tags:['抗挫力强','耐心调试','不轻易放弃','敢于面对失败','情绪稳定','心态积极','AI出错不慌张','反复尝试优化','容易焦虑','急于求成','遇挫容易放弃']},
|
||
{name:'🤝 合作沟通', colorClass:'tag-cat-social',tags:['乐于分享','帮助同学','善于表达','沟通顺畅','团队协作意识强','能与同伴讨论方案','会向AI清晰提问','能用语言描述创作思路','独占材料','不愿交流']},
|
||
{name:'⚠️ 待优化问题', colorClass:'tag-cat-issue',tags:['结构松散','程序逻辑有误','不够牢固','功能不稳定','完成度有待提高','零件脱落频繁','过度依赖AI生成','提示词不够清晰','不检查AI输出结果','不知如何修改AI作品','缺乏调试耐心']},
|
||
{name:'🚀 下节课建议', colorClass:'tag-cat-suggest',tags:['加强结构稳定性','优化程序逻辑','尝试新功能','挑战更高难度','关注底盘设计','改进传动系统','增加传感器反馈','练习精准提问技巧','尝试AI独立创作','学习调整AI参数','多与同学交流分享']},
|
||
{name:'🤖 AI素养', colorClass:'tag-cat-skill',tags:['能理解AI基本原理','知道AI会出错需验证','善用AI辅助学习','会修改优化AI产出','能比较AI与自己创作的差异','了解AI工具的优势与局限','主动学习AI新功能','能表达对AI的疑问','合理使用AI不盲目依赖','尊重原创和版权意识']}
|
||
];
|
||
|
||
function renderTagLibrary() {
|
||
const container=document.getElementById('tagLibrary');
|
||
let html='';
|
||
TAG_LIBRARY.forEach((cat,ci)=>{
|
||
const collapsed=false;
|
||
html+=`<div class="tag-category ${cat.colorClass}">`;
|
||
html+=`<div class="tag-cat-header" onclick="toggleCategory(this)" title="点击折叠/展开"><span class="arrow">▼</span> ${cat.name} (${cat.tags.length})</div>`;
|
||
html+=`<div class="tag-items">`;
|
||
cat.tags.forEach(tag=>{
|
||
const sel=selectedTags.includes(tag)?' selected':'';
|
||
html+=`<span class="tag-chip${sel}" onclick="toggleTag('${tag.replace(/'/g,"\\'")}')">${tag}</span>`;
|
||
});
|
||
html+=`</div></div>`;
|
||
});
|
||
container.innerHTML=html;
|
||
updateTagTargetIndicator();
|
||
}
|
||
|
||
function toggleCategory(header) {
|
||
header.classList.toggle('collapsed');
|
||
}
|
||
|
||
function toggleTag(tag) {
|
||
const idx=selectedTags.indexOf(tag);
|
||
if(idx!==-1){selectedTags.splice(idx,1);}
|
||
else{selectedTags.push(tag);}
|
||
renderTagLibrary();
|
||
applyTagsToStudent();
|
||
}
|
||
|
||
function resetAllTags() {
|
||
selectedTags=[];
|
||
renderTagLibrary();
|
||
// 不调用 applyTagsToStudent(),保留右侧学生框内已有【标签观察】内容
|
||
}
|
||
|
||
function setTagTarget(i) {
|
||
tagTargetIndex=i;
|
||
tagTargetMode='student';
|
||
updateTagTargetIndicator();
|
||
}
|
||
|
||
function setMakeupTagTarget(idx) {
|
||
tagTargetMode='makeup';
|
||
tagTargetMakeupIdx=idx;
|
||
updateTagTargetIndicator();
|
||
}
|
||
|
||
function updateTagTargetIndicator() {
|
||
const label=document.getElementById('tagTargetLabel');
|
||
if(tagTargetMode==='makeup'){
|
||
const input=document.getElementById(`mname-${tagTargetMakeupIdx}`);
|
||
const name=input?input.value.trim()||'补课/体验学生':'补课/体验学生';
|
||
label.textContent=name;
|
||
label.className='tag-target-indicator active';
|
||
return;
|
||
}
|
||
if(students.length===0){label.textContent='无学生';label.className='tag-target-indicator';return;}
|
||
const s=students[tagTargetIndex];
|
||
if(!s){label.textContent='无学生';label.className='tag-target-indicator';return;}
|
||
label.textContent=s.name||'当前学生';
|
||
label.className='tag-target-indicator active';
|
||
}
|
||
|
||
function cycleTagTarget() {
|
||
if(tagTargetMode==='makeup'){
|
||
// 从补课模式切回学生模式
|
||
tagTargetMode='student';
|
||
if(tagTargetIndex>=students.length)tagTargetIndex=0;
|
||
updateTagTargetIndicator();
|
||
const card=document.getElementById(`card-${tagTargetIndex}`);
|
||
if(card)card.scrollIntoView({behavior:'smooth',block:'center'});
|
||
return;
|
||
}
|
||
// 先收集有填写姓名的补课/体验条目
|
||
const makeups=[];
|
||
document.querySelectorAll('.makeup-entry').forEach(entry=>{
|
||
const idx=entry.id.replace('makeup-','');
|
||
const ni=document.getElementById(`mname-${idx}`);
|
||
if(ni&&ni.value.trim())makeups.push(parseInt(idx));
|
||
});
|
||
// 若当前是最后一个学生且有补课条目,切到补课模式
|
||
if(tagTargetIndex>=students.length-1&&makeups.length>0){
|
||
tagTargetMode='makeup';
|
||
tagTargetMakeupIdx=makeups[0];
|
||
updateTagTargetIndicator();
|
||
const el=document.getElementById(`makeup-${tagTargetMakeupIdx}`);
|
||
if(el)el.scrollIntoView({behavior:'smooth',block:'center'});
|
||
return;
|
||
}
|
||
// 否则继续循环学生
|
||
if(students.length===0)return;
|
||
tagTargetIndex=(tagTargetIndex+1)%students.length;
|
||
updateTagTargetIndicator();
|
||
const card=document.getElementById(`card-${tagTargetIndex}`);
|
||
if(card)card.scrollIntoView({behavior:'smooth',block:'center'});
|
||
}
|
||
|
||
function applyTagsToStudent() {
|
||
const tagText=selectedTags.length>0?('\n【标签观察】'+selectedTags.join('、')):'';
|
||
if(tagTargetMode==='makeup'){
|
||
const ta=document.getElementById(`mdesc-${tagTargetMakeupIdx}`);
|
||
if(!ta)return;
|
||
const existing=ta.value.replace(/【标签观察】[^]*/,'').trim();
|
||
ta.value=existing+tagText;
|
||
ta.classList.toggle('filled',ta.value.trim().length>0);
|
||
updateProgress();
|
||
return;
|
||
}
|
||
if(students.length===0)return;
|
||
const ta=document.getElementById(`input-${tagTargetIndex}`);
|
||
if(!ta)return;
|
||
// 保留已有的速记文字,在末尾追加标签文本
|
||
const existing=ta.value.replace(/【标签观察】[^]*/,'').trim();
|
||
ta.value=existing+tagText;
|
||
ta.classList.toggle('filled',ta.value.trim().length>0);
|
||
updateProgress();
|
||
}
|
||
|
||
// ===============================================
|
||
// 表单生成输出
|
||
// ===============================================
|
||
function generate() {
|
||
if(!currentClass){showToast('请先选择班级','error');return;}
|
||
const date=getDateForWeekAndWeekday(currentWeek,currentWeekday);
|
||
const dateStr=formatDate(date);
|
||
let theme=document.getElementById('courseTheme').textContent;
|
||
let knowledge=document.getElementById('courseKnowledge').textContent;
|
||
let code=document.getElementById('courseCode').textContent;
|
||
const ct=document.getElementById('customTheme').value.trim();
|
||
const ck=document.getElementById('customKnowledge').value.trim();
|
||
if(ct){theme=ct;knowledge=ck||'自定义课程';code=`${currentClass.coursePrefix}-${String(currentWeek).padStart(2,'0')}`;}
|
||
let output=`时间:${dateStr}\n班级:${currentClass.id}\n主题:${theme}\n`;
|
||
if(knowledge&&knowledge!=='-')output+=`目标/知识:${knowledge}\n`;
|
||
output+=`---\n课评\n---\n`;
|
||
students.forEach((s,i)=>{
|
||
const status=statuses[i];
|
||
const input=document.getElementById(`input-${i}`).value.trim();
|
||
if(status==='leave')output+=`${s.name}:[请假]\n`;
|
||
else if(input)output+=`${s.name}:${input}\n`;
|
||
else if(currentClass&¤tClass.isCustom)output+=`${s.name}:\n`;
|
||
});
|
||
const makeup=collectMakeupData();
|
||
makeup.forEach(m=>{if(m.desc)output+=`${m.name}[${m.type}]:${m.desc}\n`;else if(m.name)output+=`${m.name}[${m.type}]\n`;});
|
||
document.getElementById('outputContent').textContent=output;
|
||
document.getElementById('outputTitle').textContent='⬇️ 输出结果(复制保存,或交给Claude生成课评)';
|
||
document.getElementById('output').classList.add('show');
|
||
document.getElementById('output').scrollIntoView({behavior:'smooth',block:'start'});
|
||
}
|
||
|
||
function copyOutput() {
|
||
const text=document.getElementById('outputContent').textContent;
|
||
navigator.clipboard.writeText(text).then(()=>{
|
||
const btn=document.getElementById('copyBtn');
|
||
btn.textContent='✅ 已复制'; btn.classList.add('copied');
|
||
setTimeout(()=>{btn.textContent='📋 复制到剪贴板';btn.classList.remove('copied');},2000);
|
||
});
|
||
}
|
||
|
||
function clearAll() {
|
||
if(!confirm('确定要清空所有内容吗?'))return;
|
||
const csi=document.getElementById('customStudentsInput');
|
||
if(csi){csi.value='';const csc=document.getElementById('customStudentCards');if(csc)csc.innerHTML='';}
|
||
students.forEach((_,i)=>{const el=document.getElementById(`input-${i}`);if(el){el.value='';el.classList.remove('filled');setStatus(i,'present');}});
|
||
document.getElementById('customClassInput').value='';
|
||
document.getElementById('customTheme').value='';
|
||
document.getElementById('customKnowledge').value='';
|
||
document.getElementById('makeupContainer').innerHTML=`
|
||
<div class="makeup-entry" id="makeup-0">
|
||
<div class="makeup-row">
|
||
<input class="makeup-input" id="mname-0" placeholder="输入补课学生姓名" />
|
||
<div class="makeup-status-btns">
|
||
<button class="btn-small makeup" id="mstatus-makeup-0" onclick="setMakeupStatus(0,'makeup')">补课</button>
|
||
<button class="btn-small trial" id="mstatus-trial-0" onclick="setMakeupStatus(0,'trial')" style="opacity:0.5;">体验</button>
|
||
<button class="btn-small btn-remove" onclick="removeMakeup(0)">✕</button>
|
||
</div>
|
||
</div>
|
||
<textarea class="textarea" id="mdesc-0" placeholder="输入补课/体验学生的表现..." oninput="updateProgress()" onclick="setMakeupTagTarget(0)"></textarea>
|
||
</div>
|
||
`;
|
||
makeupCounter=1;
|
||
document.getElementById('output').classList.remove('show');
|
||
selectedTags=[]; renderTagLibrary();
|
||
updateProgress();
|
||
}
|
||
|
||
// ===============================================
|
||
// AI 一键生成课评
|
||
// ===============================================
|
||
async function generateWithAI() {
|
||
const key=document.getElementById('aiApiKey').value.trim();
|
||
const url=document.getElementById('aiApiUrl').value.trim();
|
||
const model=document.getElementById('aiModel').value.trim();
|
||
if(!key){showToast('请先配置 AI API Key','error');document.getElementById('aiApiKey').focus();return;}
|
||
if(!url){showToast('请先配置 AI API 地址','error');return;}
|
||
if(!currentClass){showToast('请先选择班级','error');return;}
|
||
|
||
const btn=document.getElementById('genAiBtn');
|
||
const origText=btn.textContent;
|
||
btn.disabled=true; btn.textContent='⏳ AI生成中...';
|
||
|
||
// 收集数据
|
||
const date=getDateForWeekAndWeekday(currentWeek,currentWeekday);
|
||
const dateStr=formatDate(date);
|
||
let theme=document.getElementById('courseTheme').textContent;
|
||
let knowledge=document.getElementById('courseKnowledge').textContent;
|
||
let code=document.getElementById('courseCode').textContent;
|
||
const ct=document.getElementById('customTheme').value.trim();
|
||
const ck=document.getElementById('customKnowledge').value.trim();
|
||
if(ct){theme=ct;knowledge=ck||'自定义课程';code=`${currentClass.coursePrefix}-${String(currentWeek).padStart(2,'0')}`;}
|
||
|
||
// 构建学生表现描述
|
||
let studentRecords='';
|
||
students.forEach((s,i)=>{
|
||
if(statuses[i]==='leave')return;
|
||
const input=document.getElementById(`input-${i}`).value.trim();
|
||
if(input)studentRecords+=`${s.name}:${input}\n`;
|
||
});
|
||
const makeup=collectMakeupData();
|
||
makeup.forEach(m=>{if(m.desc)studentRecords+=`${m.name}[${m.type}]:${m.desc}\n`;});
|
||
|
||
if(!studentRecords.trim()){showToast('请先填写至少一位学生的表现','error');btn.disabled=false;btn.textContent=origText;return;}
|
||
|
||
// 构建 Prompt
|
||
const systemPrompt=`你是穹狼乐高编程教育的课评助手。请根据以下课堂观察记录,为每位学生生成个性化课评。
|
||
|
||
课评要求:
|
||
1. 约150-250字,2-3自然段,纯文本
|
||
2. 将课堂观察标签自然融入,不模板化
|
||
3. 有问题的话用温柔语气表达为"成长点"
|
||
4. 每句话结尾加匹配的 emoji
|
||
5. 结尾固定附上「【温馨提示】」段落(关于下节课准备建议)
|
||
6. 课程类型:${currentClass.courseType||'编程搭建'}类,主题为${theme}
|
||
7. 称呼学生时使用亲切语气,注意正向激励`;
|
||
|
||
const userPrompt=`课程信息:
|
||
- 日期:${dateStr}
|
||
- 班级:${currentClass.id}
|
||
- 课程主题:${theme}
|
||
- 核心知识:${knowledge&&knowledge!=='-'?knowledge:'乐高搭建与编程'}
|
||
- 课程代码:${code}
|
||
|
||
以下学生的课堂表现记录:
|
||
${studentRecords}
|
||
|
||
请为以上每位出勤学生生成个性化课评。格式要求:
|
||
- 每位学生以"【学生姓名】"开头
|
||
- 课评正文约150-250字
|
||
- 每句话末尾加匹配的 emoji
|
||
- 末尾固定【温馨提示】段落`;
|
||
|
||
try {
|
||
const messages=[{role:'system',content:systemPrompt},{role:'user',content:userPrompt}];
|
||
const resp=await fetch(url,{method:'POST',headers:{'Content-Type':'application/json','Authorization':`Bearer ${key}`},body:JSON.stringify({model:model||'deepseek-chat',messages:messages,max_tokens:4000,temperature:0.7})});
|
||
if(!resp.ok){const err=await resp.json();throw new Error(err.error?.message||`HTTP ${resp.status}`);}
|
||
const data=await resp.json();
|
||
const aiContent=data.choices?.[0]?.message?.content||JSON.stringify(data);
|
||
document.getElementById('outputContent').textContent=aiContent;
|
||
document.getElementById('outputTitle').textContent='🤖 AI 生成课评';
|
||
document.getElementById('output').classList.add('show');
|
||
document.getElementById('copyBtn').textContent='📋 复制到剪贴板';
|
||
document.getElementById('copyBtn').classList.remove('copied');
|
||
showToast('✅ AI 课评生成成功!','success');
|
||
document.getElementById('output').scrollIntoView({behavior:'smooth',block:'start'});
|
||
}catch(e){
|
||
showToast('❌ AI生成失败: '+e.message,'error');
|
||
document.getElementById('outputContent').textContent=`错误:${e.message}\n\n请检查 API 配置是否正确,或点击"测试连接"验证。`;
|
||
document.getElementById('outputTitle').textContent='❌ 生成失败';
|
||
document.getElementById('output').classList.add('show');
|
||
}finally{
|
||
btn.disabled=false; btn.textContent=origText;
|
||
}
|
||
}
|
||
|
||
// ===============================================
|
||
// 快速跳转
|
||
// ===============================================
|
||
function handleQuickJump(e){if(e.key==='Enter')doQuickJump();}
|
||
function doQuickJump(){
|
||
const inp=document.getElementById('quickInput').value.trim();
|
||
if(!inp)return;
|
||
const match=inp.match(/\/?(周[一二三四五六日])\s*(\d+)[点:]?/);
|
||
if(!match){showToast('格式不对哦,请用:/周六 16点','error');return;}
|
||
const weekday=match[1], hour=parseInt(match[2]);
|
||
const mc=CONFIG.classes.find(c=>{if(c.weekday!==weekday)return false;const ch=parseInt(c.time.split(':')[0]);return ch===hour;});
|
||
if(!mc){showToast(`没有找到 ${weekday} ${hour}点的班级`,'error');return;}
|
||
const cwn=getCurrentWeekNumber();
|
||
document.getElementById('weekSelect').value=cwn; onWeekChange();
|
||
document.getElementById('weekdaySelect').value=weekday; onWeekdayChange();
|
||
document.getElementById('classSelect').value=mc.id; onClassChange();
|
||
document.getElementById('quickInput').value='';
|
||
setTimeout(()=>document.getElementById('cardsContainer').scrollIntoView({behavior:'smooth',block:'start'}),100);
|
||
}
|
||
|
||
// ===============================================
|
||
// 初始化
|
||
// ===============================================
|
||
function getCurrentWeekNumber() {
|
||
const start=new Date('2026-03-02');
|
||
const today=new Date();
|
||
// 使用本地日期计算,避免 UTC 时区偏差
|
||
const startLocal=Date.UTC(start.getFullYear(),start.getMonth(),start.getDate());
|
||
const todayLocal=Date.UTC(today.getFullYear(),today.getMonth(),today.getDate());
|
||
const diff=(todayLocal-startLocal)/(1000*60*60*24);
|
||
return Math.max(1,Math.min(21,Math.floor(diff/7)+1));
|
||
}
|
||
|
||
function init() {
|
||
fillWeekOptions();
|
||
loadAiConfig();
|
||
updateAiStatusDot();
|
||
|
||
const today=new Date();
|
||
const weekMap=['周日','周一','周二','周三','周四','周五','周六'];
|
||
const cwn=getCurrentWeekNumber();
|
||
const tw=weekMap[today.getDay()];
|
||
|
||
// 1. 自动选择当前周数
|
||
document.getElementById('weekSelect').value=String(cwn);
|
||
onWeekChange();
|
||
|
||
// 2. 延迟一帧后自动选择今天周几(确保 weekdaySelect DOM 已就绪)
|
||
requestAnimationFrame(()=>{
|
||
const ws=document.getElementById('weekdaySelect');
|
||
if(!ws.disabled){
|
||
ws.value=tw;
|
||
onWeekdayChange();
|
||
}
|
||
});
|
||
|
||
// 3. 在日期显示区标记当前周
|
||
const dd=document.getElementById('dateDisplay');
|
||
dd.textContent=dd.textContent+' (当前周)';
|
||
|
||
document.getElementById('quickInput').focus();
|
||
renderTagLibrary();
|
||
}
|
||
|
||
init();
|
||
// 监听进度更新
|
||
setInterval(()=>{try{updateProgress()}catch(e){}},2000);
|
||
</script>
|
||
</body>
|
||
</html> |