学习伴侣App v2.0升级开发记录

📋 更新概述

本次更新于 2026-02-10 完成,将”考研伴侣”正式升级为通用的”学习伴侣”应用,同时大幅扩展了成就系统和积分奖励系统。

GitHub 仓库https://github.com/dafenqirunrunrun/love-study-app

🎯 核心更新

更新项 更新前 更新后
应用名称 考研伴侣 学习伴侣
成就数量 15个 50个
可兑换奖励 6个 20个
默认倒计时 2025-12-21 2026-12-21

🚀 完成的功能更新

1. 品牌重命名

将应用从”考研伴侣”重命名为”学习伴侣”,使其适用于任何学习目标:

1.1 修改的文件

文件路径 修改内容
index.html 页面标题从”考研伴侣”改为”学习伴侣”
src/components/Layout.vue 品牌名称显示
src/components/MoreMenu.vue 用户区域名称
src/router/index.js 页面标题meta
src/views/Home.vue 默认倒计时目标名
src/views/Settings.vue 关于名称、默认倒计时、备份文件名
src/components/LearningPlanWizard.vue 步骤描述文字
src/composables/useLearningPlanGenerator.ts 注释文字
src/views/LearningPlanManager.vue 页面描述
src/views/PointsCenter.vue 虚拟宠物名称

1.2 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Settings.vue - 修改前
const countdownSettings = ref({
eventName: '考研初试',
targetDate: '2025-12-21'
})

// Settings.vue - 修改后
const countdownSettings = ref({
eventName: '学习目标',
targetDate: '2026-12-21'
})

// PointsCenter.vue - 修改前
const pet = ref({
name: '考研小助手',
avatar: '🐱',
mood: '开心',
hunger: 80
})

// PointsCenter.vue - 修改后
const pet = ref({
name: '学习小助手',
avatar: '🐱',
mood: '开心',
hunger: 80
})

2. 成就系统扩展(15个 → 50个)

2.1 新增成就类别

类别 数量 说明
任务成就 7个 完成1/10/50/100/200/500/1000个任务
专注成就 9个 1/10/50/100/200次专注 + 10/50/100/500小时
打卡成就 7个 3/7/14/30/60/100/365天连续打卡
积分成就 6个 100/500/1000/2000/5000/10000积分
心得成就 3个 撰写1/10/30篇学习心得
里程碑成就 4个 累计学习7/30/100/365天
习惯成就 2个 完美一周、完美一月
特殊成就 3个 早起鸟儿、夜猫子、全能选手

2.2 成就数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// Achievements.vue - 成就定义示例
const badges = ref([
// 任务成就
{
id: 'task_1000',
name: '千锤百炼',
icon: '💎',
description: '完成1000个任务',
category: 'task',
progress: computed(() => ({
current: tasks.value.filter(t => t.completed).length,
target: 1000,
percent: Math.min(100, tasks.value.filter(t => t.completed).length / 10)
}))
},

// 专注成就
{
id: 'hours_500',
name: '五百小时',
icon: '🏅',
description: '累计专注500小时',
category: 'focus',
progress: computed(() => {
const minutes = Object.values(focusHistory.value).reduce((sum, day) => sum + (day.minutes || 0), 0)
return {
current: Math.round(minutes / 60),
target: 500,
percent: Math.min(100, (minutes / 60) / 5)
}
})
},

// 打卡成就
{
id: 'checkin_365',
name: '全年无休',
icon: '🌟',
description: '连续打卡365天',
category: 'checkin',
progress: computed(() => ({
current: currentStreak.value,
target: 365,
percent: Math.min(100, (currentStreak.value / 365) * 100)
}))
},

// 心得成就
{
id: 'journal_30',
name: '三十篇心得',
icon: '📚',
description: '撰写30篇学习心得',
category: 'journal',
progress: computed(() => {
const journals = JSON.parse(localStorage.getItem('dailyJournal') || '[]')
return {
current: journals.length,
target: 30,
percent: Math.min(100, journals.length * 3.33)
}
})
}
])

