From a4505f1aa5aa82a89ce53786efc8a5dc3f8c6568 Mon Sep 17 00:00:00 2001 From: grtsinry43 Date: Fri, 12 Sep 2025 10:41:45 +0800 Subject: [PATCH] feat: Add statistics page and enhance settings with data management features --- README.md | 269 +++++++ miniprogram/app.json | 33 +- miniprogram/app.wxss | 136 +++- miniprogram/pages/index/index.json | 9 +- miniprogram/pages/index/index.ts | 378 +++++++++- miniprogram/pages/index/index.wxml | 262 ++++++- miniprogram/pages/index/index.wxss | 628 ++++++++++++++-- miniprogram/pages/list/list.json | 10 +- miniprogram/pages/list/list.ts | 460 +++++++++++- miniprogram/pages/list/list.wxml | 421 ++++++++++- miniprogram/pages/list/list.wxss | 713 +++++++++++++++++-- miniprogram/pages/logs/logs.json | 4 - miniprogram/pages/logs/logs.ts | 21 - miniprogram/pages/logs/logs.wxml | 6 - miniprogram/pages/logs/logs.wxss | 16 - miniprogram/pages/settings/settings.json | 5 +- miniprogram/pages/settings/settings.ts | 227 +++++- miniprogram/pages/settings/settings.wxml | 238 ++++++- miniprogram/pages/settings/settings.wxss | 264 ++++++- miniprogram/pages/statistics/statistics.json | 11 + miniprogram/pages/statistics/statistics.ts | 237 ++++++ miniprogram/pages/statistics/statistics.wxml | 175 +++++ miniprogram/pages/statistics/statistics.wxss | 361 ++++++++++ miniprogram/utils/todoStorage.ts | 219 ++++++ project.private.config.json | 3 +- 25 files changed, 4808 insertions(+), 298 deletions(-) create mode 100644 README.md delete mode 100644 miniprogram/pages/logs/logs.json delete mode 100644 miniprogram/pages/logs/logs.ts delete mode 100644 miniprogram/pages/logs/logs.wxml delete mode 100644 miniprogram/pages/logs/logs.wxss create mode 100644 miniprogram/pages/statistics/statistics.json create mode 100644 miniprogram/pages/statistics/statistics.ts create mode 100644 miniprogram/pages/statistics/statistics.wxml create mode 100644 miniprogram/pages/statistics/statistics.wxss create mode 100644 miniprogram/utils/todoStorage.ts 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)}} + + + + + + + + 全部 + + + + 添加 + + + + + + + + + + 今日任务 + {{todayTodos.length}} + + + + + 暂无任务 + + + + + + + {{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}} + + + + + 没有找到匹配的任务 + + 还没有任务,添加一个吧! + + + + + + + + + + + + {{item.text}} + + + {{item.priority === 'high' ? '高' : item.priority === 'medium' ? '中' : '低'}} + + + + + + + {{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}}% + + + + + + + + 外观 + + + + + + + 深色模式 + + + + 切换深色主题,保护眼睛 + + + + + + + + 通知 + + + + + + + 推送通知 + + + + 接收任务提醒和完成通知 + + + + + + + + 数据管理 + + + + + + + 自动备份 + + + + 定期自动备份数据到云端 + + + + + + + 导出数据 + + + + 将数据导出为备份文件 + + + + + + + 导入数据 + + + + 从备份文件恢复数据 + + + + + + + 清空数据 + + + + 删除所有任务和设置 + + + + + + + + 应用 + + + + + + + 重置设置 + + + + 恢复所有设置为默认值 + + + + + + + 检查更新 + + + + 检查是否有新版本可用 + + + + + + + 给应用评分 + + + + 在应用商店给我们好评 + + + + + + + 关于应用 + + + + 查看应用信息和开发者联系方式 + + + + + + + 联系开发者 + + + + 反馈问题或建议新功能 + + + + + + + + + + Pure Todo + 版本 {{appVersion}} + + + 一个简洁高效的待办事项管理应用,帮助你更好地管理时间和任务。 + + + • 简洁直观的界面设计 + • 智能的任务分类和筛选 + • 详细的数据统计和分析 + • 安全的数据备份和恢复 + + + + + + + + 以下是你的备份数据,请妥善保存: + + {{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.name}} + {{formatPercentage(item.rate)}} + + + 已完成: {{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