feat: Add statistics page and enhance settings with data management features

This commit is contained in:
grtsinry43 2025-09-12 10:41:45 +08:00
parent 5c3d956de9
commit a4505f1aa5
Signed by: grtsinry43
GPG Key ID: F3305FB3A978C934
25 changed files with 4808 additions and 298 deletions

269
README.md Normal file
View File

@ -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** - 让任务管理变得简单高效 ✨

View File

@ -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"
}
}

View File

@ -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);
}

View File

@ -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"
}
}

View File

@ -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();
}
}
},
}
});

View File

@ -1,29 +1,265 @@
<!--pages/index/index.wxml-->
<view class="container">
<t-navbar title="首页" />
<view class="hero-section">
<image class="hero-image" src="/assets/images/dashboard-art.png" mode="aspectFit" />
<text class="hero-title">欢迎回来!</text>
<text class="hero-subtitle">今天有什么计划?让我们开始吧。</text>
<!-- 顶部渐变背景 -->
<view class="gradient-background"></view>
<!-- 顶部问候区域 -->
<view class="greeting-section">
<view class="greeting-content">
<text class="greeting">{{greeting}}</text>
<text class="date">{{formatTime(Date.now())}}</text>
</view>
</view>
<!-- 统计概览 -->
<view class="stats-overview">
<!-- 紧凑型进度卡片 -->
<view class="compact-progress-card">
<view class="progress-mini-circle">
<view class="mini-circle-bg"></view>
<view class="mini-circle-progress" style="transform: rotate({{stats.completionRate * 3.6 - 90}}deg);"></view>
<view class="mini-circle-inner">
<text class="mini-percentage">{{stats.completionRate}}%</text>
</view>
</view>
<view class="compact-stats">
<view class="compact-info">
<text class="compact-title">今日进度</text>
<view class="compact-numbers">
<text class="compact-number">{{stats.completed}}/{{stats.total}} 完成</text>
<view class="achievement-mini">
<t-icon name="{{getAchievementIcon(stats.completionRate)}}" size="12" color="{{getAchievementColor(stats.completionRate)}}" />
<text class="achievement-mini-text">{{getAchievementText(stats.completionRate)}}</text>
</view>
</view>
</view>
<view class="quick-actions-mini">
<view class="action-item" bind:tap="goToTasks">
<t-icon name="bulletpoint" size="16" color="#0052d9" />
<text class="action-text">全部</text>
</view>
<view class="action-item" bind:tap="showAddSheet">
<t-icon name="add" size="16" color="#00a870" />
<text class="action-text">添加</text>
</view>
</view>
</view>
</view>
</view>
<!-- 今日任务 -->
<view class="today-tasks">
<view class="section-header">
<text class="section-title">今日任务</text>
<text class="task-count">{{todayTodos.length}}</text>
</view>
<view wx:if="{{todayTodos.length === 0}}" class="empty-state">
<t-icon name="bulletpoint" size="24" color="#cccccc" />
<text class="empty-text">暂无任务</text>
</view>
<view wx:else class="task-list">
<view wx:for="{{todayTodos}}" wx:key="id" class="task-item">
<view class="task-content">
<t-checkbox
checked="{{item.completed}}"
bind:change="toggleTodo"
data-id="{{item.id}}"
/>
<text class="task-text {{item.completed ? 'completed' : ''}}">{{item.text}}</text>
</view>
<view class="task-actions">
<t-tag
theme="{{item.priority === 'high' ? 'danger' : item.priority === 'medium' ? 'warning' : 'primary'}}"
size="small"
variant="light"
>
{{item.priority === 'high' ? '高' : item.priority === 'medium' ? '中' : '低'}}
</t-tag>
</view>
</view>
</view>
</view>
<!-- 快速操作 -->
<view class="quick-actions">
<t-button theme="primary" size="large" block bind:tap="goToTasks" class="action-button">
<t-button
theme="light"
size="medium"
bind:tap="goToTasks"
block
>
<t-icon name="bulletpoint" slot="icon" />
查看我的任务
</t-button>
<t-button theme="default" size="large" block bind:tap="goToSettings" class="action-button">
<t-icon name="setting" slot="icon" />
打开设置
查看所有任务
</t-button>
</view>
<view class="footer-space"></view>
<!-- 添加任务半屏 -->
<view class="add-task-sheet {{showAddSheet ? 'show' : ''}}" bind:tap="hideAddSheet">
<view class="sheet-content" catch:tap="stopPropagation">
<view class="sheet-header">
<view class="sheet-handle"></view>
<text class="sheet-title">添加新任务</text>
<view class="sheet-close" bind:tap="hideAddSheet">
<t-icon name="close" size="20" color="#999" />
</view>
</view>
<view class="sheet-body">
<!-- 任务内容输入 -->
<view class="input-section">
<view class="input-label">任务内容</view>
<t-input
placeholder="请输入要完成的任务..."
value="{{newTodoText}}"
bind:change="onNewTodoInput"
class="task-input"
autoFocus="{{showAddSheet}}"
maxlength="100"
/>
</view>
<!-- 优先级选择 -->
<view class="input-section">
<view class="input-label">优先级</view>
<view class="priority-selector">
<view
class="priority-item {{newTodoPriority === 'high' ? 'active' : ''}}"
bind:tap="onPriorityChange"
data-value="high"
>
<view class="priority-dot high"></view>
<text class="priority-text">高优先级</text>
</view>
<view
class="priority-item {{newTodoPriority === 'medium' ? 'active' : ''}}"
bind:tap="onPriorityChange"
data-value="medium"
>
<view class="priority-dot medium"></view>
<text class="priority-text">中优先级</text>
</view>
<view
class="priority-item {{newTodoPriority === 'low' ? 'active' : ''}}"
bind:tap="onPriorityChange"
data-value="low"
>
<view class="priority-dot low"></view>
<text class="priority-text">低优先级</text>
</view>
</view>
</view>
<!-- 分类选择 -->
<view class="input-section">
<view class="input-label">分类</view>
<view class="category-selector">
<view
class="category-item {{newTodoCategory === 'work' ? 'active' : ''}}"
bind:tap="onCategoryChange"
data-value="work"
>
<t-icon name="business" size="16" />
<text>工作</text>
</view>
<view
class="category-item {{newTodoCategory === 'personal' ? 'active' : ''}}"
bind:tap="onCategoryChange"
data-value="personal"
>
<t-icon name="user" size="16" />
<text>个人</text>
</view>
<view
class="category-item {{newTodoCategory === 'study' ? 'active' : ''}}"
bind:tap="onCategoryChange"
data-value="study"
>
<t-icon name="education" size="16" />
<text>学习</text>
</view>
<view
class="category-item {{newTodoCategory === 'life' ? 'active' : ''}}"
bind:tap="onCategoryChange"
data-value="life"
>
<t-icon name="home" size="16" />
<text>生活</text>
</view>
</view>
</view>
<!-- 截止时间 -->
<view class="input-section">
<view class="input-label">截止时间(可选)</view>
<view class="datetime-picker" bind:tap="showDateTimePicker">
<t-icon name="time" size="16" color="#666" />
<text class="datetime-text {{newTodoDeadline ? '' : 'placeholder'}}">
{{newTodoDeadline ? formatDateTime(newTodoDeadline) : '点击选择截止时间'}}
</text>
<t-icon name="chevron-right" size="16" color="#ccc" />
</view>
<view wx:if="{{newTodoDeadline}}" class="datetime-clear" bind:tap="clearDeadline">
<text class="clear-text">清除截止时间</text>
</view>
</view>
<!-- 备注 -->
<view class="input-section">
<view class="input-label">备注(可选)</view>
<t-input
placeholder="添加备注信息..."
value="{{newTodoNote}}"
bind:change="onNewTodoNoteInput"
class="note-input"
type="textarea"
maxlength="200"
/>
</view>
</view>
<view class="sheet-footer">
<t-button
theme="light"
size="large"
bind:tap="hideAddSheet"
class="cancel-btn"
>
取消
</t-button>
<t-button
theme="primary"
size="large"
bind:tap="addTodo"
class="confirm-btn"
disabled="{{newTodoText === '' || newTodoText.length === 0}}"
>
添加任务
</t-button>
</view>
</view>
</view>
<!-- 日期时间选择器 -->
<picker
wx:if="{{showDateTimePicker}}"
mode="multiSelector"
range="{{dateTimeRange}}"
value="{{dateTimeValue}}"
bind:change="onDateTimeChange"
bind:cancel="hideDateTimePicker"
>
<view></view>
</picker>
<!-- 自定义标签栏 -->
<t-tab-bar value="{{activeTab}}" bind:change="onTabChange" t-class="custom-tab-bar">
<t-tab-bar-item value="home" icon="home" aria-label="首页">首页</t-tab-bar-item>
<t-tab-bar-item value="list" icon="bulletpoint" aria-label="任务">任务</t-tab-bar-item>
<t-tab-bar-item value="statistics" icon="chart" aria-label="统计">统计</t-tab-bar-item>
<t-tab-bar-item value="settings" icon="setting" aria-label="设置">设置</t-tab-bar-item>
</t-tab-bar>
</view>

