Files
aca-syllabu-front/syllabus-print.html
T
wanghu 15762faeac
build / build (push) Successful in 5m20s
commit
2026-10-13 10:47:07 +08:00

844 lines
37 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" data-page-node-id="4IHq8iGJWszgAfjN9qFnIY">
<head data-page-node-id="EqlGyBSyL7Aad7LyHJQBf3">
<meta charset="UTF-8" data-page-node-id="q9wYV7sN0Uv4R7GAVPOdu9">
<meta name="viewport" content="width=device-width, initial-scale=1" data-page-node-id="e9iG1oTO5etriOH6MGHpmB">
<title>课程表 · 电子版 / 照片版</title>
<style>
/* ============================================================
设计令牌 —— 取自参考课表的实测取色
============================================================ */
:root{
/* 框架:深红边框 + 红底白字表头 */
--frame:#8f2f28;
--frame-soft:#c07a73;
--head-bg:#9d332b;
--head-bg-2:#8a2b24;
/* 学科色板(底色 / 分隔线 / 文字)—— 低饱和马卡龙系 */
--c-main-1:#dff0d8; --c-main-1-l:#bcdcb0; --c-main-1-t:#2f5e23;
--c-main-2:#e4f3e0; --c-main-2-l:#c3e0bb; --c-main-2-t:#33642a;
--c-art:#e8e6f5; --c-art-l:#c3bfe4; --c-art-t:#3d3470;
--c-ethics:#f0e5ef; --c-ethics-l:#d8bfd6; --c-ethics-t:#66305f;
--c-sports:#f8e6e9; --c-sports-l:#eec4cb; --c-sports-t:#7d2c39;
--c-school:#f7f3e2; --c-school-l:#e0d8b8; --c-school-t:#6b5c26;
--c-science:#ddf0f4; --c-science-l:#a9d9e3; --c-science-t:#1f5561;
--c-service:#eceee0; --c-service-l:#cfd3b8; --c-service-t:#4e5334;
--c-break:#fdfaf2;
--c-meal:#fbf6ec;
/* 中性 */
--ink:#3a3632;
--ink-2:#6a635c;
--ink-3:#9a938b;
--line:#d8cdc4;
--paper:#ffffff;
--page:#eef1f5;
--r:3px;
--font:"PingFang SC","Microsoft YaHei","Hiragino Sans GB",Inter,system-ui,-apple-system,sans-serif;
}
*,*::before,*::after{box-sizing:border-box}
body{
margin:0;padding:0;
font-family:var(--font);
color:var(--ink);
background:var(--page);
font-size:13px;line-height:1.45;
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
}
button{font-family:inherit;cursor:pointer}
:where(button,[tabindex],.tab):focus-visible{outline:2px solid var(--head-bg);outline-offset:2px;border-radius:var(--r)}
.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
/* ============================================================
页面骨架
============================================================ */
.wrap{max-width:1480px;margin:0 auto;padding:16px 24px 40px}
.crumbs{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--ink-3);padding:2px 0 12px}
.crumbs .cb-on{color:var(--ink);font-weight:500}
.crumbs .cb-sep{color:var(--line)}
.bar{
display:flex;align-items:center;gap:16px;flex-wrap:wrap;
padding:10px 16px;margin-bottom:16px;
background:var(--paper);border:1px solid #dfe4ea;border-radius:6px;
}
.tabs{display:inline-flex;padding:3px;background:#f1f3f6;border:1px solid #dfe4ea;border-radius:5px}
.tab{
height:30px;padding:0 20px;border:0;border-radius:4px;
background:transparent;font-size:13px;color:#5a6472;
transition:background 150ms ease,color 150ms ease;
}
.tab:hover{color:#3d4757}
.tab[aria-selected="true"]{background:#6f86e8;color:#fff;font-weight:500;box-shadow:0 1px 3px rgba(89,121,234,.35)}
.bar-spacer{flex:1 1 auto}
.picker{display:flex;align-items:center;gap:8px}
.picker label{font-size:13px;color:var(--ink-2)}
.picker select{
height:32px;min-width:220px;padding:0 30px 0 10px;font-size:13px;color:var(--ink);
background:#fff;border:1px solid #d7dde5;border-radius:5px;appearance:none;cursor:pointer;
background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' fill='none' stroke='%236a635c' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat:no-repeat;background-position:right 10px center;
}
.mini-btn{
height:32px;padding:0 12px;font-size:13px;color:var(--ink-2);
background:#fff;border:1px solid #d7dde5;border-radius:5px;
transition:border-color 150ms ease,color 150ms ease,background 150ms ease;
}
.mini-btn:hover{border-color:#6f86e8;color:#4c63c9;background:#f4f6fe}
.panel{display:none}
.panel.is-on{display:block}
.stage{display:flex;flex-direction:column;align-items:center;gap:16px}
.stage-hint{font-size:12.5px;color:var(--ink-3);text-align:center}
.stage-hint b{color:var(--ink-2);font-weight:500}
.preview-btn{
height:36px;padding:0 26px;border:0;border-radius:5px;
background:#6f86e8;color:#fff;font-size:14px;font-weight:500;
box-shadow:0 2px 6px rgba(89,121,234,.3);
transition:background 150ms ease,transform 150ms ease;
}
.preview-btn:hover{background:#5b74e0;transform:translateY(-1px)}
/* ============================================================
课表纸张 —— A4 横向:297 × 210mm
============================================================ */
.a4{
width:297mm;height:210mm;flex:0 0 auto;
background:var(--paper);position:relative;overflow:hidden;
padding:5.5mm;
box-shadow:0 1px 3px rgba(30,35,45,.14),0 10px 30px rgba(30,35,45,.13);
}
/* 外框:深红双线 */
.a4-inner{
height:100%;border:1.4px solid var(--frame);
padding:1.2mm;position:relative;
}
.a4-inner::after{
content:"";position:absolute;inset:1.1mm;border:.5px solid var(--frame-soft);pointer-events:none;
}
/* ---------- 题头 ---------- */
.masthead{
display:flex;align-items:center;justify-content:space-between;
gap:6mm;padding:1.6mm 3mm 1.4mm;
border-bottom:.8px solid var(--frame);
}
.brand{display:flex;align-items:center;gap:2.4mm;min-width:0}
.brand-mark{
width:11mm;height:11mm;flex:0 0 auto;border-radius:50%;
background:var(--frame);color:#fff;
display:grid;place-items:center;font-size:4.6mm;font-weight:500;
letter-spacing:-.3px;position:relative;
}
.brand-mark::after{
content:"";position:absolute;inset:1mm;border:.4px solid rgba(255,255,255,.55);border-radius:50%;
}
.brand-text{min-width:0}
.brand-cn{
font-size:4.4mm;font-weight:500;color:var(--frame);
letter-spacing:1.2px;line-height:1.25;white-space:nowrap;
}
.brand-en{
font-size:1.9mm;color:#a8625b;letter-spacing:.3px;line-height:1.3;
text-transform:uppercase;white-space:nowrap;
}
/* 题名:红色剪纸风 + 白描边 */
.mast-title{display:flex;align-items:center;gap:3mm;flex:0 0 auto}
.mast-title .mt{
font-size:10.5mm;font-weight:500;color:#b8352c;
letter-spacing:6px;line-height:1;
text-shadow:
1px 1px 0 #fff, -1px 1px 0 #fff, 1px -1px 0 #fff, -1px -1px 0 #fff,
0 2px 3px rgba(143,47,40,.22);
}
.mast-title .orn{
width:16mm;height:3.2mm;flex:0 0 auto;
background:
radial-gradient(circle at 2mm 1.6mm, #cb5a4e 1.1mm, transparent 1.2mm),
radial-gradient(circle at 7mm 1.6mm, #cb5a4e 1.1mm, transparent 1.2mm),
radial-gradient(circle at 12mm 1.6mm, #cb5a4e 1.1mm, transparent 1.2mm);
opacity:.75;
}
.class-tag{
position:absolute;right:5mm;top:12mm;
font-size:3.2mm;color:var(--frame);letter-spacing:2px;font-weight:500;
}
/* ---------- 主体:左信息栏 + 右课表 ---------- */
.board{
display:grid;grid-template-columns:34mm minmax(0,1fr);
gap:2mm;padding:2mm 3mm 1mm;
}
/* 左栏 */
.side{
border:.7px solid var(--frame-soft);border-radius:var(--r);
padding:2mm 2mm 2.4mm;
background:linear-gradient(180deg,#fffdfc 0%,#fdf8f6 100%);
display:flex;flex-direction:column;align-items:center;gap:2.2mm;
}
.photo-slot{
width:29mm;height:36mm;flex:0 0 auto;
border:1px dashed #d9b9b3;border-radius:var(--r);
background:
repeating-linear-gradient(45deg,#faf5f3 0 3px,#f3eae7 3px 6px);
display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1.4mm;
color:#b7999a;font-size:2.4mm;text-align:center;padding:2mm;overflow:hidden;
}
.photo-slot img{width:100%;height:100%;object-fit:cover;display:block}
.photo-slot .ps-ico{opacity:.6}
.side-roles{width:100%;display:flex;flex-direction:column;gap:1.6mm}
.role{
display:flex;align-items:baseline;gap:1.6mm;
padding-bottom:1.4mm;border-bottom:.4px dashed #e2d2ce;
}
.role:last-child{border-bottom:0;padding-bottom:0}
.role .k{font-size:2.9mm;color:#9c6f68;letter-spacing:.6px;flex:0 0 auto}
.role .v{font-size:3.3mm;color:#2f6b3a;font-weight:500;letter-spacing:1.2px}
.quote{
margin-top:auto;padding-top:2mm;text-align:center;width:100%;
border-top:.4px dashed #e2d2ce;
}
.quote .q1{font-size:3.1mm;color:#c2453a;line-height:.1.35;font-style:italic;line-height:1.4}
.quote .q2{font-size:3.1mm;color:#c2453a;font-style:italic;line-height:1.4}
/* 右侧课表 */
.table-wrap{min-width:0}
table.sched{
width:100%;border-collapse:collapse;table-layout:fixed;
border:1px solid var(--frame);
}
table.sched col.c-time{width:13mm}
table.sched col.c-seg{width:6mm}
table.sched col.c-day{width:auto}
table.sched th,table.sched td{
border:.5px solid var(--frame-soft);
padding:.9mm 1.2mm;
text-align:center;vertical-align:middle;
font-size:2.85mm;line-height:1.25;
overflow:hidden;
}
/* 表头 */
table.sched thead th{
background:var(--head-bg);color:#fff;
font-size:3.3mm;font-weight:500;letter-spacing:1.4px;
padding:1.5mm .8mm;border-color:var(--frame);white-space:nowrap;
}
table.sched thead th.th-time{background:var(--head-bg-2);letter-spacing:1px;padding:1.5mm .6mm}
table.sched thead th.th-seg{background:var(--head-bg-2);letter-spacing:0;font-size:2.7mm;padding:1.5mm .4mm}
/* 分区标签(上午/中午/下午):竖排、跨行 */
td.seg{
background:#faf6f4;color:#8d5a54;
font-size:3.4mm;font-weight:500;
letter-spacing:3px;width:7.5mm;
writing-mode:vertical-rl;text-orientation:upright;
padding:0;
}
td.seg span{transform:rotate(180deg);display:inline-block}
/* 时间列 */
td.t1{
background:#fdfbfa;font-variant-numeric:tabular-nums;
font-size:2.6mm;color:#6a635c;letter-spacing:-.15px;white-space:nowrap;
}
td.t2{
background:#fdfbfa;font-variant-numeric:tabular-nums;
font-size:2.6mm;color:#6a635c;letter-spacing:-.15px;white-space:nowrap;
border-right:1.4px solid var(--frame);
position:relative;
}
/* 同一时段含两行时间:上行 起、下行 止 */
td.t2.at-start{border-top:1px solid var(--frame-soft)}
/* 学科单元格 */
td.s{font-weight:500;letter-spacing:.2px}
td.s .sub{display:block;font-size:2.35mm;font-weight:400;opacity:.82;letter-spacing:0}
td.s .tee{display:inline-block;font-size:2.15mm;font-weight:400;opacity:.7;margin-left:1mm}
.s-main1{background:var(--c-main-1);color:var(--c-main-1-t)}
.s-main2{background:var(--c-main-2);color:var(--c-main-2-t)}
.s-art{background:var(--c-art);color:var(--c-art-t)}
.s-ethics{background:var(--c-ethics);color:var(--c-ethics-t)}
.s-sports{background:var(--c-sports);color:var(--c-sports-t)}
.s-school{background:var(--c-school);color:var(--c-school-t)}
.s-science{background:var(--c-science);color:var(--c-science-t)}
.s-service{background:var(--c-service);color:var(--c-service-t)}
/* 课间 / 大课间 / 午休 等横向通栏 */
td.brk{
background:var(--c-break);color:#8a7f72;
font-size:2.5mm;letter-spacing:1.4px;padding:.7mm;
}
td.brk.meal{
background:var(--c-meal);color:#8a6a4a;text-align:left;
padding:.8mm 2mm;font-size:2.45mm;letter-spacing:0;
}
td.brk.meal b{color:#a3541f;font-weight:500}
/* 角标:跨行合并的午休/课后服务 */
td.span-col{background:#fdfcf6;color:#7a6a4a;font-size:2.5mm;letter-spacing:1.6px}
/* ============================================================
照片版 —— 课表压在照片上的“成品”效果
============================================================ */
.photostage{
position:relative;width:297mm;height:210mm;flex:0 0 auto;
overflow:hidden;background:#2b2622;
box-shadow:0 2px 6px rgba(30,35,45,.2),0 16px 44px rgba(30,35,45,.24);
}
.ph-bg{
position:absolute;inset:0;
width:100%;height:100%;object-fit:cover;display:block;
}
/* 压膜/光泽质感:斜向高光 + 轻微暗角,纯 CSS */
.ph-glare{
position:absolute;inset:0;pointer-events:none;
background:
linear-gradient(118deg, rgba(255,255,255,.20) 0%, rgba(255,255,255,.05) 22%,
rgba(255,255,255,0) 42%, rgba(255,255,255,.07) 62%,
rgba(255,255,255,0) 100%),
radial-gradient(120% 90% at 50% 45%, rgba(0,0,0,0) 52%, rgba(0,0,0,.30) 100%);
}
/* 课表纸张:贴在照片上,带白边与投影 */
.ph-sheet{
position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
width:266mm;height:186mm;background:var(--paper);padding:4.4mm;
box-shadow:0 3px 10px rgba(0,0,0,.34),0 14px 40px rgba(0,0,0,.30);
display:flex;flex-direction:column;align-items:center;justify-content:center;
overflow:hidden;
}
/* 照片版 = 电子版等比缩小:内层尺寸与 A4 内框完全一致(286 × 199mm),
再整体缩放到 0.88,保证版式 1:1 复刻、不产生任何溢出 */
.ph-scale{flex:0 0 auto;width:286mm;height:199mm;zoom:.88}
.ph-scale .a4-inner{height:100%;padding:1.2mm}
.ph-empty{
position:absolute;inset:0;display:flex;flex-direction:column;
align-items:center;justify-content:center;gap:14px;
background:#f7f8fa;color:#8a9099;font-size:13px;text-align:center;padding:24px;
}
.ph-tools{display:flex;align-items:center;gap:10px;flex-wrap:wrap;justify-content:center}
.file-btn{position:relative;overflow:hidden;display:inline-flex;align-items:center;height:34px;padding:0 16px;
background:#fff;border:1px solid #d7dde5;border-radius:5px;font-size:13px;color:#4a5361}
.file-btn:hover{border-color:#6f86e8;color:#4c63c9}
.file-btn input{position:absolute;inset:0;opacity:0;cursor:pointer;font-size:100px}
/* 图例 */
.legend{
display:flex;flex-wrap:wrap;align-items:center;gap:8px 18px;
margin-top:16px;padding:10px 16px;max-width:1108px;width:100%;
background:#fff;border:1px solid #dfe4ea;border-radius:6px;
}
.legend .lg-t{font-size:13px;color:var(--ink-3)}
.legend .lg-i{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;color:var(--ink-2)}
.legend .lg-d{width:13px;height:13px;border-radius:3px;border:1px solid rgba(0,0,0,.12)}
.legend .lg-sep{width:1px;height:14px;background:#e3e7ec}
/* ============================================================
响应式:小屏缩放展示,不破坏 A4 版面
============================================================ */
.stage-zoom{display:flex;flex-direction:column;align-items:center}
@media (max-width:1200px){
.zoom-a4{transform:scale(.78);transform-origin:top center}
.zoom-box-a4{height:calc(210mm * .78);width:calc(297mm * .78)}
.zoom-ph{transform:scale(.78);transform-origin:top center}
.zoom-box-ph{height:calc(210mm * .78);width:calc(297mm * .78)}
}
@media (max-width:940px){
.zoom-a4{transform:scale(.58)}
.zoom-box-a4{height:calc(210mm * .58);width:calc(297mm * .58)}
.zoom-ph{transform:scale(.58)}
.zoom-box-ph{height:calc(210mm * .58);width:calc(297mm * .58)}
}
@media (prefers-reduced-motion:reduce){*{transition-duration:.01ms!important}}
/* ============================================================
打印:A4 横向、零页边距、仅出当前版
============================================================ */
@page{size:A4 landscape;margin:0}
@media print{
html,body{background:#fff}
.wrap{max-width:none;padding:0;margin:0}
.crumbs,.bar,.stage-hint,.preview-btn,.legend,.ph-tools,.ph-empty,.stage-zoom>*:not(.print-target){display:none!important}
.panel{display:none!important}
.panel.is-on{display:block!important}
.zoom-a4,.zoom-ph{transform:none!important}
.zoom-box-a4,.zoom-box-ph,.zoom-a4,.zoom-ph,.photostage,.a4{
width:297mm!important;height:210mm!important;
}
.a4,.photostage,.ph-sheet{
box-shadow:none!important;-webkit-print-color-adjust:exact;print-color-adjust:exact;
}
.photostage{background:#fff!important}
.ph-glare{display:none!important}
.ph-empty{display:none!important}
.side{background:#fffdfc!important;-webkit-print-color-adjust:exact;print-color-adjust:exact}
}
</style>
</head>
<body data-page-node-id="bxFFzHNVzkHZEQp9FNNXU6">
<div class="wrap" data-page-node-id="y0T55E1XzruHWekWm3EzVR">
<nav class="crumbs" aria-label="面包屑" data-page-node-id="Zrg1iHOKQj9v1s5469ntha">
<span data-page-node-id="jcmVvxs8kLL3xt2dAAGvJv">首页</span>
<span class="cb-sep" data-page-node-id="Gk4eqOnoX79O78LWgqNQ0e">›</span>
<span class="cb-on" data-page-node-id="w4iGNQmqEg5T3lvb7TwDMI">课程表</span>
</nav>
<h1 class="sr-only" data-page-node-id="70unXvC2QeENQlUHjBsIp2">上海市大宁国际小学教育集团 一(1)班课程表,提供电子版与照片版两种样式</h1>
<!-- 工具条 -->
<div class="bar" data-page-node-id="wUVumg53FhsJyu0SQGgTp5">
<div class="tabs" role="tablist" aria-label="课程表版本" data-page-node-id="vdfOHdtDrkOTvsjPWdTMk3">
<button type="button" class="tab" role="tab" id="tab-e" aria-selected="true" aria-controls="panel-e" data-page-node-id="NQ3Fp91M71Kry9B6GaOOLf">电子版</button>
<button type="button" class="tab" role="tab" id="tab-p" aria-selected="false" aria-controls="panel-p" data-page-node-id="fBwVAPsfIjaZbUrG6ZO8gU">照片版</button>
</div>
<div class="bar-spacer" data-page-node-id="hQOHx0XzF352daijWHmwXO"></div>
<div class="picker" data-page-node-id="C2qNtDJbE4B5hL1KsFVX1G">
<label for="sel-class" data-page-node-id="NACAv44CG56r3VHipBHAWq">班级</label>
<select id="sel-class" data-page-node-id="osHMkSTLlk9o4N1ZQ1WeiF"></select>
</div>
<button type="button" class="mini-btn" id="btn-print" data-page-node-id="QW51ZtLWRqga0RiryG7Ahi">打印 / 导出 PDF</button>
</div>
<!-- ================= 电子版 ================= -->
<section class="panel is-on" id="panel-e" role="tabpanel" aria-labelledby="tab-e" data-page-node-id="QW0LBEBkBOFYDSKIjounqn">
<div class="stage" data-page-node-id="cXlSCgmpnHGSzHTBArc4zn">
<div class="stage-zoom zoom-box-a4" data-page-node-id="TxD1JbTLAZQEYaVbj797y2">
<div class="zoom-a4" id="mount-e" data-page-node-id="GCwxiV9D6OmPIB4AxCxDIC"></div>
</div>
<p class="stage-hint" data-page-node-id="jZknGjqYmZUaLFFpYsVbyF">A4 横向 · 297 × 210mm · 纯矢量排版,可直接打印或另存为 PDF</p>
<div class="legend" id="legend-e" data-page-node-id="Si3JENy57TPf048GVMRRsi"></div>
<button type="button" class="preview-btn" id="btn-preview-e" data-page-node-id="p4OOeHHFcXeYj1Q6hdnuX3">预览</button>
</div>
</section>
<!-- ================= 照片版 ================= -->
<section class="panel" id="panel-p" role="tabpanel" aria-labelledby="tab-p" hidden data-page-node-id="2rWOKGS46Y3J2obP0q8Obd">
<div class="stage" data-page-node-id="sLOcvahZFCxebUEifEJdqV">
<div class="stage-zoom zoom-box-ph" data-page-node-id="vofu9Zr4ki9JLF0jYbEF88">
<div class="zoom-ph" id="mount-p" data-page-node-id="ov2pNr2ImNSqtEHbu3in7m"></div>
</div>
<p class="stage-hint" id="hint-p" data-page-node-id="CDJYfSI5Z7JGFMvYY3HEvj">课表叠印在照片上 · 模拟压膜成品效果</p>
<div class="ph-tools" data-page-node-id="Nhlcd26IL4p7Xs1T841iUs">
<span class="file-btn" data-page-node-id="whXrEmxCnYSo3CNeosqzdC">选择本地照片
<input type="file" id="file-photo" accept="image/*" aria-label="选择本地照片作为底图" data-page-node-id="JxiX4epAos3SApImmDAgC2">
</span>
<button type="button" class="mini-btn" id="btn-reset-photo" data-page-node-id="VX9BVUKYxO8sjQBkTQrfUV">恢复默认底图</button>
</div>
<button type="button" class="preview-btn" id="btn-preview-p" data-page-node-id="lnrZ13BeOU4pGYjTysFf7G">预览</button>
</div>
</section>
</div>
<script>
"use strict";
/* ============================================================
课程数据 —— 按参考课表录入
结构:每日 = 表格行序列,行类型
seg 分区标签(竖排跨行):上午 / 中午 / 下午
brk 横向通栏(课间 / 大课间 / 午休说明)
meal 午休说明(左对齐多行)
lesson 课程(t1 起、t2 止)
span 跨行留白(如午休时段占位单元格)
============================================================ */
/* 学科 → 配色类。同一学科跨班级、跨周次颜色恒定,便于形成条件反射 */
var SUBJECT_STYLE = {
"语文":"s-main1", "数学":"s-main1", "英语":"s-main1", "外语":"s-main1",
"科学":"s-science", "体育与健康":"s-sports", "体育":"s-sports", "劳动":"s-sports",
"道德与法治":"s-ethics", "道德与法治(心理)":"s-ethics",
"美术":"s-art", "唱游与音乐":"s-art", "唱游与音乐(音乐)":"s-art", "美术(造型·美术)":"s-art",
"校本课程":"s-school", "校本课程(体育活动课)":"s-school",
"课后服务":"s-service", "课后服务(体育活动课)":"s-service"
};
/* 学科 → 通俗简称,, 用于图例分组 */
var SUBJECT_GROUP = {
"语文":"语文","数学":"数学","英语":"英语","外语":"英语",
"科学":"科学","体育与健康":"体育","体育":"体育","劳动":"劳动",
"道德与法治":"道法","道德与法治(心理)":"道法",
"美术":"艺术","唱游与音乐":"艺术","唱游与音乐(音乐)":"艺术","美术(造型·美术)":"艺术",
"校本课程":"校本","校本课程(体育活动课)":"校本",
"课后服务":"课后服务","课后服务(体育活动课)":"课后服务"
};
var CLASSES = {
"一(1)班": {
name:"一(1)班",
teacher:"黄晨晨",
quote:["Where there is a will,","there is a way."],
/* 每日 5 列(周一至周五)。
每行用 t 显式声明该时段的起止时刻,避免「午休等跨行单元格」打乱
行序后时间列与节次错位 */
rows:[
{ k:"brk", text:"欢乐操场" },
{ k:"lesson", no:1, t:["7:50","8:20"], cells:["升旗仪式/\n综合实践活动","", "", "", ""], colspan:[true,false,false,false,false] },
{ k:"seg-start", seg:"上午" },
{ k:"lesson", no:2, t:["8:25","9:00"], cells:["体育与健康","体育与健康","数学","美术\n(造型·美术)","体育与健康"] },
{ k:"brk", text:"课 间" },
{ k:"lesson", no:3, t:["9:15","9:50"], cells:["外语","数学","体育与健康","科学","外语"] },
{ k:"brk", text:"大 课 间 活 动" },
{ k:"lesson", no:4, t:["10:20","10:55"], cells:["语文","语文","校本课程\n(体育活动课)","语文","语文"] },
{ k:"brk", text:"课 间(眼 保 健 操)" },
{ k:"lesson", no:5, t:["11:10","11:45"], cells:["数学","语文","校本课程","体育与健康","语文"] },
{ k:"meal", t:["11:45","13:00"], text:'<b>午餐、午休、午间活动</b>  周一:卫生健康教育 / 心理健康教育,书香教室 周二、周四:快乐 70 分 周五:大劳动 <span style="opacity:.8">12:30—12:40</span>' },
{ k:"lesson", no:6, t:["13:00","13:35"], seg:"中午", cells:["语文","科学","道德与法治","语文",""], colspan:[false,false,false,false,true], rowspan:[1,1,1,1,4] },
{ k:"brk", text:"课 间" },
{ k:"lesson", no:7, t:["13:50","14:25"], cells:["体育与健康","美术\n(唱游·音乐)","美术\n(造型·美术)","美术\n(唱游·音乐)"] },
{ k:"brk", text:"课 间 ( 室 内 操 )", t:["14:25","14:40"], freeSpan:true },
{ k:"lesson", no:9, t:["14:40","15:15"], seg:"下午", cells:["课后服务","道德与法治","课后服务","劳动"] },
{ k:"brk", text:"课 间" },
{ k:"lesson", no:10, t:["15:15","15:30"], cells:["课后服务\n(体育活动课)","课后服务","课后服务"] }
]
}
};
var DEFAULT_CLASS = "一(1)班";
var ACTIVE_CLASS = DEFAULT_CLASS;
/* 默认底图:程序化生成的滨海人像风渐变,保证离线可用 */
function defaultPhoto() {
var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="1130">' +
'<defs>' +
'<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">' +
'<stop offset="0%" stop-color="#8fb8d6"/><stop offset="52%" stop-color="#cfe0e6"/>' +
'<stop offset="58%" stop-color="#9fbfc4"/><stop offset="100%" stop-color="#dfe6e2"/>' +
'</linearGradient>' +
'<linearGradient id="sea" x1="0" y1="0" x2="0" y2="1">' +
'<stop offset="0%" stop-color="#a8c6c8"/><stop offset="100%" stop-color="#cfdcd6"/>' +
'</linearGradient>' +
'</defs>' +
'<rect width="1600" height="1130" fill="url(#sky)"/>' +
'<rect y="655" width="1600" height="475" fill="url(#sea)"/>' +
'<g fill="#ffffff" opacity=".55">' +
'<ellipse cx="330" cy="150" rx="150" ry="46"/><ellipse cx="440" cy="132" rx="104" ry="38"/>' +
'<ellipse cx="1240" cy="196" rx="132" ry="40"/><ellipse cx="1140" cy="182" rx="90" ry="32"/>' +
'</g>' +
'<g fill="#3c3a36" opacity=".8">' +
'<rect x="1180" y="600" width="250" height="14" rx="4"/>' +
'<rect x="1216" y="548" width="26" height="56" rx="3"/>' +
'<rect x="1300" y="530" width="18" height="74" rx="3"/>' +
'</g>' +
'<g fill="#eef2ef" opacity=".5">' +
'<rect y="655" width="1600" height="3"/><rect y="676" width="1600" height="2"/>' +
'<rect y="700" width="1600" height="2"/><rect y="748" width="1600" height="2"/>' +
'<rect y="812" width="1600" height="3"/>' +
'</g>' +
'<g fill="#3d3a35" opacity=".86">' +
'<path d="M690 812c-4-46 6-78 22-96 8-9 18-14 26-14s18 5 26 14c16 18 26 50 22 96z"/>' +
'<circle cx="738" cy="672" r="40"/>' +
'<path d="M700 664c2-30 16-46 38-46s36 16 38 46c-8-12-20-18-38-18s-30 6-38 18z"/>' +
'</g>' +
'<path d="M640 772c30-16 62-22 98-22s68 6 98 22" fill="none" stroke="#3d3a35" stroke-width="7" opacity=".7" stroke-linecap="round"/>' +
'</svg>';
return "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg);
}
/* ============================================================
渲染
============================================================ */
function cls(name, extra) { return [name, extra || ""].filter(Boolean).join(" "); }
function subjectStyle(subject) {
var key = String(subject || "").replace(/\s+/g, "");
if (SUBJECT_STYLE[key]) return SUBJECT_STYLE[key];
for (var k in SUBJECT_STYLE) {
if (key.indexOf(k) > -1 || k.indexOf(key) > -1) return SUBJECT_STYLE[k];
}
return "s-main2";
}
/* 课表网格:6 列 —— [分区标签][第1节][第2节][第3节][第4节][第5节]
第1节列合并为「时间」列(上下两行显示起止时刻),
第2-5 节列的标题即周一至周五。
用 rowspan/colspan 表达跨行跨列,渲染前先算出每行的实际格位,
避免表头列数与表体列数不一致导致的列宽挤压。 */
function scheduleTable(cfg) {
var DAYS = ["星期一", "星期二", "星期三", "星期四", "星期五"];
/* 逐行展开成「格位」数组,方便统一处理 rowspan 占位与 colspan 合并 */
var occupy = {}; // "row-col" → true,被 rowspan 占用的位置
/* 摊平行:独立 seg-start 行、或课表行自带的 seg 属性,都转成 segmark,
保证「上午 / 中午 / 下午」三个分区标签都能生成跨行单元格 */
var rows = [];
cfg.rows.forEach(function (r) {
if (r.k === "seg-start") { rows.push({ k: "segmark", seg: r.seg }); return; }
if (r.seg) { rows.push({ k: "segmark", seg: r.seg }); }
rows.push(r);
});
/* 生成表体 */
var body = "<tbody>";
var bodyRow = 0; // 实际 <tr> 序号
var i2 = 0;
var pendingSeg = null; // 待输出的分区标签(遇到 segmark 时挂起,下一行落地)
while (i2 < rows.length) {
var r = rows[i2];
/* 分区标记:挂起,等下一张课表行输出时一并落地并统计跨度 */
if (r.k === "segmark") { pendingSeg = { label: r.seg, span: 0 }; i2++; continue; }
/* 找到本行所属的分区标签(若为分区首行,则输出 rowspan 单元格) */
var segCell = null;
if (pendingSeg) { segCell = pendingSeg; pendingSeg = null;
for (var j = i2; j < rows.length && rows[j].k !== "segmark"; j++) segCell.span++;
}
var html = "<tr>";
/* 第 1 列:分区标签 */
if (segCell) {
html += '<td class="seg" rowspan="' + segCell.span + '"><span>' + segCell.label + "</span></td>";
}
if (r.k === "brk" || r.k === "meal") {
/* 通栏行:时间列两格 + 横跨「当前未被 rowspan 占用」的天数列。
直接用剩余列数,避免与跨行单元格冲突导致整表列宽错位 */
var freeCols = 0;
for (var fc = 0; fc < 5; fc++) if (!occupy[bodyRow + "-" + fc]) freeCols++;
var tb = r.t || ["", ""];
html += '<td class="t1">' + (tb[0] || "") + '</td><td class="t2">' + (tb[1] || "") + "</td>";
html += '<td class="' + (r.k === "meal" ? "brk meal" : "brk") + '" colspan="' + freeCols + '">' + r.text + "</td>";
html += "</tr>";
body += html;
bodyRow++; i2++;
continue;
}
/* 课程行:时间列 + 5 天 */
var t = r.t || ["", ""];
html += '<td class="t1">' + (t[0] || "") + '</td><td class="t2">' + (t[1] || "") + "</td>";
/* 横向通栏行(如「课间(室内操)」):时间列后横跨所有空闲天数列 */
if (r.freeSpan) {
var freeCols2 = 0;
for (var fc2 = 0; fc2 < 5; fc2++) if (!occupy[bodyRow + "-" + fc2]) freeCols2++;
html += '<td class="brk" colspan="' + freeCols2 + '">' + r.text + "</td>";
html += "</tr>";
body += html;
bodyRow++; i2++;
continue;
}
/* 课程行:按列扫描。已跨行占位(如午休空白块)的列跳过不输出,
保证每天 1 个单元格与表头 5 天严格一一对应,不额外补格 */
for (var ci = 0; ci < 5; ci++) {
if (occupy[bodyRow + "-" + ci]) continue;
var c = r.cells[ci];
var cs = (r.colspan && r.colspan[ci]) ? (5 - ci) : 1;
var rs = (r.rowspan && r.rowspan[ci]) ? r.rowspan[ci] : 1;
if (r.textOverride) {
html += '<td class="brk" colspan="' + cs + '">' + r.textOverride + "</td>";
markUsed(occupy, bodyRow, ci, cs, rs);
continue;
}
if (!c) {
html += '<td class="span-col" colspan="' + cs + '"' +
(rs > 1 ? ' rowspan="' + rs + '"' : "") + "></td>";
markUsed(occupy, bodyRow, ci, cs, rs);
continue;
}
var parts = String(c).split("\n");
var main = parts[0];
var sub = parts[1] ? '<span class="sub">' + parts[1] + "</span>" : "";
html += '<td class="' + cls("s", subjectStyle(main)) + '"' +
(cs > 1 ? ' colspan="' + cs + '"' : "") + '>' + main + sub + "</td>";
markUsed(occupy, bodyRow, ci, cs, rs);
}
html += "</tr>";
body += html;
bodyRow++; i2++;
}
body += "</tbody>";
/* 表头:时间 | 一 | 二 | 三 | 四 | 五。
首列「时间」横跨 t1+t2 两格,故用 colspan=2 与表体严格对齐 */
var head = '<colgroup><col class="c-time"><col class="c-time"><col class="c-seg">' +
'<col class="c-day"><col class="c-day"><col class="c-day"><col class="c-day"><col class="c-day"></colgroup>';
var thead = "<thead><tr>" +
'<th class="th-time" colspan="2" scope="col">时 间</th>' +
'<th class="th-seg" scope="col"></th>' +
DAYS.map(function (d) { return '<th scope="col">' + d + "</th>"; }).join("") +
"</tr></thead>";
return '<table class="sched">' + head + thead + body + "</table>";
}
function markUsed(used, row, col, cs, rs) {
for (var a = 0; a < cs; a++) for (var b = 0; b < rs; b++) used[(row + b) + "-" + (col + a)] = 1;
}
/* 生成一张完整课表纸(电子版 / 照片版共用) */
function syllabusSheet(cfg) {
var roles = '<div class="side-roles">' +
'<div class="role"><span class="k">班主任</span><span class="v">' + cfg.teacher + "</span></div>" +
'<div class="role"><span class="k">班 级</span><span class="v">' + cfg.name + "</span></div>" +
"</div>";
var quote = '<div class="quote">' +
cfg.quote.map(function (l) { return '<div class="q1">' + l + "</div>"; }).join("") +
"</div>";
return '' +
'<div class="a4">' +
'<div class="a4-inner">' +
'<div class="masthead">' +
'<div class="brand">' +
'<span class="brand-mark" aria-hidden="true">大宁</span>' +
'<span class="brand-text">' +
'<span class="brand-cn">上海市大宁国际小学教育集团</span>' +
'<span class="brand-en">Shanghai Daning International Elementary School Education Group</span>' +
"</span>" +
"</div>" +
'<div class="mast-title">' +
'<span class="orn" aria-hidden="true"></span>' +
'<span class="mt">课程表</span>' +
"</div>" +
"</div>" +
'<span class="class-tag">' + cfg.name + "</span>" +
'<div class="board">' +
'<div class="side">' +
'<div class="photo-slot" id="slot-' + cfg.name + '">' +
'<svg class="ps-ico" width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
'<rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="8.5" cy="10" r="1.6"/><path d="M4 17l5-4 4 3 3-2.5 4 3.5"/>' +
"</svg>" +
"<span>班主任照片</span>" +
"</div>" +
roles +
quote +
"</div>" +
'<div class="table-wrap">' + scheduleTable(cfg) + "</div>" +
"</div>" +
"</div>" +
"</div>";
}
/* ============================================================
图例
============================================================ */
function renderLegend(cfg) {
var groups = {};
cfg.rows.forEach(function (r) {
if (!r.cells) return;
r.cells.forEach(function (c) {
if (!c) return;
var main = String(c).split("\n")[0];
var st = subjectStyle(main);
var g = SUBJECT_GROUP[String(main).replace(/\s+/g, "")] || main;
if (!groups[g]) groups[g] = st;
});
});
var order = ["语文","数学","英语","科学","道法","艺术","体育","劳动","校本","课后服务"];
var html = '<span class="lg-t">学科配色</span>';
order.forEach(function (g) {
if (!groups[g]) return;
html += '<span class="lg-i"><span class="lg-d ' + groups[g] + '"></span>' + g + "</span>";
delete groups[g];
});
Object.keys(groups).forEach(function (g) {
html += '<span class="lg-i"><span class="lg-d ' + groups[g] + '"></span>' + g + "</span>";
});
html += '<span class="lg-sep" aria-hidden="true"></span>' +
'<span class="lg-i"><span class="lg-d" style="background:var(--c-break)"></span>课间 / 通栏安排</span>';
return html;
}
/* ============================================================
挂载
============================================================ */
var mountE = document.getElementById("mount-e");
var mountP = document.getElementById("mount-p");
var selClass = document.getElementById("sel-class");
var photoSrc = defaultPhoto();
function mountPhotoSheet() {
mountP.innerHTML =
'<div class="photostage">' +
'<img class="ph-bg" id="ph-bg" alt="课表底图" src="' + photoSrc + '">' +
'<div class="ph-glare" aria-hidden="true"></div>' +
'<div class="ph-sheet"><div class="ph-scale">' + syllabusSheet(CLASSES[ACTIVE_CLASS]) + "</div></div>" +
"</div>";
}
function render() {
var cfg = CLASSES[ACTIVE_CLASS];
mountE.innerHTML = syllabusSheet(cfg);
mountPhotoSheet();
document.getElementById("legend-e").innerHTML = renderLegend(cfg);
document.getElementById("hint-p").innerHTML =
"课表叠印在照片上 · 模拟压膜成品效果 · <b>" + cfg.name + "</b> · " + cfg.teacher + " 老师";
document.title = "课程表 · " + cfg.name + " · 电子版 / 照片版";
}
/* 班级下拉 */
Object.keys(CLASSES).forEach(function (k) {
var op = document.createElement("option");
op.value = k; op.textContent = k;
selClass.appendChild(op);
});
selClass.value = ACTIVE_CLASS;
selClass.addEventListener("change", function () {
ACTIVE_CLASS = selClass.value;
render();
});
/* 版本标签页 */
function switchTab(which) {
var isE = which === "e";
document.getElementById("tab-e").setAttribute("aria-selected", String(isE));
document.getElementById("tab-p").setAttribute("aria-selected", String(!isE));
var pe = document.getElementById("panel-e"), pp = document.getElementById("panel-p");
pe.classList.toggle("is-on", isE);
pp.classList.toggle("is-on", !isE);
pe.hidden = !isE;
pp.hidden = isE;
}
document.getElementById("tab-e").addEventListener("click", function () { switchTab("e"); });
document.getElementById("tab-p").addEventListener("click", function () { switchTab("p"); });
/* 自助换底图 */
document.getElementById("file-photo").addEventListener("change", function (e) {
var f = e.target.files && e.target.files[0];
if (!f) return;
var rd = new FileReader();
rd.onload = function () {
photoSrc = rd.result;
var bg = document.getElementById("ph-bg");
if (bg) bg.src = photoSrc;
};
rd.readAsDataURL(f);
});
document.getElementById("btn-reset-photo").addEventListener("click", function () {
photoSrc = defaultPhoto();
var bg = document.getElementById("ph-bg");
if (bg) bg.src = photoSrc;
document.getElementById("file-photo").value = "";
});
/* 打印 / 预览 */
document.getElementById("btn-print").addEventListener("click", function () { window.print(); });
document.getElementById("btn-preview-e").addEventListener("click", function () { window.print(); });
document.getElementById("btn-preview-p").addEventListener("click", function () { window.print(); });
render();
</script>
</body>
</html>