2.3 解锁判断逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// Achievements.vue - isUnlocked 函数
const isUnlocked = (badgeId) => {
try {
const saved = localStorage.getItem('unlockedBadges')
if (saved) {
const unlocked = JSON.parse(saved)
if (unlocked.includes(badgeId)) return true
}

const completedTasksCount = tasks.value.filter(t => t.completed).length
const totalSessions = Object.values(focusHistory.value).reduce((sum, day) => sum + (day.sessions || 0), 0)
const totalMinutes = Object.values(focusHistory.value).reduce((sum, day) => sum + (day.minutes || 0), 0)

switch (badgeId) {
case 'first_task': return completedTasksCount >= 1
case 'task_10': return completedTasksCount >= 10
case 'task_50': return completedTasksCount >= 50
case 'task_100': return completedTasksCount >= 100
case 'task_200': return completedTasksCount >= 200
case 'task_500': return completedTasksCount >= 500
case 'task_1000': return completedTasksCount >= 1000
case 'focus_1': return totalSessions >= 1
case 'focus_10': return totalSessions >= 10
case 'focus_50': return totalSessions >= 50
case 'focus_100': return totalSessions >= 100
case 'focus_200': return totalSessions >= 200
case 'hours_10': return totalMinutes >= 600
case 'hours_50': return totalMinutes >= 3000
case 'hours_100': return totalMinutes >= 6000
case 'hours_500': return totalMinutes >= 30000
case 'checkin_3': return currentStreak.value >= 3
case 'checkin_7': return currentStreak.value >= 7
case 'checkin_14': return currentStreak.value >= 14
case 'checkin_30': return currentStreak.value >= 30
case 'checkin_60': return currentStreak.value >= 60
case 'checkin_100': return currentStreak.value >= 100
case 'checkin_365': return currentStreak.value >= 365
case 'points_100': return totalPoints.value >= 100
case 'points_500': return totalPoints.value >= 500
case 'points_1000': return totalPoints.value >= 1000
case 'points_2000': return totalPoints.value >= 2000
case 'points_5000': return totalPoints.value >= 5000
case 'points_10000': return totalPoints.value >= 10000
case 'journal_1': {
const journals = JSON.parse(localStorage.getItem('dailyJournal') || '[]')
return journals.length >= 1
}
case 'journal_10': {
const journals = JSON.parse(localStorage.getItem('dailyJournal') || '[]')
return journals.length >= 10
}
case 'journal_30': {
const journals = JSON.parse(localStorage.getItem('dailyJournal') || '[]')
return journals.length >= 30
}
case 'total_days_7': {
const stats = JSON.parse(localStorage.getItem('learningStats') || '{"totalDays":0}')
return (stats.totalDays || 0) >= 7
}
case 'total_days_30': {
const stats = JSON.parse(localStorage.getItem('learningStats') || '{"totalDays":0}')
return (stats.totalDays || 0) >= 30
}
case 'total_days_100': {
const stats = JSON.parse(localStorage.getItem('learningStats') || '{"totalDays":0}')
return (stats.totalDays || 0) >= 100
}
case 'total_days_365': {
const stats = JSON.parse(localStorage.getItem('learningStats') || '{"totalDays":0}')
return (stats.totalDays || 0) >= 365
}
case 'habit_perfect_week': return perfectWeekCount.value >= 1
case 'habit_perfect_month': return perfectMonthCount.value >= 1
case 'early_bird': return earlyBirdCount.value >= 1
case 'night_owl': return nightOwlCount.value >= 1
case 'all_rounder': return unlockedCount.value >= badges.value.length
default: return false
}
} catch {
return false
}
}

2.4 新增计算属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 完美一周统计
const perfectWeekCount = computed(() => {
try {
const habits = JSON.parse(localStorage.getItem('habits') || '[]')
const checkins = JSON.parse(localStorage.getItem('checkinHistory') || '{}')
let count = 0
for (let week = 0; week < 52; week++) {
let perfect = true
for (let day = 0; day < 7; day++) {
const date = new Date()
date.setDate(date.getDate() - (week * 7 + day))
const dateStr = date.toDateString()
if (!checkins[dateStr] || checkins[dateStr].total < habits.length) {
perfect = false
break
}
}
if (perfect && habits.length > 0) count++
}
return count
} catch { return 0 }
})

