-
Notifications
You must be signed in to change notification settings - Fork 789
/
Copy pathSummary.vue
280 lines (254 loc) · 8.45 KB
/
Summary.vue
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
<template>
<UModal
v-model="showModal"
prevent-close
>
<UContainer
:ui="{
base: 'w-[90vw]',
constrained: 'max-w-[780px]',
}"
>
<div class="flex justify-between">
<h3 className="font-bold text-lg mb-4">🎉 恭喜!</h3>
<button
tabindex="0"
class="btn btn-ghost btn-sm mx-1 h-7 w-7 rounded-md p-0"
@click="soundSentence"
>
<UIcon
name="i-ph-speaker-simple-high"
class="h-full w-full"
></UIcon>
</button>
</div>
<div class="flex flex-col">
<div class="flex">
<span class="text-3xl font-bold sm:text-4xl lg:text-6xl">"</span>
<div class="flex-1 text-center text-sm leading-loose sm:text-base lg:text-xl">
{{ enSentence }}
</div>
<span class="invisible text-3xl font-bold sm:text-4xl lg:text-6xl">"</span>
</div>
<div class="flex">
<span class="invisible text-3xl font-bold sm:text-4xl lg:text-6xl">"</span>
<div class="flex-1 text-center text-sm leading-loose sm:text-base lg:text-xl">
{{ zhSentence }}
</div>
<span class="text-3xl font-bold sm:text-4xl lg:text-6xl">"</span>
</div>
<p class="text-right text-xs text-gray-200 sm:text-sm">—— 金山词霸「每日一句」</p>
<p
class="pl-2 text-xs leading-loose text-gray-600 sm:pl-4 sm:text-sm lg:pl-14 lg:text-base"
>
{{
`恭喜您一共完成 ${courseTimer.totalRecordNumber()} 道题,用时 ${formatSecondsToTime(
courseTimer.calculateTotalTime(),
)} `
}}
</p>
<p
v-if="isAuthenticated()"
class="pl-2 text-xs leading-loose text-gray-400 sm:pl-4 sm:text-sm lg:pl-14 lg:text-base"
>
今天一共学习 <span class="text-purple-500">{{ formattedMinutes }}分钟</span> 啦!
<span v-if="totalMinutes >= 30">太强了,给自己来点掌声 😄</span>
</p>
</div>
<div className="modal-action flex flex-col sm:flex-row gap-2 justify-center sm:justify-end">
<button
class="btn btn-primary w-full sm:w-auto"
@click="toShare"
>
生成打卡图
</button>
<button
class="btn w-full sm:w-auto"
@click="handleDoAgain"
>
再来一次
</button>
<button
class="btn w-full sm:w-auto"
@click="handleGoToCourseList"
>
课程列表
</button>
<button
class="btn w-full sm:w-auto"
@click="goToNextCourse"
>
下一课
<UKbd> ↵ </UKbd>
</button>
</div>
</UContainer>
</UModal>
<canvas
ref="confettiCanvasRef"
class="pointer-events-none absolute left-0 top-0 z-[1000] h-full w-full"
></canvas>
</template>
<script setup lang="ts">
import { useModal } from "#imports";
import { computed, ref, watch } from "vue";
import { toast } from "vue-sonner";
import Dialog from "~/components/common/Dialog.vue";
import { useActiveCourseMap } from "~/composables/courses/activeCourse";
import { courseTimer } from "~/composables/courses/courseTimer";
import { useConfetti } from "~/composables/main/confetti/useConfetti";
import { readOneSentencePerDayAloud } from "~/composables/main/englishSound";
import { useGameMode } from "~/composables/main/game";
import { useLearningTimeTracker } from "~/composables/main/learningTimeTracker";
import { useShareModal } from "~/composables/main/shareImage/share";
import { useDailySentence, useSummary } from "~/composables/main/summary";
import { useNavigation } from "~/composables/useNavigation";
import { isAuthenticated, signIn } from "~/services/auth";
import { useCourseStore } from "~/store/course";
import { useCoursePackStore } from "~/store/coursePack";
import { useGameStore } from "~/store/game";
import { permitSaveStatement, preventSaveStatement } from "~/store/statement";
import { formatSecondsToTime } from "~/utils/date";
import { cancelShortcut, registerShortcut } from "~/utils/keyboardShortcuts";
const courseStore = useCourseStore();
const coursePackStore = useCoursePackStore();
const { gotoCourseList, gotoGame } = useNavigation();
const { showQuestion } = useGameMode();
const { handleGoToCourseList, goToNextCourse, completeCourse } = useCourse();
const { handleDoAgain } = useDoAgain();
const { showModal, hideSummary } = useSummary();
const { zhSentence, enSentence } = useDailySentence();
const { confettiCanvasRef, playConfetti } = useConfetti();
const { showShareModal } = useShareModal();
const { updateActiveCourseMap } = useActiveCourseMap();
const { totalMinutes, formattedMinutes } = useTotalLearningTime();
const gameStore = useGameStore();
const modal = useModal();
watch(showModal, (val) => {
if (val) {
// 阻止包含 statement 完成课程后会自动把用户的进度设置成下一课
// 这里是为了防止先设置成下一课 后更新了 statement 的进度
// 这就会造成获取用户最近的课程包进度出现错误 因为是基于时间来获取的
preventSaveStatement();
// 注册回车键进入下一课
registerShortcut("enter", goToNextCourse);
// 显示结算面板代表当前课程已经完成
completeCourse();
// 朗读每日一句
soundSentence();
// 延迟一小会放彩蛋
// 停止计时
gameStore.completeLevel();
setTimeout(async () => {
playConfetti();
}, 300);
} else {
// 取消回车键进入下一课
cancelShortcut("enter", goToNextCourse);
permitSaveStatement();
}
});
function useTotalLearningTime() {
const { totalSeconds } = useLearningTimeTracker();
const totalMinutes = computed(() => Math.ceil(totalSeconds.value / 60));
const formattedMinutes = computed(() => {
return Math.max(totalMinutes.value, 1).toString();
});
return {
totalMinutes,
formattedMinutes,
};
}
function useDoAgain() {
async function handleDoAgain() {
// 看看是不是没有全部掌握了
// 如果是全部掌握了 那么给个提示 然后挑战到课程列表
if (courseStore.isAllMastered()) {
toast.info("你已经全部都掌握 自动帮你跳转到课程列表啦", {
duration: 1500,
onAutoClose: () => {
handleGoToCourseList();
},
});
return;
}
courseStore.doAgain();
hideSummary();
showQuestion();
courseTimer.reset();
gameStore.startGame();
}
return {
handleDoAgain,
};
}
// 朗读每日一句
function soundSentence() {
readOneSentencePerDayAloud(enSentence.value);
}
function useCourse() {
let nextCourseId = ref("");
const haveNextCourse = computed(() => {
return nextCourseId.value;
});
async function goToNextCourse() {
if (!isAuthenticated()) {
// 去注册
modal.open(Dialog, {
title: "✨ 解锁更多学习体验",
content: "注册后可以进行下一课学习 记录每日学习数据 开启更多功能哦",
showCancel: true,
showConfirm: true,
cancelText: "稍后再说",
confirmText: "立即注册",
async onConfirm() {
courseStore.resetStatementIndex();
showQuestion();
signIn();
},
});
return;
}
hideSummary();
if (!haveNextCourse.value) {
toast.info("已经是最后一课 自动帮你跳转到课程列表啦", {
duration: 1500,
onAutoClose: () => {
handleGoToCourseList();
},
});
return;
}
if (courseStore.currentCourse) {
gotoGame(courseStore.currentCourse.coursePackId, nextCourseId.value);
}
}
function handleGoToCourseList() {
hideSummary();
if (courseStore.currentCourse) {
gotoCourseList(courseStore.currentCourse.coursePackId);
}
}
async function completeCourse() {
if (isAuthenticated() && courseStore.currentCourse) {
const { coursePackId } = courseStore.currentCourse;
const { nextCourse } = await courseStore.completeCourse();
coursePackStore.updateCoursesCompleteCount(coursePackId);
if (nextCourse) {
nextCourseId.value = nextCourse.id;
updateActiveCourseMap(coursePackId, nextCourseId.value);
} else {
updateActiveCourseMap(coursePackId, "");
}
}
}
return {
completeCourse,
goToNextCourse,
handleGoToCourseList,
};
}
const toShare = () => {
showShareModal();
};
</script>