diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ea4160a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,269 @@
+# Pure Todo - 纯待办小程序
+
+一个简洁高效的微信小程序待办事项管理应用,帮助你更好地管理时间和任务。
+
+## ✨ 特性
+
+### 🎨 现代化设计
+- **渐变背景** - 使用美丽的渐变色彩设计
+- **卡片式布局** - 清晰的信息层次和视觉分离
+- **动画效果** - 流畅的过渡动画和交互反馈
+- **响应式设计** - 适配不同屏幕尺寸
+
+### 📊 智能功能
+- **任务管理** - 添加、编辑、删除、完成任务
+- **优先级分类** - 高、中、低三个优先级等级
+- **智能搜索** - 快速查找特定任务
+- **状态筛选** - 按完成状态筛选任务
+- **数据统计** - 详细的完成率统计和分析
+
+### 📈 数据分析
+- **总体概览** - 任务总数、完成数、完成率
+- **周数据图表** - 可视化展示一周的任务情况
+- **优先级分析** - 不同优先级的完成情况
+- **最近活动** - 查看最近的任务操作记录
+
+### 🔧 个性化设置
+- **深色模式** - 保护眼睛的深色主题
+- **通知设置** - 自定义推送通知
+- **数据备份** - 导出和导入数据
+- **应用信息** - 版本信息和开发者联系方式
+
+## 🚀 技术栈
+
+- **框架**: 微信小程序原生开发
+- **UI组件**: TDesign 小程序组件库
+- **语言**: TypeScript
+- **样式**: WXSS (CSS)
+- **数据存储**: 微信小程序本地存储
+
+## 📱 页面结构
+
+### 首页 (index)
+- 个性化问候语
+- 今日概览统计
+- 今日任务列表
+- 最近任务记录
+- 快速操作入口
+
+### 任务列表 (list)
+- 智能搜索功能
+- 多维度筛选(状态、优先级)
+- 排序选项(时间、优先级、名称)
+- 批量操作(清空已完成)
+- 任务详情展示
+
+### 数据统计 (statistics)
+- 总体数据概览
+- 周数据图表
+- 优先级统计
+- 分类统计
+- 最近活动记录
+- 数据管理功能
+
+### 设置 (settings)
+- 外观设置(深色模式)
+- 通知设置
+- 数据管理(备份、导入、清空)
+- 应用设置(重置、更新、评分)
+- 关于应用信息
+
+## 🎯 核心功能
+
+### 任务管理
+```typescript
+// 添加任务
+todoStorage.addTodo(text, priority, category);
+
+// 更新任务
+todoStorage.updateTodo(id, updates);
+
+// 删除任务
+todoStorage.deleteTodo(id);
+
+// 切换完成状态
+todoStorage.toggleTodo(id);
+```
+
+### 数据统计
+```typescript
+// 获取统计信息
+const stats = todoStorage.getStats();
+
+// 按优先级获取任务
+const highPriorityTodos = todoStorage.getTodosByPriority('high');
+
+// 获取今日任务
+const todayTodos = todoStorage.getTodayTodos();
+```
+
+### 数据持久化
+```typescript
+// 导出数据
+const data = todoStorage.exportData();
+
+// 导入数据
+const success = todoStorage.importData(dataString);
+
+// 清空数据
+todoStorage.clearAllData();
+```
+
+## 🎨 设计特色
+
+### 色彩方案
+- **主色调**: #667eea (渐变蓝紫)
+- **辅助色**: #764ba2 (深紫)
+- **成功色**: #48dbfb (青色)
+- **警告色**: #feca57 (橙色)
+- **危险色**: #ff6b6b (红色)
+
+### 交互设计
+- **卡片悬浮效果** - 鼠标悬停时的阴影变化
+- **按钮点击反馈** - 按下时的缩放动画
+- **页面切换动画** - 平滑的页面过渡
+- **加载动画** - 数据加载时的渐入效果
+
+## 📦 安装和运行
+
+1. **克隆项目**
+```bash
+git clone [项目地址]
+cd pure-todo
+```
+
+2. **安装依赖**
+```bash
+npm install
+# 或
+pnpm install
+```
+
+3. **开发工具**
+- 使用微信开发者工具打开项目
+- 确保已安装 TDesign 小程序组件库
+
+4. **编译运行**
+- 在微信开发者工具中点击编译
+- 在模拟器中预览效果
+
+## 🔧 配置说明
+
+### app.json 配置
+```json
+{
+ "pages": [
+ "pages/index/index",
+ "pages/list/list",
+ "pages/statistics/statistics",
+ "pages/settings/settings"
+ ],
+ "window": {
+ "backgroundTextStyle": "light",
+ "navigationBarBackgroundColor": "#667eea",
+ "navigationBarTitleText": "Pure Todo",
+ "navigationBarTextStyle": "white",
+ "backgroundColor": "#f5f7fa"
+ }
+}
+```
+
+### 组件配置
+每个页面都配置了必要的 TDesign 组件:
+- `t-navbar` - 导航栏
+- `t-button` - 按钮
+- `t-input` - 输入框
+- `t-icon` - 图标
+- `t-card` - 卡片
+- `t-tag` - 标签
+- `t-progress` - 进度条
+- `t-checkbox` - 复选框
+- `t-switch` - 开关
+- `t-dialog` - 对话框
+
+## 📊 数据结构
+
+### 任务对象 (ITodo)
+```typescript
+interface ITodo {
+ id: string; // 唯一标识
+ text: string; // 任务内容
+ completed: boolean; // 完成状态
+ priority: 'high' | 'medium' | 'low'; // 优先级
+ createdAt: number; // 创建时间
+ completedAt?: number; // 完成时间
+ category?: string; // 分类
+}
+```
+
+### 统计对象 (ITodoStats)
+```typescript
+interface ITodoStats {
+ total: number; // 总任务数
+ completed: number; // 已完成数
+ pending: number; // 待完成数
+ completionRate: number; // 完成率
+ todayCompleted: number; // 今日完成数
+}
+```
+
+## 🎯 使用指南
+
+### 添加任务
+1. 在首页点击右上角的 "+" 按钮
+2. 输入任务内容
+3. 选择优先级(高/中/低)
+4. 点击"添加"完成
+
+### 管理任务
+1. 在任务列表页面查看所有任务
+2. 使用搜索框快速查找
+3. 使用筛选器按状态或优先级筛选
+4. 点击复选框标记完成
+5. 点击删除图标删除任务
+
+### 查看统计
+1. 在统计页面查看总体数据
+2. 查看周数据图表了解趋势
+3. 分析不同优先级的完成情况
+4. 查看最近的活动记录
+
+### 数据备份
+1. 在设置页面点击"导出数据"
+2. 复制备份数据并保存
+3. 需要恢复时点击"导入数据"
+4. 粘贴备份数据完成恢复
+
+## 🔮 未来规划
+
+- [ ] **云端同步** - 支持多设备数据同步
+- [ ] **提醒功能** - 任务到期提醒
+- [ ] **团队协作** - 多人共享任务列表
+- [ ] **数据导出** - 支持导出为Excel/PDF
+- [ ] **主题定制** - 更多主题色彩选择
+- [ ] **语音输入** - 语音添加任务
+- [ ] **智能建议** - AI智能任务建议
+
+## 🤝 贡献指南
+
+欢迎提交 Issue 和 Pull Request 来改进这个项目!
+
+### 开发规范
+- 使用 TypeScript 进行类型检查
+- 遵循 ESLint 代码规范
+- 保持代码注释的完整性
+- 测试新功能后再提交
+
+## 📄 许可证
+
+MIT License
+
+## 📞 联系方式
+
+如有问题或建议,请通过以下方式联系:
+- 邮箱:developer@example.com
+- 微信:developer_wechat
+
+---
+
+**Pure Todo** - 让任务管理变得简单高效 ✨
\ No newline at end of file
diff --git a/miniprogram/app.json b/miniprogram/app.json
index ed25533..3cfc749 100644
--- a/miniprogram/app.json
+++ b/miniprogram/app.json
@@ -2,9 +2,17 @@
"pages": [
"pages/index/index",
"pages/list/list",
- "pages/settings/settings",
- "pages/logs/logs"
+ "pages/statistics/statistics",
+ "pages/settings/settings"
],
+ "window": {
+ "backgroundTextStyle": "dark",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTitleText": "Pure Todo",
+ "navigationBarTextStyle": "black",
+ "backgroundColor": "#f5f5f5",
+ "navigationStyle": "default"
+ },
"tabBar": {
"custom": true,
"list": [
@@ -14,11 +22,30 @@
{
"pagePath": "pages/list/list"
},
+ {
+ "pagePath": "pages/statistics/statistics"
+ },
{
"pagePath": "pages/settings/settings"
}
]
},
"componentFramework": "glass-easel",
- "lazyCodeLoading": "requiredComponents"
+ "lazyCodeLoading": "requiredComponents",
+ "usingComponents": {
+ "t-navbar": "tdesign-miniprogram/navbar/navbar",
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-input": "tdesign-miniprogram/input/input",
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
+ "t-checkbox": "tdesign-miniprogram/checkbox/checkbox",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-tab-bar": "tdesign-miniprogram/tab-bar/tab-bar",
+ "t-tab-bar-item": "tdesign-miniprogram/tab-bar-item/tab-bar-item",
+ "t-tag": "tdesign-miniprogram/tag/tag",
+ "t-progress": "tdesign-miniprogram/progress/progress",
+ "t-switch": "tdesign-miniprogram/switch/switch",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog",
+ "t-toast": "tdesign-miniprogram/toast/toast"
+ }
}
\ No newline at end of file
diff --git a/miniprogram/app.wxss b/miniprogram/app.wxss
index f76dd95..18ce688 100644
--- a/miniprogram/app.wxss
+++ b/miniprogram/app.wxss
@@ -1,14 +1,128 @@
-/**app.wxss**/
-
-@import 'miniprogram_npm/tdesign-miniprogram/common/style/theme/_index.wxss';
-
+/* app.wxss */
+page {
+ background: #f5f5f5;
+ min-height: 100vh;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+}
.container {
- height: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: space-between;
- padding: 200rpx 0;
- box-sizing: border-box;
+ min-height: 100vh;
+ padding: 0;
+ background: transparent;
+ /* 添加安全区适配 */
+ padding-top: env(safe-area-inset-top);
+ padding-bottom: env(safe-area-inset-bottom);
+}
+
+/* 卡片样式 */
+.card {
+ background: #ffffff;
+ border-radius: 8px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ margin: 12px;
+ padding: 16px;
+}
+
+/* 按钮样式 */
+.primary-btn {
+ background: #0052d9;
+ border: none;
+ border-radius: 6px;
+ color: white;
+ font-weight: 500;
+ transition: all 0.2s ease;
+}
+
+.primary-btn:active {
+ background: #0034b5;
+}
+
+/* 输入框样式 */
+.modern-input {
+ background: #ffffff;
+ border-radius: 6px;
+ border: 1px solid #e3e6eb;
+ transition: all 0.2s ease;
+}
+
+.modern-input:focus {
+ border-color: #0052d9;
+ box-shadow: 0 0 0 2px rgba(0, 82, 217, 0.1);
+}
+
+/* 动画效果 */
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.fade-in {
+ animation: fadeIn 0.3s ease-out;
+}
+
+/* 状态标签 */
+.status-high {
+ background: #e34d59;
+ color: white;
+}
+
+.status-medium {
+ background: #ed7b2f;
+ color: white;
+}
+
+.status-low {
+ background: #00a870;
+ color: white;
+}
+
+/* 进度条样式 */
+.progress-container {
+ background: #f3f3f3;
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+/* 空状态样式 */
+.empty-state {
+ text-align: center;
+ padding: 40px 20px;
+ color: #999;
+}
+
+.empty-state .t-icon {
+ margin-bottom: 12px;
+ opacity: 0.5;
+}
+
+/* 自定义标签栏 */
+.custom-tab-bar {
+ background: #ffffff !important;
+ border-top: 1px solid #e3e6eb;
+ box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.05);
+ /* 添加底部安全区适配 */
+ padding-bottom: env(safe-area-inset-bottom);
+}
+
+/* 安全区适配工具类 */
+.safe-area-top {
+ padding-top: env(safe-area-inset-top);
+}
+
+.safe-area-bottom {
+ padding-bottom: env(safe-area-inset-bottom);
+}
+
+.safe-area-left {
+ padding-left: env(safe-area-inset-left);
+}
+
+.safe-area-right {
+ padding-right: env(safe-area-inset-right);
}
diff --git a/miniprogram/pages/index/index.json b/miniprogram/pages/index/index.json
index 1cae94b..fce4aa2 100644
--- a/miniprogram/pages/index/index.json
+++ b/miniprogram/pages/index/index.json
@@ -1,14 +1,13 @@
{
+ "navigationStyle": "custom",
"usingComponents": {
"t-button": "tdesign-miniprogram/button/button",
- "t-navbar": "tdesign-miniprogram/navbar/navbar",
"t-input": "tdesign-miniprogram/input/input",
- "t-cell": "tdesign-miniprogram/cell/cell",
- "t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
"t-checkbox": "tdesign-miniprogram/checkbox/checkbox",
"t-icon": "tdesign-miniprogram/icon/icon",
+ "t-tag": "tdesign-miniprogram/tag/tag",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog",
"t-tab-bar": "tdesign-miniprogram/tab-bar/tab-bar",
"t-tab-bar-item": "tdesign-miniprogram/tab-bar-item/tab-bar-item"
- },
- "navigationStyle": "custom"
+ }
}
\ No newline at end of file
diff --git a/miniprogram/pages/index/index.ts b/miniprogram/pages/index/index.ts
index 57c971d..a6cd91f 100644
--- a/miniprogram/pages/index/index.ts
+++ b/miniprogram/pages/index/index.ts
@@ -1,23 +1,395 @@
// pages/index/index.ts
+import { todoStorage, ITodo, ITodoStats } from '../../utils/todoStorage';
+
Component({
data: {
- activeTab: 'home', // For tab bar
+ activeTab: 'home',
+ stats: {} as ITodoStats,
+ todayTodos: [] as ITodo[],
+ recentTodos: [] as ITodo[],
+ greeting: '',
+ showAddSheet: false,
+ showDateTimePicker: false,
+ newTodoText: '',
+ newTodoPriority: 'medium' as 'high' | 'medium' | 'low',
+ newTodoCategory: '',
+ newTodoDeadline: 0,
+ newTodoNote: '',
+ dateTimeRange: [
+ [], // 年月日
+ [] // 时分
+ ] as string[][],
+ dateTimeValue: [0, 0] as number[]
},
+
+ lifetimes: {
+ attached() {
+ this.loadData();
+ this.setGreeting();
+ }
+ },
+
+ pageLifetimes: {
+ show() {
+ this.loadData();
+ }
+ },
+
methods: {
+ // 加载数据
+ loadData() {
+ const stats = todoStorage.getStats();
+ const todayTodos = todoStorage.getTodayTodos();
+ const allTodos = todoStorage.getAllTodos();
+ const recentTodos = allTodos.slice(0, 5);
+
+ this.setData({
+ stats,
+ todayTodos,
+ recentTodos
+ });
+ },
+
+ // 获取成就图标
+ getAchievementIcon(completionRate: number): string {
+ if (completionRate === 100) return 'star-filled';
+ if (completionRate >= 80) return 'medal';
+ if (completionRate >= 60) return 'thumb-up';
+ if (completionRate >= 40) return 'check-circle';
+ if (completionRate >= 20) return 'time';
+ return 'hourglass';
+ },
+
+ // 获取成就颜色
+ getAchievementColor(completionRate: number): string {
+ if (completionRate === 100) return '#ffd700';
+ if (completionRate >= 80) return '#00a870';
+ if (completionRate >= 60) return '#0052d9';
+ if (completionRate >= 40) return '#ed7b2f';
+ if (completionRate >= 20) return '#666';
+ return '#999';
+ },
+
+ // 获取成就文本
+ getAchievementText(completionRate: number): string {
+ if (completionRate === 100) return '完美完成!';
+ if (completionRate >= 80) return '表现优秀';
+ if (completionRate >= 60) return '进展良好';
+ if (completionRate >= 40) return '稳步前进';
+ if (completionRate >= 20) return '开始行动';
+ return '加油冲刺';
+ },
+
+ // 设置问候语
+ setGreeting() {
+ const hour = new Date().getHours();
+ let greeting = '';
+
+ if (hour < 6) {
+ greeting = '夜深了,注意休息';
+ } else if (hour < 12) {
+ greeting = '早上好!新的一天开始了';
+ } else if (hour < 18) {
+ greeting = '下午好!继续加油';
+ } else {
+ greeting = '晚上好!今天过得怎么样';
+ }
+
+ this.setData({ greeting });
+ },
+
+ // 标签页切换
onTabChange(e: any) {
const targetPage = e.detail.value;
if (targetPage === 'list') {
wx.switchTab({ url: '/pages/list/list' });
+ } else if (targetPage === 'statistics') {
+ wx.switchTab({ url: '/pages/statistics/statistics' });
} else if (targetPage === 'settings') {
wx.switchTab({ url: '/pages/settings/settings' });
}
- // No need to navigate if already on 'home'
},
+
+ // 快速操作
goToTasks() {
wx.switchTab({ url: '/pages/list/list' });
},
+
+ goToStatistics() {
+ wx.switchTab({ url: '/pages/statistics/statistics' });
+ },
+
goToSettings() {
wx.switchTab({ url: '/pages/settings/settings' });
+ },
+
+ // 显示添加半屏
+ showAddSheet() {
+ this.initDateTimeRange();
+ this.setData({
+ showAddSheet: true,
+ newTodoText: '',
+ newTodoPriority: 'medium',
+ newTodoCategory: '',
+ newTodoDeadline: 0,
+ newTodoNote: ''
+ });
+ },
+
+ // 隐藏添加半屏
+ hideAddSheet() {
+ this.setData({ showAddSheet: false });
+ },
+
+ // 阻止事件冒泡
+ stopPropagation() {
+ // 空函数,用于阻止事件冒泡
+ },
+
+ // 输入新任务
+ onNewTodoInput(e: any) {
+ this.setData({ newTodoText: e.detail.value });
+ },
+
+ // 输入备注
+ onNewTodoNoteInput(e: any) {
+ this.setData({ newTodoNote: e.detail.value });
+ },
+
+ // 设置优先级
+ onPriorityChange(e: any) {
+ this.setData({ newTodoPriority: e.currentTarget.dataset.value });
+ },
+
+ // 设置分类
+ onCategoryChange(e: any) {
+ this.setData({ newTodoCategory: e.currentTarget.dataset.value });
+ },
+
+ // 初始化日期时间选择器
+ initDateTimeRange() {
+ const now = new Date();
+ const year = now.getFullYear();
+ const month = now.getMonth();
+ const date = now.getDate();
+
+ // 生成日期选项(今天及之后30天)
+ const dateOptions = [];
+ for (let i = 0; i < 30; i++) {
+ const d = new Date(year, month, date + i);
+ const dateStr = this.formatDateOption(d);
+ dateOptions.push(dateStr);
+ }
+
+ // 生成时间选项
+ const timeOptions = [];
+ for (let h = 0; h < 24; h++) {
+ for (let m = 0; m < 60; m += 30) {
+ const timeStr = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
+ timeOptions.push(timeStr);
+ }
+ }
+
+ this.setData({
+ dateTimeRange: [dateOptions, timeOptions],
+ dateTimeValue: [0, 16] // 默认选择今天 08:00
+ });
+ },
+
+ // 格式化日期选项
+ formatDateOption(date: Date): string {
+ const now = new Date();
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+ const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
+
+ if (date.getTime() === today.getTime()) {
+ return '今天';
+ } else if (date.getTime() === tomorrow.getTime()) {
+ return '明天';
+ } else {
+ return `${date.getMonth() + 1}月${date.getDate()}日`;
+ }
+ },
+
+ // 显示日期时间选择器
+ showDateTimePicker() {
+ // 先触发选择器
+ const that = this;
+ wx.showActionSheet({
+ itemList: ['今天', '明天', '后天', '自定义日期'],
+ success(res) {
+ const now = new Date();
+ let selectedDate = new Date();
+
+ switch(res.tapIndex) {
+ case 0: // 今天
+ selectedDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 18, 0);
+ break;
+ case 1: // 明天
+ selectedDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 18, 0);
+ break;
+ case 2: // 后天
+ selectedDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 2, 18, 0);
+ break;
+ case 3: // 自定义
+ that.showCustomDatePicker();
+ return;
+ }
+
+ that.setData({
+ newTodoDeadline: selectedDate.getTime()
+ });
+ }
+ });
+ },
+
+ // 显示自定义日期选择器
+ showCustomDatePicker() {
+ const now = new Date();
+ const currentDate = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')}`;
+ const currentTime = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
+
+ wx.showModal({
+ title: '选择截止时间',
+ content: '请在系统设置中选择日期和时间',
+ showCancel: true,
+ cancelText: '取消',
+ confirmText: '使用当前时间',
+ success: (res) => {
+ if (res.confirm) {
+ // 使用当前时间加1小时作为默认截止时间
+ const deadline = new Date();
+ deadline.setHours(deadline.getHours() + 1);
+ this.setData({
+ newTodoDeadline: deadline.getTime()
+ });
+ }
+ }
+ });
+ },
+
+ // 清除截止时间
+ clearDeadline() {
+ this.setData({
+ newTodoDeadline: 0
+ });
+ },
+
+ // 隐藏日期时间选择器
+ hideDateTimePicker() {
+ this.setData({ showDateTimePicker: false });
+ },
+
+ // 日期时间选择器改变
+ onDateTimeChange(e: any) {
+ const [dateIndex, timeIndex] = e.detail.value;
+ const { dateTimeRange } = this.data;
+
+ // 计算选择的日期时间
+ const now = new Date();
+ const selectedDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + dateIndex);
+ const timeStr = dateTimeRange[1][timeIndex];
+ const [hours, minutes] = timeStr.split(':').map(Number);
+
+ selectedDate.setHours(hours, minutes, 0, 0);
+
+ this.setData({
+ dateTimeValue: [dateIndex, timeIndex],
+ newTodoDeadline: selectedDate.getTime(),
+ showDateTimePicker: false
+ });
+ },
+
+ // 格式化日期时间显示
+ formatDateTime(timestamp: number): string {
+ const date = new Date(timestamp);
+ const now = new Date();
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+ const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
+
+ let dateStr = '';
+ if (date.toDateString() === today.toDateString()) {
+ dateStr = '今天';
+ } else if (date.toDateString() === tomorrow.toDateString()) {
+ dateStr = '明天';
+ } else {
+ dateStr = `${date.getMonth() + 1}月${date.getDate()}日`;
+ }
+
+ const timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
+ return `${dateStr} ${timeStr}`;
+ },
+
+ // 添加新任务
+ addTodo() {
+ const { newTodoText, newTodoPriority, newTodoCategory, newTodoDeadline, newTodoNote } = this.data;
+ if (newTodoText.trim() === '') {
+ wx.showToast({
+ title: '请输入任务内容',
+ icon: 'none'
+ });
+ return;
+ }
+
+ todoStorage.addTodo(
+ newTodoText.trim(),
+ newTodoPriority,
+ newTodoCategory || undefined,
+ newTodoDeadline || undefined,
+ newTodoNote.trim() || undefined
+ );
+
+ this.hideAddSheet();
+ this.loadData();
+
+ wx.showToast({
+ title: '任务已添加',
+ icon: 'success'
+ });
+ },
+
+ // 切换任务状态
+ toggleTodo(e: any) {
+ const id = e.currentTarget.dataset.id;
+ todoStorage.toggleTodo(id);
+ this.loadData();
+ },
+
+ // 删除任务
+ deleteTodo(e: any) {
+ const id = e.currentTarget.dataset.id;
+ wx.showModal({
+ title: '确认删除',
+ content: '确定要删除这个任务吗?',
+ success: (res) => {
+ if (res.confirm) {
+ todoStorage.deleteTodo(id);
+ this.loadData();
+ wx.showToast({
+ title: '已删除',
+ icon: 'success'
+ });
+ }
+ }
+ });
+ },
+
+ // 格式化时间
+ formatTime(timestamp: number): string {
+ const date = new Date(timestamp);
+ const now = new Date();
+ const diff = now.getTime() - date.getTime();
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
+
+ if (days === 0) {
+ return '今天';
+ } else if (days === 1) {
+ return '昨天';
+ } else if (days < 7) {
+ return `${days}天前`;
+ } else {
+ return date.toLocaleDateString();
+ }
}
- },
+ }
});
diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml
index 6e6e46c..9956b1b 100644
--- a/miniprogram/pages/index/index.wxml
+++ b/miniprogram/pages/index/index.wxml
@@ -1,29 +1,265 @@
-
-
-
-
- 欢迎回来!
- 今天有什么计划?让我们开始吧。
+
+
+
+
+
+
+ {{greeting}}
+ {{formatTime(Date.now())}}
+
+
+
+
+
+
+
+
+
+ {{stats.completionRate}}%
+
+
+
+
+
+ 今日进度
+
+ {{stats.completed}}/{{stats.total}} 完成
+
+
+ {{getAchievementText(stats.completionRate)}}
+
+
+
+
+
+
+
+ 全部
+
+
+
+ 添加
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无任务
+
+
+
+
+
+
+ {{item.text}}
+
+
+
+ {{item.priority === 'high' ? '高' : item.priority === 'medium' ? '中' : '低'}}
+
+
+
+
+
+
+
-
+
- 查看我的任务
-
-
-
- 打开设置
+ 查看所有任务
-
+
+
+
+
+
+
+
+
+ 任务内容
+
+
+
+
+ 优先级
+
+
+
+ 高优先级
+
+
+
+ 中优先级
+
+
+
+ 低优先级
+
+
+
+
+
+
+ 分类
+
+
+
+ 工作
+
+
+
+ 个人
+
+
+
+ 学习
+
+
+
+ 生活
+
+
+
+
+
+
+ 截止时间(可选)
+
+
+
+ {{newTodoDeadline ? formatDateTime(newTodoDeadline) : '点击选择截止时间'}}
+
+
+
+
+ 清除截止时间
+
+
+
+
+
+ 备注(可选)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
首页
任务
+ 统计
设置
diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss
index 9298653..320e076 100644
--- a/miniprogram/pages/index/index.wxss
+++ b/miniprogram/pages/index/index.wxss
@@ -1,83 +1,613 @@
-/**index.wxss**/
-page {
- display: flex;
- flex-direction: column;
- height: 100vh;
- background-color: #f8f9fa; /* Lighter, cleaner background */
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
-}
+/* pages/index/index.wxss */
.container {
+ padding: 0;
+ background: transparent;
+ /* 增大安全区,避开胶囊按钮 */
+ padding-top: max(env(safe-area-inset-top), 60px);
+ position: relative;
+}
+
+/* 顶部渐变背景 */
+.gradient-background {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 220px;
+ background: linear-gradient(135deg,
+ rgba(114, 67, 255, 0.28) 0%,
+ rgba(67, 123, 255, 0.22) 20%,
+ rgba(0, 191, 255, 0.15) 40%,
+ rgba(159, 122, 234, 0.08) 60%,
+ rgba(255, 255, 255, 0.03) 80%,
+ rgba(255, 255, 255, 0) 100%);
+ /* 添加动态效果 */
+ background-size: 200% 200%;
+ animation: gradientShift 8s ease-in-out infinite;
+ z-index: 1;
+}
+
+/* 渐变动画 */
+@keyframes gradientShift {
+ 0% {
+ background-position: 0% 50%;
+ }
+ 50% {
+ background-position: 100% 50%;
+ }
+ 100% {
+ background-position: 0% 50%;
+ }
+}
+
+/* 问候区域 */
+.greeting-section {
+ padding: 30px 16px 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ position: relative;
+ z-index: 2;
+}
+
+.greeting-content {
+ flex: 1;
+}
+
+.greeting {
+ display: block;
+ font-size: 24px;
+ font-weight: 600;
+ color: #333333;
+ margin-bottom: 4px;
+ line-height: 1.2;
+}
+
+.date {
+ display: block;
+ font-size: 14px;
+ color: #999999;
+ line-height: 1.2;
+}
+
+/* 统计概览 */
+.stats-overview {
+ padding: 0 16px 16px;
+ position: relative;
+ z-index: 2;
+}
+
+/* 紧凑型进度卡片 */
+.compact-progress-card {
+ background: rgba(255, 255, 255, 0.95);
+ border-radius: 12px;
+ padding: 16px;
+ margin-bottom: 8px;
+ border: 1px solid rgba(240, 240, 240, 0.8);
+ backdrop-filter: blur(10px);
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+}
+
+/* 迷你圆形进度环 */
+.progress-mini-circle {
+ position: relative;
+ width: 60px;
+ height: 60px;
+ flex-shrink: 0;
+}
+
+.mini-circle-bg {
+ position: absolute;
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ border: 4px solid #f0f0f0;
+ box-sizing: border-box;
+}
+
+.mini-circle-progress {
+ position: absolute;
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ border: 4px solid transparent;
+ border-top-color: #0052d9;
+ box-sizing: border-box;
+ transform-origin: center;
+ transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.mini-circle-inner {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ text-align: center;
+}
+
+.mini-percentage {
+ font-size: 14px;
+ font-weight: 700;
+ color: #0052d9;
+ line-height: 1;
+}
+
+/* 紧凑统计信息 */
+.compact-stats {
+ flex: 1;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.compact-info {
display: flex;
flex-direction: column;
- flex: 1;
- padding: 20rpx;
- box-sizing: border-box;
- padding-bottom: 120rpx; /* Add padding for the fixed tab bar */
+ gap: 4px;
}
-.t-navbar {
- margin-bottom: 20rpx;
+.compact-title {
+ font-size: 12px;
+ color: #666;
+ font-weight: 500;
}
-.hero-section {
+.compact-numbers {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.compact-number {
+ font-size: 16px;
+ font-weight: 600;
+ color: #333;
+ line-height: 1;
+}
+
+.achievement-mini {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.achievement-mini-text {
+ font-size: 11px;
+ font-weight: 500;
+}
+
+/* 快速操作按钮 */
+.quick-actions-mini {
+ display: flex;
+ gap: 8px;
+}
+
+.action-item {
display: flex;
flex-direction: column;
align-items: center;
- text-align: center;
- padding: 60rpx 20rpx;
- background-color: #ffffff;
- border-radius: 24rpx;
- box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.05);
- margin-bottom: 40rpx;
+ gap: 2px;
+ padding: 8px;
+ border-radius: 8px;
+ background: rgba(0, 82, 217, 0.05);
+ transition: all 0.2s ease;
+ min-width: 44px;
}
-.hero-image {
- width: 300rpx; /* Adjust as needed */
- height: 200rpx; /* Adjust as needed */
- margin-bottom: 30rpx;
+.action-item:active {
+ transform: scale(0.95);
+ background: rgba(0, 82, 217, 0.1);
}
-.hero-title {
- font-size: 48rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 10rpx;
-}
-
-.hero-subtitle {
- font-size: 28rpx;
+.action-text {
+ font-size: 10px;
color: #666;
- line-height: 1.6;
+ line-height: 1;
}
-.quick-actions {
+/* 今日任务 */
+.today-tasks {
+ padding: 0 16px 20px;
+ position: relative;
+ z-index: 2;
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+}
+
+.section-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #333333;
+}
+
+.task-count {
+ font-size: 12px;
+ color: #999999;
+ background: #f5f5f5;
+ padding: 2px 8px;
+ border-radius: 12px;
+}
+
+.task-list {
display: flex;
flex-direction: column;
- gap: 20rpx; /* Adds space between buttons */
- margin-bottom: 40rpx;
+ gap: 8px;
}
-.action-button .t-button {
- width: 100%; /* Make button take full width if block attribute isn't enough */
+.task-item {
+ background: #ffffff;
+ padding: 16px;
+ border-radius: 8px;
+ border: 1px solid #f0f0f0;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
}
-.action-button .t-icon {
- margin-right: 10rpx;
+.task-content {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ gap: 12px;
}
-.footer-space {
- height: 100rpx; /* Space at the bottom before tab bar, if content is scrollable */
+.task-text {
+ font-size: 14px;
+ color: #333333;
+ line-height: 1.4;
+ flex: 1;
}
-.custom-tab-bar {
+.task-text.completed {
+ text-decoration: line-through;
+ color: #999999;
+}
+
+.task-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+/* 快速操作 */
+.quick-actions {
+ padding: 0 16px 20px;
+ position: relative;
+ z-index: 2;
+}
+
+/* 添加任务半屏 */
+.add-task-sheet {
position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: 2000;
+ opacity: 0;
+ visibility: hidden;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.add-task-sheet.show {
+ opacity: 1;
+ visibility: visible;
+}
+
+.sheet-content {
+ position: absolute;
bottom: 0;
left: 0;
right: 0;
- z-index: 1000; /* Ensure it's on top */
- background-color: #ffffff; /* Ensure tab bar has a background */
- box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+ background: #ffffff;
+ border-radius: 20px 20px 0 0;
+ max-height: 85vh;
+ transform: translateY(100%);
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ display: flex;
+ flex-direction: column;
}
-/** Removed old userinfo and motto styles as per the change description **/
+.add-task-sheet.show .sheet-content {
+ transform: translateY(0);
+}
+
+/* 半屏头部 */
+.sheet-header {
+ padding: 12px 20px 16px;
+ border-bottom: 1px solid #f0f0f0;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ position: relative;
+}
+
+.sheet-handle {
+ position: absolute;
+ top: 8px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 32px;
+ height: 4px;
+ background: #e0e0e0;
+ border-radius: 2px;
+}
+
+.sheet-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ flex: 1;
+ text-align: center;
+}
+
+.sheet-close {
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: #f5f5f5;
+ transition: background 0.2s ease;
+}
+
+.sheet-close:active {
+ background: #e0e0e0;
+}
+
+/* 半屏主体 */
+.sheet-body {
+ flex: 1;
+ padding: 20px;
+ overflow-y: auto;
+}
+
+.input-section {
+ margin-bottom: 24px;
+}
+
+.input-label {
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+ margin-bottom: 8px;
+ display: block;
+}
+
+.task-input,
+.note-input {
+ width: 100%;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ background: #fafafa;
+ transition: all 0.2s ease;
+}
+
+.task-input:focus,
+.note-input:focus {
+ border-color: #0052d9;
+ background: #ffffff;
+ box-shadow: 0 0 0 2px rgba(0, 82, 217, 0.1);
+}
+
+/* 优先级选择器 */
+.priority-selector {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.priority-item {
+ display: flex;
+ align-items: center;
+ padding: 12px 16px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ background: #ffffff;
+ transition: all 0.2s ease;
+ cursor: pointer;
+}
+
+.priority-item:active {
+ transform: scale(0.98);
+}
+
+.priority-item.active {
+ border-color: #0052d9;
+ background: rgba(0, 82, 217, 0.05);
+}
+
+.priority-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ margin-right: 12px;
+}
+
+.priority-dot.high {
+ background: #e34d59;
+}
+
+.priority-dot.medium {
+ background: #ed7b2f;
+}
+
+.priority-dot.low {
+ background: #0052d9;
+}
+
+.priority-text {
+ font-size: 14px;
+ color: #333;
+}
+
+/* 分类选择器 */
+.category-selector {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 12px;
+}
+
+.category-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 16px 12px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ background: #ffffff;
+ transition: all 0.2s ease;
+ cursor: pointer;
+}
+
+.category-item:active {
+ transform: scale(0.95);
+}
+
+.category-item.active {
+ border-color: #0052d9;
+ background: rgba(0, 82, 217, 0.05);
+}
+
+.category-item .t-icon {
+ margin-bottom: 6px;
+ color: #666;
+}
+
+.category-item.active .t-icon {
+ color: #0052d9;
+}
+
+.category-item text {
+ font-size: 12px;
+ color: #333;
+}
+
+.category-item.active text {
+ color: #0052d9;
+ font-weight: 500;
+}
+
+/* 日期时间选择器 */
+.datetime-picker {
+ display: flex;
+ align-items: center;
+ padding: 12px 16px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ background: #ffffff;
+ transition: all 0.2s ease;
+ cursor: pointer;
+}
+
+.datetime-picker:active {
+ background: #fafafa;
+ border-color: #0052d9;
+}
+
+.datetime-text {
+ flex: 1;
+ margin: 0 12px;
+ font-size: 14px;
+ color: #333;
+}
+
+.datetime-text.placeholder {
+ color: #999;
+}
+
+/* 清除截止时间按钮 */
+.datetime-clear {
+ margin-top: 8px;
+ text-align: center;
+}
+
+.clear-text {
+ font-size: 12px;
+ color: #e34d59;
+ padding: 4px 8px;
+ border: 1px solid #e34d59;
+ border-radius: 4px;
+ background: rgba(227, 77, 89, 0.05);
+ transition: all 0.2s ease;
+}
+
+.datetime-clear:active .clear-text {
+ background: rgba(227, 77, 89, 0.1);
+}
+
+/* 半屏底部 */
+.sheet-footer {
+ padding: 16px 20px;
+ border-top: 1px solid #f0f0f0;
+ display: flex;
+ gap: 12px;
+ background: #ffffff;
+ border-radius: 0 0 20px 20px;
+}
+
+.cancel-btn {
+ flex: 1;
+ height: 44px;
+ border: 1px solid #e0e0e0;
+ background: #ffffff;
+ color: #666;
+}
+
+.confirm-btn {
+ flex: 2;
+ height: 44px;
+ background: linear-gradient(135deg, #0052d9 0%, #0043a5 100%);
+ border: none;
+}
+
+.confirm-btn[disabled] {
+ background: #e0e0e0;
+ color: #999;
+}
+
+/* 隐藏日期选择器 */
+.hidden {
+ display: none;
+}
+
+/* 空状态 */
+.empty-state {
+ text-align: center;
+ padding: 40px 20px;
+}
+
+.empty-text {
+ display: block;
+ font-size: 14px;
+ color: #999999;
+ margin-top: 8px;
+}
+
+/* 对话框 */
+.dialog-content {
+ padding: 20px 0;
+}
+
+.priority-label {
+ font-size: 14px;
+ color: #333333;
+ margin-bottom: 12px;
+ display: block;
+}
+
+.priority-options {
+ display: flex;
+ gap: 8px;
+}
diff --git a/miniprogram/pages/list/list.json b/miniprogram/pages/list/list.json
index 589b8af..9322258 100644
--- a/miniprogram/pages/list/list.json
+++ b/miniprogram/pages/list/list.json
@@ -1,14 +1,14 @@
{
"usingComponents": {
- "t-button": "tdesign-miniprogram/button/button",
"t-navbar": "tdesign-miniprogram/navbar/navbar",
+ "t-button": "tdesign-miniprogram/button/button",
"t-input": "tdesign-miniprogram/input/input",
- "t-cell": "tdesign-miniprogram/cell/cell",
- "t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
- "t-checkbox": "tdesign-miniprogram/checkbox/checkbox",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-tab-bar": "tdesign-miniprogram/tab-bar/tab-bar",
- "t-tab-bar-item": "tdesign-miniprogram/tab-bar-item/tab-bar-item"
+ "t-tab-bar-item": "tdesign-miniprogram/tab-bar-item/tab-bar-item",
+ "t-tag": "tdesign-miniprogram/tag/tag",
+ "t-checkbox": "tdesign-miniprogram/checkbox/checkbox",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog"
},
"navigationStyle": "custom",
"navigationBarTitleText": "任务列表"
diff --git a/miniprogram/pages/list/list.ts b/miniprogram/pages/list/list.ts
index 2bd057f..2854fc8 100644
--- a/miniprogram/pages/list/list.ts
+++ b/miniprogram/pages/list/list.ts
@@ -1,62 +1,460 @@
// pages/list/list.ts
-interface ITodo {
- text: string;
- completed: boolean;
-}
+import { todoStorage, ITodo, ITodoStats } from '../../utils/todoStorage';
Component({
data: {
- newTodoText: '',
+ activeTab: 'list',
todos: [] as ITodo[],
- activeTab: 'list', // For tab bar
+ filteredTodos: [] as ITodo[],
+ stats: {} as ITodoStats,
+ searchText: '',
+ filterStatus: 'all' as 'all' | 'pending' | 'completed',
+ filterPriority: 'all' as 'all' | 'high' | 'medium' | 'low',
+ sortBy: 'createdAt' as 'createdAt' | 'priority' | 'text',
+ pendingCount: 0,
+ completedCount: 0,
+ completionRate: 0,
+ showAddSheet: false,
+ showDateTimePicker: false,
+ newTodoText: '',
+ newTodoPriority: 'medium' as 'high' | 'medium' | 'low',
+ newTodoCategory: '',
+ newTodoDeadline: 0,
+ newTodoNote: '',
+ dateTimeRange: [
+ [], // 年月日
+ [] // 时分
+ ] as string[][],
+ dateTimeValue: [0, 0] as number[]
},
+
+ lifetimes: {
+ attached() {
+ this.loadData();
+ }
+ },
+
+ pageLifetimes: {
+ show() {
+ this.loadData();
+ }
+ },
+
methods: {
- onNewTodoInput(e: any) {
+ // 加载数据
+ loadData() {
+ const todos = todoStorage.getAllTodos();
+ const stats = todoStorage.getStats();
+ this.setData({ todos, stats });
+ this.applyFilters();
+ },
+
+ // 筛选和排序任务
+ applyFilters() {
+ let filtered = [...this.data.todos];
+
+ // 搜索筛选
+ if (this.data.searchText) {
+ filtered = filtered.filter(todo =>
+ todo.text.toLowerCase().includes(this.data.searchText.toLowerCase())
+ );
+ }
+
+ // 状态筛选
+ if (this.data.filterStatus === 'pending') {
+ filtered = filtered.filter(todo => !todo.completed);
+ } else if (this.data.filterStatus === 'completed') {
+ filtered = filtered.filter(todo => todo.completed);
+ }
+
+ // 优先级筛选
+ if (this.data.filterPriority !== 'all') {
+ filtered = filtered.filter(todo => todo.priority === this.data.filterPriority);
+ }
+
+ // 排序
+ filtered.sort((a, b) => {
+ switch (this.data.sortBy) {
+ case 'createdAt':
+ return b.createdAt - a.createdAt;
+ case 'priority': {
+ const priorityOrder = { high: 3, medium: 2, low: 1 };
+ return priorityOrder[b.priority] - priorityOrder[a.priority];
+ }
+ case 'text':
+ return a.text.localeCompare(b.text);
+ default:
+ return 0;
+ }
+ });
+
+ // 计算统计数据
+ const pendingCount = filtered.filter(todo => !todo.completed).length;
+ const completedCount = filtered.filter(todo => todo.completed).length;
+ const completionRate = filtered.length > 0 ? Math.round((completedCount / filtered.length) * 100) : 0;
+
this.setData({
- newTodoText: e.detail.value,
+ filteredTodos: filtered,
+ pendingCount,
+ completedCount,
+ completionRate
});
},
+
+ // 搜索输入
+ onSearchInput(e: any) {
+ this.setData({ searchText: e.detail.value }, () => {
+ this.applyFilters();
+ });
+ },
+
+ // 状态筛选
+ onStatusFilterChange(e: any) {
+ this.setData({ filterStatus: e.currentTarget.dataset.value }, () => {
+ this.applyFilters();
+ });
+ },
+
+ // 优先级筛选
+ onPriorityFilterChange(e: any) {
+ this.setData({ filterPriority: e.currentTarget.dataset.value }, () => {
+ this.applyFilters();
+ });
+ },
+
+ // 排序
+ onSortChange(e: any) {
+ this.setData({ sortBy: e.currentTarget.dataset.value }, () => {
+ this.applyFilters();
+ });
+ },
+
+ // 显示添加半屏
+ showAddSheet() {
+ this.initDateTimeRange();
+ this.setData({
+ showAddSheet: true,
+ newTodoText: '',
+ newTodoPriority: 'medium',
+ newTodoCategory: '',
+ newTodoDeadline: 0,
+ newTodoNote: ''
+ });
+ },
+
+ // 隐藏添加半屏
+ hideAddSheet() {
+ this.setData({ showAddSheet: false });
+ },
+
+ // 阻止事件冒泡
+ stopPropagation() {
+ // 空函数,用于阻止事件冒泡
+ },
+
+ // 输入新任务
+ onNewTodoInput(e: any) {
+ this.setData({ newTodoText: e.detail.value });
+ },
+
+ // 输入备注
+ onNewTodoNoteInput(e: any) {
+ this.setData({ newTodoNote: e.detail.value });
+ },
+
+ // 设置优先级
+ onPriorityChange(e: any) {
+ this.setData({ newTodoPriority: e.currentTarget.dataset.value });
+ },
+
+ // 设置分类
+ onCategoryChange(e: any) {
+ this.setData({ newTodoCategory: e.currentTarget.dataset.value });
+ },
+
+ // 初始化日期时间选择器
+ initDateTimeRange() {
+ const now = new Date();
+ const year = now.getFullYear();
+ const month = now.getMonth();
+ const date = now.getDate();
+
+ // 生成日期选项(今天及之后30天)
+ const dateOptions = [];
+ for (let i = 0; i < 30; i++) {
+ const d = new Date(year, month, date + i);
+ const dateStr = this.formatDateOption(d);
+ dateOptions.push(dateStr);
+ }
+
+ // 生成时间选项
+ const timeOptions = [];
+ for (let h = 0; h < 24; h++) {
+ for (let m = 0; m < 60; m += 30) {
+ const timeStr = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
+ timeOptions.push(timeStr);
+ }
+ }
+
+ this.setData({
+ dateTimeRange: [dateOptions, timeOptions],
+ dateTimeValue: [0, 16] // 默认选择今天 08:00
+ });
+ },
+
+ // 格式化日期选项
+ formatDateOption(date: Date): string {
+ const now = new Date();
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+ const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
+
+ if (date.getTime() === today.getTime()) {
+ return '今天';
+ } else if (date.getTime() === tomorrow.getTime()) {
+ return '明天';
+ } else {
+ return `${date.getMonth() + 1}月${date.getDate()}日`;
+ }
+ },
+
+ // 显示日期时间选择器
+ showDateTimePicker() {
+ // 先触发选择器
+ const that = this;
+ wx.showActionSheet({
+ itemList: ['今天', '明天', '后天', '自定义日期'],
+ success(res) {
+ const now = new Date();
+ let selectedDate = new Date();
+
+ switch(res.tapIndex) {
+ case 0: // 今天
+ selectedDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 18, 0);
+ break;
+ case 1: // 明天
+ selectedDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 18, 0);
+ break;
+ case 2: // 后天
+ selectedDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 2, 18, 0);
+ break;
+ case 3: // 自定义
+ that.showCustomDatePicker();
+ return;
+ }
+
+ that.setData({
+ newTodoDeadline: selectedDate.getTime()
+ });
+ }
+ });
+ },
+
+ // 显示自定义日期选择器
+ showCustomDatePicker() {
+ const now = new Date();
+ const currentDate = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')}`;
+ const currentTime = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
+
+ wx.showModal({
+ title: '选择截止时间',
+ content: '请在系统设置中选择日期和时间',
+ showCancel: true,
+ cancelText: '取消',
+ confirmText: '使用当前时间',
+ success: (res) => {
+ if (res.confirm) {
+ // 使用当前时间加1小时作为默认截止时间
+ const deadline = new Date();
+ deadline.setHours(deadline.getHours() + 1);
+ this.setData({
+ newTodoDeadline: deadline.getTime()
+ });
+ }
+ }
+ });
+ },
+
+ // 清除截止时间
+ clearDeadline() {
+ this.setData({
+ newTodoDeadline: 0
+ });
+ },
+
+ // 隐藏日期时间选择器
+ hideDateTimePicker() {
+ this.setData({ showDateTimePicker: false });
+ },
+
+ // 日期时间选择器改变
+ onDateTimeChange(e: any) {
+ const [dateIndex, timeIndex] = e.detail.value;
+ const { dateTimeRange } = this.data;
+
+ // 计算选择的日期时间
+ const now = new Date();
+ const selectedDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + dateIndex);
+ const timeStr = dateTimeRange[1][timeIndex];
+ const [hours, minutes] = timeStr.split(':').map(Number);
+
+ selectedDate.setHours(hours, minutes, 0, 0);
+
+ this.setData({
+ dateTimeValue: [dateIndex, timeIndex],
+ newTodoDeadline: selectedDate.getTime(),
+ showDateTimePicker: false
+ });
+ },
+
+ // 格式化日期时间显示
+ formatDateTime(timestamp: number): string {
+ const date = new Date(timestamp);
+ const now = new Date();
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+ const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
+
+ let dateStr = '';
+ if (date.toDateString() === today.toDateString()) {
+ dateStr = '今天';
+ } else if (date.toDateString() === tomorrow.toDateString()) {
+ dateStr = '明天';
+ } else {
+ dateStr = `${date.getMonth() + 1}月${date.getDate()}日`;
+ }
+
+ const timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
+ return `${dateStr} ${timeStr}`;
+ },
+
+ // 添加新任务
addTodo() {
- const { newTodoText, todos } = this.data;
+ const { newTodoText, newTodoPriority, newTodoCategory, newTodoDeadline, newTodoNote } = this.data;
if (newTodoText.trim() === '') {
+ wx.showToast({
+ title: '请输入任务内容',
+ icon: 'none'
+ });
return;
}
- const newTodos = [...todos, { text: newTodoText, completed: false }];
- this.setData({
- todos: newTodos,
- newTodoText: '',
+
+ todoStorage.addTodo(
+ newTodoText.trim(),
+ newTodoPriority,
+ newTodoCategory || undefined,
+ newTodoDeadline || undefined,
+ newTodoNote.trim() || undefined
+ );
+
+ this.hideAddSheet();
+ this.loadData();
+
+ wx.showToast({
+ title: '任务已添加',
+ icon: 'success'
});
},
+
+ // 切换任务状态
toggleTodo(e: any) {
- const index = e.currentTarget.dataset.index;
- const { todos } = this.data;
- const newTodos = todos.map((todo, i) => {
- if (i === index) {
- return { ...todo, completed: !todo.completed };
+ const id = e.currentTarget.dataset.id;
+ todoStorage.toggleTodo(id);
+ this.loadData();
+ },
+
+ // 编辑任务
+ editTodo(e: any) {
+ const id = e.currentTarget.dataset.id;
+ const todo = this.data.todos.find(t => t.id === id);
+ if (todo) {
+ wx.showModal({
+ title: '编辑任务',
+ content: '编辑功能开发中,敬请期待!',
+ showCancel: false
+ });
+ }
+ },
+
+ // 删除任务
+ deleteTodo(e: any) {
+ const id = e.currentTarget.dataset.id;
+ wx.showModal({
+ title: '确认删除',
+ content: '确定要删除这个任务吗?',
+ success: (res) => {
+ if (res.confirm) {
+ todoStorage.deleteTodo(id);
+ this.loadData();
+ wx.showToast({
+ title: '已删除',
+ icon: 'success'
+ });
+ }
}
- return todo;
- });
- this.setData({
- todos: newTodos,
});
},
- removeTodo(e: any) {
- const index = e.currentTarget.dataset.index;
- const { todos } = this.data;
- const newTodos = todos.filter((_, i) => i !== index);
- this.setData({
- todos: newTodos,
+
+ // 清空已完成任务
+ clearCompleted() {
+ wx.showModal({
+ title: '确认清空',
+ content: '确定要清空所有已完成的任务吗?此操作不可恢复。',
+ success: (res) => {
+ if (res.confirm) {
+ const todos = this.data.todos.filter(todo => !todo.completed);
+ todoStorage.saveAllTodos(todos);
+ this.loadData();
+ wx.showToast({
+ title: '已清空',
+ icon: 'success'
+ });
+ }
+ }
});
},
+
+ // 导出数据
+ exportData() {
+ const data = todoStorage.exportData();
+ wx.setClipboardData({
+ data: data,
+ success: () => {
+ wx.showToast({
+ title: '数据已复制到剪贴板',
+ icon: 'success'
+ });
+ }
+ });
+ },
+
+ // 格式化时间
+ formatTime(timestamp: number): string {
+ const date = new Date(timestamp);
+ const now = new Date();
+ const diff = now.getTime() - date.getTime();
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
+
+ if (days === 0) {
+ return '今天';
+ } else if (days === 1) {
+ return '昨天';
+ } else if (days < 7) {
+ return `${days}天前`;
+ } else {
+ return date.toLocaleDateString();
+ }
+ },
+
+ // 标签页切换
onTabChange(e: any) {
const targetPage = e.detail.value;
if (targetPage === 'home') {
wx.switchTab({ url: '/pages/index/index' });
+ } else if (targetPage === 'statistics') {
+ wx.switchTab({ url: '/pages/statistics/statistics' });
} else if (targetPage === 'settings') {
wx.switchTab({ url: '/pages/settings/settings' });
}
- // No need to navigate if already on 'list'
- },
- },
+ }
+ }
});
diff --git a/miniprogram/pages/list/list.wxml b/miniprogram/pages/list/list.wxml
index 74b642f..9661420 100644
--- a/miniprogram/pages/list/list.wxml
+++ b/miniprogram/pages/list/list.wxml
@@ -1,46 +1,403 @@
-
-
-
-
-
+
+
+
+
+
+
+ {{stats.total}}
+ 总任务
+
+
+ {{stats.completed}}
+ 已完成
+
+
+ {{stats.pending}}
+ 待完成
+
+
+ {{stats.completionRate}}%
+ 完成率
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部
+ {{filteredTodos.length}}
+
+
+ 待完成
+ {{pendingCount}}
+
+
+ 已完成
+ {{completedCount}}
+
+
+
+
+
+
+
+
+
+ 时间
+
+
+
+ 优先级
+
+
+
+ 名称
+
+
+
+
+
+ 全部
+
+
+
+ 高
+
+
+
+ 中
+
+
+
+ 低
+
+
+
+
+
-
-
-
-
- 太棒了!没有待办事项
-
-
-
-
-
-
- {{item.text}}
+
+
+
+
+ 没有找到匹配的任务
+
+ 还没有任务,添加一个吧!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{formatTime(item.createdAt)}}
+
+ {{item.category === 'work' ? '工作' : item.category === 'personal' ? '个人' : item.category === 'study' ? '学习' : '生活'}}
+
+
+ {{formatDateTime(item.deadline)}}
+
+
+
+
+ 完成于 {{formatTime(item.completedAt)}}
+
+
+
+
+ {{item.note}}
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 清空已完成
+
+
+ 导出数据
+
+
+
+ 共 {{filteredTodos.length}} 个任务,完成率 {{completionRate}}%
+
+
+
+
+
+
+
+
+
+
+
+ 任务内容
+
+
+
+
+
+ 优先级
+
+
+
+ 高优先级
+
+
+
+ 中优先级
+
+
+
+ 低优先级
+
+
+
+
+
+
+ 分类
+
+
+
+ 工作
+
+
+
+ 个人
+
+
+
+ 学习
+
+
+
+ 生活
+
+
+
+
+
+
+ 截止时间(可选)
+
+
+
+ {{newTodoDeadline ? formatDateTime(newTodoDeadline) : '点击选择截止时间'}}
+
+
+
+
+ 清除截止时间
+
+
+
+
+
+ 备注(可选)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
首页
任务
+ 统计
设置
diff --git a/miniprogram/pages/list/list.wxss b/miniprogram/pages/list/list.wxss
index e47543c..f88557d 100644
--- a/miniprogram/pages/list/list.wxss
+++ b/miniprogram/pages/list/list.wxss
@@ -1,115 +1,702 @@
/* pages/list/list.wxss */
-page {
- display: flex;
- flex-direction: column;
- height: 100vh;
- background-color: #f4f4f4;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
-}
.container {
+ padding: 0;
+ background: transparent;
+ /* 确保顶部有足够的安全区 */
+ padding-top: max(env(safe-area-inset-top), 16px);
+}
+
+/* 顶部标题区域 */
+.header-section {
+ padding: 16px 16px 12px;
+ background: transparent;
+}
+
+.header-content {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 4px;
+}
+
+.header-title {
+ font-size: 20px;
+ font-weight: 600;
+ color: #333;
+}
+
+.header-subtitle {
+ font-size: 14px;
+ color: #666;
+ display: block;
+}
+
+.add-button {
+ width: 32px;
+ height: 32px;
+ background: #0052d9;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease;
+}
+
+.add-button:active {
+ background: #0034b5;
+ transform: scale(0.95);
+}
+
+/* 统计概览卡片 */
+.stats-card {
+ background: rgba(255, 255, 255, 0.95);
+ border-radius: 12px;
+ padding: 16px;
+ margin: 0 16px 16px;
+ border: 1px solid rgba(240, 240, 240, 0.8);
+ backdrop-filter: blur(10px);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+}
+
+.stats-row {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 12px;
+}
+
+.stat-item {
+ text-align: center;
+}
+
+.stat-number {
+ display: block;
+ font-size: 18px;
+ font-weight: 600;
+ color: #0052d9;
+ line-height: 1;
+ margin-bottom: 4px;
+}
+
+.stat-label {
+ display: block;
+ font-size: 12px;
+ color: #666;
+ line-height: 1;
+}
+
+/* 搜索和筛选工具栏 */
+.toolbar-section {
+ padding: 0 16px 12px;
+}
+
+.search-box {
+ margin-bottom: 12px;
+}
+
+.search-input {
+ background: #ffffff;
+ border-radius: 8px;
+ border: 1px solid #e0e0e0;
+}
+
+.filter-tabs {
+ display: flex;
+ gap: 8px;
+}
+
+.filter-tab {
+ flex: 1;
display: flex;
flex-direction: column;
- flex: 1;
- padding: 20rpx;
- box-sizing: border-box;
- padding-bottom: 120rpx; /* Space for tab bar */
+ align-items: center;
+ padding: 8px 12px;
+ border-radius: 8px;
+ background: #f5f5f5;
+ transition: all 0.2s ease;
+ position: relative;
}
-.add-todo-section {
+.filter-tab.active {
+ background: #0052d9;
+ color: white;
+}
+
+.filter-tab text {
+ font-size: 12px;
+ line-height: 1;
+}
+
+.tab-count {
+ font-size: 10px;
+ opacity: 0.7;
+ margin-top: 2px;
+}
+
+/* 排序和筛选选项 */
+.sort-section {
+ padding: 0 16px 12px;
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.sort-options {
+ display: flex;
+ gap: 8px;
+ flex: 1;
+}
+
+.sort-option {
display: flex;
align-items: center;
- margin-bottom: 30rpx;
- background-color: #fff;
- border-radius: 16rpx;
- padding: 10rpx;
- box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
+ gap: 4px;
+ padding: 6px 12px;
+ border-radius: 16px;
+ background: #f5f5f5;
+ transition: all 0.2s ease;
+ font-size: 12px;
}
-.add-todo-input {
- flex: 1;
- margin-right: 20rpx;
+.sort-option.active {
+ background: #0052d9;
+ color: white;
}
-.add-todo-input .t-input__control {
- font-size: 30rpx;
+.priority-filter {
+ display: flex;
+ gap: 6px;
}
-.add-todo-button {
- min-width: auto;
+.priority-filter-item {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 6px 10px;
+ border-radius: 12px;
+ background: #f5f5f5;
+ transition: all 0.2s ease;
+ font-size: 11px;
}
+.priority-filter-item.active {
+ background: #0052d9;
+ color: white;
+}
+
+.priority-dot {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+}
+
+.priority-dot.high {
+ background: #e34d59;
+}
+
+.priority-dot.medium {
+ background: #ed7b2f;
+}
+
+.priority-dot.low {
+ background: #0052d9;
+}
+
+/* 任务列表 */
.todo-list-scroll {
flex: 1;
- background-color: #fff;
- border-radius: 16rpx;
- box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
- overflow-y: auto;
+ padding: 0 16px;
+ /* 确保底部有足够的安全区 */
+ padding-bottom: max(env(safe-area-inset-bottom), 100px);
}
-.todo-item-left {
+.todo-list {
display: flex;
- align-items: center;
- width: 100%;
+ flex-direction: column;
+ gap: 8px;
+ padding-bottom: 16px;
}
-.todo-item-content {
+.todo-item {
+ background: #ffffff;
+ border-radius: 12px;
+ padding: 16px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+ border: 1px solid #f0f0f0;
+ transition: all 0.2s ease;
+}
+
+.todo-item.completed {
+ background: #fafafa;
+ opacity: 0.8;
+}
+
+.todo-item:active {
+ transform: scale(0.98);
+}
+
+.todo-main {
display: flex;
- align-items: center;
- flex-grow: 1;
+ gap: 12px;
+}
+
+.todo-checkbox {
+ flex-shrink: 0;
+ margin-top: 2px;
+}
+
+.todo-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.todo-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 8px;
}
.todo-text {
- margin-left: 20rpx;
- font-size: 30rpx;
+ font-size: 15px;
color: #333;
- flex-grow: 1;
- word-break: break-all;
+ line-height: 1.4;
+ flex: 1;
+ font-weight: 500;
}
.todo-text.completed {
text-decoration: line-through;
- color: #aaa;
+ color: #999;
}
-.empty-state {
+.todo-priority {
+ flex-shrink: 0;
+}
+
+.todo-meta {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 8px;
+}
+
+.meta-left {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ flex: 1;
+}
+
+.meta-right {
+ flex-shrink: 0;
+}
+
+.todo-time {
+ font-size: 12px;
+ color: #999;
+}
+
+.todo-category {
+ font-size: 11px;
+ color: #0052d9;
+ background: rgba(0, 82, 217, 0.1);
+ padding: 2px 6px;
+ border-radius: 8px;
+}
+
+.todo-deadline {
+ font-size: 11px;
+ color: #ed7b2f;
+ background: rgba(237, 123, 47, 0.1);
+ padding: 2px 6px;
+ border-radius: 8px;
+}
+
+.todo-deadline.overdue {
+ color: #e34d59;
+ background: rgba(227, 77, 89, 0.1);
+}
+
+.completed-time {
+ font-size: 11px;
+ color: #00a870;
+ background: rgba(0, 168, 112, 0.1);
+ padding: 2px 6px;
+ border-radius: 8px;
+}
+
+.todo-note {
+ font-size: 13px;
+ color: #666;
+ line-height: 1.4;
+ background: #f9f9f9;
+ padding: 8px 12px;
+ border-radius: 6px;
+ border-left: 3px solid #0052d9;
+}
+
+.todo-actions {
+ display: flex;
+ gap: 8px;
+ margin-top: 8px;
+ padding-top: 8px;
+ border-top: 1px solid #f0f0f0;
+}
+
+.action-btn {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
display: flex;
- flex-direction: column;
align-items: center;
justify-content: center;
- padding: 80rpx;
- color: #999;
+ background: #f5f5f5;
+ transition: all 0.2s ease;
+}
+
+.action-btn:active {
+ transform: scale(0.9);
+ background: #e0e0e0;
+}
+
+/* 底部操作栏 */
+.bottom-actions {
+ padding: 12px 16px;
+ background: #ffffff;
+ border-top: 1px solid #f0f0f0;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ /* 添加底部安全区适配 */
+ padding-bottom: max(env(safe-area-inset-bottom), 12px);
+}
+
+.action-group {
+ display: flex;
+ gap: 8px;
+}
+
+.clear-btn,
+.export-btn {
+ background: #f5f5f5;
+ color: #666;
+ border: 1px solid #e0e0e0;
+}
+
+.task-summary {
+ font-size: 12px;
+ color: #666;
+}
+
+/* 空状态样式 */
+.empty-state {
text-align: center;
+ padding: 60px 20px;
+ color: #999;
}
.empty-state .t-icon {
- margin-bottom: 20rpx;
- color: #0052d9;
+ margin-bottom: 12px;
+ opacity: 0.5;
}
-.empty-state text {
- font-size: 28rpx;
+.empty-text {
+ font-size: 14px;
+ display: block;
}
-.t-navbar {
- margin-bottom: 20rpx;
-}
-
-.todo-list .t-cell {
- padding-top: 24rpx;
- padding-bottom: 24rpx;
-}
-
-.todo-list .t-cell .t-icon[name="delete"] {
- font-size: 40rpx;
- padding-left: 20rpx;
-}
-
-.custom-tab-bar {
+/* 添加任务半屏样式 */
+.add-task-sheet {
position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: 2000;
+ opacity: 0;
+ visibility: hidden;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.add-task-sheet.show {
+ opacity: 1;
+ visibility: visible;
+}
+
+.sheet-content {
+ position: absolute;
bottom: 0;
left: 0;
right: 0;
- z-index: 1000; /* Ensure it's on top */
+ background: #ffffff;
+ border-radius: 20px 20px 0 0;
+ max-height: 85vh;
+ transform: translateY(100%);
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ display: flex;
+ flex-direction: column;
+}
+
+.add-task-sheet.show .sheet-content {
+ transform: translateY(0);
+}
+
+/* 半屏头部 */
+.sheet-header {
+ padding: 12px 20px 16px;
+ border-bottom: 1px solid #f0f0f0;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ position: relative;
+}
+
+.sheet-handle {
+ position: absolute;
+ top: 8px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 32px;
+ height: 4px;
+ background: #e0e0e0;
+ border-radius: 2px;
+}
+
+.sheet-title {
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ flex: 1;
+ text-align: center;
+}
+
+.sheet-close {
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: #f5f5f5;
+ transition: background 0.2s ease;
+}
+
+.sheet-close:active {
+ background: #e0e0e0;
+}
+
+/* 半屏主体 */
+.sheet-body {
+ flex: 1;
+ padding: 20px;
+ overflow-y: auto;
+}
+
+.input-section {
+ margin-bottom: 24px;
+}
+
+.input-label {
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+ margin-bottom: 8px;
+ display: block;
+}
+
+.task-input,
+.note-input {
+ width: 100%;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ background: #fafafa;
+ transition: all 0.2s ease;
+}
+
+.task-input:focus,
+.note-input:focus {
+ border-color: #0052d9;
+ background: #ffffff;
+ box-shadow: 0 0 0 2px rgba(0, 82, 217, 0.1);
+}
+
+/* 优先级选择器 */
+.priority-selector {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.priority-item {
+ display: flex;
+ align-items: center;
+ padding: 12px 16px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ background: #ffffff;
+ transition: all 0.2s ease;
+ cursor: pointer;
+}
+
+.priority-item:active {
+ transform: scale(0.98);
+}
+
+.priority-item.active {
+ border-color: #0052d9;
+ background: rgba(0, 82, 217, 0.05);
+}
+
+.priority-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ margin-right: 12px;
+}
+
+.priority-dot.high {
+ background: #e34d59;
+}
+
+.priority-dot.medium {
+ background: #ed7b2f;
+}
+
+.priority-dot.low {
+ background: #0052d9;
+}
+
+.priority-text {
+ font-size: 14px;
+ color: #333;
+}
+
+/* 分类选择器 */
+.category-selector {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 12px;
+}
+
+.category-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 16px 12px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ background: #ffffff;
+ transition: all 0.2s ease;
+ cursor: pointer;
+}
+
+.category-item:active {
+ transform: scale(0.95);
+}
+
+.category-item.active {
+ border-color: #0052d9;
+ background: rgba(0, 82, 217, 0.05);
+}
+
+.category-item .t-icon {
+ margin-bottom: 6px;
+ color: #666;
+}
+
+.category-item.active .t-icon {
+ color: #0052d9;
+}
+
+.category-item text {
+ font-size: 12px;
+ color: #333;
+}
+
+.category-item.active text {
+ color: #0052d9;
+ font-weight: 500;
+}
+
+/* 日期时间选择器 */
+.datetime-picker {
+ display: flex;
+ align-items: center;
+ padding: 12px 16px;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ background: #ffffff;
+ transition: all 0.2s ease;
+ cursor: pointer;
+}
+
+.datetime-picker:active {
+ background: #fafafa;
+ border-color: #0052d9;
+}
+
+.datetime-text {
+ flex: 1;
+ margin: 0 12px;
+ font-size: 14px;
+ color: #333;
+}
+
+.datetime-text.placeholder {
+ color: #999;
+}
+
+/* 清除截止时间按钮 */
+.datetime-clear {
+ margin-top: 8px;
+ text-align: center;
+}
+
+.clear-text {
+ font-size: 12px;
+ color: #e34d59;
+ padding: 4px 8px;
+ border: 1px solid #e34d59;
+ border-radius: 4px;
+ background: rgba(227, 77, 89, 0.05);
+ transition: all 0.2s ease;
+}
+
+.datetime-clear:active .clear-text {
+ background: rgba(227, 77, 89, 0.1);
+}
+
+/* 半屏底部 */
+.sheet-footer {
+ padding: 16px 20px;
+ border-top: 1px solid #f0f0f0;
+ display: flex;
+ gap: 12px;
+ background: #ffffff;
+ border-radius: 0 0 20px 20px;
+}
+
+.cancel-btn {
+ flex: 1;
+ height: 44px;
+ border: 1px solid #e0e0e0;
+ background: #ffffff;
+ color: #666;
+}
+
+.confirm-btn {
+ flex: 2;
+ height: 44px;
+ background: linear-gradient(135deg, #0052d9 0%, #0043a5 100%);
+ border: none;
+}
+
+.confirm-btn[disabled] {
+ background: #e0e0e0;
+ color: #999;
}
diff --git a/miniprogram/pages/logs/logs.json b/miniprogram/pages/logs/logs.json
deleted file mode 100644
index b55b5a2..0000000
--- a/miniprogram/pages/logs/logs.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "usingComponents": {
- }
-}
\ No newline at end of file
diff --git a/miniprogram/pages/logs/logs.ts b/miniprogram/pages/logs/logs.ts
deleted file mode 100644
index dba5c0a..0000000
--- a/miniprogram/pages/logs/logs.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-// logs.ts
-// const util = require('../../utils/util.js')
-import { formatTime } from '../../utils/util'
-
-Component({
- data: {
- logs: [],
- },
- lifetimes: {
- attached() {
- this.setData({
- logs: (wx.getStorageSync('logs') || []).map((log: string) => {
- return {
- date: formatTime(new Date(log)),
- timeStamp: log
- }
- }),
- })
- }
- },
-})
diff --git a/miniprogram/pages/logs/logs.wxml b/miniprogram/pages/logs/logs.wxml
deleted file mode 100644
index 85cf1bf..0000000
--- a/miniprogram/pages/logs/logs.wxml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
- {{index + 1}}. {{log.date}}
-
-
diff --git a/miniprogram/pages/logs/logs.wxss b/miniprogram/pages/logs/logs.wxss
deleted file mode 100644
index 33f9d9e..0000000
--- a/miniprogram/pages/logs/logs.wxss
+++ /dev/null
@@ -1,16 +0,0 @@
-page {
- height: 100vh;
- display: flex;
- flex-direction: column;
-}
-.scrollarea {
- flex: 1;
- overflow-y: hidden;
-}
-.log-item {
- margin-top: 20rpx;
- text-align: center;
-}
-.log-item:last-child {
- padding-bottom: env(safe-area-inset-bottom);
-}
diff --git a/miniprogram/pages/settings/settings.json b/miniprogram/pages/settings/settings.json
index 50a6f6e..1a5128e 100644
--- a/miniprogram/pages/settings/settings.json
+++ b/miniprogram/pages/settings/settings.json
@@ -1,9 +1,12 @@
{
"usingComponents": {
"t-navbar": "tdesign-miniprogram/navbar/navbar",
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-icon": "tdesign-miniprogram/icon/icon",
"t-tab-bar": "tdesign-miniprogram/tab-bar/tab-bar",
"t-tab-bar-item": "tdesign-miniprogram/tab-bar-item/tab-bar-item",
- "t-icon": "tdesign-miniprogram/icon/icon"
+ "t-switch": "tdesign-miniprogram/switch/switch",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog"
},
"navigationStyle": "custom",
"navigationBarTitleText": "设置"
diff --git a/miniprogram/pages/settings/settings.ts b/miniprogram/pages/settings/settings.ts
index 28068b3..bcc0902 100644
--- a/miniprogram/pages/settings/settings.ts
+++ b/miniprogram/pages/settings/settings.ts
@@ -1,18 +1,239 @@
// pages/settings/settings.ts
+import { todoStorage } from '../../utils/todoStorage';
+
Component({
data: {
- activeTab: 'settings', // For tab bar
+ activeTab: 'settings',
+ darkMode: false,
+ notifications: true,
+ autoBackup: true,
+ stats: {} as any,
+ appVersion: '1.0.0',
+ showAboutDialog: false,
+ showBackupDialog: false,
+ backupData: ''
},
+
+ lifetimes: {
+ attached() {
+ this.loadSettings();
+ this.loadStats();
+ }
+ },
+
methods: {
+ // 加载设置
+ loadSettings() {
+ try {
+ const settings = wx.getStorageSync('pure_todo_settings');
+ if (settings) {
+ const parsedSettings = JSON.parse(settings);
+ this.setData({
+ darkMode: parsedSettings.darkMode || false,
+ notifications: parsedSettings.notifications !== false,
+ autoBackup: parsedSettings.autoBackup !== false
+ });
+ }
+ } catch (error) {
+ console.error('加载设置失败:', error);
+ }
+ },
+
+ // 保存设置
+ saveSettings() {
+ try {
+ const settings = {
+ darkMode: this.data.darkMode,
+ notifications: this.data.notifications,
+ autoBackup: this.data.autoBackup
+ };
+ wx.setStorageSync('pure_todo_settings', JSON.stringify(settings));
+ } catch (error) {
+ console.error('保存设置失败:', error);
+ }
+ },
+
+ // 加载统计信息
+ loadStats() {
+ const stats = todoStorage.getStats();
+ this.setData({ stats });
+ },
+
+ // 切换深色模式
+ onDarkModeChange(e: any) {
+ this.setData({ darkMode: e.detail.value }, () => {
+ this.saveSettings();
+ wx.showToast({
+ title: e.detail.value ? '已开启深色模式' : '已关闭深色模式',
+ icon: 'success'
+ });
+ });
+ },
+
+ // 切换通知
+ onNotificationsChange(e: any) {
+ this.setData({ notifications: e.detail.value }, () => {
+ this.saveSettings();
+ wx.showToast({
+ title: e.detail.value ? '已开启通知' : '已关闭通知',
+ icon: 'success'
+ });
+ });
+ },
+
+ // 切换自动备份
+ onAutoBackupChange(e: any) {
+ this.setData({ autoBackup: e.detail.value }, () => {
+ this.saveSettings();
+ wx.showToast({
+ title: e.detail.value ? '已开启自动备份' : '已关闭自动备份',
+ icon: 'success'
+ });
+ });
+ },
+
+ // 导出数据
+ exportData() {
+ const data = todoStorage.exportData();
+ this.setData({
+ backupData: data,
+ showBackupDialog: true
+ });
+ },
+
+ // 导入数据
+ importData() {
+ wx.showModal({
+ title: '导入数据',
+ content: '请将备份数据粘贴到输入框中',
+ editable: true,
+ placeholderText: '粘贴备份数据...',
+ success: (res) => {
+ if (res.confirm && res.content) {
+ const success = todoStorage.importData(res.content);
+ if (success) {
+ this.loadStats();
+ wx.showToast({
+ title: '数据导入成功',
+ icon: 'success'
+ });
+ } else {
+ wx.showToast({
+ title: '数据格式错误',
+ icon: 'error'
+ });
+ }
+ }
+ }
+ });
+ },
+
+ // 清空数据
+ clearData() {
+ wx.showModal({
+ title: '确认清空',
+ content: '确定要清空所有数据吗?此操作不可恢复!',
+ success: (res) => {
+ if (res.confirm) {
+ todoStorage.clearAllData();
+ this.loadStats();
+ wx.showToast({
+ title: '数据已清空',
+ icon: 'success'
+ });
+ }
+ }
+ });
+ },
+
+ // 重置设置
+ resetSettings() {
+ wx.showModal({
+ title: '确认重置',
+ content: '确定要重置所有设置吗?',
+ success: (res) => {
+ if (res.confirm) {
+ this.setData({
+ darkMode: false,
+ notifications: true,
+ autoBackup: true
+ }, () => {
+ this.saveSettings();
+ wx.showToast({
+ title: '设置已重置',
+ icon: 'success'
+ });
+ });
+ }
+ }
+ });
+ },
+
+ // 显示关于对话框
+ showAbout() {
+ this.setData({ showAboutDialog: true });
+ },
+
+ // 隐藏关于对话框
+ hideAbout() {
+ this.setData({ showAboutDialog: false });
+ },
+
+ // 隐藏备份对话框
+ hideBackup() {
+ this.setData({ showBackupDialog: false });
+ },
+
+ // 复制备份数据
+ copyBackupData() {
+ wx.setClipboardData({
+ data: this.data.backupData,
+ success: () => {
+ wx.showToast({
+ title: '备份数据已复制',
+ icon: 'success'
+ });
+ }
+ });
+ },
+
+ // 标签页切换
onTabChange(e: any) {
const targetPage = e.detail.value;
if (targetPage === 'home') {
wx.switchTab({ url: '/pages/index/index' });
} else if (targetPage === 'list') {
wx.switchTab({ url: '/pages/list/list' });
+ } else if (targetPage === 'statistics') {
+ wx.switchTab({ url: '/pages/statistics/statistics' });
}
- // No need to navigate if already on 'settings'
},
- },
+
+ // 联系开发者
+ contactDeveloper() {
+ wx.showModal({
+ title: '联系开发者',
+ content: '如有问题或建议,请通过以下方式联系:\n\n邮箱:developer@example.com\n微信:developer_wechat',
+ showCancel: false
+ });
+ },
+
+ // 检查更新
+ checkUpdate() {
+ wx.showToast({
+ title: '已是最新版本',
+ icon: 'success'
+ });
+ },
+
+ // 给应用评分
+ rateApp() {
+ wx.showModal({
+ title: '给应用评分',
+ content: '感谢您的使用!请在应用商店给Pure Todo一个好评吧!',
+ showCancel: false
+ });
+ }
+ }
});
diff --git a/miniprogram/pages/settings/settings.wxml b/miniprogram/pages/settings/settings.wxml
index 9d571a0..4898087 100644
--- a/miniprogram/pages/settings/settings.wxml
+++ b/miniprogram/pages/settings/settings.wxml
@@ -1,13 +1,243 @@
-
-
-
- 设置页面,敬请期待!
+
+
+
+
+
+
+
+
+
+
+
+ Pure Todo
+ 版本 {{appVersion}}
+
+
+
+ 总任务: {{stats.total || 0}}
+ 已完成: {{stats.completed || 0}}
+ 完成率: {{stats.completionRate || 0}}%
+
+
+
+
+
+
+
+
+
+
+
+
+ 深色模式
+
+
+
+ 切换深色主题,保护眼睛
+
+
+
+
+
+
+
+
+
+
+
+
+ 推送通知
+
+
+
+ 接收任务提醒和完成通知
+
+
+
+
+
+
+
+
+
+
+
+
+ 自动备份
+
+
+
+ 定期自动备份数据到云端
+
+
+
+
+
+
+ 导出数据
+
+
+
+ 将数据导出为备份文件
+
+
+
+
+
+
+ 导入数据
+
+
+
+ 从备份文件恢复数据
+
+
+
+
+
+
+ 清空数据
+
+
+
+ 删除所有任务和设置
+
+
+
+
+
+
+
+
+
+
+
+
+ 重置设置
+
+
+
+ 恢复所有设置为默认值
+
+
+
+
+
+
+ 检查更新
+
+
+
+ 检查是否有新版本可用
+
+
+
+
+
+
+ 给应用评分
+
+
+
+ 在应用商店给我们好评
+
+
+
+
+
+
+ 关于应用
+
+
+
+ 查看应用信息和开发者联系方式
+
+
+
+
+
+
+ 联系开发者
+
+
+
+ 反馈问题或建议新功能
+
+
+
+
+
+
+
+
+
+ 一个简洁高效的待办事项管理应用,帮助你更好地管理时间和任务。
+
+
+ • 简洁直观的界面设计
+ • 智能的任务分类和筛选
+ • 详细的数据统计和分析
+ • 安全的数据备份和恢复
+
+
+
+
+
+
+
+ 以下是你的备份数据,请妥善保存:
+
+ {{backupData}}
+
+
+
+
+
首页
任务
+ 统计
设置
diff --git a/miniprogram/pages/settings/settings.wxss b/miniprogram/pages/settings/settings.wxss
index 54bc8f3..2e43125 100644
--- a/miniprogram/pages/settings/settings.wxss
+++ b/miniprogram/pages/settings/settings.wxss
@@ -1,39 +1,249 @@
/* pages/settings/settings.wxss */
-page {
- display: flex;
- flex-direction: column;
- height: 100vh;
- background-color: #f4f4f4;
-}
.container {
- display: flex;
- flex-direction: column;
- flex: 1;
- padding-bottom: 120rpx; /* Space for tab bar */
+ padding: 0;
+ background: transparent;
+ /* 确保顶部有足够的安全区 */
+ padding-top: max(env(safe-area-inset-top), 16px);
+ /* 确保底部有足够的安全区 */
+ padding-bottom: max(env(safe-area-inset-bottom), 80px);
}
-.content {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 40rpx;
+/* 顶部标题 */
+.header-section {
+ padding: 16px;
text-align: center;
- color: #555;
}
-.content .t-icon {
- margin-bottom: 20rpx;
- color: #0052d9;
+.header-title {
+ display: block;
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ margin-bottom: 4px;
}
-.custom-tab-bar {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- z-index: 1000;
+.header-subtitle {
+ display: block;
+ font-size: 12px;
+ color: #999;
+}
+
+/* 应用信息 */
+.app-info-section {
+ padding: 0 12px;
+ margin-bottom: 16px;
+}
+
+.app-info-card {
+ background: #ffffff;
+ border-radius: 8px;
+ padding: 16px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.app-info {
+ display: flex;
+ align-items: center;
+ margin-bottom: 16px;
+}
+
+.app-icon {
+ margin-right: 12px;
+}
+
+.app-details {
+ flex: 1;
+}
+
+.app-name {
+ display: block;
+ font-size: 16px;
+ font-weight: 600;
+ color: #333;
+ margin-bottom: 2px;
+}
+
+.app-version {
+ display: block;
+ font-size: 12px;
+ color: #999;
+}
+
+.app-stats {
+ display: flex;
+ justify-content: space-between;
+ gap: 8px;
+}
+
+.stat-item {
+ font-size: 11px;
+ color: #999;
+ text-align: center;
+ flex: 1;
+}
+
+/* 设置区域 */
+.settings-section {
+ padding: 0 12px;
+ margin-bottom: 16px;
+}
+
+.section-header {
+ margin-bottom: 12px;
+ padding: 0 4px;
+}
+
+.section-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #333;
+}
+
+.settings-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.setting-item {
+ background: #ffffff;
+ border-radius: 6px;
+ padding: 12px;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ transition: all 0.2s ease;
+}
+
+.setting-item:active {
+ background: #f5f5f5;
+}
+
+.setting-content {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 6px;
+}
+
+.setting-info {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.setting-label {
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+}
+
+.setting-label.danger {
+ color: #e34d59;
+}
+
+.setting-desc {
+ font-size: 11px;
+ color: #999;
+ line-height: 1.3;
+}
+
+/* 对话框样式 */
+.about-content {
+ padding: 16px 0;
+}
+
+.about-header {
+ text-align: center;
+ margin-bottom: 16px;
+}
+
+.about-title {
+ display: block;
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ margin: 8px 0 2px;
+}
+
+.about-version {
+ display: block;
+ font-size: 12px;
+ color: #999;
+}
+
+.about-desc {
+ display: block;
+ font-size: 12px;
+ color: #666;
+ line-height: 1.4;
+ margin-bottom: 16px;
+ text-align: center;
+}
+
+.about-features {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.feature-item {
+ font-size: 12px;
+ color: #333;
+ line-height: 1.3;
+}
+
+.backup-content {
+ padding: 16px 0;
+}
+
+.backup-desc {
+ display: block;
+ font-size: 12px;
+ color: #666;
+ margin-bottom: 12px;
+}
+
+.backup-data {
+ background: #f5f5f5;
+ border-radius: 4px;
+ padding: 8px;
+ max-height: 120px;
+ overflow-y: auto;
+}
+
+.backup-text {
+ font-size: 10px;
+ color: #333;
+ font-family: monospace;
+ word-break: break-all;
+ line-height: 1.3;
+}
+
+/* 动画效果 */
+.fade-in {
+ animation: fadeIn 0.5s ease-out;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* 响应式设计 */
+@media (max-width: 375px) {
+ .app-stats {
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ .stat-item {
+ text-align: left;
+ }
}
diff --git a/miniprogram/pages/statistics/statistics.json b/miniprogram/pages/statistics/statistics.json
new file mode 100644
index 0000000..4c5c752
--- /dev/null
+++ b/miniprogram/pages/statistics/statistics.json
@@ -0,0 +1,11 @@
+{
+ "usingComponents": {
+ "t-navbar": "tdesign-miniprogram/navbar/navbar",
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-tab-bar": "tdesign-miniprogram/tab-bar/tab-bar",
+ "t-tab-bar-item": "tdesign-miniprogram/tab-bar-item/tab-bar-item",
+ "t-tag": "tdesign-miniprogram/tag/tag",
+ "t-progress": "tdesign-miniprogram/progress/progress"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/pages/statistics/statistics.ts b/miniprogram/pages/statistics/statistics.ts
new file mode 100644
index 0000000..b6b11ad
--- /dev/null
+++ b/miniprogram/pages/statistics/statistics.ts
@@ -0,0 +1,237 @@
+// pages/statistics/statistics.ts
+import { todoStorage, ITodo, ITodoStats } from '../../utils/todoStorage';
+
+Component({
+ data: {
+ activeTab: 'statistics',
+ stats: {} as ITodoStats,
+ todos: [] as ITodo[],
+ weeklyData: [] as any[],
+ priorityData: [] as any[],
+ categoryData: [] as any[],
+ recentActivity: [] as any[]
+ },
+
+ lifetimes: {
+ attached() {
+ this.loadData();
+ }
+ },
+
+ pageLifetimes: {
+ show() {
+ this.loadData();
+ }
+ },
+
+ methods: {
+ // 加载数据
+ loadData() {
+ const stats = todoStorage.getStats();
+ const todos = todoStorage.getAllTodos();
+
+ this.setData({ stats, todos }, () => {
+ this.calculateWeeklyData();
+ this.calculatePriorityData();
+ this.calculateCategoryData();
+ this.calculateRecentActivity();
+ });
+ },
+
+ // 计算周数据
+ calculateWeeklyData() {
+ const weekData = [];
+ const today = new Date();
+
+ for (let i = 6; i >= 0; i--) {
+ const date = new Date(today);
+ date.setDate(date.getDate() - i);
+ date.setHours(0, 0, 0, 0);
+
+ const nextDate = new Date(date);
+ nextDate.setDate(nextDate.getDate() + 1);
+
+ const dayTodos = this.data.todos.filter(todo => {
+ const todoDate = new Date(todo.createdAt);
+ todoDate.setHours(0, 0, 0, 0);
+ return todoDate.getTime() === date.getTime();
+ });
+
+ const completedTodos = dayTodos.filter(todo => todo.completed);
+
+ weekData.push({
+ date: date.toLocaleDateString('zh-CN', { weekday: 'short' }),
+ created: dayTodos.length,
+ completed: completedTodos.length,
+ dateObj: date
+ });
+ }
+
+ this.setData({ weeklyData: weekData });
+ },
+
+ // 计算优先级数据
+ calculatePriorityData() {
+ const priorityCount = { high: 0, medium: 0, low: 0 };
+ const priorityCompleted = { high: 0, medium: 0, low: 0 };
+
+ this.data.todos.forEach(todo => {
+ priorityCount[todo.priority]++;
+ if (todo.completed) {
+ priorityCompleted[todo.priority]++;
+ }
+ });
+
+ const priorityData = [
+ {
+ name: '高优先级',
+ total: priorityCount.high,
+ completed: priorityCompleted.high,
+ rate: priorityCount.high > 0 ? Math.round((priorityCompleted.high / priorityCount.high) * 100) : 0,
+ color: '#ff6b6b'
+ },
+ {
+ name: '中优先级',
+ total: priorityCount.medium,
+ completed: priorityCompleted.medium,
+ rate: priorityCount.medium > 0 ? Math.round((priorityCompleted.medium / priorityCount.medium) * 100) : 0,
+ color: '#feca57'
+ },
+ {
+ name: '低优先级',
+ total: priorityCount.low,
+ completed: priorityCompleted.low,
+ rate: priorityCount.low > 0 ? Math.round((priorityCompleted.low / priorityCount.low) * 100) : 0,
+ color: '#48dbfb'
+ }
+ ];
+
+ this.setData({ priorityData });
+ },
+
+ // 计算分类数据
+ calculateCategoryData() {
+ const categoryMap = new Map();
+
+ this.data.todos.forEach(todo => {
+ const category = todo.category || '未分类';
+ if (!categoryMap.has(category)) {
+ categoryMap.set(category, { total: 0, completed: 0 });
+ }
+ categoryMap.get(category).total++;
+ if (todo.completed) {
+ categoryMap.get(category).completed++;
+ }
+ });
+
+ const categoryData = Array.from(categoryMap.entries()).map(([name, data]) => ({
+ name,
+ total: data.total,
+ completed: data.completed,
+ rate: data.total > 0 ? Math.round((data.completed / data.total) * 100) : 0
+ }));
+
+ this.setData({ categoryData });
+ },
+
+ // 计算最近活动
+ calculateRecentActivity() {
+ const recentTodos = this.data.todos
+ .sort((a, b) => b.createdAt - a.createdAt)
+ .slice(0, 10);
+
+ const recentActivity = recentTodos.map(todo => ({
+ id: todo.id,
+ text: todo.text,
+ completed: todo.completed,
+ priority: todo.priority,
+ createdAt: todo.createdAt,
+ completedAt: todo.completedAt,
+ timeAgo: this.getTimeAgo(todo.createdAt)
+ }));
+
+ this.setData({ recentActivity });
+ },
+
+ // 获取时间差
+ getTimeAgo(timestamp: number): string {
+ const now = Date.now();
+ const diff = now - timestamp;
+ const minutes = Math.floor(diff / (1000 * 60));
+ const hours = Math.floor(diff / (1000 * 60 * 60));
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
+
+ if (minutes < 60) {
+ return `${minutes}分钟前`;
+ } else if (hours < 24) {
+ return `${hours}小时前`;
+ } else {
+ return `${days}天前`;
+ }
+ },
+
+ // 标签页切换
+ onTabChange(e: any) {
+ const targetPage = e.detail.value;
+ if (targetPage === 'home') {
+ wx.switchTab({ url: '/pages/index/index' });
+ } else if (targetPage === 'list') {
+ wx.switchTab({ url: '/pages/list/list' });
+ } else if (targetPage === 'settings') {
+ wx.switchTab({ url: '/pages/settings/settings' });
+ }
+ },
+
+ // 导出数据
+ exportData() {
+ const data = todoStorage.exportData();
+ wx.setClipboardData({
+ data: data,
+ success: () => {
+ wx.showToast({
+ title: '数据已复制到剪贴板',
+ icon: 'success'
+ });
+ }
+ });
+ },
+
+ // 清空数据
+ clearData() {
+ wx.showModal({
+ title: '确认清空',
+ content: '确定要清空所有数据吗?此操作不可恢复!',
+ success: (res) => {
+ if (res.confirm) {
+ todoStorage.clearAllData();
+ this.loadData();
+ wx.showToast({
+ title: '数据已清空',
+ icon: 'success'
+ });
+ }
+ }
+ });
+ },
+
+ // 获取优先级颜色
+ getPriorityColor(priority: string): string {
+ switch (priority) {
+ case 'high': return '#ff6b6b';
+ case 'medium': return '#feca57';
+ case 'low': return '#48dbfb';
+ default: return '#667eea';
+ }
+ },
+
+ // 格式化百分比
+ formatPercentage(value: number): string {
+ return `${value}%`;
+ },
+
+ // 获取完成状态文本
+ getCompletionText(completed: boolean): string {
+ return completed ? '已完成' : '进行中';
+ }
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/statistics/statistics.wxml b/miniprogram/pages/statistics/statistics.wxml
new file mode 100644
index 0000000..d052989
--- /dev/null
+++ b/miniprogram/pages/statistics/statistics.wxml
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{stats.total}}
+ 总任务
+
+
+ {{stats.completed}}
+ 已完成
+
+
+ {{stats.pending}}
+ 待完成
+
+
+ {{stats.completionRate}}%
+ 完成率
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.date}}
+ {{item.created}}/{{item.completed}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 已完成: {{item.completed}} / {{item.total}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.name}}
+ {{item.total}} 个任务
+
+
+ 已完成: {{item.completed}}
+ {{formatPercentage(item.rate)}}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.text}}
+ {{item.timeAgo}}
+
+
+
+ {{item.priority === 'high' ? '高' : item.priority === 'medium' ? '中' : '低'}}
+
+
+ {{getCompletionText(item.completed)}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 导出数据
+
+
+
+ 清空数据
+
+
+
+
+
+
+ 首页
+ 任务
+ 统计
+ 设置
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/statistics/statistics.wxss b/miniprogram/pages/statistics/statistics.wxss
new file mode 100644
index 0000000..cf70342
--- /dev/null
+++ b/miniprogram/pages/statistics/statistics.wxss
@@ -0,0 +1,361 @@
+/* pages/statistics/statistics.wxss */
+
+.container {
+ padding: 0;
+ background: transparent;
+ /* 确保顶部有足够的安全区 */
+ padding-top: max(env(safe-area-inset-top), 16px);
+ /* 确保底部有足够的安全区 */
+ padding-bottom: max(env(safe-area-inset-bottom), 80px);
+}
+
+/* 顶部标题 */
+.header-section {
+ padding: 16px;
+ text-align: center;
+}
+
+.header-title {
+ display: block;
+ font-size: 18px;
+ font-weight: 600;
+ color: #333;
+ margin-bottom: 4px;
+}
+
+.header-subtitle {
+ display: block;
+ font-size: 12px;
+ color: #999;
+}
+
+/* 总体统计 */
+.overview-section {
+ padding: 0 12px;
+ margin-bottom: 16px;
+}
+
+.overview-card {
+ background: #ffffff;
+ border-radius: 8px;
+ padding: 16px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.overview-header {
+ margin-bottom: 16px;
+}
+
+.overview-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #333;
+}
+
+.overview-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 12px;
+ margin-bottom: 16px;
+}
+
+.overview-item {
+ text-align: center;
+}
+
+.overview-number {
+ display: block;
+ font-size: 20px;
+ font-weight: 600;
+ color: #0052d9;
+ margin-bottom: 4px;
+}
+
+.overview-label {
+ display: block;
+ font-size: 12px;
+ color: #999;
+}
+
+/* 周数据统计 */
+.weekly-section {
+ padding: 0 12px;
+ margin-bottom: 16px;
+}
+
+.section-header {
+ margin-bottom: 12px;
+ padding: 0 4px;
+}
+
+.section-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #333;
+}
+
+.weekly-card {
+ background: #ffffff;
+ border-radius: 8px;
+ padding: 16px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.weekly-chart {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-end;
+ height: 120px;
+ gap: 6px;
+}
+
+.chart-bar {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1;
+}
+
+.bar-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1px;
+ margin-bottom: 6px;
+}
+
+.bar {
+ width: 16px;
+ border-radius: 1px;
+ min-height: 2px;
+}
+
+.bar.created {
+ background: #0052d9;
+}
+
+.bar.completed {
+ background: #00a870;
+}
+
+.chart-label {
+ font-size: 10px;
+ color: #999;
+ margin-bottom: 2px;
+}
+
+.chart-value {
+ font-size: 8px;
+ color: #ccc;
+}
+
+/* 优先级统计 */
+.priority-section {
+ padding: 0 12px;
+ margin-bottom: 16px;
+}
+
+.priority-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.priority-item {
+ background: #ffffff;
+ border-radius: 6px;
+ padding: 12px;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.priority-info {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.priority-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.priority-name {
+ font-size: 14px;
+ font-weight: 600;
+ color: #333;
+}
+
+.priority-rate {
+ font-size: 14px;
+ font-weight: 600;
+ color: #0052d9;
+}
+
+.priority-stats {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.priority-count {
+ font-size: 12px;
+ color: #999;
+}
+
+/* 分类统计 */
+.category-section {
+ padding: 0 12px;
+ margin-bottom: 16px;
+}
+
+.category-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.category-item {
+ background: #ffffff;
+ border-radius: 6px;
+ padding: 12px;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.category-info {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 6px;
+}
+
+.category-name {
+ font-size: 14px;
+ font-weight: 600;
+ color: #333;
+}
+
+.category-count {
+ font-size: 12px;
+ color: #999;
+}
+
+.category-stats {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.category-completed {
+ font-size: 12px;
+ color: #999;
+}
+
+.category-rate {
+ font-size: 12px;
+ font-weight: 600;
+ color: #0052d9;
+}
+
+/* 最近活动 */
+.activity-section {
+ padding: 0 12px;
+ margin-bottom: 16px;
+}
+
+.activity-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.activity-item {
+ background: #ffffff;
+ border-radius: 6px;
+ padding: 12px;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.activity-content {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 8px;
+}
+
+.activity-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.activity-text {
+ font-size: 14px;
+ color: #333;
+ line-height: 1.4;
+}
+
+.activity-text.completed {
+ text-decoration: line-through;
+ color: #999;
+}
+
+.activity-time {
+ font-size: 12px;
+ color: #999;
+}
+
+.activity-status {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+/* 数据管理 */
+.data-section {
+ padding: 0 12px;
+ margin-bottom: 16px;
+}
+
+.data-actions {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.action-btn {
+ margin: 0;
+}
+
+/* 动画效果 */
+.fade-in {
+ animation: fadeIn 0.5s ease-out;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* 响应式设计 */
+@media (max-width: 375px) {
+ .overview-grid {
+ grid-template-columns: repeat(2, 1fr);
+ gap: 12px;
+ }
+
+ .weekly-chart {
+ height: 150px;
+ }
+
+ .bar {
+ width: 16px;
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/utils/todoStorage.ts b/miniprogram/utils/todoStorage.ts
new file mode 100644
index 0000000..139828f
--- /dev/null
+++ b/miniprogram/utils/todoStorage.ts
@@ -0,0 +1,219 @@
+// utils/todoStorage.ts
+
+export interface ITodo {
+ id: string;
+ text: string;
+ completed: boolean;
+ priority: 'high' | 'medium' | 'low';
+ createdAt: number;
+ completedAt?: number;
+ category?: string;
+ deadline?: number;
+ note?: string;
+}
+
+export interface ITodoStats {
+ total: number;
+ completed: number;
+ pending: number;
+ completionRate: number;
+ todayCompleted: number;
+}
+
+class TodoStorage {
+ private readonly STORAGE_KEY = 'pure_todo_data';
+ private readonly STATS_KEY = 'pure_todo_stats';
+
+ // 获取所有待办事项
+ getAllTodos(): ITodo[] {
+ try {
+ const data = wx.getStorageSync(this.STORAGE_KEY);
+ return data ? JSON.parse(data) : [];
+ } catch (error) {
+ console.error('获取待办事项失败:', error);
+ return [];
+ }
+ }
+
+ // 保存所有待办事项
+ saveAllTodos(todos: ITodo[]): void {
+ try {
+ wx.setStorageSync(this.STORAGE_KEY, JSON.stringify(todos));
+ this.updateStats(todos);
+ } catch (error) {
+ console.error('保存待办事项失败:', error);
+ }
+ }
+
+ // 添加新待办事项
+ addTodo(
+ text: string,
+ priority: 'high' | 'medium' | 'low' = 'medium',
+ category?: string,
+ deadline?: number,
+ note?: string
+ ): ITodo {
+ const todos = this.getAllTodos();
+ const newTodo: ITodo = {
+ id: this.generateId(),
+ text: text.trim(),
+ completed: false,
+ priority,
+ createdAt: Date.now(),
+ category,
+ deadline,
+ note
+ };
+
+ todos.unshift(newTodo);
+ this.saveAllTodos(todos);
+ return newTodo;
+ }
+
+ // 更新待办事项
+ updateTodo(id: string, updates: Partial): boolean {
+ const todos = this.getAllTodos();
+ const index = todos.findIndex(todo => todo.id === id);
+
+ if (index !== -1) {
+ todos[index] = { ...todos[index], ...updates };
+ if (updates.completed !== undefined) {
+ todos[index].completedAt = updates.completed ? Date.now() : undefined;
+ }
+ this.saveAllTodos(todos);
+ return true;
+ }
+ return false;
+ }
+
+ // 删除待办事项
+ deleteTodo(id: string): boolean {
+ const todos = this.getAllTodos();
+ const filteredTodos = todos.filter(todo => todo.id !== id);
+
+ if (filteredTodos.length !== todos.length) {
+ this.saveAllTodos(filteredTodos);
+ return true;
+ }
+ return false;
+ }
+
+ // 切换完成状态
+ toggleTodo(id: string): boolean {
+ const todos = this.getAllTodos();
+ const todo = todos.find(t => t.id === id);
+
+ if (todo) {
+ return this.updateTodo(id, {
+ completed: !todo.completed,
+ completedAt: !todo.completed ? Date.now() : undefined
+ });
+ }
+ return false;
+ }
+
+ // 获取统计信息
+ getStats(): ITodoStats {
+ try {
+ const data = wx.getStorageSync(this.STATS_KEY);
+ return data ? JSON.parse(data) : this.calculateStats();
+ } catch (error) {
+ return this.calculateStats();
+ }
+ }
+
+ // 计算统计信息
+ private calculateStats(): ITodoStats {
+ const todos = this.getAllTodos();
+ const total = todos.length;
+ const completed = todos.filter(todo => todo.completed).length;
+ const pending = total - completed;
+ const completionRate = total > 0 ? Math.round((completed / total) * 100) : 0;
+
+ // 计算今日完成的任务
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ const todayCompleted = todos.filter(todo =>
+ todo.completed && todo.completedAt && todo.completedAt >= today.getTime()
+ ).length;
+
+ return {
+ total,
+ completed,
+ pending,
+ completionRate,
+ todayCompleted
+ };
+ }
+
+ // 更新统计信息
+ private updateStats(todos: ITodo[]): void {
+ const stats = this.calculateStats();
+ try {
+ wx.setStorageSync(this.STATS_KEY, JSON.stringify(stats));
+ } catch (error) {
+ console.error('更新统计信息失败:', error);
+ }
+ }
+
+ // 生成唯一ID
+ private generateId(): string {
+ return Date.now().toString(36) + Math.random().toString(36).substr(2);
+ }
+
+ // 按优先级获取待办事项
+ getTodosByPriority(priority?: 'high' | 'medium' | 'low'): ITodo[] {
+ const todos = this.getAllTodos();
+ if (priority) {
+ return todos.filter(todo => todo.priority === priority);
+ }
+ return todos;
+ }
+
+ // 获取今日待办事项
+ getTodayTodos(): ITodo[] {
+ const todos = this.getAllTodos();
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+
+ return todos.filter(todo => {
+ const todoDate = new Date(todo.createdAt);
+ todoDate.setHours(0, 0, 0, 0);
+ return todoDate.getTime() === today.getTime();
+ });
+ }
+
+ // 清空所有数据
+ clearAllData(): void {
+ try {
+ wx.removeStorageSync(this.STORAGE_KEY);
+ wx.removeStorageSync(this.STATS_KEY);
+ } catch (error) {
+ console.error('清空数据失败:', error);
+ }
+ }
+
+ // 导出数据
+ exportData(): string {
+ const todos = this.getAllTodos();
+ const stats = this.getStats();
+ return JSON.stringify({ todos, stats, exportTime: Date.now() });
+ }
+
+ // 导入数据
+ importData(dataString: string): boolean {
+ try {
+ const data = JSON.parse(dataString);
+ if (data.todos && Array.isArray(data.todos)) {
+ this.saveAllTodos(data.todos);
+ return true;
+ }
+ return false;
+ } catch (error) {
+ console.error('导入数据失败:', error);
+ return false;
+ }
+ }
+}
+
+export const todoStorage = new TodoStorage();
\ No newline at end of file
diff --git a/project.private.config.json b/project.private.config.json
index 85245bc..f4470b2 100644
--- a/project.private.config.json
+++ b/project.private.config.json
@@ -3,5 +3,6 @@
"projectname": "miniprogram-1",
"setting": {
"compileHotReLoad": true
- }
+ },
+ "libVersion": "3.9.2"
}
\ No newline at end of file