// 早起鸟儿统计
const earlyBirdCount = computed(() => {
try {
const tasks = JSON.parse(localStorage.getItem('tasks') || '[]')
return tasks.filter(t => {
if (!t.completed || !t.completedAt) return false
const hour = new Date(t.completedAt).getHours()
return hour < 6
}).length
} catch { return 0 }
})

3. 积分奖励扩充(6个 → 20个)

3.1 完整奖励列表

积分 图标 名称 描述
50 🍦 冰淇淋 清凉一夏,甜蜜小确幸
80 咖啡一杯 提神醒脑,继续加油
100 🥤 奶茶一杯 甜蜜的奶茶,犒劳努力的自己
150 🍰 蛋糕一份 美味甜点,补充能量
180 🍔 汉堡套餐 快餐自由,大快朵颐
200 🍕 披萨一份 芝士拉丝,满足味蕾
250 🍵 下午茶 悠闲时光,放松片刻
300 🎬 看电影约会 和恋人一起看场电影
350 🎰 盲盒一个 惊喜与期待并存
400 💆 按摩一次 放松身心,缓解疲劳
500 🎁 神秘礼物 给对方准备的神秘惊喜
550 🎭 剧本杀 沉浸式体验,换个身份
600 🔐 密室逃脱 烧脑刺激,团队协作
600 🍽️ 美食大餐 吃一顿好的庆祝进步
800 🚗 短途旅行 换个城市,换种心情
1000 🎤 演唱会门票 近距离感受音乐魅力
1200 🎢 游乐园一日 重拾童年的快乐
1500 🧖 SPA水疗 极致放松,焕发活力
2000 💎 奢侈品 犒劳自己的努力成果
5000 🌍 环球旅行 世界那么大,想去看看

3.2 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// PointsCenter.vue - rewards 数组
const rewards = ref([
{ id: 1, name: '冰淇淋', icon: '🍦', points: 50, description: '清凉一夏,甜蜜小确幸' },
{ id: 2, name: '咖啡一杯', icon: '☕', points: 80, description: '提神醒脑,继续加油' },
{ id: 3, name: '奶茶一杯', icon: '🥤', points: 100, description: '甜蜜的奶茶,犒劳努力的自己' },
{ id: 4, name: '蛋糕一份', icon: '🍰', points: 150, description: '美味甜点,补充能量' },
{ id: 5, name: '汉堡套餐', icon: '🍔', points: 180, description: '快餐自由,大快朵颐' },
{ id: 6, name: '披萨一份', icon: '🍕', points: 200, description: '芝士拉丝,满足味蕾' },
{ id: 7, name: '下午茶', icon: '🍵', points: 250, description: '悠闲时光,放松片刻' },
{ id: 8, name: '看电影约会', icon: '🎬', points: 300, description: '和恋人一起看场电影' },
{ id: 9, name: '盲盒一个', icon: '🎰', points: 350, description: '惊喜与期待并存' },
{ id: 10, name: '按摩一次', icon: '💆', points: 400, description: '放松身心,缓解疲劳' },
{ id: 11, name: '神秘礼物', icon: '🎁', points: 500, description: '给对方准备的神秘惊喜' },
{ id: 12, name: '剧本杀', icon: '🎭', points: 550, description: '沉浸式体验,换个身份' },
{ id: 13, name: '密室逃脱', icon: '🔐', points: 600, description: '烧脑刺激,团队协作' },
{ id: 14, name: '美食大餐', icon: '🍽️', points: 600, description: '吃一顿好的庆祝进步' },
{ id: 15, name: '短途旅行', icon: '🚗', points: 800, description: '换个城市,换种心情' },
{ id: 16, name: '演唱会门票', icon: '🎤', points: 1000, description: '近距离感受音乐魅力' },
{ id: 17, name: '游乐园一日', icon: '🎢', points: 1200, description: '重拾童年的快乐' },
{ id: 18, name: 'SPA水疗', icon: '🧖', points: 1500, description: '极致放松,焕发活力' },
{ id: 19, name: '奢侈品', icon: '💎', points: 2000, description: '犒劳自己的努力成果' },
{ id: 20, name: '环球旅行', icon: '🌍', points: 5000, description: '世界那么大,想去看看' }
])