View File

@ -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;
}

View File

@ -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": "任务列表"

View File

@ -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'
},
},
}
}
});

View File

@ -1,46 +1,403 @@
<!--pages/list/list.wxml-->
<view class="container">
<t-navbar title="我的任务" />
<view class="add-todo-section">
<t-input
t-class="add-todo-input"
placeholder="写下你的任务..."
value="{{newTodoText}}"
bind:change="onNewTodoInput"
bind:enter="addTodo"
/>
<t-button t-class="add-todo-button" theme="primary" icon="add" bind:tap="addTodo" />
<!-- 顶部标题区域 -->
<view class="header-section">
<view class="header-content">
<text class="header-title">任务清单</text>
<view class="header-actions">
<view class="add-button" bind:tap="showAddSheet">
<t-icon name="add" size="18" color="white" />
</view>
</view>
</view>
<text class="header-subtitle">管理你的所有任务</text>
</view>
<!-- 统计概览卡片 -->
<view class="stats-card">
<view class="stats-row">
<view class="stat-item">
<text class="stat-number">{{stats.total}}</text>
<text class="stat-label">总任务</text>
</view>
<view class="stat-item">
<text class="stat-number">{{stats.completed}}</text>
<text class="stat-label">已完成</text>
</view>
<view class="stat-item">
<text class="stat-number">{{stats.pending}}</text>
<text class="stat-label">待完成</text>
</view>
<view class="stat-item">
<text class="stat-number">{{stats.completionRate}}%</text>
<text class="stat-label">完成率</text>
</view>
</view>
</view>
<!-- 搜索和筛选工具栏 -->
<view class="toolbar-section">
<view class="search-box">
<t-input
placeholder="搜索任务..."
value="{{searchText}}"
bind:change="onSearchInput"
prefix-icon="search"
class="search-input"
/>
</view>
<view class="filter-tabs">
<view
class="filter-tab {{filterStatus === 'all' ? 'active' : ''}}"
bind:tap="onStatusFilterChange"
data-value="all"
>
<text>全部</text>
<text class="tab-count">{{filteredTodos.length}}</text>
</view>
<view
class="filter-tab {{filterStatus === 'pending' ? 'active' : ''}}"
bind:tap="onStatusFilterChange"
data-value="pending"
>
<text>待完成</text>
<text class="tab-count">{{pendingCount}}</text>
</view>
<view
class="filter-tab {{filterStatus === 'completed' ? 'active' : ''}}"
bind:tap="onStatusFilterChange"
data-value="completed"
>
<text>已完成</text>
<text class="tab-count">{{completedCount}}</text>
</view>
</view>
</view>
<!-- 排序和筛选选项 -->
<view class="sort-section">
<view class="sort-options">
<view
class="sort-option {{sortBy === 'createdAt' ? 'active' : ''}}"
bind:tap="onSortChange"
data-value="createdAt"
>
<t-icon name="time" size="14" />
<text>时间</text>
</view>
<view
class="sort-option {{sortBy === 'priority' ? 'active' : ''}}"
bind:tap="onSortChange"
data-value="priority"
>
<t-icon name="star" size="14" />
<text>优先级</text>
</view>
<view
class="sort-option {{sortBy === 'text' ? 'active' : ''}}"
bind:tap="onSortChange"
data-value="text"
>
<t-icon name="text" size="14" />
<text>名称</text>
</view>
</view>
<view class="priority-filter">
<view
class="priority-filter-item {{filterPriority === 'all' ? 'active' : ''}}"
bind:tap="onPriorityFilterChange"
data-value="all"
>
<text>全部</text>
</view>
<view
class="priority-filter-item {{filterPriority === 'high' ? 'active' : ''}}"
bind:tap="onPriorityFilterChange"
data-value="high"
>
<view class="priority-dot high"></view>
<text>高</text>
</view>
<view
class="priority-filter-item {{filterPriority === 'medium' ? 'active' : ''}}"
bind:tap="onPriorityFilterChange"
data-value="medium"
>
<view class="priority-dot medium"></view>
<text>中</text>
</view>
<view
class="priority-filter-item {{filterPriority === 'low' ? 'active' : ''}}"
bind:tap="onPriorityFilterChange"
data-value="low"
>
<view class="priority-dot low"></view>
<text>低</text>
</view>
</view>
</view>
<!-- 任务列表 -->
<scroll-view class="todo-list-scroll" scroll-y type="list">
<t-cell-group t-class="todo-list">
<block wx:if="{{todos.length === 0}}">
<view class="empty-state">
<t-icon name="check-circle" size="xl" />
<text>太棒了!没有待办事项</text>
</view>
</block>
<block wx:else>
<t-cell wx:for="{{todos}}" wx:key="index" t-class-left="todo-item-left">
<view class="todo-item-content">
<t-checkbox
checked="{{item.completed}}"
bind:change="toggleTodo"
data-index="{{index}}"
icon="{{ item.completed ? ['check-circle-filled', 'circle'] : ['circle', 'check-circle-filled'] }}"
/>
<text class="{{item.completed ? 'todo-text completed' : 'todo-text'}}">{{item.text}}</text>
<view class="todo-list">
<view wx:if="{{filteredTodos.length === 0}}" class="empty-state">
<t-icon name="search" size="32" color="#ccc" />
<text wx:if="{{searchText || filterStatus !== 'all' || filterPriority !== 'all'}}" class="empty-text">
没有找到匹配的任务
</text>
<text wx:else class="empty-text">还没有任务,添加一个吧!</text>
</view>
<view wx:else>
<view wx:for="{{filteredTodos}}" wx:key="id" class="todo-item {{item.completed ? 'completed' : ''}}">
<view class="todo-main">
<view class="todo-checkbox">
<t-checkbox
checked="{{item.completed}}"
bind:change="toggleTodo"
data-id="{{item.id}}"
icon="{{ item.completed ? ['check-circle-filled', 'circle'] : ['circle', 'check-circle-filled'] }}"
/>
</view>
<view class="todo-content">
<view class="todo-header">
<text class="todo-text {{item.completed ? 'completed' : ''}}">{{item.text}}</text>
<view class="todo-priority">
<t-tag
theme="{{item.priority === 'high' ? 'danger' : item.priority === 'medium' ? 'warning' : 'primary'}}"
size="small"
variant="light"
>
{{item.priority === 'high' ? '高' : item.priority === 'medium' ? '中' : '低'}}
</t-tag>
</view>
</view>
<view class="todo-meta">
<view class="meta-left">
<text class="todo-time">{{formatTime(item.createdAt)}}</text>
<text wx:if="{{item.category}}" class="todo-category">
{{item.category === 'work' ? '工作' : item.category === 'personal' ? '个人' : item.category === 'study' ? '学习' : '生活'}}
</text>
<text wx:if="{{item.deadline}}" class="todo-deadline {{item.deadline < Date.now() && !item.completed ? 'overdue' : ''}}">
{{formatDateTime(item.deadline)}}
</text>
</view>
<view class="meta-right">
<text wx:if="{{item.completed}}" class="completed-time">
完成于 {{formatTime(item.completedAt)}}
</text>
</view>
</view>
<text wx:if="{{item.note}}" class="todo-note">{{item.note}}</text>
</view>
</view>
<t-icon name="delete" slot="right-icon" color="#e54d42" bind:tap="removeTodo" data-index="{{index}}" />
</t-cell>
</block>
</t-cell-group>
<view class="todo-actions">
<view class="action-btn" bind:tap="editTodo" data-id="{{item.id}}">
<t-icon name="edit" size="16" color="#666" />
</view>
<view class="action-btn" bind:tap="deleteTodo" data-id="{{item.id}}">
<t-icon name="delete" size="16" color="#e34d59" />
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-actions">
<view class="action-group">
<t-button
theme="light"
size="small"
bind:tap="clearCompleted"
class="clear-btn"
>
清空已完成
</t-button>
<t-button
theme="light"
size="small"
bind:tap="exportData"
class="export-btn"
>
导出数据
</t-button>
</view>
<text class="task-summary">
共 {{filteredTodos.length}} 个任务,完成率 {{completionRate}}%
</text>
</view>
<!-- 添加任务半屏 -->
<view class="add-task-sheet {{showAddSheet ? 'show' : ''}}" bind:tap="hideAddSheet">
<view class="sheet-content" catch:tap="stopPropagation">
<view class="sheet-header">
<view class="sheet-handle"></view>
<text class="sheet-title">添加新任务</text>
<view class="sheet-close" bind:tap="hideAddSheet">
<t-icon name="close" size="20" color="#999" />
</view>
</view>
<view class="sheet-body">
<!-- 任务内容输入 -->
<view class="input-section">
<view class="input-label">任务内容</view>
<t-input
placeholder="请输入要完成的任务..."
value="{{newTodoText}}"
bind:change="onNewTodoInput"
class="task-input"
autoFocus="{{showAddSheet}}"
maxlength="100"
/>
</view>
<!-- 优先级选择 -->
<view class="input-section">
<view class="input-label">优先级</view>
<view class="priority-selector">
<view
class="priority-item {{newTodoPriority === 'high' ? 'active' : ''}}"
bind:tap="onPriorityChange"
data-value="high"
>
<view class="priority-dot high"></view>
<text class="priority-text">高优先级</text>
</view>
<view
class="priority-item {{newTodoPriority === 'medium' ? 'active' : ''}}"
bind:tap="onPriorityChange"
data-value="medium"
>
<view class="priority-dot medium"></view>
<text class="priority-text">中优先级</text>
</view>
<view
class="priority-item {{newTodoPriority === 'low' ? 'active' : ''}}"
bind:tap="onPriorityChange"
data-value="low"
>
<view class="priority-dot low"></view>
<text class="priority-text">低优先级</text>
</view>
</view>
</view>
<!-- 分类选择 -->
<view class="input-section">
<view class="input-label">分类</view>
<view class="category-selector">
<view
class="category-item {{newTodoCategory === 'work' ? 'active' : ''}}"
bind:tap="onCategoryChange"
data-value="work"
>
<t-icon name="business" size="16" />
<text>工作</text>
</view>
<view
class="category-item {{newTodoCategory === 'personal' ? 'active' : ''}}"
bind:tap="onCategoryChange"
data-value="personal"
>
<t-icon name="user" size="16" />
<text>个人</text>
</view>
<view
class="category-item {{newTodoCategory === 'study' ? 'active' : ''}}"
bind:tap="onCategoryChange"
data-value="study"
>
<t-icon name="education" size="16" />
<text>学习</text>
</view>
<view
class="category-item {{newTodoCategory === 'life' ? 'active' : ''}}"
bind:tap="onCategoryChange"
data-value="life"
>
<t-icon name="home" size="16" />
<text>生活</text>
</view>
</view>
</view>
<!-- 截止时间 -->
<view class="input-section">
<view class="input-label">截止时间(可选)</view>
<view class="datetime-picker" bind:tap="showDateTimePicker">
<t-icon name="time" size="16" color="#666" />
<text class="datetime-text {{newTodoDeadline ? '' : 'placeholder'}}">
{{newTodoDeadline ? formatDateTime(newTodoDeadline) : '点击选择截止时间'}}
</text>
<t-icon name="chevron-right" size="16" color="#ccc" />
</view>
<view wx:if="{{newTodoDeadline}}" class="datetime-clear" bind:tap="clearDeadline">
<text class="clear-text">清除截止时间</text>
</view>
</view>
<!-- 备注 -->
<view class="input-section">
<view class="input-label">备注(可选)</view>
<t-input
placeholder="添加备注信息..."
value="{{newTodoNote}}"
bind:change="onNewTodoNoteInput"
class="note-input"
type="textarea"
maxlength="200"
/>
</view>
</view>
<view class="sheet-footer">
<t-button
theme="light"
size="large"
bind:tap="hideAddSheet"
class="cancel-btn"
>
取消
</t-button>
<t-button
theme="primary"
size="large"
bind:tap="addTodo"
class="confirm-btn"
disabled="{{newTodoText === '' || newTodoText.length === 0}}"
>
添加任务
</t-button>
</view>
</view>
</view>
<!-- 日期时间选择器 -->
<picker
wx:if="{{showDateTimePicker}}"
mode="multiSelector"
range="{{dateTimeRange}}"
value="{{dateTimeValue}}"
bind:change="onDateTimeChange"
bind:cancel="hideDateTimePicker"
>
<view></view>
</picker>
<!-- 自定义标签栏 -->
<t-tab-bar value="{{activeTab}}" bind:change="onTabChange" t-class="custom-tab-bar">
<t-tab-bar-item value="home" icon="home" aria-label="首页">首页</t-tab-bar-item>
<t-tab-bar-item value="list" icon="bulletpoint" aria-label="任务">任务</t-tab-bar-item>
<t-tab-bar-item value="statistics" icon="chart" aria-label="统计">统计</t-tab-bar-item>
<t-tab-bar-item value="settings" icon="setting" aria-label="设置">设置</t-tab-bar-item>
</t-tab-bar>
</view>

