mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
460 lines
11 KiB
HTML
460 lines
11 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>课程表组件 Mock - 阑山桌面</title>
|
|||
|
|
<style>
|
|||
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|||
|
|
|
|||
|
|
:root {
|
|||
|
|
--bg-primary: #F7F8FC;
|
|||
|
|
--bg-secondary: #ECEFF6;
|
|||
|
|
--bg-card: #FFFFFF;
|
|||
|
|
--text-primary: #151821;
|
|||
|
|
--text-secondary: #667084;
|
|||
|
|
--text-muted: #9AA3B2;
|
|||
|
|
--border-color: rgba(0,0,0,0.06);
|
|||
|
|
--surface-raised: #FFFFFF;
|
|||
|
|
--accent: #FF4D5A;
|
|||
|
|
--time-color: #848B99;
|
|||
|
|
--divider-color: rgba(0,0,0,0.04);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dark {
|
|||
|
|
--bg-primary: #171A21;
|
|||
|
|
--bg-secondary: #0C0E14;
|
|||
|
|
--bg-card: rgba(255,255,255,0.04);
|
|||
|
|
--text-primary: #F9FBFF;
|
|||
|
|
--text-secondary: #848B99;
|
|||
|
|
--text-muted: #5A6170;
|
|||
|
|
--border-color: rgba(255,255,255,0.08);
|
|||
|
|
--surface-raised: #1E2230;
|
|||
|
|
--accent: #4FC3F7;
|
|||
|
|
--time-color: #6B7280;
|
|||
|
|
--divider-color: rgba(255,255,255,0.04);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
body {
|
|||
|
|
font-family: -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
|||
|
|
background: #1a1a2e;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 32px;
|
|||
|
|
padding: 40px 20px;
|
|||
|
|
min-height: 100vh;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 16px;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls button {
|
|||
|
|
padding: 8px 20px;
|
|||
|
|
border: 1px solid rgba(255,255,255,0.2);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
background: rgba(255,255,255,0.1);
|
|||
|
|
color: #fff;
|
|||
|
|
cursor: pointer;
|
|||
|
|
font-size: 14px;
|
|||
|
|
transition: all 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls button:hover { background: rgba(255,255,255,0.2); }
|
|||
|
|
.controls button.active { background: rgba(79,195,247,0.3); border-color: #4FC3F7; }
|
|||
|
|
|
|||
|
|
.mock-container {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 32px;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
justify-content: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.widget {
|
|||
|
|
width: 320px;
|
|||
|
|
border-radius: 24px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
background: linear-gradient(135deg, var(--bg-primary), var(--bg-secondary));
|
|||
|
|
border: 1px solid var(--border-color);
|
|||
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.12);
|
|||
|
|
transition: all 0.3s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.widget.large {
|
|||
|
|
width: 380px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.widget-header {
|
|||
|
|
padding: 16px 16px 10px 16px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.date-group {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: baseline;
|
|||
|
|
gap: 1px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.date-group .month, .date-group .day {
|
|||
|
|
font-size: 32px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
color: var(--text-primary);
|
|||
|
|
line-height: 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.date-group .slash {
|
|||
|
|
font-size: 32px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
color: var(--accent);
|
|||
|
|
line-height: 1;
|
|||
|
|
margin: 0 1px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.header-center {
|
|||
|
|
flex: 1;
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.weekday {
|
|||
|
|
font-size: 15px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: var(--text-secondary);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.class-count-badge {
|
|||
|
|
padding: 4px 10px;
|
|||
|
|
border-radius: 10px;
|
|||
|
|
background: rgba(79,195,247,0.12);
|
|||
|
|
font-size: 13px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: var(--accent);
|
|||
|
|
white-space: nowrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dark .class-count-badge {
|
|||
|
|
background: rgba(79,195,247,0.15);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.course-list {
|
|||
|
|
padding: 4px 12px 12px 12px;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.course-item {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: 44px 1fr;
|
|||
|
|
gap: 8px;
|
|||
|
|
align-items: stretch;
|
|||
|
|
min-height: 56px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.time-column {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: flex-end;
|
|||
|
|
padding-right: 4px;
|
|||
|
|
gap: 2px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.time-start {
|
|||
|
|
font-size: 12px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: var(--time-color);
|
|||
|
|
line-height: 1.2;
|
|||
|
|
font-variant-numeric: tabular-nums;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.time-end {
|
|||
|
|
font-size: 10px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: var(--text-muted);
|
|||
|
|
line-height: 1.2;
|
|||
|
|
font-variant-numeric: tabular-nums;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.course-card {
|
|||
|
|
border-radius: 12px;
|
|||
|
|
padding: 10px 12px;
|
|||
|
|
position: relative;
|
|||
|
|
overflow: hidden;
|
|||
|
|
transition: all 0.2s;
|
|||
|
|
min-height: 52px;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 2px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.course-card.current {
|
|||
|
|
min-height: 60px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.course-card .accent-bar {
|
|||
|
|
position: absolute;
|
|||
|
|
left: 0;
|
|||
|
|
top: 0;
|
|||
|
|
bottom: 0;
|
|||
|
|
width: 3px;
|
|||
|
|
border-radius: 0 2px 2px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.course-name {
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 700;
|
|||
|
|
line-height: 1.3;
|
|||
|
|
padding-left: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.course-detail {
|
|||
|
|
font-size: 11px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: var(--text-secondary);
|
|||
|
|
line-height: 1.3;
|
|||
|
|
padding-left: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.progress-container {
|
|||
|
|
margin-top: 4px;
|
|||
|
|
padding-left: 4px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.progress-bar {
|
|||
|
|
flex: 1;
|
|||
|
|
height: 3px;
|
|||
|
|
border-radius: 2px;
|
|||
|
|
background: rgba(255,255,255,0.15);
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dark .progress-bar {
|
|||
|
|
background: rgba(255,255,255,0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.progress-fill {
|
|||
|
|
height: 100%;
|
|||
|
|
border-radius: 2px;
|
|||
|
|
transition: width 0.5s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.progress-text {
|
|||
|
|
font-size: 10px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
min-width: 28px;
|
|||
|
|
text-align: right;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.break-indicator {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: 44px 1fr;
|
|||
|
|
gap: 8px;
|
|||
|
|
padding: 2px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.break-line {
|
|||
|
|
grid-column: 2;
|
|||
|
|
border-top: 1px dashed var(--divider-color);
|
|||
|
|
margin: 2px 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status-empty {
|
|||
|
|
padding: 40px 16px;
|
|||
|
|
text-align: center;
|
|||
|
|
color: var(--text-muted);
|
|||
|
|
font-size: 14px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.label {
|
|||
|
|
color: #aaa;
|
|||
|
|
font-size: 13px;
|
|||
|
|
text-align: center;
|
|||
|
|
margin-top: -16px;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
|
|||
|
|
<div class="controls">
|
|||
|
|
<button id="btnLight" class="active" onclick="setTheme('light')">☀️ 亮色</button>
|
|||
|
|
<button id="btnDark" onclick="setTheme('dark')">🌙 暗色</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="mock-container">
|
|||
|
|
<div>
|
|||
|
|
<div class="widget" id="widgetLight">
|
|||
|
|
<div class="widget-header">
|
|||
|
|
<div class="date-group">
|
|||
|
|
<span class="month">7</span>
|
|||
|
|
<span class="slash">/</span>
|
|||
|
|
<span class="day">24</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="header-center">
|
|||
|
|
<span class="weekday">周一</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="class-count-badge">6节课</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="course-list" id="courseListLight"></div>
|
|||
|
|
</div>
|
|||
|
|
<p class="label">2×4 标准尺寸</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<div class="widget large" id="widgetDark">
|
|||
|
|
<div class="widget-header">
|
|||
|
|
<div class="date-group">
|
|||
|
|
<span class="month">7</span>
|
|||
|
|
<span class="slash">/</span>
|
|||
|
|
<span class="day">24</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="header-center">
|
|||
|
|
<span class="weekday">周一</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="class-count-badge">6节课</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="course-list" id="courseListDark"></div>
|
|||
|
|
</div>
|
|||
|
|
<p class="label">4×4 大尺寸</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
const SUBJECT_COLORS = {
|
|||
|
|
'语文': '#5B8FF9',
|
|||
|
|
'数学': '#F6903D',
|
|||
|
|
'英语': '#5AD8A6',
|
|||
|
|
'物理': '#E8684A',
|
|||
|
|
'化学': '#9270CA',
|
|||
|
|
'生物': '#FF9845',
|
|||
|
|
'历史': '#1E9493',
|
|||
|
|
'地理': '#FF99C3',
|
|||
|
|
'政治': '#7262FD',
|
|||
|
|
'体育': '#78D3F8',
|
|||
|
|
'音乐': '#F25E7E',
|
|||
|
|
'美术': '#C2A1FD',
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const DEFAULT_COLOR = '#8B95A5';
|
|||
|
|
|
|||
|
|
function getColor(name) {
|
|||
|
|
for (const [key, val] of Object.entries(SUBJECT_COLORS)) {
|
|||
|
|
if (name.includes(key)) return val;
|
|||
|
|
}
|
|||
|
|
let hash = 5381;
|
|||
|
|
for (let i = 0; i < name.length; i++) hash = ((hash << 5) + hash) ^ name.charCodeAt(i);
|
|||
|
|
const keys = Object.keys(SUBJECT_COLORS);
|
|||
|
|
return SUBJECT_COLORS[keys[Math.abs(hash) % keys.length]];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function hexToRgba(hex, alpha) {
|
|||
|
|
const r = parseInt(hex.slice(1,3), 16);
|
|||
|
|
const g = parseInt(hex.slice(3,5), 16);
|
|||
|
|
const b = parseInt(hex.slice(5,7), 16);
|
|||
|
|
return `rgba(${r},${g},${b},${alpha})`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const courses = [
|
|||
|
|
{ name: '语文', start: '08:00', end: '08:45', detail: '王老师 · 教室301', isCurrent: false, progress: 0 },
|
|||
|
|
{ name: '数学', start: '08:55', end: '09:40', detail: '李老师 · 教室205', isCurrent: true, progress: 0.62 },
|
|||
|
|
{ name: '英语', start: '09:50', end: '10:35', detail: '张老师 · 教室108', isCurrent: false, progress: 0 },
|
|||
|
|
{ name: '物理', start: '10:45', end: '11:30', detail: '赵老师 · 实验室2', isCurrent: false, progress: 0 },
|
|||
|
|
{ name: '化学', start: '14:00', end: '14:45', detail: '陈老师 · 实验室1', isCurrent: false, progress: 0 },
|
|||
|
|
{ name: '生物', start: '14:55', end: '15:40', detail: '刘老师 · 教室303', isCurrent: false, progress: 0 },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
function renderCourseList(containerId, isDark) {
|
|||
|
|
const container = document.getElementById(containerId);
|
|||
|
|
container.innerHTML = '';
|
|||
|
|
|
|||
|
|
courses.forEach((course, idx) => {
|
|||
|
|
const color = getColor(course.name);
|
|||
|
|
const bgAlpha = course.isCurrent ? 0.15 : 0.07;
|
|||
|
|
const bgColor = hexToRgba(color, bgAlpha);
|
|||
|
|
const fgColor = isDark
|
|||
|
|
? hexToRgba(color, 1).replace('rgb', 'rgb').replace(')', ',1)') || color
|
|||
|
|
: color;
|
|||
|
|
|
|||
|
|
if (idx > 0) {
|
|||
|
|
const breakEl = document.createElement('div');
|
|||
|
|
breakEl.className = 'break-indicator';
|
|||
|
|
breakEl.innerHTML = `<div></div><div class="break-line"></div>`;
|
|||
|
|
container.appendChild(breakEl);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const item = document.createElement('div');
|
|||
|
|
item.className = 'course-item';
|
|||
|
|
|
|||
|
|
const timeCol = document.createElement('div');
|
|||
|
|
timeCol.className = 'time-column';
|
|||
|
|
timeCol.innerHTML = `
|
|||
|
|
<span class="time-start">${course.start}</span>
|
|||
|
|
<span class="time-end">${course.end}</span>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
const card = document.createElement('div');
|
|||
|
|
card.className = 'course-card' + (course.isCurrent ? ' current' : '');
|
|||
|
|
card.style.background = bgColor;
|
|||
|
|
|
|||
|
|
let cardHTML = '';
|
|||
|
|
if (course.isCurrent) {
|
|||
|
|
cardHTML += `<div class="accent-bar" style="background:${color}"></div>`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cardHTML += `<div class="course-name" style="color:${fgColor}">${course.name}</div>`;
|
|||
|
|
cardHTML += `<div class="course-detail">${course.detail}</div>`;
|
|||
|
|
|
|||
|
|
if (course.isCurrent) {
|
|||
|
|
const pct = Math.round(course.progress * 100);
|
|||
|
|
cardHTML += `
|
|||
|
|
<div class="progress-container">
|
|||
|
|
<div class="progress-bar">
|
|||
|
|
<div class="progress-fill" style="width:${pct}%;background:${color}"></div>
|
|||
|
|
</div>
|
|||
|
|
<span class="progress-text" style="color:${fgColor}">${pct}%</span>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
card.innerHTML = cardHTML;
|
|||
|
|
item.appendChild(timeCol);
|
|||
|
|
item.appendChild(card);
|
|||
|
|
container.appendChild(item);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function setTheme(theme) {
|
|||
|
|
const btnLight = document.getElementById('btnLight');
|
|||
|
|
const btnDark = document.getElementById('btnDark');
|
|||
|
|
const widgetLight = document.getElementById('widgetLight');
|
|||
|
|
const widgetDark = document.getElementById('widgetDark');
|
|||
|
|
|
|||
|
|
if (theme === 'dark') {
|
|||
|
|
btnDark.classList.add('active');
|
|||
|
|
btnLight.classList.remove('active');
|
|||
|
|
widgetLight.classList.add('dark');
|
|||
|
|
widgetDark.classList.add('dark');
|
|||
|
|
} else {
|
|||
|
|
btnLight.classList.add('active');
|
|||
|
|
btnDark.classList.remove('active');
|
|||
|
|
widgetLight.classList.remove('dark');
|
|||
|
|
widgetDark.classList.remove('dark');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
renderCourseList('courseListLight', theme === 'dark');
|
|||
|
|
renderCourseList('courseListDark', theme === 'dark');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
renderCourseList('courseListLight', false);
|
|||
|
|
renderCourseList('courseListDark', false);
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|