学习了一段时间vue3
的基础知识学习,百学不如一练,想着还是做出一个实际的demo项目(适配为移动端),来实践巩固自己所学的知识点。
vue3.0
全家桶(组合式API)vuex
➕vuex-persistedstate
(持久化数据存储)Vite
(极速的开发服务器启动)├─ src
├─api // 网路请求代码
├─assets // 字体配置及全局样式
├─style // 公共样式
├─components // 可复用的 UI 组件
├─utils // 工具类函数和相关配置
├─views // 页面
├─router // 路由配置文件
└─store // redux 相关文件
App.jsx // 根组件
main.jsx // 入口文件
import {
createRouter,
createWebHistory,
RouteRecordRaw
} from 'vue-router'
const Login = () => import('../views/Login/Login.vue')
const Phone = () => import('../views/Login/Phone.vue')
const Vcode = () => import('../views/Login/VerCode.vue')
const Home = () => import('../views/Home.vue')
const View = () => import('../views/View.vue')
const Comment = () => import('../views/Comment.vue')
//类型校验,规范化typescript,增加路由对象类型限制,好处:允许在基础路由里增加开发自定义属性
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/login',
},
{
path: '/login',
name: 'Login',
meta: {
type: 'Login'
},
component: Login,
},
{
path: '/login/phone',
name: 'Phone',
meta: {
type: 'Login'
},
component: Phone,
},
{
path: '/login/phone/vcode',
name: 'Vcode',
component: Vcode,
}
.......
router-Link
点击导航、编程式导航,代码片段如下:
......
setup(props) {
const router = useRouter();
let Nickname = ref("");
let avatarUrl = ref("");
function jumpComment() {
router.push({
path: "comment",
query: { id: props.listID },
});
}
.....
}
EventBus
(vue3
中,取消了全局事件总线,如果想要使用全局事件总线,那么就需要使用一个插件mitt
)安装mitt: npm i mitt -s
在utils下创建bus.js文件:
//注册event bus事件
import mitt from 'mitt'
const emitter = mitt()
export default emitter
使用代码片段:
//emitter.emit使用
setup(props) {
const store = useStore();
/* let musicObj = ref(""); */
let isListen = ref(Boolean);
let indexNumber = ref("");
function handleIcon(index) {
isListen = !isListen;
indexNumber.value = index;
//通过点击传递指定列表数据
emitter.emit("event", store.state.musicObj[index]);
}
......
}
//emitter.on的使用
setup() {
.......
//获取事件总线传递过来的数据
emitter.on("event", (e) => {
.......
});
//事件总线的卸载,否则会存粗之前的调用
onBeforeUnmount(() => {
emitter.off("event");
});
.......
},
2.消息发布与订阅PubSub
安装第三方库: npm i pubsub-js
消息订阅:
pubsub.subscribe('event',(funname, data) => {
// funname为消息名称 data为传递的参数
console.log(funname, data) })
消息发布:
pubsub.publish('event', 'Tom') //第一个参数为消息名称,第二个参数为发布的数据
具体代码点这里
当写到底部的播放音乐的组件的时候,不同的歌单列表下进行点击会影响数据的改变,发现如果单一的传递数据会过于混杂,这时候就需要用到vuex
来管理,并能够实现多组件共享存储数据(集中式存储管理应用)
效果如图:
安装:npm i vuex@next --save
store/inex.js代码如下:
import { createStore } from 'vuex'
import { getPlayList } from '../api/index'
......
const store = createStore({
state() {
return {
//整个音乐列表的数据存储
musicObj: {},
......
}
},
mutations:
{
//保存发现好歌单信息
saveMusic(state, obj) {
state.musicObj = obj
},
//通过acion异步获取底部歌单播放详情
getMusic(state, obj) {
state.bottomMusic = obj
},
......
},
......
})
export default store
代码片段:
//获取state
import { useStore } from "vuex";
import emitter from "../utils/bus";
import { getPlayList } from "../api/index";
export default {
setup() {
const store = useStore();
let musicPic = ref("");
let musicName = ref("");
let musicAhtuor = ref("");
let isActive = ref(store.state.isAcitve);
......
}
//mutations提交数据
......
import { useStore } from "vuex";
import ListBotton from "../components/listBotton.vue";
/* 消息的发布与订阅 */
import pubsub from "pubsub-js";
export default {
components: { listTop, listMiddle, ListBotton },
setup() {
const route = useRoute();
const store = useStore();
let state = reactive({ playData: {} });
let listID = ref();
let isShow=ref(false)
onBeforeMount(async () => {
isShow.value=true
let res = await getPlayList(route.query.id);
state.playData = reactive(res.data.playlist);
store.commit("saveMusic", state.playData.tracks);
......
});
}
//actions异步的使用
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
store.dispatch("getMusicList");
},
};
具体代码点这里
为了方便参考网易云的布局结构,参考的是ipad的网易云HD,所以页面有一些icon会看起来有点拥挤。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oH8ki9Nf-1662524328940)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6fc865f35884612bb6483bb93f5f6be~tplv-k3u1fbpfcp-watermark.image?)]
Phone页面是进行手机号的验证,并向手机号码发送实时验证码短信。
通过点击文件框获取焦点的时候灵活激活数字键盘,watch
来监听手机号码的长度来判断是否可以点击“下一步”的按钮,并发起请求发送验证码。
watch(number, (newNumber) => {
if (newNumber.length !== 0) {
isActive.value = false;
}
});
......
async function handleLogin(value) {
if (!isPhoneNumber(number.value) && value == false) {
return ElMessage.error("手机号不合格!");
} else if (value == false) {
let phoneInfo = number.value;
let res = await postNumber(phoneInfo);
sessionStorage.setItem("phone", phoneInfo);
console.log(res);
router.push({
path: "/login/phone/vcode",
name: "Vcode",
});
}
}
View页面是歌单列表详情页、Comment页面是评论列表详情页,都主要以处理数据并进行渲染为主,通过后端得到的数据进行分析,并用v-for
进行循环排列歌单列表。
通过点击对应的歌曲,拿到相应的index存储起来,icon是通过判断当前点击的index和歌曲的index是否一致,一致则显示icon。
......
{{ index + 1 }}
......
function handleIcon(index) {
isListen = !isListen;
indexNumber.value = index;
//通过点击传递指定列表数据
emitter.emit("event", store.state.musicObj[index]);
}
......
Comment页面效果:
通过后端数据返回来的评论时间的时间戳,并获取当前的时间戳,如果年份相同则在“精彩评论”中出现年份,否则不出现。
通过点击点赞事件来将点赞次数增加并且复制一份并播放动画,更改后端返回的某个数据或者增加属性,最后将刷新的数据交出去并进行页面渲染。
/* 计算时间戳 */
......
function hotTime(params) {
let date = new Date(params);
let newDate = new Date();
let newY = newDate.getFullYear();
let Y = date.getFullYear();
let M =
date.getMonth() + 1 < 10
? "0" + (date.getMonth() + 1)
: date.getMonth() + 1;
let D = date.getDay() > 10 ? "0" + date.getDate() : date.getDate();
return newY === Y ? M + "月" + D + "日" : Y + "年" + M + "月" + D + "日";
}
const NewComment = "new";
const HotComment = "hot";
/* 判断是否第一次点击 */
function handleLike(index, value) {
let item =
value === NewComment ? newComment.news[index] : hotComment.hot[index];
item.liked = !item.liked;
item.liked === true ? item.likedCount++ : item.likedCount--;
value === NewComment
? (newComment = reactive({ news: newComment.news }))
: (hotComment = reactive({ hot: hotComment.hot }));
}
......
具体代码点这里
Suspense
:加载异步组件时,进行 Loading 的处理
Loading...
刷新页面后vuex的数据丢失了
在使用vuex
进行存储列表歌曲数据时,在每一次页面刷新后所有的数据都丢失了,才知道vuex
不能够持久化存储数据,一开始在尝试浏览器storage
来实施本地存储,通过后来的学习,发现可以安装vuex-persistedstate
插件来进行持久化本地数据存储。
安装:npm install vuex-persistedstate
//引入
import { createStore } from 'vuex'
import persistedState from 'vuex-persistedstate'
//
const store = createStore({
......
plugins: [persistedState(/* { storage: window.sessionStorage } */)]
})
export default store
使用事件总线EventBus和消息发布与订阅PubSub的时机
EventBus
: 要先$on
来接收频道信号,后$emit
发送频道信号(就是要先知道谁接收,才能发送)
PubSub
: 先subscribe
,后publish
(就是要先知道谁订阅,才能发布)
两者都必须在得到数据的页面卸载之前把EventBus
和PubSub
事件给注销,因此这两者都具有缓存性(也就说在下一次的接收会收到本次和上一次发送的数据)
这个Demo是自己手把手撸出来的,算是比较粗糙,比如说代码规划还有代码风格可能不太好;功能也不是很全,还有比如Home页面中可以多个类似的组件可以做利用插槽进行代码优化,如果后面有时间的话可能会一点点去完善,毕竟学习不会止步。
项目源码地址:GitHub,欢迎star