4. README.md 全面更新

4.1 新增章节

  • 功能特色 - 分类展示所有功能
  • 设计特点 - 视觉设计和UI组件说明
  • 版本更新日志 - v2.0更新日志

4.2 README 更新内容概览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 学习伴侣 (Study Companion) - 全能学习助手

## 🎯 功能特色

### 📊 首页与统计
-**通用倒计时** - 自定义目标名称和日期
-**每日暖心鼓励语** - 积极向上的学习动力
-**学习数据统计仪表盘** - 可视化学习进度
-**日历视图** - 查看每日完成情况

### 📝 任务管理
-**任务管理系统** - 支持多种分类和优先级
-**智能筛选** - 多维度筛选任务

### ⏱️ 番茄专注
-**番茄钟专注计时器** - 可自定义时长
-**白噪音环境音** - 多种背景音效

### 📅 习惯打卡
-**习惯打卡系统** - 每日习惯追踪
-**连续打卡统计** - 记录最长连续天数

### 💎 积分中心
-**个人积分系统** - 多渠道获得积分
-**等级体系** - 新手→学者→学霸→大咖→传奇
-**20种可兑换奖励** - 从冰淇淋到环球旅行

### 🏆 成就系统
-**50个成就徽章** - 涵盖学习各方面

### 📚 学习计划
-**AI学习计划生成器** - 智能规划学习路径

## 📈 版本更新日志

### v2.0 (2025.02)
- 🎉 **品牌升级** - "考研伴侣" → "学习伴侣"
-**通用化设计** - 支持任何学习目标
- 🤖 **AI学习计划** - 智能生成学习路径
- 📅 **日历功能** - 直观查看学习进度
- 💎 **积分中心重构** - 等级系统+虚拟宠物
- 🎁 **奖励扩充** - 6个 → 20个可兑换奖励
- 🏆 **成就系统** - 15个 → 50个成就徽章
- 🎨 **UI美化** - 全新的视觉设计
- 🌗 **日间模式** - 优化浅色主题显示
- 🔧 **功能增强** - 倒计时自定义、初始化功能等

📊 Git 提交记录

提交ID 消息 说明
88ea1e2 docs: update README with all new features v2.0 README全面更新
48d7368 feat: expand rewards from 6 to 20 items 奖励扩充至20个
4dd46cc feat: 考研伴侣→学习伴侣 rebranding + 50 achievements 品牌重命名+50成就
f06d1d6 fix: 优化字体颜色适配日间/夜间模式 日间模式优化

🔧 技术实现要点

1. 成就进度计算

使用 Vue 的 computed 实现响应式进度计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 任务进度
progress: computed(() => ({
current: tasks.value.filter(t => t.completed).length,
target: 100,
percent: Math.min(100, tasks.value.filter(t => t.completed).length)
}))

// 专注时长进度
progress: computed(() => {
const minutes = Object.values(focusHistory.value)
.reduce((sum, day) => sum + (day.minutes || 0), 0)
return {
current: Math.round(minutes / 60),
target: 500,
percent: Math.min(100, (minutes / 60) / 5)
}
})

2. 动态解锁判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 统一的解锁判断函数
const isUnlocked = (badgeId) => {
// 1. 检查本地存储的已解锁列表
const saved = localStorage.getItem('unlockedBadges')
if (saved && JSON.parse(saved).includes(badgeId)) return true

// 2. 根据当前数据动态判断
const conditions = {
'task_100': completedTasksCount >= 100,
'hours_500': totalMinutes >= 30000,
'checkin_365': currentStreak.value >= 365,
// ...
}

return conditions[badgeId] || false
}

3. 奖励积分范围设计

