Skip to content

Commit

Permalink
feat: 加入 typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
yinmin8610 committed Dec 12, 2024
1 parent f10840c commit cddc260
Show file tree
Hide file tree
Showing 9 changed files with 313 additions and 234 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p \"build-only {@}\" --",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"test:unit": "vitest",
"build-only": "vite build",
Expand Down
41 changes: 23 additions & 18 deletions src/components/AppCard.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
<script setup lang="ts">
import type { Ref } from 'vue';
import type { Project } from '../types/global.type';
import { ref } from 'vue';
// icons
import IconChevronDown from './icons/IconChevronDown.vue';
import IconBehance from './icons/IconBehance.vue';
import IconGitHub from './icons/IconGitHub.vue';
import IconLink from './icons/IconLink.vue';
const props = defineProps({
projects: {
type: Object,
default: () => { },
},
});
interface Props {
projects: Project[]
}
const colorMap = {
const props = defineProps<Props>();
const colorMap: {
[key: string]: string
} = {
HTML: '#005856',
Bootstrap: '#7952b3',
JS: '#8E240E',
Expand All @@ -24,17 +31,15 @@ const colorMap = {
default: '#000000'
};
const activeCardId = ref(null);
const onClickHandler = (id) => {
activeCardId.value = activeCardId.value === id ? null : id;
};
const isActive = (id) => {
return activeCardId.value === id;
const clickedProjectId: Ref<string | null> = ref(null);
const hoveredProjectId: Ref<string | null> = ref(null);
const onClickHandler = (id: string) => {
clickedProjectId.value = clickedProjectId.value === id ? null : id; // 點到一樣卡就關閉(null),否則開啟(id)
};
const hoveredProjectId = ref(null);
const isHovered = (id) => {
return hoveredProjectId.value === id;
const isToggled = (id: string, stateId: string | null) => {
return stateId === id;
}
</script>

Expand All @@ -53,7 +58,7 @@ const isHovered = (id) => {
:style="{ backgroundColor: colorMap[project.course_tag] || colorMap.default }">{{ project.course
}}</span>
<transition name="fade">
<div v-show="isHovered(project.id)" class="overlay w-100 position-absolute bottom-0 px-3">
<div v-show="isToggled(project.id, hoveredProjectId)" class="overlay w-100 position-absolute bottom-0 px-3">
<div class="overlay-text d-flex justify-content-between text-white"
:class="{ 'mb-2': project.tags.length > 1 }">
<h5 class="fs-6 mb-0">作者:{{ project.name }}</h5>
Expand All @@ -74,7 +79,7 @@ const isHovered = (id) => {
</a>
<button type="button" class="btn p-0 position-relative z-1 btn-hover" @click="onClickHandler(project.id)">
<IconChevronDown />
<div v-if="isActive(project.id)" class="
<div v-if="isToggled(project.id, clickedProjectId)" class="
position-absolute
end-0
border
Expand Down
185 changes: 33 additions & 152 deletions src/components/AppLayout.vue
Original file line number Diff line number Diff line change
@@ -1,141 +1,32 @@
<script setup lang="ts">
import logo from '../assets/logo.svg';
import IconArrowRight from './icons/IconArrorRight.vue';
import type { Ref } from 'vue';
import type { CourseCategory, CourseCategoryDetail } from '../types/global.type';
import { onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { apiGetCategories } from '../apis/data';
import countdownActivity from '../composables/countdownActivity';
// composables
import { useCountdown } from '../composables/countdownActivity';
// apis
import { apiGetCategories } from '../apis/data';
// const { countdown, timeId } = activityCountdown()
// assets
import logo from '../assets/logo.svg';
// icons
import IconArrowRight from './icons/IconArrorRight.vue';
// data
import courseCategory from '../data/course-category.json';
const route = useRoute();
const router = useRouter();
// onUnmounted(() => {
// clearInterval(timeId.value);
// });
const courseCategoryList = ref([
{
id: 0,
name: '全部',
tag: 'All',
description: '六角學員作品牆,程式新手也能完成獨一無二的網頁作品。',
isActive: true,
relLinks: [{
routeName: 'all-courses',
url: 'https://www.hexschool.com/courses/?category=all-courses',
urlText: '我也想做出專屬作品!',
}],
},
{
id: 1,
name: 'Vue',
tag: 'Vue',
description: '使用業界熱門框架完成求職作品,提升職場競爭力!',
isActive: false,
relLinks: [
{
routeName: 'vue3',
url: 'https://www.hexschool.com/courses/vue3.html',
urlText: '前往 Vue 影音課',
},
{
routeName: 'vue-training',
url: 'https://www.hexschool.com/courses/vue-training.html',
urlText: '前往 Vue 直播班',
}],
},
{
id: 2,
name: '切版直播班',
tag: 'HTML',
description: '用三個月的時間,將切版技能練到巔峰。',
isActive: false,
relLinks: [{
routeName: 'web-layout-training-1st',
url: 'https://www.hexschool.com/courses/web-layout-training-1st.html',
urlText: '我也想提升切版力!',
}],
},
{
id: 3,
name: 'Node',
tag: 'Node',
description: '團隊共同完成符合商業邏輯的 Node.js 專案。',
isActive: false,
relLinks: [{
routeName: 'node-project-training',
url: 'https://www.hexschool.com/courses/node-project-training.html',
urlText: '完成專案,提升競爭力!',
}],
},
{
id: 4,
name: 'JS',
tag: 'JS',
description: '從基礎到 AJAX,體驗前端工程師的第一課!',
isActive: false,
relLinks: [{
routeName: 'js-training',
url: 'https://www.hexschool.com/courses/js-training.html',
urlText: '循序漸進掌握 JS 邏輯',
}],
},
{
id: 5,
name: 'Bootstrap',
tag: 'Bootstrap',
description:
'使用 Bootstrap 快速完成切版版型,從設計稿到可觀賞網頁的不二選擇!',
isActive: false,
relLinks: [{
routeName: 'bootstrap5',
url: 'https://www.hexschool.com/courses/bootstrap5.html',
urlText: '掌握業界必備網頁設計框架!',
}],
},
{
id: 6,
name: 'React',
tag: 'React',
description:
'使用業界熱門框架完成求職作品,提升職場競爭力!',
isActive: false,
relLinks: [{
routeName: 'react',
url: 'https://www.hexschool.com/courses/react.html',
urlText: '前往 React 影音課!',
},
{
routeName: 'js-react-training',
url: 'https://www.hexschool.com/courses/js-react-training.html',
urlText: '前往前端工程師培訓班',
}],
},
{
id: 7,
name: 'UI',
tag: 'UI',
description: '好的設計,讓網站品質大加分!',
isActive: false,
relLinks: [{
routeName: 'ui',
url: 'https://www.hexschool.com/courses/ui.html',
urlText: '我也想畫出有程式邏輯的設計稿!',
}],
},
]);
const courseCategoryDetail = ref({
const courseCategoryList: Ref<CourseCategory[]> = ref(courseCategory);
const courseCategoryDetail: Ref<CourseCategoryDetail> = ref({
name: '全部',
tag: 'All',
category: 'All',
description: '六角學員作品牆,程式新手也能完成獨一無二的網頁作品。',
relLinks: [{
routeName: 'all-courses',
Expand All @@ -144,10 +35,10 @@ const courseCategoryDetail = ref({
}],
});
const categories = ref([]);
const searchValue = ref('');
const tags = ref([]);
const selectedTag = ref('');
const categories: Ref<Record<string, string[]>> = ref({});
const searchValue: Ref<string> = ref('');
const tags: Ref<string[]> = ref([]);
const selectedTag: Ref<string> = ref('');
onMounted(async () => {
Expand All @@ -157,15 +48,15 @@ onMounted(async () => {
// 透過網址分類進來
if (route.query.category) {
const category = courseCategoryList.value.filter((courseCategory) => courseCategory.tag === route.query.category);
const category = courseCategoryList.value.filter((courseCategory) => courseCategory.category === route.query.category);
onClickHandler(category[0]);
}
} catch (err) {
console.log(err);
}
})
const onClickHandler = (course) => {
const onClickHandler = (course: CourseCategory) => {
// 切換 category active
courseCategoryList.value = courseCategoryList.value.map((courseCategory) => ({
...courseCategory,
Expand All @@ -178,7 +69,7 @@ const onClickHandler = (course) => {
}
// tag 選項更新
tags.value = categories.value[course.tag];
tags.value = categories.value[course.category];
// 搜尋與下拉選項回到初始狀態
searchValue.value = '';
Expand All @@ -188,28 +79,16 @@ const onClickHandler = (course) => {
router.replace({
query: {
...route.query,
category: course.tag,
category: course.category,
}
});
};
// 活動倒數公告
const countdown = ref({
isExpired: false,
seconds: 0,
minutes: 0,
hours: 0,
days: 0
});
const deadline = ref('Nov 13 2024 23:59:59');
if (countdown.value.isExpired) {
countdownActivity(deadline.value, (updatedDeadline) => {
countdown.value = { ...updatedDeadline };
});
}
// 活動倒數公告
const deadline = 'Nov 13 2024 23:59:59';
const { formattedTime } = useCountdown(deadline);
</script>

Expand Down Expand Up @@ -264,9 +143,10 @@ if (countdown.value.isExpired) {
</div>
</form>

<router-view :filterCourses="{ category: courseCategoryDetail.tag, searchValue, tag: selectedTag }"></router-view>
<router-view
:filterCourses="{ category: courseCategoryDetail.category, searchValue, tag: selectedTag }"></router-view>

<footer class="py-3 mt-5" :class="{ 'pb-6': countdown.isExpired }">
<footer class="py-3 mt-5" :class="{ 'pb-6': formattedTime }">
<div class="container">
<p class="d-flex flex-column flex-md-row justify-content-md-between">
<small class="fw-normal">
Expand All @@ -284,7 +164,7 @@ if (countdown.value.isExpired) {
bg-danger
px-2
py-2
" v-if="countdown.isExpired">
" v-if="formattedTime">

<div class="
d-flex
Expand All @@ -304,8 +184,9 @@ if (countdown.value.isExpired) {
</span>

<div class="d-flex justify-content-start text-white text-nowrap">
<span class="fw-bold">{{ countdown.days }} 天 {{ countdown.hours }} 時
{{ countdown.minutes }} 分 {{ countdown.seconds }} 秒</span>
<span class="fw-bold">
{{ formattedTime }}
</span>
</div>
</div>
<div class="
Expand Down
26 changes: 15 additions & 11 deletions src/components/AppPagination.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
<script setup lang="ts">
import type { Ref } from 'vue';
import type { Project } from '../types/global.type';
import { ref, watch } from "vue";
import { useRoute } from 'vue-router';
// plugin
import { VueAwesomePaginate } from 'vue-awesome-paginate'
import 'vue-awesome-paginate/dist/style.css'
import { useRoute } from 'vue-router';
interface Props {
projects: Project[]
}
const route = useRoute();
defineProps({
projects: {
type: Object,
default: () => { },
},
});
const props = defineProps<Props>();
const emit = defineEmits(["update:currentPage"]);
const currentPage = ref(1);
const itemsPerPage = ref(21);
const onClickHandler = (newPage) => {
const currentPage: Ref<number> = ref(1);
const itemsPerPage: Ref<number> = ref(21);
const onClickHandler = (newPage: number) => {
currentPage.value = newPage;
emit('update:currentPage', newPage);
};
Expand All @@ -34,7 +38,7 @@ watch(
</script>
<template>
<div class="d-flex justify-content-center py-5">
<VueAwesomePaginate :total-items="projects.length" :items-per-page="itemsPerPage" :max-pages-shown="5"
<VueAwesomePaginate :total-items="props.projects.length" :items-per-page="itemsPerPage" :max-pages-shown="5"
v-model="currentPage" @click="onClickHandler" />
</div>
</template>
Expand Down
Loading

0 comments on commit cddc260

Please sign in to comment.