View File

@ -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;
}

View File

@ -1,4 +0,0 @@
{
"usingComponents": {
}
}

View File

@ -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
}
}),
})
}
},
})

View File

@ -1,6 +0,0 @@
<!--logs.wxml-->
<scroll-view class="scrollarea" scroll-y type="list">
<block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
<view class="log-item">{{index + 1}}. {{log.date}}</view>
</block>
</scroll-view>

View File

@ -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);
}

View File

@ -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": "设置"

View File

@ -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
});
}
}
});

View File

@ -1,13 +1,243 @@
<!--pages/settings/settings.wxml-->
<view class="container">
<t-navbar title="设置" />
<view class="content">
<t-icon name="setting" size="xl" />
<text>设置页面,敬请期待!</text>
<!-- 顶部标题 -->
<view class="header-section">
<text class="header-title">设置</text>
<text class="header-subtitle">个性化你的使用体验</text>
</view>
<!-- 应用信息 -->
<view class="app-info-section">
<view class="app-info-card">
<view class="app-info">
<view class="app-icon">
<t-icon name="check-circle" size="24" color="#0052d9" />
</view>
<view class="app-details">
<text class="app-name">Pure Todo</text>
<text class="app-version">版本 {{appVersion}}</text>
</view>
</view>
<view class="app-stats">
<text class="stat-item">总任务: {{stats.total || 0}}</text>
<text class="stat-item">已完成: {{stats.completed || 0}}</text>
<text class="stat-item">完成率: {{stats.completionRate || 0}}%</text>
</view>
</view>
</view>
<!-- 外观设置 -->
<view class="settings-section">
<view class="section-header">
<text class="section-title">外观</text>
</view>
<view class="settings-list">
<view class="setting-item">
<view class="setting-content">
<view class="setting-info">
<t-icon name="theme" size="16" color="#0052d9" />
<text class="setting-label">深色模式</text>
</view>
<t-switch
value="{{darkMode}}"
bind:change="onDarkModeChange"
color="#0052d9"
/>
</view>
<text class="setting-desc">切换深色主题,保护眼睛</text>
</view>
</view>
</view>
<!-- 通知设置 -->
<view class="settings-section">
<view class="section-header">
<text class="section-title">通知</text>
</view>
<view class="settings-list">
<view class="setting-item">
<view class="setting-content">
<view class="setting-info">
<t-icon name="notification" size="16" color="#0052d9" />
<text class="setting-label">推送通知</text>
</view>
<t-switch
value="{{notifications}}"
bind:change="onNotificationsChange"
color="#0052d9"
/>
</view>
<text class="setting-desc">接收任务提醒和完成通知</text>
</view>
</view>
</view>
<!-- 数据管理 -->
<view class="settings-section">
<view class="section-header">
<text class="section-title">数据管理</text>
</view>
<view class="settings-list">
<view class="setting-item">
<view class="setting-content">
<view class="setting-info">
<t-icon name="cloud" size="16" color="#0052d9" />
<text class="setting-label">自动备份</text>
</view>
<t-switch
value="{{autoBackup}}"
bind:change="onAutoBackupChange"
color="#0052d9"
/>
</view>
<text class="setting-desc">定期自动备份数据到云端</text>
</view>
<view class="setting-item" bind:tap="exportData">
<view class="setting-content">
<view class="setting-info">
<t-icon name="download" size="16" color="#0052d9" />
<text class="setting-label">导出数据</text>
</view>
<t-icon name="chevron-right" size="14" color="#ccc" />
</view>
<text class="setting-desc">将数据导出为备份文件</text>
</view>
<view class="setting-item" bind:tap="importData">
<view class="setting-content">
<view class="setting-info">
<t-icon name="upload" size="16" color="#0052d9" />
<text class="setting-label">导入数据</text>
</view>
<t-icon name="chevron-right" size="14" color="#ccc" />
</view>
<text class="setting-desc">从备份文件恢复数据</text>
</view>
<view class="setting-item" bind:tap="clearData">
<view class="setting-content">
<view class="setting-info">
<t-icon name="delete" size="16" color="#e34d59" />
<text class="setting-label danger">清空数据</text>
</view>
<t-icon name="chevron-right" size="14" color="#ccc" />
</view>
<text class="setting-desc">删除所有任务和设置</text>
</view>
</view>
</view>
<!-- 应用设置 -->
<view class="settings-section">
<view class="section-header">
<text class="section-title">应用</text>
</view>
<view class="settings-list">
<view class="setting-item" bind:tap="resetSettings">
<view class="setting-content">
<view class="setting-info">
<t-icon name="refresh" size="16" color="#0052d9" />
<text class="setting-label">重置设置</text>
</view>
<t-icon name="chevron-right" size="14" color="#ccc" />
</view>
<text class="setting-desc">恢复所有设置为默认值</text>
</view>
<view class="setting-item" bind:tap="checkUpdate">
<view class="setting-content">
<view class="setting-info">
<t-icon name="update" size="16" color="#0052d9" />
<text class="setting-label">检查更新</text>
</view>
<t-icon name="chevron-right" size="14" color="#ccc" />
</view>
<text class="setting-desc">检查是否有新版本可用</text>
</view>
<view class="setting-item" bind:tap="rateApp">
<view class="setting-content">
<view class="setting-info">
<t-icon name="star" size="16" color="#0052d9" />
<text class="setting-label">给应用评分</text>
</view>
<t-icon name="chevron-right" size="14" color="#ccc" />
</view>
<text class="setting-desc">在应用商店给我们好评</text>
</view>
<view class="setting-item" bind:tap="showAbout">
<view class="setting-content">
<view class="setting-info">
<t-icon name="info-circle" size="16" color="#0052d9" />
<text class="setting-label">关于应用</text>
</view>
<t-icon name="chevron-right" size="14" color="#ccc" />
</view>
<text class="setting-desc">查看应用信息和开发者联系方式</text>
</view>
<view class="setting-item" bind:tap="contactDeveloper">
<view class="setting-content">
<view class="setting-info">
<t-icon name="service" size="16" color="#0052d9" />
<text class="setting-label">联系开发者</text>
</view>
<t-icon name="chevron-right" size="14" color="#ccc" />
</view>
<text class="setting-desc">反馈问题或建议新功能</text>
</view>
</view>
</view>
<!-- 关于对话框 -->
<t-dialog
visible="{{showAboutDialog}}"
title="关于 Pure Todo"
confirm-btn="确定"
bind:confirm="hideAbout"
>
<view class="about-content">
<view class="about-header">
<t-icon name="check-circle" size="32" color="#0052d9" />
<text class="about-title">Pure Todo</text>
<text class="about-version">版本 {{appVersion}}</text>
</view>
<text class="about-desc">
一个简洁高效的待办事项管理应用,帮助你更好地管理时间和任务。
</text>
<view class="about-features">
<text class="feature-item">• 简洁直观的界面设计</text>
<text class="feature-item">• 智能的任务分类和筛选</text>
<text class="feature-item">• 详细的数据统计和分析</text>
<text class="feature-item">• 安全的数据备份和恢复</text>
</view>
</view>
</t-dialog>
<!-- 备份数据对话框 -->
<t-dialog
visible="{{showBackupDialog}}"
title="备份数据"
confirm-btn="复制"
cancel-btn="关闭"
bind:confirm="copyBackupData"
bind:cancel="hideBackup"
>
<view class="backup-content">
<text class="backup-desc">以下是你的备份数据,请妥善保存:</text>
<view class="backup-data">
<text class="backup-text">{{backupData}}</text>
</view>
</view>
</t-dialog>
<!-- 自定义标签栏 -->
<t-tab-bar value="{{activeTab}}" bind:change="onTabChange" t-class="custom-tab-bar">
<t-tab-bar-item value="home" icon="home" aria-label="首页">首页</t-tab-bar-item>
<t-tab-bar-item value="list" icon="bulletpoint" aria-label="任务">任务</t-tab-bar-item>
<t-tab-bar-item value="statistics" icon="chart" aria-label="统计">统计</t-tab-bar-item>
<t-tab-bar-item value="settings" icon="setting" aria-label="设置">设置</t-tab-bar-item>
</t-tab-bar>
</view>

