暂借lettes服务
Vercel Deploy / deploy (push) Failing after 2m39s

This commit is contained in:
2026-04-16 20:14:01 +08:00
Unverified
parent f2fc35d2d7
commit f956f53cf7
+312
View File
@@ -0,0 +1,312 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>墨水屏倒计时 Pro - 阵列卡片版</title>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<style>
:root { --ep-black: #000000; --ep-red: #ff0000; --ep-white: #ffffff; }
body { font-family: "PingFang SC", "STHeiti", sans-serif; background: #f0f2f5; display: flex; flex-direction: column; align-items: center; padding: 20px; gap: 20px; }
/* --- 4.2寸预览区 (400x300) --- */
#capture-area {
width: 400px; height: 300px;
background: var(--ep-white);
border: 1px solid #000;
display: flex; flex-direction: column;
box-sizing: border-box; overflow: hidden;
position: relative;
}
.top-date {
position: absolute;
top: 8px; right: 12px;
font-size: 10px;
color: var(--ep-black);
font-weight: bold;
text-align: right;
line-height: 1.2;
z-index: 10;
}
.header {
text-align: left;
padding: 15px 0 10px 20px;
flex-shrink: 0;
}
#title-text {
color: var(--ep-black);
border-left: 5px solid var(--ep-red);
display: inline-block;
padding: 0 10px;
font-weight: 900;
margin: 0;
font-size: 20px;
}
/* --- 阵列布局容器 --- */
.exam-grid-preview {
padding: 0 15px 15px 15px;
flex-grow: 1;
display: grid;
/* 默认两列排布 */
grid-template-columns: repeat(2, 1fr);
grid-auto-rows: 1fr;
gap: 10px;
}
/* --- 矩形卡片样式 --- */
.preview-card {
border: 2px solid var(--ep-black);
border-radius: 4px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 8px;
position: relative;
background: white;
text-align: center;
}
/* 重要考试:红色边框 + 填充效果 */
.is-important-card {
border: 3px solid var(--ep-red) !important;
}
.is-important-card::after {
content: "★";
position: absolute;
top: 2px; right: 4px;
color: var(--ep-red);
font-size: 14px;
}
.preview-name {
font-weight: bold;
color: var(--ep-black);
font-size: 14px;
margin-bottom: 4px;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.preview-days-val {
font-weight: 900;
color: var(--ep-red);
font-family: 'Arial Black', sans-serif;
font-size: 32px;
line-height: 1;
}
.preview-unit {
font-size: 12px;
color: var(--ep-black);
margin-left: 2px;
}
/* --- 管理区 --- */
.admin-panel { width: 520px; background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
.input-card { display: flex; gap: 8px; margin-bottom: 15px; align-items: center; background: #f8f9fa; padding: 12px; border-radius: 8px; }
.input-card input { padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
#sortable-list { list-style: none; padding: 0; margin: 0; }
.manage-item {
display: flex; align-items: center; gap: 12px;
background: #fff; border: 1px solid #eee; margin-bottom: 8px;
padding: 10px; border-radius: 6px; cursor: move;
}
.drag-handle { color: #ccc; cursor: grab; font-size: 20px; user-select: none; }
.manage-info { flex-grow: 1; display: flex; gap: 8px; }
.manage-info input[type="text"] { flex: 2; }
.btn-del { background: #ff4d4f; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; }
.action-btns { display: flex; gap: 10px; margin-top: 20px; }
.btn-main { flex: 1; padding: 12px; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; }
.btn-save { background: var(--ep-red); color: white; }
.btn-data { background: #555; color: white; }
</style>
</head>
<body>
<div id="capture-area">
<div class="top-date" id="live-date"></div>
<div class="header">
<h1 id="title-text">考试倒计时</h1>
</div>
<div class="exam-grid-preview" id="preview-container"></div>
</div>
<div class="admin-panel">
<h3 style="margin-top:0; border-left: 4px solid var(--ep-red); padding-left: 10px;">项目管理</h3>
<div class="input-card">
<input type="text" id="name-in" placeholder="项目名称">
<input type="date" id="date-in">
<label style="font-size:13px; cursor:pointer"><input type="checkbox" id="imp-in"> 重要</label>
<button onclick="addItem()" style="background:#000; color:white; border:none; padding:8px 15px; border-radius:4px; cursor:pointer">添加</button>
</div>
<ul id="sortable-list"></ul>
<div class="action-btns">
<button class="btn-main btn-data" onclick="exportJSON()">备份</button>
<button class="btn-main btn-data" onclick="document.getElementById('file-in').click()">恢复</button>
<input type="file" id="file-in" style="display:none" onchange="importJSON(this)">
<button class="btn-main btn-save" onclick="downloadImage()">保存 400x300 图片</button>
</div>
</div>
<script>
const getTodayStr = () => new Date().toISOString().split('T')[0];
// 默认数据
let exams = JSON.parse(localStorage.getItem('dragExamGridV1')) || [
{ name: "期末考试", date: "2026-06-20", imp: true },
{ name: "英语四级", date: "2026-06-15", imp: false },
{ name: "驾照科目一", date: "2026-05-10", imp: false },
{ name: "健身计划", date: getTodayStr(), imp: false }
];
function updateLiveDate() {
const now = new Date();
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
document.getElementById('live-date').innerHTML = `${now.getFullYear()}${now.getMonth() + 1}${now.getDate()}日<br>${weekDays[now.getDay()]}`;
}
Sortable.create(document.getElementById('sortable-list'), {
animation: 150, handle: '.drag-handle', onEnd: () => saveOrder()
});
function calculateDays(targetDate) {
const today = new Date().setHours(0,0,0,0);
const target = new Date(targetDate).setHours(0,0,0,0);
const diff = target - today;
return Math.ceil(diff / (1000 * 60 * 60 * 24));
}
function renderAll() {
renderPreview();
renderManageList();
updateLiveDate();
localStorage.setItem('dragExamGridV1', JSON.stringify(exams));
}
function renderPreview() {
const container = document.getElementById('preview-container');
container.innerHTML = '';
const count = exams.length;
// 根据数量动态调整列数
if (count <= 1) {
container.style.gridTemplateColumns = "1fr";
} else if (count > 4) {
container.style.gridTemplateColumns = "repeat(3, 1fr)";
} else {
container.style.gridTemplateColumns = "repeat(2, 1fr)";
}
exams.forEach(item => {
const days = calculateDays(item.date);
let dayHTML = '';
if (days === 0) {
dayHTML = `<span class="preview-days-val" style="font-size:24px">今天</span>`;
} else if (days < 0) {
dayHTML = `<span class="preview-days-val" style="font-size:24px; color:#666">已过</span>`;
} else {
dayHTML = `<span class="preview-days-val">${days}</span><span class="preview-unit">天</span>`;
}
container.innerHTML += `
<div class="preview-card ${item.imp ? 'is-important-card' : ''}">
<div class="preview-name">${item.name}</div>
<div class="preview-days-info">
${dayHTML}
</div>
</div>
`;
});
}
function renderManageList() {
const list = document.getElementById('sortable-list');
list.innerHTML = '';
exams.forEach((item, index) => {
list.innerHTML += `
<li class="manage-item" data-index="${index}">
<span class="drag-handle">☰</span>
<div class="manage-info">
<input type="text" value="${item.name}" onchange="updateItem(${index}, 'name', this.value)">
<input type="date" value="${item.date}" onchange="updateItem(${index}, 'date', this.value)">
<label><input type="checkbox" ${item.imp?'checked':''} onchange="updateItem(${index}, 'imp', this.checked)"> 重要</label>
</div>
<button class="btn-del" onclick="removeItem(${index})">×</button>
</li>
`;
});
}
function updateItem(index, key, value) {
exams[index][key] = value;
renderPreview();
localStorage.setItem('dragExamGridV1', JSON.stringify(exams));
}
function saveOrder() {
const newExams = [];
document.querySelectorAll('.manage-item').forEach(el => {
newExams.push(exams[el.getAttribute('data-index')]);
});
exams = newExams;
renderAll();
}
function addItem() {
const n = document.getElementById('name-in').value;
const d = document.getElementById('date-in').value;
const i = document.getElementById('imp-in').checked;
if(n && d) {
exams.push({name:n, date:d, imp:i});
renderAll();
document.getElementById('name-in').value = '';
}
}
function removeItem(index) {
exams.splice(index, 1);
renderAll();
}
function downloadImage() {
html2canvas(document.getElementById('capture-area'), {
width: 400,
height: 300,
scale: 2,
backgroundColor: "#ffffff"
})
.then(canvas => {
const link = document.createElement('a');
link.download = `E-Paper_Grid_${getTodayStr()}.png`;
link.href = canvas.toDataURL("image/png");
link.click();
});
}
function exportJSON() {
const blob = new Blob([JSON.stringify(exams)], {type: 'application/json'});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob); a.download = 'exams_config.json'; a.click();
}
function importJSON(input) {
const reader = new FileReader();
reader.onload = e => { exams = JSON.parse(e.target.result); renderAll(); };
reader.readAsText(input.files[0]);
}
renderAll();
</script>
</body>
</html>