Vercel Deploy / deploy (push) Successful in 1m10s

This commit is contained in:
2026-04-17 18:31:14 +08:00
Unverified
parent 11f0f030b4
commit 61da354a51
3 changed files with 412 additions and 54 deletions
+50 -54
View File
@@ -2,7 +2,7 @@
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>墨水屏倒计时 Pro - 阵列卡片</title> <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/sortablejs@1.15.0/Sortable.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<style> <style>
@@ -32,7 +32,7 @@
.header { .header {
text-align: left; text-align: left;
padding: 15px 0 10px 20px; padding: 12px 0 8px 15px;
flex-shrink: 0; flex-shrink: 0;
} }
#title-text { #title-text {
@@ -42,68 +42,68 @@
padding: 0 10px; padding: 0 10px;
font-weight: 900; font-weight: 900;
margin: 0; margin: 0;
font-size: 20px; font-size: 18px;
} }
/* --- 阵列布局容器 --- */ /* --- 阵列布局容器 --- */
.exam-grid-preview { .exam-grid-preview {
padding: 0 15px 15px 15px; padding: 0 12px 12px 12px;
flex-grow: 1; flex-grow: 1;
display: grid; display: grid;
/* 默认两列排布 */
grid-template-columns: repeat(2, 1fr);
grid-auto-rows: 1fr;
gap: 10px; gap: 10px;
align-content: stretch;
} }
/* --- 矩形卡片样式 --- */ /* --- 矩形卡片样式 --- */
.preview-card { .preview-card {
border: 2px solid var(--ep-black); border: 2px solid var(--ep-black);
border-radius: 4px; border-radius: 6px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 8px; padding: 10px;
position: relative; position: relative;
background: white; background: white;
text-align: center; text-align: center;
box-sizing: border-box;
} }
/* 重要考试:红色边框 + 填充效果 */
.is-important-card { .is-important-card {
border: 3px solid var(--ep-red) !important; border: 4px solid var(--ep-red) !important;
} }
.is-important-card::after { .is-important-card::after {
content: "★"; content: "★";
position: absolute; position: absolute;
top: 2px; right: 4px; top: 4px; right: 6px;
color: var(--ep-red); color: var(--ep-red);
font-size: 14px;
} }
.preview-name { .preview-name {
font-weight: bold; font-weight: bold;
color: var(--ep-black); color: var(--ep-black);
font-size: 14px; margin-bottom: 5px;
margin-bottom: 4px;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.preview-days-info {
display: flex;
align-items: baseline;
justify-content: center;
}
.preview-days-val { .preview-days-val {
font-weight: 900; font-weight: 900;
color: var(--ep-red); color: var(--ep-red);
font-family: 'Arial Black', sans-serif; font-family: 'Arial Black', sans-serif;
font-size: 32px;
line-height: 1; line-height: 1;
} }
.preview-unit { .preview-unit {
font-size: 12px; font-weight: bold;
color: var(--ep-black); color: var(--ep-black);
margin-left: 2px; margin-left: 3px;
} }
/* --- 管理区 --- */ /* --- 管理区 --- */
@@ -133,7 +133,7 @@
<div id="capture-area"> <div id="capture-area">
<div class="top-date" id="live-date"></div> <div class="top-date" id="live-date"></div>
<div class="header"> <div class="header">
<h1 id="title-text">考试倒计时</h1> <h1 id="title-text">倒计时看板</h1>
</div> </div>
<div class="exam-grid-preview" id="preview-container"></div> <div class="exam-grid-preview" id="preview-container"></div>
</div> </div>
@@ -161,12 +161,9 @@
<script> <script>
const getTodayStr = () => new Date().toISOString().split('T')[0]; const getTodayStr = () => new Date().toISOString().split('T')[0];
// 默认数据 let exams = JSON.parse(localStorage.getItem('dragExamGridV2')) || [
let exams = JSON.parse(localStorage.getItem('dragExamGridV1')) || [ { name: "期末大考", date: "2026-06-20", imp: true },
{ name: "期末考试", date: "2026-06-20", imp: true }, { name: "驾照预约", date: getTodayStr(), imp: false }
{ name: "英语四级", date: "2026-06-15", imp: false },
{ name: "驾照科目一", date: "2026-05-10", imp: false },
{ name: "健身计划", date: getTodayStr(), imp: false }
]; ];
function updateLiveDate() { function updateLiveDate() {
@@ -190,7 +187,7 @@
renderPreview(); renderPreview();
renderManageList(); renderManageList();
updateLiveDate(); updateLiveDate();
localStorage.setItem('dragExamGridV1', JSON.stringify(exams)); localStorage.setItem('dragExamGridV2', JSON.stringify(exams));
} }
function renderPreview() { function renderPreview() {
@@ -198,13 +195,23 @@
container.innerHTML = ''; container.innerHTML = '';
const count = exams.length; const count = exams.length;
// 根据数量动态调整列数 // 1. 动态确定网格布局
let cols = 2;
if (count === 1) cols = 1;
else if (count > 4) cols = 3;
container.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
// 2. 根据数量动态计算字体大小 (单位 px)
// 逻辑:项目越少,字号越大
let baseNameSize, baseNumSize, starSize;
if (count <= 1) { if (count <= 1) {
container.style.gridTemplateColumns = "1fr"; baseNameSize = 64; baseNumSize = 80; starSize = 36;
} else if (count > 4) { } else if (count <= 2) {
container.style.gridTemplateColumns = "repeat(3, 1fr)"; baseNameSize = 24; baseNumSize = 64; starSize = 20;
} else if (count <= 4) {
baseNameSize = 18; baseNumSize = 48; starSize = 16;
} else { } else {
container.style.gridTemplateColumns = "repeat(2, 1fr)"; baseNameSize = 14; baseNumSize = 32; starSize = 14;
} }
exams.forEach(item => { exams.forEach(item => {
@@ -212,16 +219,18 @@
let dayHTML = ''; let dayHTML = '';
if (days === 0) { if (days === 0) {
dayHTML = `<span class="preview-days-val" style="font-size:24px">今天</span>`; dayHTML = `<span class="preview-days-val" style="font-size:${baseNumSize * 0.6}px">今天</span>`;
} else if (days < 0) { } else if (days < 0) {
dayHTML = `<span class="preview-days-val" style="font-size:24px; color:#666">已过</span>`; dayHTML = `<span class="preview-days-val" style="font-size:${baseNumSize * 0.6}px; color:#666">已过</span>`;
} else { } else {
dayHTML = `<span class="preview-days-val">${days}</span><span class="preview-unit"></span>`; dayHTML = `<span class="preview-days-val" style="font-size:${baseNumSize}px">${days}</span>
<span class="preview-unit" style="font-size:${baseNumSize * 0.3}px"></span>`;
} }
container.innerHTML += ` container.innerHTML += `
<div class="preview-card ${item.imp ? 'is-important-card' : ''}"> <div class="preview-card ${item.imp ? 'is-important-card' : ''}">
<div class="preview-name">${item.name}</div> ${item.imp ? `<style>.is-important-card::after{font-size:${starSize}px}</style>` : ''}
<div class="preview-name" style="font-size:${baseNameSize}px">${item.name}</div>
<div class="preview-days-info"> <div class="preview-days-info">
${dayHTML} ${dayHTML}
</div> </div>
@@ -247,13 +256,11 @@
`; `;
}); });
} }
function updateItem(index, key, value) { function updateItem(index, key, value) {
exams[index][key] = value; exams[index][key] = value;
renderPreview(); renderPreview();
localStorage.setItem('dragExamGridV1', JSON.stringify(exams)); localStorage.setItem('dragExamGridV2', JSON.stringify(exams));
} }
function saveOrder() { function saveOrder() {
const newExams = []; const newExams = [];
document.querySelectorAll('.manage-item').forEach(el => { document.querySelectorAll('.manage-item').forEach(el => {
@@ -262,7 +269,6 @@
exams = newExams; exams = newExams;
renderAll(); renderAll();
} }
function addItem() { function addItem() {
const n = document.getElementById('name-in').value; const n = document.getElementById('name-in').value;
const d = document.getElementById('date-in').value; const d = document.getElementById('date-in').value;
@@ -273,39 +279,29 @@
document.getElementById('name-in').value = ''; document.getElementById('name-in').value = '';
} }
} }
function removeItem(index) { function removeItem(index) {
exams.splice(index, 1); exams.splice(index, 1);
renderAll(); renderAll();
} }
function downloadImage() { function downloadImage() {
html2canvas(document.getElementById('capture-area'), { html2canvas(document.getElementById('capture-area'), { width: 400, height: 300, scale: 2 })
width: 400,
height: 300,
scale: 2,
backgroundColor: "#ffffff"
})
.then(canvas => { .then(canvas => {
const link = document.createElement('a'); const link = document.createElement('a');
link.download = `E-Paper_Grid_${getTodayStr()}.png`; link.download = `Grid_Countdown.png`;
link.href = canvas.toDataURL("image/png"); link.href = canvas.toDataURL();
link.click(); link.click();
}); });
} }
function exportJSON() { function exportJSON() {
const blob = new Blob([JSON.stringify(exams)], {type: 'application/json'}); const blob = new Blob([JSON.stringify(exams)], {type: 'application/json'});
const a = document.createElement('a'); const a = document.createElement('a');
a.href = URL.createObjectURL(blob); a.download = 'exams_config.json'; a.click(); a.href = URL.createObjectURL(blob); a.download = 'exams.json'; a.click();
} }
function importJSON(input) { function importJSON(input) {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = e => { exams = JSON.parse(e.target.result); renderAll(); }; reader.onload = e => { exams = JSON.parse(e.target.result); renderAll(); };
reader.readAsText(input.files[0]); reader.readAsText(input.files[0]);
} }
renderAll(); renderAll();
</script> </script>
</body> </body>
+248
View File
@@ -0,0 +1,248 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>简介生成器</title>
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<style>
:root {
--ink-red: #ff0000;
}
body {
font-family: "Source Han Sans CN", "PingFang SC", "Microsoft YaHei", sans-serif;
background-color: #f4f4f7;
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 20px;
margin: 0;
}
/* 墨水屏预览区 400x300 */
#screen-wrap {
padding: 10px;
background: #333;
border-radius: 4px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
margin-bottom: 30px;
}
#screen-canvas {
width: 400px;
height: 300px;
background-color: #fff;
color: #000;
display: flex;
padding: 25px;
box-sizing: border-box;
position: relative;
overflow: hidden;
}
/* 布局样式 */
.info-side {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
border-right: 2px solid var(--ink-red);
padding-right: 15px;
}
#disp-name {
font-size: 36px;
font-weight: 900;
margin-bottom: 5px;
color: var(--ink-red);
}
#disp-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
background-color: #000;
color: #fff;
padding: 2px 8px;
display: inline-block;
align-self: flex-start;
}
.details {
font-size: 14px;
line-height: 1.8;
font-weight: bold;
}
.qr-side {
width: 130px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-left: 15px;
}
#qr-img {
width: 100px;
height: 100px;
padding: 5px;
border: 2px solid #000;
}
#disp-qr-label {
font-size: 12px;
margin-top: 10px;
font-weight: bold;
color: var(--ink-red);
text-align: center;
}
/* 编辑面板 */
.editor-panel {
background: white;
padding: 25px;
border-radius: 12px;
width: 420px;
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
}
.input-group {
margin-bottom: 15px;
}
label {
display: block;
font-size: 13px;
color: #555;
margin-bottom: 6px;
font-weight: bold;
}
input, textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
box-sizing: border-box;
font-size: 14px;
outline: none;
}
input:focus { border-color: var(--ink-red); }
.btn-download {
width: 100%;
padding: 12px;
background-color: var(--ink-red);
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
margin-top: 10px;
transition: opacity 0.2s;
}
.btn-download:hover { opacity: 0.9; }
.hint {
font-size: 12px;
color: #999;
margin-top: 15px;
text-align: center;
}
</style>
</head>
<body>
<div id="screen-wrap">
<div id="screen-canvas">
<div class="info-side">
<div id="disp-name">张三</div>
<div id="disp-title">全栈开发工程师</div>
<div class="details" id="disp-details">
📍 坐标:北京 · 朝阳<br>
📧 邮箱:zhangsan@dev.com<br>
🔗 博客:blog.zhangsan.me
</div>
</div>
<div class="qr-side">
<img id="qr-img" src="https://api.qrserver.com/v1/create-qr-code/?size=100x100&data=https://github.com" alt="QR" crossOrigin="anonymous">
<div id="disp-qr-label">扫码获取简历</div>
</div>
</div>
</div>
<div class="editor-panel">
<div class="input-group">
<label>姓名 (红色)</label>
<input type="text" id="in-name" value="张三" oninput="update()">
</div>
<div class="input-group">
<label>职业标签 (黑底白字)</label>
<input type="text" id="in-title" value="全栈开发工程师" oninput="update()">
</div>
<div class="input-group">
<label>个人简介 (支持换行)</label>
<textarea id="in-details" rows="3" oninput="update()">📍 坐标:北京 · 朝阳&#10;📧 邮箱:zhangsan@dev.com&#10;🔗 博客:blog.zhangsan.me</textarea>
</div>
<div class="input-group">
<label>二维码链接</label>
<input type="text" id="in-qr-data" value="https://github.com" onchange="update()">
</div>
<div class="input-group">
<label>二维码下方文案 (红色)</label>
<input type="text" id="in-qr-label" value="扫码获取简历" oninput="update()">
</div>
<button class="btn-download" onclick="downloadImage()">保存图片到本地</button>
<div class="hint">
生成的图片尺寸固定为 400x300,完美适配 4.2" 墨水屏。
</div>
</div>
<script>
// 更新预览内容
function update() {
document.getElementById('disp-name').innerText = document.getElementById('in-name').value;
document.getElementById('disp-title').innerText = document.getElementById('in-title').value;
const details = document.getElementById('in-details').value;
document.getElementById('disp-details').innerHTML = details.replace(/\n/g, '<br>');
document.getElementById('disp-qr-label').innerText = document.getElementById('in-qr-label').value;
// 二维码更新
const qrData = encodeURIComponent(document.getElementById('in-qr-data').value);
// 注意:qrserver支持跨域,html2canvas 才能捕获它
document.getElementById('qr-img').src = `https://api.qrserver.com/v1/create-qr-code/?size=100x100&data=${qrData}`;
}
// 下载图片功能
function downloadImage() {
const screen = document.getElementById('screen-canvas');
// 使用 html2canvas 捕捉指定节点
html2canvas(screen, {
width: 400,
height: 300,
scale: 1, // 保持 1:1 像素
useCORS: true, // 允许加载跨域二维码图片
backgroundColor: "#ffffff"
}).then(canvas => {
const link = document.createElement('a');
link.download = `eink_profile_${Date.now()}.png`;
link.href = canvas.toDataURL("image/png");
link.click();
});
}
// 初始运行一次
window.onload = update;
</script>
</body>
</html>
+114
View File
@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>4.2寸墨水屏看板 (红色提醒版)</title>
<style>
:root { --ink-red: #ff0000; --ink-black: #000000; }
body { font-family: sans-serif; background: #f0f2f5; display: flex; flex-direction: column; align-items: center; padding: 20px; }
.container { display: flex; gap: 20px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.editor { width: 300px; }
.input-box { display: flex; gap: 5px; margin-bottom: 15px; }
input { flex: 1; padding: 8px; border: 1px solid #ddd; }
.task-item { display: flex; align-items: center; padding: 8px; border-bottom: 1px solid #eee; font-size: 14px; }
canvas { border: 1px solid #000; background: #fff; width: 400px; height: 300px; }
.btn-dl { width: 100%; margin-top: 15px; padding: 10px; background: var(--ink-red); color: white; border: none; cursor: pointer; font-weight: bold; }
</style>
</head>
<body>
<h2>三色墨水屏待办生成器</h2>
<p style="color: #666;">提示:红色代表<strong>未完成</strong>(急需处理),黑色代表<strong>已完成</strong></p>
<div class="container">
<div class="editor">
<div class="input-box">
<input type="text" id="taskInput" placeholder="添加任务...">
<button onclick="addTask()">添加</button>
</div>
<div id="listUI"></div>
<button class="btn-dl" onclick="download()">下载 400x300 图片</button>
</div>
<div>
<canvas id="canvas" width="400" height="300"></canvas>
</div>
</div>
<script>
let tasks = [
{ text: "Class 612 网站数据库备份", done: false },
{ text: "Surveying 测量平差作业", done: false },
{ text: "已完成的演示任务", done: true }
];
function addTask() {
const val = document.getElementById('taskInput').value;
if(val) { tasks.push({text: val, done: false}); render(); }
}
function render() {
// UI 渲染
const listUI = document.getElementById('listUI');
listUI.innerHTML = '';
tasks.forEach((t, i) => {
const div = document.createElement('div');
div.className = 'task-item';
div.innerHTML = `<input type="checkbox" ${t.done?'checked':''} onchange="tasks[${i}].done=!tasks[${i}].done;render()">
<span style="flex:1; margin-left:8px; ${t.done?'text-decoration:line-through;color:#999':''}">${t.text}</span>
<button onclick="tasks.splice(${i},1);render()">×</button>`;
listUI.appendChild(div);
});
// Canvas 绘图
const ctx = document.getElementById('canvas').getContext('2d');
ctx.fillStyle = "#fff"; ctx.fillRect(0,0,400,300);
// 标题栏 (黑色)
ctx.fillStyle = "#000"; ctx.font = "bold 24px 'Microsoft YaHei'";
ctx.fillText("Focus Tasks", 20, 45);
ctx.fillRect(20, 55, 360, 2); // 黑线下划线
// 绘制列表
tasks.forEach((t, i) => {
const y = 90 + i * 38;
if(y > 270) return;
if(!t.done) {
// --- 未完成任务:红色强调 ---
ctx.strokeStyle = "#f00"; ctx.fillStyle = "#f00"; ctx.lineWidth = 2;
// 空心框
ctx.strokeRect(20, y - 16, 18, 18);
// 粗体文字
ctx.font = "bold 19px 'Microsoft YaHei'";
ctx.fillText(t.text, 50, y);
} else {
// --- 已完成任务:黑色弱化 ---
ctx.strokeStyle = "#000"; ctx.fillStyle = "#000"; ctx.lineWidth = 1;
// 打钩框
ctx.strokeRect(20, y - 16, 18, 18);
ctx.beginPath(); ctx.moveTo(22, y-8); ctx.lineTo(28, y); ctx.lineTo(36, y-12); ctx.stroke();
// 普通文字 + 删除线
ctx.font = "17px 'Microsoft YaHei'";
ctx.fillText(t.text, 50, y);
ctx.beginPath(); ctx.moveTo(50, y-6); ctx.lineTo(380, y-6); ctx.stroke();
}
});
// 底部提示 (黑色)
ctx.fillStyle = "#000"; ctx.font = "12px monospace";
ctx.fillText(`Update: ${new Date().toLocaleTimeString()}`, 20, 290);
ctx.fillText(`Pending: ${tasks.filter(x=>!x.done).length}`, 310, 290);
}
function download() {
const link = document.createElement('a');
link.download = 'eink_todo.png';
link.href = document.getElementById('canvas').toDataURL();
link.click();
}
render();
</script>
</body>
</html>