前言
前端基础都了解的差不多了,天天看 CSS 啥的也啥都记不住,不如动手做。所以这篇文章记录 Vue 框架的一些实际案例。写基础都太没意思了,跟记流水账一样,所以就不写那些基础操作了。
初始化项目
1 2 3 4 5 6 7 8
| npm create npm create vue@latest
pnpm create npm create vue@latest
npm/pnpm install
pnpm dev
|
项目结构
- Vue-app
- src
- api <– 网络请求封装(自己加的)
- assets <– css样式与图片
- components <– 暂时不知道有啥用
- router <– 路由
- views <– 页面
- App.vue <– 主入口
- main.ts <– 脚本主入口
- index.html
- … <– 一堆默认配置文件
- vite.config.ts <– vite配置文件
实际场景
将返回信息以列表形式显示
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
| <script setup lang="ts">
...
const newSongs = ref<NewSong[]>([]);
const fetchNewSongs = async () => { try { const data = await api.get<PersonalizedNewSongRes>('/personalized/newsong');
newSongs.value = (data.result || []).map((item) => ({ id: item.id, name: item.name, cover: item.picUrl, artist: item.song?.artists?.map((a) => a.name).join('/') || '', })); } catch(err) { console.log("推荐新音乐失败", err); } }
...
</script>
<template> ... <ul class="song-list"> <li class="song-item" v-for="song in newSongs" :key="song.id" > <div class="song-cover"> <img :src="song.cover" :alt="song.name"> </div> <div class="song-info"> <p class="song-name">{{ song.name }}</p> <p class="song-artist">{{ song.artist }}</p> </div> </li> </ul> ... </template>
|
注: 这里已经对网络请求进行了封装,所以直接用 api.get<>('...'); 就能直接从后端获取数据。
简单的翻页效果的实现
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
| <script setup lang="ts">
...
interface Artist { id: number; name: string; rank: number; avatar: string; }
...
const singerRank = ref<Artist[]>([]); const currentSingerSlide = ref<number>(0);
const SINGER_PAGE_SIZE = 5;
const singerSlides = computed(() => { const list = singerRank.value || []; const sliders: Artist[][] = []; for (let i = 0; i < list.length; i += SINGER_PAGE_SIZE) { sliders.push(list.slice(i, i + SINGER_PAGE_SIZE)); } return sliders; });
const prevSingerSlide = () => { if (!singerSlides.value.length) return; currentSingerSlide.value = (currentSingerSlide.value - 1 + singerSlides.value.length) % singerSlides.value.length; }
const nextSingerSlide = () => { if (!singerSlides.value.length) return; currentSingerSlide.value = (currentSingerSlide.value + 1) % singerSlides.value.length; } ...
</script>
<template> ... <div v-for="(sliders, index) in singerSlides" :key="index" class="singer-slide" v-show="index == currentSingerSlide" > <ul class="singer-list"> <li v-for="singer in sliders" :key="singer.id" class="singer-item"> <div class="singer-avatar"> <img :src="singer.avatar" :alt="singer.name"> </div> <span class="singer-name">{{ singer.rank + 1}} {{ singer.name }}</span> </li> </ul> </div> ... <div class="singer-carousel-controls" v-if="singerSlides.length > 1"> <button class="singer-arrow" @click="prevSingerSlide"><ChevronLeft :stroke-width="3" /></button> <button class="singer-arrow" @click="nextSingerSlide"><ChevronRight :stroke-width="3" /></button> </div> ... </template>
|
注: 这里用了 Icons-Lucide 的图标元素<ChevronRight/>。
实现页面的跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <script setup lang="ts">
... const router = useRouter(); const pageChange = (id:number) => { router.push({ name: "pagename", id: {id}, }); } ...
</script>
<template> ... <ul> <li @click = "pageChange(something.id)"> ... <li> <ul> ... </template>
|
点击元素就能跳转页面
从页面 url 中获取信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <script setup lang="ts">
... const route = useRoute(); const somethingId = computed(() => Number(route.query.id) || 0);
onMounted(() => { console.log(songthingId); }); ...
</script>
<template> ... <p>{{ somethingId }}<p> ... </template>
|
这样就能把信息在控制台和页面中显示出来了,onMounted()是生命周期函数…目前只知道把函数丢进去它就能跑
实现音乐的播放
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
| <script setup lang="ts">
...
const audioRef = ref<HTMLAudioElement | null>(null);
const audioUrl = ref<string>(''); const currentTime = ref<number>(0); const duration = ref<number>(0); const isPlay = ref<boolean>(false);
const fetchSongUrl = async () => { const id: number = songId.value; if (!id) return; try { const data = await api.get<SongUrlRes>("/song/url", { id: id }); audioUrl.value = data.data[0]?.url || ''; console.log(audioUrl.value); duration.value = 0; currentTime.value = 0; isPlay.value = false; } catch(err) { console.log("获取音乐播放地址失败", err); audioUrl.value = ''; isPlay.value = false; } }
const handleLoadedMetadata = () => { const audio = audioRef.value; if (!audio) return; duration.value = audio.duration || 0; currentTime.value = audio.currentTime || 0; }
const handleTogglePlay = () => { const audio = audioRef.value; if (!audio) return; if (audio.paused) { audio.play().then(() => { isPlay.value = true; }).catch(() => {}); } else { audio.pause(); isPlay.value = false; } }
const handleAudioEnded = () => { isPlay.value = false; }
const handleTimeUpdate = () => { const audio = audioRef.value; if(!audio) return; currentTime.value = audio.currentTime || 0; }
...
</script>
<template> ... <div class="controls-main"> <button class="btn-circle btn-large" @click="handleTogglePlay"> <Pause v-if="isPlay"/> <Play v-else/> </button> </div> ... <audio v-if="audioUrl" class="audio-hidden" ref="audioRef" :src="audioUrl" @loadedmetadata="handleLoadedMetadata" @timeupdate="handleTimeUpdate" @ended="handleAudioEnded" ></audio> ... </template>
|
还要使用 audio 元素,这个还是第一次接触并使用
实现进度条控制歌曲
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
| <script setup lang="ts">
...
const currentTime = ref<number>(0); const duration = ref<number>(0);
const formateTime = (sec: number) => { if (!sec || !Number.isFinite(sec)) return '00:00'; const s = Math.floor(sec); const m = Math.floor(s / 60); const rs = s % 60; const mm = m.toString().padStart(2, '0'); const ss = rs.toString().padStart(2, '0'); return `${mm}:${ss}`; }
const handleProgressClick = (event: MouseEvent) => { const bar = event.currentTarget as HTMLElement; const audio = audioRef.value; if(!audio || !bar) return;
const rect = bar.getBoundingClientRect(); if (rect.width === 0) return; const ratio = Math.min(1, Math.max(0, (event.clientX - rect.left) / rect.width));
const newTime = duration.value * ratio; audio.currentTime = newTime; currentTime.value = newTime; }
...
</script>
<template> ... <div class="progress-wrap"> <span class="time-label">{{ formateTime(currentTime) }}</span> <div class="progress-bar" @click="handleProgressClick"> <div class="progress-inner" :style="{ width: duration ? `${(currentTime / duration) * 100}%`: '0%' }"></div> </div> <span class="time-label">{{ formateTime(duration) }}</span> </div> ... </template>
|
通过 progress-bar 和 progress-inner 实现进度条效果
结语
依旧为了方便翻阅复习,于是写在了博客上。还有一大堆 interface 就不在文章里列举了…