init commit.

This commit is contained in:
2025-11-11 11:07:25 +08:00
commit 7a5fb889c5
4 changed files with 567 additions and 0 deletions

44
README.md Normal file
View File

@@ -0,0 +1,44 @@
# 任务日历系统
一个简洁美观的任务日历系统,可以标记每一天的任务完成状态。
## 功能特性
- 📅 **日历视图**:清晰的月份日历展示
-**任务标记**:支持三种状态
- ✓ 任务已完成(绿色)
- ○ 任务部分未完成(橙色)
- 未标记(灰色)
- 💾 **数据保存**:使用浏览器 localStorage 自动保存标记数据
- 🎨 **简洁界面**:现代化的 UI 设计,响应式布局
- 🖱️ **便捷操作**:点击日期即可切换标记状态
## 使用方法
1. 直接在浏览器中打开 `index.html` 文件
2. 点击日历上的日期来切换标记状态:
- 第一次点击:标记为"已完成"(✓)
- 第二次点击:标记为"部分完成"(○)
- 第三次点击:清除标记
3. 使用左右箭头按钮切换月份
4. 点击"今天"按钮快速回到当前月份
## 文件结构
```
timeline/
├── index.html # 主页面
├── style.css # 样式文件
├── app.js # 应用逻辑
└── README.md # 说明文档
```
## 技术说明
- 纯 HTML/CSS/JavaScript 实现,无需构建工具
- 使用 localStorage 保存数据,刷新页面后数据不会丢失
- 响应式设计,支持移动端和桌面端
## 浏览器兼容性
支持所有现代浏览器Chrome、Firefox、Safari、Edge 等)

211
www/app.js Normal file
View File

@@ -0,0 +1,211 @@
// 日历状态管理
class CalendarApp {
constructor() {
this.currentDate = new Date();
this.markedDates = this.loadData();
this.init();
}
init() {
this.renderCalendar();
this.bindEvents();
}
// 获取月份的第一天是星期几0=周日, 1=周一...
getFirstDayOfMonth(date) {
const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
return firstDay.getDay();
}
// 获取月份的天数
getDaysInMonth(date) {
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
}
// 格式化日期为 YYYY-MM-DD
formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// 检查是否为今天
isToday(date) {
const today = new Date();
return date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear();
}
// 获取日期标记状态
getDateStatus(date) {
const dateStr = this.formatDate(date);
return this.markedDates[dateStr] || 'none';
}
// 切换日期标记状态
toggleDateStatus(date) {
const dateStr = this.formatDate(date);
const currentStatus = this.markedDates[dateStr] || 'none';
// 状态循环: none -> completed -> partial -> none
let newStatus;
switch (currentStatus) {
case 'none':
newStatus = 'completed';
break;
case 'completed':
newStatus = 'partial';
break;
case 'partial':
newStatus = 'none';
break;
default:
newStatus = 'none';
}
if (newStatus === 'none') {
delete this.markedDates[dateStr];
} else {
this.markedDates[dateStr] = newStatus;
}
this.saveData();
this.renderCalendar();
}
// 渲染日历
renderCalendar() {
const calendar = document.getElementById('calendar');
calendar.innerHTML = '';
// 星期标题
const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
weekdays.forEach(day => {
const weekdayEl = document.createElement('div');
weekdayEl.className = 'weekday';
weekdayEl.textContent = day;
calendar.appendChild(weekdayEl);
});
// 更新月份显示
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月',
'七月', '八月', '九月', '十月', '十一月', '十二月'];
const monthDisplay = document.getElementById('currentMonth');
monthDisplay.textContent = `${this.currentDate.getFullYear()}${monthNames[this.currentDate.getMonth()]}`;
// 获取月份信息
const firstDay = this.getFirstDayOfMonth(this.currentDate);
const daysInMonth = this.getDaysInMonth(this.currentDate);
const today = new Date();
// 添加上个月的日期(填充空白)
const prevMonth = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() - 1, 0);
const daysInPrevMonth = prevMonth.getDate();
for (let i = firstDay - 1; i >= 0; i--) {
const day = daysInPrevMonth - i;
const date = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() - 1, day);
this.createDayElement(calendar, date, day, true);
}
// 添加当前月的日期
for (let day = 1; day <= daysInMonth; day++) {
const date = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), day);
this.createDayElement(calendar, date, day, false);
}
// 添加下个月的日期(填充到完整周)
const totalCells = calendar.children.length - 7; // 减去星期标题
const remainingCells = 42 - totalCells; // 6行 x 7列 = 42
for (let day = 1; day <= remainingCells; day++) {
const date = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth() + 1, day);
this.createDayElement(calendar, date, day, true);
}
}
// 创建日期元素
createDayElement(container, date, dayNumber, isOtherMonth) {
const dayEl = document.createElement('div');
dayEl.className = 'day';
if (isOtherMonth) {
dayEl.classList.add('other-month');
}
if (this.isToday(date)) {
dayEl.classList.add('today');
}
const status = this.getDateStatus(date);
if (status !== 'none') {
dayEl.classList.add(status);
}
const dayNumberEl = document.createElement('div');
dayNumberEl.className = 'day-number';
dayNumberEl.textContent = dayNumber;
dayEl.appendChild(dayNumberEl);
if (status !== 'none') {
const markEl = document.createElement('div');
markEl.className = 'day-mark';
markEl.textContent = status === 'completed' ? '✓' : '○';
dayEl.appendChild(markEl);
}
// 添加点击事件
dayEl.addEventListener('click', () => {
if (!isOtherMonth) {
this.toggleDateStatus(date);
}
});
container.appendChild(dayEl);
}
// 绑定事件
bindEvents() {
document.getElementById('prevMonth').addEventListener('click', () => {
this.currentDate.setMonth(this.currentDate.getMonth() - 1);
this.renderCalendar();
});
document.getElementById('nextMonth').addEventListener('click', () => {
this.currentDate.setMonth(this.currentDate.getMonth() + 1);
this.renderCalendar();
});
document.getElementById('todayBtn').addEventListener('click', () => {
this.currentDate = new Date();
this.renderCalendar();
});
}
// 保存数据到 localStorage
saveData() {
try {
localStorage.setItem('calendarMarkedDates', JSON.stringify(this.markedDates));
} catch (e) {
console.error('保存数据失败:', e);
}
}
// 从 localStorage 加载数据
loadData() {
try {
const data = localStorage.getItem('calendarMarkedDates');
return data ? JSON.parse(data) : {};
} catch (e) {
console.error('加载数据失败:', e);
return {};
}
}
}
// 初始化应用
document.addEventListener('DOMContentLoaded', () => {
new CalendarApp();
});

