来自吾爱论坛大神的杰作
html代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>拖拽定制课程表</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0 5%;
}
h1 {
text-align: center;
color: #333;
}
.controls {
margin-bottom: 20px;
display: flex;
justify-content: space-evenly;
align-items: center;
flex-wrap: wrap;
}
.subject-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
justify-content: center;
}
.subject-item {
padding: 8px 15px;
color: white;
border-radius: 4px;
cursor: move;
user-select: none;
position: relative;
}
.subject-item .edit-btn {
position: absolute;
top: -8px;
right: -8px;
background-color: #ff9800;
color: white;
border: none;
border-radius: 50%;
width: 16px;
height: 16px;
font-size: 10px;
cursor: pointer;
display: none;
}
.subject-item:hover .edit-btn {
display: block;
}
.timetable {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.timetable th,
.timetable td {
border: 1px solid #ddd;
padding: 6px;
text-align: center;
}
.timetable th {
background-color: #f2f2f2;
font-weight: bold;
}
.timetable td {
min-height: 50px;
height: 50px;
vertical-align: middle;
}
.time-cell {
font-size: 1.2rem;
width: 180px;
background-color: #f9f9f9;
white-space: pre-wrap;
word-wrap: break-word;
}
.section-time {
font-size: 1.4rem;
width: 60px;
background-color: #f0f0f0;
font-weight: bold;
writing-mode: vertical-lr;
text-orientation: upright;
}
.timetable-cell {
background-color: #fff;
position: relative;
}
.timetable-cell.dragover {
background-color: #e6f7ff;
}
.subject-in-cell {
font-size: 1.4rem;
color: white;
padding: 5px;
border-radius: 3px;
margin: 2px;
cursor: pointer;
position: relative;
}
.remove-btn {
position: absolute;
top: -8px;
right: -8px;
background-color: #ff5252;
color: white;
border: none;
border-radius: 50%;
width: 16px;
height: 16px;
font-size: 10px;
cursor: pointer;
display: none;
}
.subject-in-cell:hover .remove-btn {
display: block;
}
.add-subject {
display: flex;
gap: 10px;
align-items: center;
margin-bottom: 10px;
justify-content: center;
}
.add-subject input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
width: 200px;
}
.add-subject button {
padding: 8px 15px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.download-btns {
display: flex;
gap: 10px;
}
.download-btns button {
padding: 8px 15px;
background-color: #607d8b;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.edit-subject-dialog {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
z-index: 1000;
}
.edit-subject-dialog h3 {
margin-top: 0;
}
.edit-subject-dialog input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
width: 200px;
margin-bottom: 10px;
}
.edit-subject-dialog button {
padding: 8px 15px;
margin-right: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.edit-subject-dialog .save-btn {
background-color: #4CAF50;
color: white;
}
.edit-subject-dialog .cancel-btn {
background-color: #f44336;
color: white;
}
.edit-subject-dialog .delete-btn {
background-color: #ff9800;
color: white;
}
.time-controls {
display: flex;
gap: 10px;
align-items: center;
}
.time-controls select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.time-controls button {
padding: 8px 15px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.edit-error {
color: red;
margin-bottom: 10px;
display: none;
}
@media print {
body * {
visibility: hidden;
}
.timetable, .timetable th, .timetable td {
visibility: visible;
}
.timetable {
position: absolute;
left: 0;
top: 0;
width: 100%;
}
.timetable th,
.timetable td {
border: 1px solid #000;
background-color: transparent;
}
.subject-in-cell {
background-color: transparent !important;
border: none !important;
color: #000 !important;
font-weight: bold;
}
.remove-btn {
display: none !important;
}
}
.bw-version {
filter: grayscale(100%) contrast(120%);
}
.bw-version .timetable th,
.bw-version .timetable td {
background-color: transparent;
border-color: #000;
}
.bw-version .subject-in-cell {
background-color: transparent !important;
border: none !important;
color: #000 !important;
font-weight: bold;
}
</style>
</head>
<body>
<h1>拖拽定制课程表</h1>
<div class="add-subject">
<input type="text" id="newSubject" placeholder="输入新科目">
<button id="addSubjectBtn">添加科目</button>
</div>
<div class="subject-list" id="subjectList">
<!-- 科目将通过JavaScript动态生成 -->
</div>
<div class="controls">
<div class="time-controls">
<select id="timeSectionSelect">
<option value="morning">上午</option>
<option value="afternoon">下午</option>
</select>
<button id="addTimeBtn">添加课时</button>
<button id="removeTimeBtn">删除课时</button>
</div>
<div class="download-btns">
<button id="resetBtn">重置课程表</button>
<button id="downloadColorBtn">下载彩色课程表</button>
<button id="downloadBWBtn">下载黑白课程表</button>
</div>
</div>
<table class="timetable" id="timetable">
<thead>
<tr>
<th colspan="9" contenteditable="true" style="font-size:1.6rem; text-align: center;">课程表</th>
</tr>
<tr>
<th style="font-size:1.4rem; text-align: center;">时间</th>
<th style="font-size:1.4rem; text-align: center;">课时</th>
<th style="font-size:1.4rem; text-align: center;">星期一</th>
<th style="font-size:1.4rem; text-align: center;">星期二</th>
<th style="font-size:1.4rem; text-align: center;">星期三</th>
<th style="font-size:1.4rem; text-align: center;">星期四</th>
<th style="font-size:1.4rem; text-align: center;">星期五</th>
<th style="font-size:1.4rem; text-align: center;">星期六</th>
<th style="font-size:1.4rem; text-align: center;">星期日</th>
</tr>
</thead>
<tbody id="timetableBody">
<!-- 课时将通过JavaScript动态生成 -->
</tbody>
</table>
<div class="edit-subject-dialog" id="editSubjectDialog">
<h3>编辑科目</h3>
<input type="text" id="editSubjectInput">
<div class="edit-error" id="editError">科目已存在,请修改名称</div>
<div>
<button class="save-btn" id="saveSubjectBtn">保存</button>
<button class="delete-btn" id="deleteSubjectBtn">删除</button>
<button class="cancel-btn" id="cancelEditBtn">取消</button>
</div>
</div>
<footer style="text-align: center;">
<div>
<p>©Power by: ysjd22@52pojie | 99tonine@52pojie | black_hd@52pojie 修订优化</p>
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function () {
const subjectList = document.getElementById('subjectList');
const timetableBody = document.getElementById('timetableBody');
const addSubjectBtn = document.getElementById('addSubjectBtn');
const newSubjectInput = document.getElementById('newSubject');
const resetBtn = document.getElementById('resetBtn');
const downloadColorBtn = document.getElementById('downloadColorBtn');
const downloadBWBtn = document.getElementById('downloadBWBtn');
const editSubjectDialog = document.getElementById('editSubjectDialog');
const editSubjectInput = document.getElementById('editSubjectInput');
const editError = document.getElementById('editError');
const saveSubjectBtn = document.getElementById('saveSubjectBtn');
const deleteSubjectBtn = document.getElementById('deleteSubjectBtn');
const cancelEditBtn = document.getElementById('cancelEditBtn');
const timeSectionSelect = document.getElementById('timeSectionSelect');
const addTimeBtn = document.getElementById('addTimeBtn');
const removeTimeBtn = document.getElementById('removeTimeBtn');
// 课时数据
const timeData = {
morning: [
{ name: "第一节", time: "7:40-8:20" },
{ name: "第二节", time: "8:30-9:10" },
{ name: "第三节", time: "9:20-10:00" },
{ name: "第四节", time: "10:10-10:50" }
],
afternoon: [
{ name: "第五节", time: "14:00-14:40" },
{ name: "第六节", time: "14:50-15:30" },
{ name: "第七节", time: "15:40-16:20" },
{ name: "第八节", time: "16:30-17:10" }
]
};
// 科目颜色映射表
let subjectColors = {};
// 当前正在编辑的科目
let currentEditingSubject = null;
// 初始化应用
function init() {
// 从本地存储加载科目颜色
const savedColors = localStorage.getItem('subjectColors');
if (savedColors) {
subjectColors = JSON.parse(savedColors);
} else {
// 默认科目颜色
subjectColors = {
'语文': '#4CAF50',
'数学': '#2196F3',
'英语': '#FF9800',
'体育': '#E91E63',
'音乐': '#00BCD4',
'美术': '#8BC34A',
'电脑': '#CDDC39'
};
// 保存默认科目
localStorage.setItem('subjectColors', JSON.stringify(subjectColors));
}
// 初始化科目列表
initSubjectList();
// 加载其他数据
loadTimetable();
}
// 初始化课程表
function initTimetable() {
timetableBody.innerHTML = '';
// 添加上午课时
addTimeSection('morning', '上午');
// 添加下午课时
addTimeSection('afternoon', '下午');
}
// 添加时间段
function addTimeSection(section, sectionName) {
const times = timeData[section];
if (!times || times.length === 0) return;
const firstRow = document.createElement('tr');
firstRow.innerHTML = `
<td class="section-time" rowspan="${times.length}">${sectionName}</td>
<td class="time-cell" contenteditable="true">${times[0].name}<br>${times[0].time}</td>
<td class="timetable-cell" data-time="${section}1" data-day="mon"></td>
<td class="timetable-cell" data-time="${section}1" data-day="tue"></td>
<td class="timetable-cell" data-time="${section}1" data-day="wed"></td>
<td class="timetable-cell" data-time="${section}1" data-day="thu"></td>
<td class="timetable-cell" data-time="${section}1" data-day="fri"></td>
<td class="timetable-cell" data-time="${section}1" data-day="sat"></td>
<td class="timetable-cell" data-time="${section}1" data-day="sun"></td>
`;
timetableBody.appendChild(firstRow);
// 添加剩余课时
for (let i = 1; i < times.length; i++) {
const row = document.createElement('tr');
row.innerHTML = `
<td class="time-cell" contenteditable="true">${times[i].name}<br>${times[i].time}</td>
<td class="timetable-cell" data-time="${section}${i+1}" data-day="mon"></td>
<td class="timetable-cell" data-time="${section}${i+1}" data-day="tue"></td>
<td class="timetable-cell" data-time="${section}${i+1}" data-day="wed"></td>
<td class="timetable-cell" data-time="${section}${i+1}" data-day="thu"></td>
<td class="timetable-cell" data-time="${section}${i+1}" data-day="fri"></td>
<td class="timetable-cell" data-time="${section}${i+1}" data-day="sat"></td>
<td class="timetable-cell" data-time="${section}${i+1}" data-day="sun"></td>
`;
timetableBody.appendChild(row);
}
// 设置单元格事件
document.querySelectorAll('.timetable-cell').forEach(cell => {
setupCellEvents(cell);
});
// 设置时间单元格事件
document.querySelectorAll('.time-cell').forEach(cell => {
cell.addEventListener('blur', saveTimeData);
});
}
// 初始化科目列表
function initSubjectList() {
subjectList.innerHTML = '';
for (const subject in subjectColors) {
createSubjectItem(subject);
}
}
// 创建科目项
function createSubjectItem(subject) {
const newSubject = document.createElement('div');
newSubject.className = 'subject-item';
newSubject.textContent = subject;
newSubject.setAttribute('draggable', 'true');
newSubject.style.backgroundColor = subjectColors[subject];
// 添加编辑按钮
const editBtn = document.createElement('button');
editBtn.className = 'edit-btn';
editBtn.textContent = '✎';
editBtn.addEventListener('click', function(e) {
e.stopPropagation();
openEditDialog(subject);
});
newSubject.appendChild(editBtn);
// 设置拖拽事件 - 只拖拽文本内容
newSubject.addEventListener('dragstart', function (e) {
e.dataTransfer.setData('text/plain', subject);
e.dataTransfer.effectAllowed = 'copy';
this.style.opacity = '0.5';
});
newSubject.addEventListener('dragend', function () {
this.style.opacity = '1';
});
subjectList.appendChild(newSubject);
}
// 打开编辑对话框
function openEditDialog(subject) {
currentEditingSubject = subject;
editSubjectInput.value = subject;
editError.style.display = 'none';
editSubjectDialog.style.display = 'block';
editSubjectInput.focus();
}
// 关闭编辑对话框
function closeEditDialog() {
editSubjectDialog.style.display = 'none';
currentEditingSubject = null;
editError.style.display = 'none';
}
// 添加/更新科目
addSubjectBtn.addEventListener('click', function () {
const subjectName = newSubjectInput.value.trim();
if (subjectName) {
if (subjectColors[subjectName]) {
alert('科目已存在');
return;
}
// 为新科目分配颜色
const colors = ['#4CAF50', '#2196F3', '#FF9800', '#9C27B0',
'#F44336', '#607D8B', '#795548', '#009688',
'#3F51B5', '#E91E63', '#00BCD4', '#8BC34A', '#CDDC39'];
const usedColors = Object.values(subjectColors);
const availableColors = colors.filter(c => !usedColors.includes(c));
subjectColors[subjectName] = availableColors.length > 0
? availableColors[0]
: `#${Math.floor(Math.random()*16777215).toString(16)}`;
// 保存科目颜色
localStorage.setItem('subjectColors', JSON.stringify(subjectColors));
// 重新初始化科目列表
initSubjectList();
newSubjectInput.value = '';
}
});
// 保存科目编辑
saveSubjectBtn.addEventListener('click', function() {
const newName = editSubjectInput.value.trim();
if (!newName || !currentEditingSubject) return;
// 检查科目是否已存在(除了当前编辑的科目)
if (newName !== currentEditingSubject && subjectColors[newName]) {
editError.style.display = 'block';
return;
}
// 如果名称没变化,直接关闭对话框
if (newName === currentEditingSubject) {
closeEditDialog();
return;
}
// 保存旧颜色
const oldColor = subjectColors[currentEditingSubject];
// 更新颜色映射
subjectColors[newName] = oldColor;
delete subjectColors[currentEditingSubject];
// 保存科目颜色
localStorage.setItem('subjectColors', JSON.stringify(subjectColors));
// 更新科目列表中的科目
initSubjectList();
// 更新课程表中的科目
document.querySelectorAll('.subject-in-cell').forEach(cell => {
if (cell.textContent.replace('×', '').trim() === currentEditingSubject) {
cell.textContent = newName;
cell.style.backgroundColor = oldColor;
// 更新删除按钮
const removeBtn = cell.querySelector('.remove-btn');
if (removeBtn) {
const newRemoveBtn = removeBtn.cloneNode(true);
cell.removeChild(removeBtn);
newRemoveBtn.addEventListener('click', function(e) {
e.stopPropagation();
cell.parentNode.removeChild(cell);
saveTimetable();
});
cell.appendChild(newRemoveBtn);
}
}
});
closeEditDialog();
saveTimetable();
});
// 删除科目
deleteSubjectBtn.addEventListener('click', function() {
if (!currentEditingSubject) return;
if (!confirm(`确定要删除科目"${currentEditingSubject}"吗?`)) {
return;
}
// 从颜色映射中删除
delete subjectColors[currentEditingSubject];
// 保存科目颜色
localStorage.setItem('subjectColors', JSON.stringify(subjectColors));
// 从课程表中删除所有该科目
document.querySelectorAll('.subject-in-cell').forEach(cell => {
if (cell.textContent.replace('×', '').trim() === currentEditingSubject) {
cell.parentNode.removeChild(cell);
}
});
// 重新初始化科目列表
initSubjectList();
closeEditDialog();
saveTimetable();
});
// 取消编辑
cancelEditBtn.addEventListener('click', closeEditDialog);
// 回车键添加科目
newSubjectInput.addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
addSubjectBtn.click();
}
});
// 重置课程表
resetBtn.addEventListener('click', function () {
if (confirm('确定要重置整个课程表吗?所有课程安排将被清除。')) {
document.querySelectorAll('.timetable-cell').forEach(cell => {
cell.innerHTML = '';
});
localStorage.removeItem('timetableData');
localStorage.removeItem('timeData');
}
});
// 保存时间修改
function saveTimeData() {
const timeCells = document.querySelectorAll('.time-cell');
const timeTextData = Array.from(timeCells).map(cell => cell.innerHTML);
localStorage.setItem('timeData', JSON.stringify(timeTextData));
}
// 实现下载课程表函数
function downloadTimetable(isBW) {
const timetable = document.getElementById('timetable');
if (isBW) {
timetable.classList.add('bw-version');
const subjectInCells = timetable.querySelectorAll('.subject-in-cell');
subjectInCells.forEach(cell => {
cell.style.backgroundColor = 'transparent';
cell.style.border = 'none';
});
const tableCells = timetable.querySelectorAll('th, td');
tableCells.forEach(cell => {
cell.style.backgroundColor = 'transparent';
});
}
// 使用 html2canvas 库将表格转换为图片
html2canvas(timetable, { useCORS: true }).then(canvas => {
if (isBW) {
timetable.classList.remove('bw-version');
const subjectInCells = timetable.querySelectorAll('.subject-in-cell');
subjectInCells.forEach(cell => {
cell.style.backgroundColor = '';
cell.style.border = '';
});
const tableCells = timetable.querySelectorAll('th, td');
tableCells.forEach(cell => {
cell.style.backgroundColor = '';
});
}
// 创建下载链接
const link = document.createElement('a');
link.download = isBW ? '黑白课程表.png' : '彩色课程表.png';
link.href = canvas.toDataURL();
link.click();
});
}
// 彩色版本下载
downloadColorBtn.addEventListener('click', function () {
downloadTimetable(false);
});
// 黑白版本下载 - 修改为不改变原课程表样式
downloadBWBtn.addEventListener('click', function () {
// 创建一个临时副本用于下载
const timetable = document.getElementById('timetable');
const clone = timetable.cloneNode(true);
clone.classList.add('bw-version');
// 隐藏原始表格
timetable.style.visibility = 'hidden';
// 将副本添加到body中
clone.style.position = 'absolute';
clone.style.left = '-9999px';
document.body.appendChild(clone);
// 使用html2canvas转换副本
html2canvas(clone, { useCORS: true }).then(canvas => {
// 创建下载链接
const link = document.createElement('a');
link.download = '黑白课程表.png';
link.href = canvas.toDataURL();
link.click();
// 移除副本并恢复原始表格
document.body.removeChild(clone);
timetable.style.visibility = 'visible';
});
});
// 设置单元格事件
function setupCellEvents(cell) {
cell.addEventListener('dragover', function (e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
this.classList.add('dragover');
});
cell.addEventListener('dragleave', function () {
this.classList.remove('dragover');
});
cell.addEventListener('drop', function (e) {
e.preventDefault();
this.classList.remove('dragover');
const subjectName = e.dataTransfer.getData('text/plain');
if (subjectName) {
addSubjectToCell(this, subjectName);
saveTimetable();
}
});
}
// 添加科目到单元格
function addSubjectToCell(cell, subjectName) {
// 检查是否已存在相同科目
const existingSubjects = cell.querySelectorAll('.subject-in-cell');
for (let i = 0; i < existingSubjects.length; i++) {
if (existingSubjects[i].textContent.replace('×', '').trim() === subjectName) {
return; // 如果已存在相同科目,则不添加
}
}
const subjectDiv = document.createElement('div');
subjectDiv.className = 'subject-in-cell';
subjectDiv.textContent = subjectName;
subjectDiv.style.backgroundColor = subjectColors[subjectName] || '#4CAF50';
const removeBtn = document.createElement('button');
removeBtn.className = 'remove-btn';
removeBtn.textContent = '×';
removeBtn.addEventListener('click', function (e) {
e.stopPropagation();
cell.removeChild(subjectDiv);
saveTimetable();
});
subjectDiv.appendChild(removeBtn);
cell.appendChild(subjectDiv);
}
// 保存课程表数据到本地存储
function saveTimetable() {
const timetableData = {};
document.querySelectorAll('.timetable-cell').forEach(cell => {
const time = cell.getAttribute('data-time');
const day = cell.getAttribute('data-day');
const subjects = [];
cell.querySelectorAll('.subject-in-cell').forEach(subjectDiv => {
subjects.push(subjectDiv.textContent.replace('×', '').trim());
});
if (!timetableData[day]) {
timetableData[day] = {};
}
timetableData[day][time] = subjects;
});
localStorage.setItem('timetableData', JSON.stringify(timetableData));
localStorage.setItem('timeConfig', JSON.stringify(timeData));
}
// 从本地存储加载课程表数据
function loadTimetable() {
// 加载课时配置
const savedTimeConfig = localStorage.getItem('timeConfig');
if (savedTimeConfig) {
const config = JSON.parse(savedTimeConfig);
if (config.morning) timeData.morning = config.morning;
if (config.afternoon) timeData.afternoon = config.afternoon;
}
const savedData = localStorage.getItem('timetableData');
const savedTimeData = localStorage.getItem('timeData');
// 初始化课程表
initTimetable();
if (savedData) {
const timetableData = JSON.parse(savedData);
for (const day in timetableData) {
for (const time in timetableData[day]) {
const cell = document.querySelector(`.timetable-cell[data-day="${day}"][data-time="${time}"]`);
if (cell) {
timetableData[day][time].forEach(subject => {
addSubjectToCell(cell, subject);
});
}
}
}
}
if (savedTimeData) {
const timeTextData = JSON.parse(savedTimeData);
const timeCells = document.querySelectorAll('.time-cell');
timeTextData.forEach((timeText, index) => {
if (timeCells[index]) {
timeCells[index].innerHTML = timeText;
}
});
}
}
// 添加课时
addTimeBtn.addEventListener('click', function() {
const section = timeSectionSelect.value;
const times = timeData[section];
const newIndex = times.length + 1;
// 添加新的课时数据
times.push({
name: `第${numberToChinese(newIndex)}节`,
time: "点击编辑时间"
});
// 重新初始化课程表
initTimetable();
saveTimetable();
});
// 删除课时
removeTimeBtn.addEventListener('click', function() {
const section = timeSectionSelect.value;
const times = timeData[section];
if (times.length <= 1) {
alert('至少保留一个课时');
return;
}
if (!confirm(`确定要删除${section === 'morning' ? '上午' : '下午'}的最后一个课时吗?`)) {
return;
}
// 删除最后一个课时
times.pop();
// 重新初始化课程表
initTimetable();
saveTimetable();
});
// 数字转中文
function numberToChinese(num) {
const chineseNums = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
if (num <= 10) return chineseNums[num];
if (num < 20) return '十' + (num % 10 === 0 ? '' : chineseNums[num % 10]);
return chineseNums[Math.floor(num / 10)] + '十' + (num % 10 === 0 ? '' : chineseNums[num % 10]);
}
// 点击单元格时显示删除按钮
document.addEventListener('click', function (e) {
if (e.target.classList.contains('timetable-cell')) {
e.target.querySelectorAll('.remove-btn').forEach(btn => {
btn.style.display = 'block';
setTimeout(() => {
btn.style.display = 'none';
}, 2000);
});
}
});
// 初始化应用
init();
});
</script>
<script src="https://www.codeae.com/data/attachment/forum/202507/20/ke.js"></script>
</body>
</html>
|