Skip to content

Commit

Permalink
feat: show the course time and numbers on summary page (#312)
Browse files Browse the repository at this point in the history
* feat: show the course time on the share page and summary page

* refactor: delete

* refactor: course time

---------

Co-authored-by: cuixiaorui <cui_xiaorui@126.com>
  • Loading branch information
zuowendong and cuixiaorui authored Mar 31, 2024
1 parent 62989d4 commit 81309ce
Show file tree
Hide file tree
Showing 13 changed files with 248 additions and 35 deletions.
8 changes: 8 additions & 0 deletions apps/client/components/main/Game.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
</template>

<script setup lang="ts">
import { onMounted } from "vue";
import { courseTimer } from "~/composables/courses/courseTimer";
import { useAnswerTip } from "~/composables/main/answerTip";
import { useGameMode } from "~/composables/main/game";
import Answer from "./Answer.vue";
Expand All @@ -33,4 +35,10 @@ import Tips from "./Tips.vue";
const { isAnswer, isQuestion } = useGameMode();
const { isAnswerTip } = useAnswerTip();
onMounted(() => {
courseTimer.reset()
})
</script>
20 changes: 11 additions & 9 deletions apps/client/components/main/Question/Question.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

<script setup lang="ts">
import { onMounted, watch } from "vue";
import { courseTimer } from "~/composables/courses/courseTimer";
import { useAnswerTip } from "~/composables/main/answerTip";
import { useGameMode } from "~/composables/main/game";
import { useInput } from "~/composables/main/question";
Expand Down Expand Up @@ -88,6 +89,7 @@ watch(
() => inputValue.value,
(val) => {
setInputValue(val);
courseTimer.time(String(courseStore.statementIndex));
}
);
Expand Down Expand Up @@ -217,15 +219,18 @@ function answerError() {
};
}
function handleAnswerRight() {
playRightSound(); // 正确提示
showAnswer();
hiddenAnswerTip();
courseTimer.timeEnd(String(courseStore.statementIndex));
}
function handleKeydown(e: KeyboardEvent) {
if (e.code === "Enter") {
e.stopPropagation();
submitAnswer(
() => {
playRightSound(); // 正确提示
showAnswer();
hiddenAnswerTip();
},
handleAnswerRight,
handleAnswerError // 错误提示
);
Expand All @@ -235,10 +240,7 @@ function handleKeydown(e: KeyboardEvent) {
handleKeyboardInput(e, {
useSpaceSubmitAnswer: {
enable: isUseSpaceSubmitAnswer(),
rightCallback: () => {
playRightSound(); // 正确提示
showAnswer();
},
rightCallback: handleAnswerRight,
errorCallback: handleAnswerError, // 错误提示
},
});
Expand Down
30 changes: 26 additions & 4 deletions apps/client/components/main/Summary.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<template>
<div>
<dialog className="modal mt-[-8vh]" :open="showModal">
<dialog
className="modal mt-[-8vh]"
:open="showModal"
>
<div className="modal-box max-w-[48rem]">
<div class="relative">
<h3 className="font-bold text-lg mb-4">🎉 Congratulations!</h3>
Expand Down Expand Up @@ -44,11 +47,27 @@
<span class="text-6xl font-bold">"</span>
</div>
<p class="text-right text-gray-200 text-3">—— 金山词霸「每日一句」</p>
<p class="text-gray-600 text-base leading-loose pl-14">
{{ `恭喜您一共完成 ${courseTimer.totalRecordNumber()} 道题,用时 ${formatSecondsToTime(courseTimer.calculateTotalTime())} `}}
</p>
</div>
<div className="modal-action">
<button class="btn btn-primary" @click="toShare">生成打卡图</button>
<button class="btn" @click="handleDoAgain">再来一次</button>
<button class="btn" @click="handleGoToNextCourse">
<button
class="btn btn-primary"
@click="toShare"
>
生成打卡图
</button>
<button
class="btn"
@click="handleDoAgain"
>
再来一次
</button>
<button
class="btn"
@click="handleGoToNextCourse"
>
开始下一课<kbd class="kbd"> ↵ </kbd>
</button>
</div>
Expand All @@ -65,6 +84,7 @@
import { watch } from "vue";
import { useRouter } from "vue-router";
import { useActiveCourseId } from "~/composables/courses/activeCourse";
import { courseTimer } from "~/composables/courses/courseTimer";
import { useAuthRequire } from "~/composables/main/authRequire";
import { useConfetti } from "~/composables/main/confetti/useConfetti";
import { readOneSentencePerDayAloud } from "~/composables/main/englishSound";
Expand All @@ -73,6 +93,7 @@ import { useShareModal } from "~/composables/main/shareImage/share";
import { useDailySentence, useSummary } from "~/composables/main/summary";
import { useCourseStore } from "~/store/course";
import { useUserStore } from "~/store/user";
import { formatSecondsToTime } from '~/utils/date';
import { cancelShortcut, registerShortcut } from "~/utils/keyboardShortcuts";
let nextCourseId = 1;
Expand Down Expand Up @@ -124,6 +145,7 @@ function useDoAgain() {
courseStore.doAgain();
hideSummary();
showQuestion();
courseTimer.reset();
}
return {
Expand Down
2 changes: 2 additions & 0 deletions apps/client/components/main/Tool.vue
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
import { computed, ref } from "vue";
import MessageBox from "~/components/main/MessageBox/MessageBox.vue";
import RankList from "~/components/rank/RankingList.vue";
import { courseTimer } from "~/composables/courses/courseTimer";
import { useGameMode } from "~/composables/main/game";
import { clearQuestionInput } from "~/composables/main/question";
import { useRanking } from "~/composables/rank/rankingList";
Expand Down Expand Up @@ -167,6 +168,7 @@ function useDoAgain() {
clearQuestionInput();
focusInput();
showQuestion();
courseTimer.reset()
}
return {
Expand Down
54 changes: 54 additions & 0 deletions apps/client/composables/courses/courseTimer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// CourseTimer.js
type Timestamp = {
s: number;
e: number;
time: number;
};

type Timestamps = Record<string, Timestamp>;

let timestamps: Timestamps = {};

function time(label: string) {
if (timestamps[label]) return;

timestamps[label] = {
s: Date.now(),
e: 0,
time: 0,
};
}

function timeEnd(label: string) {
const start = timestamps[label].s;
const end = Date.now();
const time = (Date.now() - start) / 1000;

timestamps[label].e = end;
timestamps[label].time = time;
}

function calculateTotalTime() {
const totalTime = Object.keys(timestamps).reduce((totalTime, key) => {
const { time } = timestamps[key];
return (totalTime += time);
}, 0);

return Math.ceil(totalTime);
}

function totalRecordNumber() {
return Object.keys(timestamps).length;
}

function reset() {
timestamps = {};
}

export const courseTimer = {
time,
timeEnd,
calculateTotalTime,
totalRecordNumber,
reset,
};
63 changes: 63 additions & 0 deletions apps/client/composables/courses/tests/courseTimer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// CourseTimer.spec.js
import { beforeEach, describe, expect, it, vi } from "vitest";
import { courseTimer } from "../courseTimer";

describe("course timers", () => {
beforeEach(() => {
vi.useFakeTimers();
courseTimer.reset();
});

it("CourseTimer calculates time correctly", async () => {
courseTimer.time("1");

vi.advanceTimersByTime(1000);

courseTimer.timeEnd("1");
expect(courseTimer.calculateTotalTime()).toBe(1);
});

it("should be rounded up", async () => {
courseTimer.time("1");

vi.advanceTimersByTime(1600);

courseTimer.timeEnd("1");
expect(courseTimer.calculateTotalTime()).toBe(2);
});

it("CourseTimer calculates total time correctly with multiple timestamps", async () => {
courseTimer.time("1");
vi.advanceTimersByTime(1000);
courseTimer.timeEnd("1");

courseTimer.time("2");
vi.advanceTimersByTime(1000);
courseTimer.timeEnd("2");

expect(courseTimer.calculateTotalTime()).toBe(2);
});

it("should only take effect on the first call", () => {
courseTimer.time("1");

vi.advanceTimersByTime(1000);

courseTimer.time("1");
courseTimer.timeEnd("1");

expect(courseTimer.calculateTotalTime()).toBe(1);
});

it("should return record numbers", () => {
courseTimer.time("1");
vi.advanceTimersByTime(1000);
courseTimer.timeEnd("1");

courseTimer.time("2");
vi.advanceTimersByTime(1000);
courseTimer.timeEnd("2");

expect(courseTimer.totalRecordNumber()).toBe(2);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const tpl_1 = ({
tw: "text-6xl font-bold flex justify-end",
children: '"',
},
},
}
],
},
},
Expand Down
7 changes: 3 additions & 4 deletions apps/client/composables/main/shareImage/share.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { ref } from "vue";
import satori, { type SatoriNode } from "satori";
import { ref } from "vue";
import { useDailySentence } from "../summary";
import {
clearCanvas,
convertSVGtoImg,
copyImage,
initCanvas,
fontEn,
fontZh,
initCanvas,
} from "./helper";
import { useDailySentence } from "../summary";
import { tpl_1 } from "./imageTemplates/tpl_1";
import { tpl_2 } from "./imageTemplates/tpl_2";

Expand Down
1 change: 1 addition & 0 deletions apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"axios": "^1.6.6",
"canvas-confetti": "^1.9.2",
"country-telephone-data": "^0.6.3",
"dayjs": "^1.11.10",
"google-libphonenumber": "^3.2.34",
"pinia": "^2.1.7",
"satori": "^0.10.13",
Expand Down
7 changes: 5 additions & 2 deletions apps/client/pages/Courses.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
v-if="courses.length"
class="h-[79vh] flex flex-wrap p-1 pb-96 overflow-x-hidden overflow-y-auto gap-8 justify-start"
>
<template v-for="course in courses" :key="course.id">
<template
v-for="course in courses"
:key="course.id"
>
<NuxtLink
:href="`/main/${course.id}`"
@click="handleChangeCourse(course)"
Expand Down Expand Up @@ -35,6 +38,7 @@ import CourseCard from "~/components/courses/CourseCard.vue";
import { useActiveCourseId } from "~/composables/courses/activeCourse";
import { type Course } from "~/store/course";
const { updateActiveCourseId } = useActiveCourseId();
const courses = ref<Course[]>([]);
async function getCourseHistory() {
Expand Down Expand Up @@ -67,7 +71,6 @@ onMounted(async () => {
courses.value = await getCourses();
});
const { updateActiveCourseId } = useActiveCourseId();
function handleChangeCourse(course: Course) {
updateActiveCourseId(course.id);
}
Expand Down
Loading

0 comments on commit 81309ce

Please sign in to comment.