View File

@ -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;
}
}

View File

@ -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"
}
}

View File

@ -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 ? '已完成' : '进行中';
}
}
});

View File

@ -0,0 +1,175 @@
<!--pages/statistics/statistics.wxml-->
<view class="container">
<!-- 顶部标题 -->
<view class="header-section">
<text class="header-title">数据统计</text>
<text class="header-subtitle">了解你的任务完成情况</text>
</view>
<!-- 总体统计 -->
<view class="overview-section">
<view class="overview-card">
<view class="overview-header">
<text class="overview-title">总体概览</text>
</view>
<view class="overview-grid">
<view class="overview-item">
<text class="overview-number">{{stats.total}}</text>
<text class="overview-label">总任务</text>
</view>
<view class="overview-item">
<text class="overview-number">{{stats.completed}}</text>
<text class="overview-label">已完成</text>
</view>
<view class="overview-item">
<text class="overview-number">{{stats.pending}}</text>
<text class="overview-label">待完成</text>
</view>
<view class="overview-item">
<text class="overview-number">{{stats.completionRate}}%</text>
<text class="overview-label">完成率</text>
</view>
</view>
<view class="progress-container">
<t-progress
percentage="{{stats.completionRate}}"
color="#0052d9"
stroke-width="4"
/>
</view>
</view>
</view>
<!-- 周数据统计 -->
<view class="weekly-section">
<view class="section-header">
<text class="section-title">本周数据</text>
</view>
<view class="weekly-card">
<view class="weekly-chart">
<view wx:for="{{weeklyData}}" wx:key="date" class="chart-bar">
<view class="bar-container">
<view class="bar created" style="height: {{item.created * 8}}px;"></view>
<view class="bar completed" style="height: {{item.completed * 8}}px;"></view>
</view>
<text class="chart-label">{{item.date}}</text>
<text class="chart-value">{{item.created}}/{{item.completed}}</text>
</view>
</view>
</view>
</view>
<!-- 优先级统计 -->
<view class="priority-section">
<view class="section-header">
<text class="section-title">优先级统计</text>
</view>
<view class="priority-list">
<view wx:for="{{priorityData}}" wx:key="name" class="priority-item">
<view class="priority-info">
<view class="priority-header">
<text class="priority-name">{{item.name}}</text>
<text class="priority-rate">{{formatPercentage(item.rate)}}</text>
</view>
<view class="priority-stats">
<text class="priority-count">已完成: {{item.completed}} / {{item.total}}</text>
</view>
<view class="progress-container">
<t-progress
percentage="{{item.rate}}"
color="{{item.color}}"
stroke-width="3"
/>
</view>
</view>
</view>
</view>
</view>
<!-- 分类统计 -->
<view class="category-section">
<view class="section-header">
<text class="section-title">分类统计</text>
</view>
<view class="category-list">
<view wx:for="{{categoryData}}" wx:key="name" class="category-item">
<view class="category-info">
<text class="category-name">{{item.name}}</text>
<text class="category-count">{{item.total}} 个任务</text>
</view>
<view class="category-stats">
<text class="category-completed">已完成: {{item.completed}}</text>
<text class="category-rate">{{formatPercentage(item.rate)}}</text>
</view>
</view>
</view>
</view>
<!-- 最近活动 -->
<view class="activity-section">
<view class="section-header">
<text class="section-title">最近活动</text>
</view>
<view class="activity-list">
<view wx:for="{{recentActivity}}" wx:key="id" class="activity-item">
<view class="activity-content">
<view class="activity-info">
<text class="activity-text {{item.completed ? 'completed' : ''}}">{{item.text}}</text>
<text class="activity-time">{{item.timeAgo}}</text>
</view>
<view class="activity-status">
<t-tag
theme="{{item.priority === 'high' ? 'danger' : item.priority === 'medium' ? 'warning' : 'primary'}}"
size="small"
>
{{item.priority === 'high' ? '高' : item.priority === 'medium' ? '中' : '低'}}
</t-tag>
<t-tag
theme="{{item.completed ? 'success' : 'default'}}"
size="small"
>
{{getCompletionText(item.completed)}}
</t-tag>
</view>
</view>
</view>
</view>
</view>
<!-- 数据管理 -->
<view class="data-section">
<view class="section-header">
<text class="section-title">数据管理</text>
</view>
<view class="data-actions">
<t-button
theme="primary"
size="large"
block
bind:tap="exportData"
class="action-btn"
>
<t-icon name="download" slot="icon" />
导出数据
</t-button>
<t-button
theme="danger"
size="large"
block
bind:tap="clearData"
class="action-btn"
>
<t-icon name="delete" slot="icon" />
清空数据
</t-button>
</view>
</view>
<!-- 自定义标签栏 -->
<t-tab-bar value="{{activeTab}}" bind:change="onTabChange" t-class="custom-tab-bar">
<t-tab-bar-item value="home" icon="home" aria-label="首页">首页</t-tab-bar-item>
<t-tab-bar-item value="list" icon="bulletpoint" aria-label="任务">任务</t-tab-bar-item>
<t-tab-bar-item value="statistics" icon="chart" aria-label="统计">统计</t-tab-bar-item>
<t-tab-bar-item value="settings" icon="setting" aria-label="设置">设置</t-tab-bar-item>
</t-tab-bar>
</view>

View File

@ -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;
}
}

View File

@ -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<ITodo>): 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();

View File

@ -3,5 +3,6 @@
"projectname": "miniprogram-1",
"setting": {
"compileHotReLoad": true
}
},
"libVersion": "3.9.2"
}