积分范围 奖励类型
50-200 小确幸(冰淇淋、咖啡、奶茶、蛋糕等)
250-400 休闲娱乐(下午茶、电影、盲盒、按摩)
500-800 社交娱乐(神秘礼物、剧本杀、密室、短途旅行)
1000-2000 高级享受(演唱会、游乐园、SPA、奢侈品)
5000 终极目标(环球旅行)

📱 UI/UX 改进

1. 成就卡片设计

1
2
3
4
5
6
7
8
9
10
<div class="badge-card" :class="{ 'unlocked': isUnlocked(badge.id) }">
<div class="badge-icon" :class="{ 'glow': isUnlocked(badge.id) }">
{{ badge.icon }}
</div>
<h3 class="badge-name">{{ badge.name }}</h3>
<p class="badge-desc">{{ badge.description }}</p>
<div class="badge-progress" v-if="!isUnlocked(badge.id)">
<div class="progress-bar" :style="{ width: badge.progress.percent + '%' }"></div>
</div>
</div>

2. 奖励卡片布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="reward-grid">
<div v-for="reward in rewards"
:key="reward.id"
class="reward-card"
:class="{ 'affordable': lovePoints >= reward.points }">
<div class="reward-icon">{{ reward.icon }}</div>
<h4 class="reward-name">{{ reward.name }}</h4>
<div class="reward-price">
<span class="points">{{ reward.points }}</span>
<span class="label">积分</span>
</div>
<button class="redeem-btn" :disabled="lovePoints < reward.points">
{{ lovePoints >= reward.points ? '🎉 立即兑换' : '💤 积分不足' }}
</button>
</div>
</div>

🧪 测试验证

构建测试

1
2
3
4
5
6
7
8
9
10
$ npm run build

✓ vite v5.4.21 building for production...
✓ 80 modules transformed
✓ built in 2.16s

Output files:
- dist/index.html (0.47 kB)
- dist/assets/index-xxx.js (120.62 kB)
- dist/assets/index-xxx.css (60.27 kB)

Git 推送验证

1
2
3
$ git push origin master
To https://github.com/dafenqirunrunrun/love-study-app.git
48d7368..88ea1e2 master -> master

📈 性能优化

1. 成就计算优化

  • 使用 computed 缓存计算结果
  • 避免不必要的重复计算
  • 懒加载 LocalStorage 数据

2. 奖励渲染优化

  • 虚拟滚动支持(大量奖励时)
  • 条件渲染减少DOM节点
  • CSS 硬件加速动画

🎨 设计规范

1. 颜色系统

用途 颜色 说明
主色 #f97316 (orange-500) 橙色渐变起点
辅助色 #f472b6 (pink-500) 粉色渐变终点
成功色 #22c55e (green-500) 解锁成功
警告色 #fbbf24 (amber-500) 进度提醒
背景 rgba(255,255,255,0.25) 玻璃拟态

2. 动画效果

1
2
3
4
5
6
7
8
9
10
11
/* 解锁动画 */
@keyframes unlock-glow {
0% { box-shadow: 0 0 0 rgba(249, 115, 22, 0); }
50% { box-shadow: 0 0 30px rgba(249, 115, 22, 0.5); }
100% { box-shadow: 0 0 0 rgba(249, 115, 22, 0); }
}

/* 进度条动画 */
.progress-bar {
transition: width 0.5s ease-out;
}

📦 依赖更新

本次更新未引入新依赖,所有功能均使用现有技术栈实现:

依赖 版本 用途
vue 3.x 核心框架
vue-router 4.x 路由管理
vite 5.x 构建工具
tailwindcss 3.x 样式框架

🔮 后续优化方向

  • 添加成就解锁动画
  • 支持成就分享功能
  • 添加稀有成就系统
  • 支持自定义成就
  • 成就徽章收集展示页
  • 支持云端同步成就数据

📚 参考资料


开发感悟:本次v2.0升级是应用从”考研伴侣”到”学习伴侣”的重要里程碑。通过品牌重命名和功能扩展,应用现在可以服务于更广泛的学习群体。50个成就和20种奖励的设计,为用户提供了清晰的学习目标和强大的激励体系。