46
www/index.html Normal file
View File

@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>任务日历系统</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<header>
<h1>任务日历</h1>
<div class="controls">
<button id="prevMonth" class="btn-nav"></button>
<span id="currentMonth" class="month-display"></span>
<button id="nextMonth" class="btn-nav"></button>
<button id="todayBtn" class="btn-today">今天</button>
</div>
</header>
<div class="legend">
<div class="legend-item">
<span class="mark completed"></span>
<span>任务已完成</span>
</div>
<div class="legend-item">
<span class="mark partial"></span>
<span>部分未完成</span>
</div>
<div class="legend-item">
<span class="mark none"></span>
<span>未标记</span>
</div>
</div>
<div class="calendar" id="calendar"></div>
<div class="instructions">
<p>💡 点击日期可切换标记状态:未标记 → 已完成 → 部分完成 → 未标记</p>
</div>
</div>
<script src="app.js"></script>
</body>
</html>

266
www/style.css Normal file
View File

@@ -0,0 +1,266 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
color: #333;
}
.container {
max-width: 900px;
margin: 0 auto;
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
padding: 30px;
}
header {
text-align: center;
margin-bottom: 30px;
}
header h1 {
font-size: 2.5em;
color: #667eea;
margin-bottom: 20px;
font-weight: 600;
}
.controls {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
flex-wrap: wrap;
}
.btn-nav {
background: #667eea;
color: white;
border: none;
width: 40px;
height: 40px;
border-radius: 50%;
font-size: 24px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.btn-nav:hover {
background: #5568d3;
transform: scale(1.1);
}
.btn-nav:active {
transform: scale(0.95);
}
.month-display {
font-size: 1.5em;
font-weight: 600;
color: #333;
min-width: 200px;
}
.btn-today {
background: #764ba2;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-today:hover {
background: #653a8a;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(118, 75, 162, 0.4);
}
.legend {
display: flex;
justify-content: center;
gap: 30px;
margin-bottom: 25px;
padding: 15px;
background: #f8f9fa;
border-radius: 10px;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.mark {
width: 24px;
height: 24px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 16px;
}
.mark.completed {
background: #10b981;
color: white;
}
.mark.partial {
background: #f59e0b;
color: white;
}
.mark.none {
background: #e5e7eb;
border: 2px solid #d1d5db;
}
.calendar {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 8px;
margin-bottom: 20px;
}
.weekday {
text-align: center;
font-weight: 600;
color: #667eea;
padding: 12px;
font-size: 14px;
background: #f0f4ff;
border-radius: 8px;
}
.day {
aspect-ratio: 1;
border: 2px solid #e5e7eb;
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
background: white;
position: relative;
padding: 8px;
}
.day:hover {
border-color: #667eea;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
}
.day.other-month {
opacity: 0.4;
background: #f9fafb;
}
.day.today {
border-color: #667eea;
background: #f0f4ff;
font-weight: 600;
}
.day.completed {
background: #d1fae5;
border-color: #10b981;
}
.day.completed .day-number {
color: #065f46;
}
.day.partial {
background: #fef3c7;
border-color: #f59e0b;
}
.day.partial .day-number {
color: #92400e;
}
.day-number {
font-size: 16px;
font-weight: 500;
margin-bottom: 4px;
}
.day-mark {
font-size: 20px;
font-weight: bold;
line-height: 1;
}
.day.completed .day-mark {
color: #10b981;
}
.day.partial .day-mark {
color: #f59e0b;
}
.instructions {
text-align: center;
padding: 15px;
background: #f0f4ff;
border-radius: 10px;
color: #667eea;
font-size: 14px;
}
@media (max-width: 768px) {
.container {
padding: 20px;
}
header h1 {
font-size: 2em;
}
.month-display {
font-size: 1.2em;
min-width: 150px;
}
.calendar {
gap: 4px;
}
.day {
padding: 4px;
}
.day-number {
font-size: 14px;
}
.day-mark {
font-size: 16px;
}
.legend {
gap: 15px;
}
}