教学视频:https://www.bilibili.com/video/BV1Vf4y1T7bw

  1. 不回要资料的评论,资料在视频底下评论有些仓库里面有。
  2. 试图自行解决问题也是程序员必备技能之一,多做尝试多查资料。

目前进度:EP109

前言

这篇文章中我会把一些实战过程中遇到的问题以及自己的想法记录下来,并且附上我自己的解决方式。因为想练习 Vue3,所以我并没有跟着老师选 Vue2,以下所有内容都是 Vue 3

工具版本:

  • @vue/cli 5.0.4
  • Vue 3.2.13
  • vue-router 4
  • node.js 16.13.1
  • vuex 4.0.2
  • swiper 8.1.4 -> 轮播图
  • vue-lazyload -> vue3-lazyload 0.3.2 -> 懒加载
  • vee-validate 4.5.11 -> 表单验证

代码并非 100% 与老师写的相同,如果我觉得不合理或不合逻辑处会自行修正。本人编程程度不精,若有任何代码有隐患或错误之处欢迎在评论一起交流。

P 1

须先自行全局安装 nodejs 及 vue-cli。

  1. nodejs 需自官网下载及安装 https://nodejs.org/en/

  2. nodejs 安装后再打开 cmd 输入指令安装 vue-cli:npm install -g @vue/cli

P 2

Vue 片段扩展可以使用: Vue 3 Snippets,直接在编辑器输入 vue init 就能自动初始化 .vue 文件内容。

14: 03 没有 package-lock.json 这个文件

有弹幕问说自己为什么没有 package-lock.json 这个文件,这是因为 package-lock.json 是你在安装过项目依赖之后才会出现的缓存性文件,如果本来没有这个文件的话,跑一个 npm i 安装一下项目依赖它就会出现了。

在没有 package-lock.json 的时候安装项目依赖会比较慢,一旦安装产生 package-lock.json 缓存文件后,下次安装依赖就会快很多。

P 3

02:43 --open 会错误

--open 前加上 --host 192.168.1.26 这里的 192.168.1.26 请改成你的本地 IP。

可以用 cmd 输入 ipconfig 找到你使用的网路 IPv4 位址。


P 5

如果安装 less-loader@5 报错,可以直接安装最新版,npm install less less-loader --save-dev 没任何问题。

14: 28

引入 reset.css 这段 rel="icon" 改成 stylesheet

<link rel="stylesheet" href="./reset.css">

然后 reset.css 中有用到 iconfont.css,所以也要一并放到 public 文件夹底下。

P 6

05:00 为什么都命名为 index.vue

弹幕有人提到为什么都用 index.vue,这不是很容易搞混?为什么不跟着组件名称一起取呢?

个人见解是如果使用 index.vue 会更好识别是这个组件的主文件,并且使用 index 作为名称的话在引入的时候可以省略不写,比如说我要在 App.vue 中引入 Header,我直接 import Header from "./components/Header" 就可以,不需要写全 import Header from "./components/Header/index.vue"

如果有人知道原因,也可以评论告诉我一下~

10:24 路由配置

Vue 3 + Vue router 4 的使用方法如下:

修改 /src/router/index.js 为:

// 配置路由
import { createRouter, createWebHistory } from "vue-router";// 引入路由組件
import Home from "@/pages/Home";
import Login from "@/pages/Login";
import Register from "@/pages/Register";
import Search from "@/pages/Search";const routes = [{path: "/home",name: "Home",component: Home,},{path: "/login",name: "Login",component: Login,},{path: "/register",name: "Register",component: Register,},{path: "/search",name: "Search",component: Search,},
];const router = createRouter({history: createWebHistory(),routes,
});export default router;

修改 main.js

import { createApp } from "vue";
import App from "./App.vue";// 引入路由
import router from "@/router";const app = createApp(App);
app.use(router);
app.mount("#app");

App.vue 的 Header 跟 Footer 之间加上 <rotuer-view /> 用于显示路由组件内容:

<template><div><Header /><router-view /><Footer /></div>
</template>

15:55

老师这里查看 $route 的工具是 chrome 的扩展 Vue.js devtools,装上之后 Ctrl + Shift + I 打开开发人员工具,会有一个 Vue 标签。


18:09

path 直接写 * 会报错 Catch all routes ("*") must now be defined using a param with a custom regexp.,因为 Vue3 对 404 配置进行了修改,必须要使用正则匹配。

解决方式是匹配所有路径,使用 /:pathMatch(.*)/:catchAll(.*) 代替 *

P 9

04:09 path 不能结合 params 参数使用

这部分老师提到 path 不能结合 params 参数使用,实际上是可以跳转,但是无法接收到 params 参数,并且控制台会有警告:

[Vue Router warn]: Path "Search" was passed with params but they will be ignored.
Use a named route alongside params instead.
router.push({path: "/search", // 改为 nameparams: {keyword: data.keyword,},query: {k: data.keyword.toUpperCase(),},
});

就是建议你将 path 改为 name 不然 params 参数会被忽略。

14:22 params 传空字串

面试第三题,如果 params 传空字串过去,params 参数占位符后面有加问号就没事,如果没有加就会报错 Missing required param "keyword"

其实这边有点不太明白老师说的,因为如果占位符后面有加问号的话,不管传什么或者不传都不会有问题。

P 10

这一集老师提到 VueRouter 的部分并不适用于 vue-router 4,大概听一下理解就好。

06:01

个人测试 vue-router 4 多次传递相同的 params 参数过去并不会在控制台报错,但如果将 push 的结果以 result 储存然后打印出来,会发现其实第二次开始的 PromiseResult 都会有出现 Error,但实际使用过程中不会有任何影响。

"Avoided redundant navigation to current location: "/search/test?k=TEST"."


用成功及失败回调函数一样无法解决这个问题,不过对使用没影响也不会跳出 error 所以就不管它了。

14:58 VueRouter 这部分讲的太绕了

个人认为老师这部分讲解的有点绕,我自己的理解如下:

  1. VueRouter 是一个
  2. push 是 VueRouter 原型上的一个方法
  3. $router 为 VueRouter 类的实例
  4. 因此 $router 可以通过原型链找到 VueRouter 身上的方法
  5. VueRouter 身上的方法有 push,所以可以用 $router.push 来呼叫 push 这个方法。
// VueRouter 类
function VueRouter() {// ...
}// push 是 VueRouter 原型上的一个方法
VueRouter.prototype.push = function(){// 函数的上下文为 VueRouter 类的一个实例
}// $router 为 VueRouter 类的实例
let $router = new VueRouter(); // 通过原型链找到 VueRouter 身上的 push 方法
$rotuer.push(xxx);this.$router.push();

补充

这部分内容是参考 Vue Router 官方文档 做的一些修正。

在 vue-router 4 之后,VueRouter 不再是一个类,而是一组函数。所以改写 replace 跟 push 这部分不能再这样做了。

// previously was
// import Router from 'vue-router'
import { createRouter } from 'vue-router'const router = createRouter({// ...
})

之前是 VueRouter 类的原型中可以找到 push 跟 replace 方法,现在则是在 createRouter 的实例中找到。

import { createRouter, createWebHistory } from "vue-router";const router = createRouter({history: createWebHistory(),routes,
});console.log(router);
/*
...
push: f push(to)
replace: f replace(to)
*/


所以应该要这样改写:

const router = createRouter({history: createWebHistory(),routes,
});// 先把 router 的 push, replace 方法保存
let originPush = router.push;
let originReplace = router.replace;// 重写 push | replace
router.push = function (location, resolve, reject) {if (resolve && reject) {originPush.call(this, location, resolve, reject);} else {originPush.call(this,location);}
};router.replace = function (location, resolve, reject) {if (resolve && reject) {originReplace.call(this, location, resolve, reject);} else {originReplace.call(this,location,() => {},() => {});}
};

不过 vue-router 4 应该是不用重写 push 跟 replace 了,毕竟没有遇到相同的情况,所以这边我选择跳过。

P 12

06:24 Vue 3注册全局组件

Vue 2.x 可以通过 Vue.component() 来注册全局组件,但 Vue 3.0 开始需要先透过 Vue.createApp() 建立一个实例后再注册全局组件,写法如下:

import { createApp } from "vue";
import App from "./App.vue";// 使用 createApp 建立一个实例
const app = createApp(App);import router from "@/router";
app.use(router);// 注册全局组件
import TypeNav from "@/pages/Home/TypeNav";
app.component(TypeNav.name, TypeNav);app.mount("#app");

P 16

09:22 API 404

老师这边估计是著急想讲跨域知识点所以没注意到其实404是因为 baseURL 写错了,最新的接口 baseURL 应该写成 baseURL: "http://gmall-h5-api.atguigu.cn/api"

而且跨域问题后端已经处理好,所以前端其实不用做任何处理。

P 18

11:57 vuex 4.0

vuex 4.0 之后的使用方式和老师演示的不一样,写法为:

store/index.js

import { createStore } from "vuex";// state: 仓库存储数据的地方
const state = {};
// mutations: 修改 state 的唯一手段
const mutations = {};
// action: 处理 action, 可以书写自己的业务逻辑, 也可以处理异步
const actions = {};
// getters: 理解为计算属性, 用于简化仓库数据, 让组件获取仓库的数据更为方便
const getters = {};export const store = createStore({state,mutations,actions,getters,
});

而 Vue 3.0 开始不再提供功能的全域注册,而是注册在根组件实例上,所以我们需要在 main.js 加上以下代码:

// 引入仓库
import { store } from "@/store";
// 注册仓库: 组件实例的身上会多一个 $store 属性
app.use(store);

P 19

18:39 多出运动健康

完成三级联动菜单部分时最后一个 运动健康 可能会跑版,修改一下 h3 样式中的 line-height 就行:

.all-sort-list2 {.item {h3 {line-height: 27px;/* ... */}}
}

虽然不应该直接写死…但资料给的 CSS 都是写死的 px,之后有空再改吧。

或者也可以直接把 运动健康 给删掉,就是把最后一个 pop 掉就行。

P 25

08:50 节流

我目前使用的 lodash 版本为 4.17.21,现在不能使用 import { throttle } from "lodash/throttle"; 来引入节流 API,而是要改成 import { throttle } from "lodash"; 才可以。

另外如果想要自己写节流的话,下面是我的写法,可以参考一下:

const data = reactive({// ...throttleTimer: false,
});// 节流
const throttle = (callback, time) => {if (data.throttleTimer) return;data.throttleTimer = true;setTimeout(() => {callback();data.throttleTimer = false;}, time);
};// 获取当前 hover 的分类 index
const changeIndex = (index) => {throttle(() => {data.currentIndex = index;}, 25);
};

P 27

17:35 element.dataset

这里一定要注意从 element.dataset 取出来的自定义属性皆为小写,所以写 categoryName, category1Id, category2Id, category3Id 是不会有任何反应的。

// 菜单路由跳转
goSearch(event) {let element = event.target;let { categoryname, category1id, category2id, category3id } = element.dataset;// 若有 categoryName 标签即为 a 标签if (categoryname) {let location = { name: 'search' };let query = { categoryName: categoryname };// 若有 category1id 即为一级菜单, 依此类推if (category1id) {query.category1Id = category1id;} else if (category2id) {query.category2Id = category2id;} else {query.category3Id = category3id;}// 整理参数location.query = query;// 路由跳转this.$router.push(location);}
},

P29

23:10 enter-from

Vue 3 不再是用 <name>-enter 来改变过渡动画开始状态(进入),而是 <name>-enter-from

记得要在后面加上 -from 不然没有效果。

// 过渡动画的样式
.sort-enter-from { // 过渡动画开始状态(进入)height: 0px;
}
.sort-enter-to { // 过渡动画结束状态(进入)height: 461px;
}
.sort-enter-active { // 定义动画时间, 速率transition: all 0.5s linear;
}

P 30

这时候应该已经差不多完成 TypeNav 商品分类的部分了,但我发现没有鼠标移到连结上的效果,包括:颜色变换、变换鼠标,因此用户可能很难判断说它是一个可以点击的连结。

我自己是将 .sort 底下的所有 a 标签加上 cursor: pointer 以及颜色变换成 darkcyan:

.sort {/* ... */a {cursor: pointer;}a:hover {color: darkcyan;}.all-sort-list2 {/* ... */}
}

这就是 cursor:pointer

darkcyan 大概是这个色:

P 31

13:16 判断 params 和 query

老师这里判断了个寂寞,因为空对象也是 true,所以不应该直接判断 if(this.$route.params),而应该判断对象的 key 值长度 Object.keys(this.$route.params).length

并且路由跳转不能也放进判断里,不然如果没有 params 的话就不会进行跳转了。

/* TypeNav/index.vue */// 判断: 如果路由跳转时, 带有 params 参数需要传地过去
if (Object.keys(this.$route.params).length) {location.params = this.$route.params;
}// 整理参数
location.query = this.$route.query;
// 路由跳转
this.$router.push(location);

Header 的部分也是,如果 query 的 key 长度大于 0 才把 query 也给放入 location:

/* Header/index.vue */
goSearch() {// 路由传递参数let location = { name: "search", params: { keyword: this.keyword } };if (Object.keys(this.$route.query).length) {location.query = this.$route.query;}this.$router.push(location);
},

P 33

05:37

  1. 老师后来貌似是有改过项目结构,pages 文件夹变成 viewsapi/requests.js 变成 api/ajax.js,注意一下就好。
  2. mock.js 的路径和域名是可以自订的。

P 35

09:50 swiper 8 CSS

swiper 8 引入 CSS 及 Swiper:

main.js

// 引入 swiper 樣式
import "swiper/css";
import 'swiper/css/navigation';
import 'swiper/css/pagination';
import SwiperCore, {Navigation,Pagination,Scrollbar,A11y,Autoplay,
} from "swiper";SwiperCore.use([Navigation, Pagination, Scrollbar, A11y, Autoplay]);

25:47 swiper 8 写法

最新写法如下:

pages/Home/ListContainer/index.vue

<!--banner轮播-->
<swiperclass="swiper-container":navigation="{nextEl: '.swiper-button-next',prevEl: '.swiper-button-prev',}":pagination="{ clickable: true }":autoplay="{ autoplay: true }"loop
><swiper-slide v-for="(carousel, index) in bannerList" :key="index"><img :src="carousel.imgUrl" /></swiper-slide><!-- 如果需要分页器 --><div class="swiper-pagination"></div><!-- 如果需要导航按钮 --><div class="swiper-button-prev"></div><div class="swiper-button-next"></div>
</swiper>

不用 setTimeout,不用 mounted(),直接使用 Swiper, SwiperSlide 组件就行:

<script>
import { onMounted, computed } from "vue";
import { useStore } from "vuex";
import { Swiper, SwiperSlide } from "swiper/vue";export default {name: "",components: {Swiper,SwiperSlide,},setup() {const store = useStore();onMounted(() => {// 派發 action: 通過 vuex 發起 ajax 請求, 將數據存在倉庫中store.dispatch("getBannerList");});return {bannerList: computed(() => store.state.home.bannerList),};},
};
</script>

但是因为引入了 navigation 样式,而 floor 组件也使用到了 navigation,所以画面左右边会多出一个navigation,先将 pages/Home/Floor/index.vue 的 swiper-button 注释掉,之后写到 floor 再打开就行。

<!-- 如果需要导航按钮 -->
<!-- <div class="swiper-button-prev"></div> -->
<!-- <div class="swiper-button-next"></div> -->

如果用 swiper 8 就可以跳过 EP36 了。

P 38

08:26 bigImg -> imgUrl

这里大图片是 <img :src="list.imgUrl" /> 而不是 bigImg。

10:51 swiper 8

轮播图这部分跟前面讲 banner 的时候一样,将原本写好的结构删掉改成:

<template><!-- ... --><div class="floorBanner"><swiperclass="swiper-container":navigation="{nextEl: '.swiper-button-next',prevEl: '.swiper-button-prev',}":pagination="{ clickable: true }":autoplay="{ autoplay: true }"loop><swiper-slidev-for="carousel in list.carouselList":key="carousel.id"><img :src="carousel.imgUrl" /></swiper-slide><div class="swiper-pagination"></div><div class="swiper-button-prev"></div><div class="swiper-button-next"></div></swiper></div><!-- ... -->
</template><script>
import { Swiper, SwiperSlide } from "swiper/vue";
export default {name: "",props: ["list"],components: {Swiper,SwiperSlide,},
};
</script>

热门、大家电这块老师漏讲了一点,就是连结要改为 :href="nav.url" ,且 class="active",正常情况下应该是选中哪个类别哪个类型就使用 active 这个 class。

可以新增一个变量 navIndex 用于储存当前切换的 nav index,当 navIndex == index 时才使用 class="active"

<liv-for="(nav, index) in list.navList":key="index":class="index == navIndex ? 'active' : undefined"
><a :href="nav.url" data-toggle="tab" @click="changeNav(index)">{{ nav.text }}</a>
</li><script>
import { ref } from "vue";export default {props: ["list"],setup(props) {const list = props.list;const navIndex = ref(0);const changeNav = (index) => {navIndex.value = index;};return {list,navIndex,changeNav,};},
};
</script>

P 46

28:12 getList

removeCategoryName 不应该调用 getList(),因为本身有监听路由变化,一旦路由变化就会自动发送请求。

P 47

11:58 Vue3 $bus

Vue3我们可以使用 Mitt 来使用 $bus,Mitt npm

  1. 安装:npm install --save mitt
  2. 在 src 底下新增 bus.js:
    import mitt from "mitt";
    export default mitt();
    
  3. 在 Search 引入 bus.js 并且执行 clear:
    import bus from '@/bus';
    // ...
    const removeCategoryName = () => {data.searchParams.keyword = undefined;// 通知 Header 清除关键字bus.emit("clear");
    };
    
  4. 在 Header 引入 bus.js 并订阅 clear:
    import bus from "@/bus";
    // ...
    onMounted(() => {bus.on("clear", () => {data.keyword = "";});
    });
    

16:12 params & query 判断

判断有没有 params 或 query:

// params
if (Object.keys(route.params).length) {router.push({ name: "Search", params: route.params });
} else {router.push({ name: "Search" });
}
// query
if (Object.keys(route.query).length) {router.push({ name: "Search", query: route.query });
} else {router.push({ name: "Search" });
}

P 55

13:03 样式

原本的样式有点奇怪,上一页缺少左边框、…跟倒数第二个页码之间没有边框、加上最后页码的话下一页会跑到下一行去。

所以我修改了一下:

.page {width: 80%; /* 修改 */height: 66px;ul {margin-left: 0;margin-bottom: 0;vertical-align: middle;/*  width: 600px; */float: left;&.active {a {background-color: #fff;color: #e1251b;border-color: 1px solid #e0e9ee; /* 加上 */cursor: default;}}&.prev {a {background-color: #fafafa;border-left: 2px solid #e0e9ee; /* 加上 */}}&.dotted {span {/* .... */border-left: 1px solid #e0e9ee; /* 加上 */}}div {/* .... *//* float: right; *//* width: ; */padding-top: 9px; /* 加上 */span {padding-left: 10px;}}
/* .... */

这样显示上就比较正常了。

P 56

06:12 v-if ERROR

v-for 搭配 v-if 会报错,把 v-if 改为 v-show 就可以:

<ul><li class="prev disabled"><a href="#">«上一页</a></li><li class="active"><a href="#">1</a></li><li class="dotted"><span>...</span></li><liv-for="(page, index) in startNumAndEndNum.end":key="index"v-show="page >= startNumAndEndNum.start"><a href="#">{{ page }}</a></li><li class="dotted"><span>...</span></li><li><a href="#">{{ totalPage }}</a></li><li class="next"><a href="#">下一页»</a></li>
</ul>

P 66

09:08 Swiper

Swiper 8 要实现一页显示三张图可以这样写:

<swiperclass="swiper-container":navigation="{nextEl: '.swiper-button-next',prevEl: '.swiper-button-prev',}":pagination="{ clickable: true }":autoplay="{ autoplay: true }":slides-per-view="3" // 加上这句
><swiper-slide v-for="slide in skuImageList" :key="slide.id"><img :src="slide.imgUrl" /></swiper-slide><div class="swiper-pagination"></div><div class="swiper-button-prev"></div><div class="swiper-button-next"></div>
</swiper>

P 67

02:40 放大镜

放大镜这部分我改写成 Vue 3 如下:

// ImageList.vue
<script>
import { reactive, computed } from "vue";
import { Swiper, SwiperSlide } from "swiper/vue";
import bus from "@/bus";export default {name: "ImageList",props: ["skuImageList"],components: {Swiper,SwiperSlide,},setup(props) {const data = reactive({currentIndex: 0,});const changeCurrentIndex = (index) => {data.currentIndex = index;// 通知兄弟组件, 当前的索引值bus.emit("getImgIndex", data.currentIndex);};return {skuImageList: props.skuImageList,currentIndex: computed(() => data.currentIndex),changeCurrentIndex,};},
};
</script>
// Zoom.vue
<script>
import { computed, onMounted, reactive } from "vue";
import bus from "@/bus";export default {name: "Zoom",props: ["skuImageList"],setup(props) {const data = reactive({currentIndex: 0});onMounted(() => {// bus 获取兄弟组件传过来的图片索引值bus.on("getImgIndex", (index) => {data.currentIndex = index;});});return {imgObj: computed(() => props.skuImageList[data.currentIndex] || {}),};},
};
</script>

P 70

注意一下 skuId 的大小写不要写错了,不然是抓不到资料的。

store.dispatch("addOrUpdateShopCart", {skuId: route.params.skuid,skuNum: data.skuNum,
});

P 72

23:11

前面说过了,可以直接空降到 23:11。

P 83

14:00 商品全部删除error

这部分还有一个BUG,若商品全部删除后,cartList 为 0,因此在 updateAllCartIsChecked 这个 action 中遍历 cartList[0] 会错误,解决办法是在 cartList[0] 后方加上问号变成可选链即可。

async updateAllCartIsChecked({ dispatch, satte }, isChecked) {let arr = [];state.cartList[0]?.cartInfoList.forEach((item) => {let promise = dispatch("updateCheckedById", {skuId: item.skuId,isChecked,});arr.push(promise);});return Promise.all(arr);
},

P 95

帐号无法登入

目前老师的帐号是无法登入,所以 getUserAddress会是空结果。可以自行用 mock 的方式建个假的,学学逻辑跟思路就好。

api前台接口文档 里面有范例 response,可以用那个或者自己改。

[{"id": 1361,"userAddress": "北京北京市朝羊区远洋天地5号楼15","userId": 2,"consignee": "张龙","phoneNum": "15852016643","isDefault": "0","fullAddress": "北京北京市朝羊区远洋天地5号楼15","provinceId": 1,"regionId": 1},{"id": 1363,"userAddress": "北京北京市北七家镇","userId": 2,"consignee": "张三","phoneNum": "13522222222","isDefault": "0","fullAddress": "北京北京市北七家镇","provinceId": 1,"regionId": 1},{"id": 1364,"userAddress": "湖北武汉市江夏区东湖网谷","userId": 2,"consignee": "李四","phoneNum": "13533333333","isDefault": "0","fullAddress": "湖北武汉市江夏区东湖网谷","provinceId": 24,"regionId": 4},{"id": 1365,"userAddress": "广东深圳市宝安区","userId": 2,"consignee": "王五","phoneNum": "13544444444","isDefault": "1","fullAddress": "广东深圳市宝安区","provinceId": 26,"regionId": 5}]

P 98

全局注册 API

全局注册 API 这部分,Vue 3 建议跳过,因为 Vue 3 全局注册属性并没有比较方便,而且还需要藉由 Vue 提供的 getCurrentInstance 或者 inject,倒不如每个组件引入。

  1. config.globalProperties:

    // main.js
    import { createApp } from "vue";
    import App from "./App.vue";
    import * as API from "@/api";
    const app = createApp(App);
    app.config.globalProperties.$API = API;// Trade/index.vue
    import { getCurrentInstance } from "vue";
    export default {name: "Demo",setup() {const app = getCurrentInstance();const getAPIList = () => {console.log(app.appContext.config.globalProperties.$API);}return {getAPIList}},
    };
    
  2. provide & inject:

    // main.js
    import { createApp } from "vue";
    import App from "./App.vue";
    const app = createApp(App);
    import * as API from "@/api";
    app.provide('$API', API);// Trade/index.vue
    import { inject } from "vue";
    export default {name: "Demo",inject: ['$API'],setup() {const API = inject("$API");const getAPIList = () => {console.log(API);}return {getAPIList}},
    };
    

P 100

05:00 element UI 无法安装在 Vue 3

如果有尝试在 Vue 3 项目中安装 element UI 会发现报错了,原因是最新版本的 element UI (2.15.8) 仅支持到 Vue 2.5.17,所以 Vue 3 无法安装 Element UI(可以透过 --legacy-peer-deps 强行安装,但还有更好的办法)

Vue 3 可以改为安装 Element-plus,这是支持 Vue 3 的 Element-ui,提供的组件涵盖了绝大部分页面 UI 的需求。

安装命令:

npm install element-plus -S

自动引入需要安装 unplugin-vue-componentsunplugin-auto-import

npm install -D unplugin-vue-components unplugin-auto-import

然后修改 vue.config.js

const { defineConfig } = require("@vue/cli-service");
const AutoImport = require("unplugin-auto-import/webpack");
const Components = require("unplugin-vue-components/webpack");
const { ElementPlusResolver } = require("unplugin-vue-components/resolvers");module.exports = defineConfig({// ...configureWebpack: {plugins: [AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),],},
});

配置完成后就不需要再去 main.js 中引入组件,直接就可以在项目中使用:

<el-button type="primary">TEST</el-button>

官方文档:https://element-plus.org/en-US/guide/design.html

19:24 Message box CSS

MessageBox 的 CSS 样式并不会自动引入,需要我们手动在 main.js 中引入:

import "element-plus/es/components/message-box/style/css";

P 106

21:04 next(false)

next(false)如果一样会跳转,只是页面为空的话,可以将它改成 next(from.path)

P 107

04:37 vue-lazyload

老师使用的这个插件只有支援 Vue 1 & Vue 2,Vue 3 建议装 vue3-lazyload 这个:npm i vue3-lazyload --save

接着在 main.js 中引入:

// 引入 vue-lazyload
import VueLazyLoad from 'vue3-lazyload';
app.use(VueLazyLoad);

然后一样的使用方法 <img v-lazy="your image url" />

P 108

14:35 vee-validate Vue 3

vee-validate 目前最新版为 v4.5.11,跟老师的版本差的有点多,Vue 3 需要使用 4.x 版本,使用方式跟 2.x 差很大,如果跟我一样用高版本的,可以参考我的写法:

一样是在 plugins 里面新建 validate.js,然后将它引入到 main.js

import "@/plugins/validate";

validate.js 中你需要使用 defineRule 去制定验证的内容,直接将判断及回传的错误讯息写在回调函数中:

// vee-validate: 表单验证
import { defineRule } from "vee-validate";defineRule("required", (value) => {if (!value || !value.length) {return "此栏位为必填";}return true;
});
// 手机
defineRule("phone", (value) => {if (!value || !value.length) {return true;}if (!/^1\d{10}$/.test(value)) {return "手机号格式无效";}return true;
});
// 验证码
defineRule("code", (value) => {if (!value || !value.length) {return true;}if (!/^\d{6}$/.test(value)) {return "验证码格式无效";}return true;
});
// 密码
defineRule("password", (value) => {if (!value || !value.length) {return true;}if (!/^[0-9A-Za-z]{8,20}$/.test(value)) {return "密码长度须为 8 ~ 20 个字符";}return true;
});defineRule("confirmed", (value, [target], ctx) => {if (value === ctx.form[target]) {return true;}return "密码不一致";
});// 协议
defineRule("agree", (value) => {if (value) {return true;}return "请勾选同意协议";
});

而在 Register 组件中,你需要将 input 替换成 Field,并且在表单外包裹一个 Form:

<template><!-- 此处省略部分代码 --><Form :validation-schema="schema" v-slot="{ errors }"><div class="register"><h3>注册新用户<span class="go">我有账号,去 <a href="login.html" target="_blank">登陆</a></span></h3><div class="content"><label>手机号:</label><Fieldname="phone"placeholder="请输入你的手机号"v-model="data.phone"/><span class="error-msg">{{ errors.phone }}</span></div><div class="content"><label>验证码:</label><Field name="code" placeholder="请输入验证码" v-model="data.code" /><buttonstyle="width: 100px; height: 38px; margin-left: 10px"@click="getCode">获取验证码</button><span class="error-msg">{{ errors.code }}</span></div><div class="content"><label>登录密码:</label><Fieldname="password"placeholder="请输入你的登录密码"v-model="data.password"/><span class="error-msg">{{ errors.password }}</span></div><div class="content"><label>确认密码:</label><Fieldtype="password"name="rePassword"v-model="data.rePassword"placeholder="请输入确认密码"/><span class="error-msg">{{ errors.rePassword }}</span></div><div class="controls"><Field name="agree" type="checkbox" :value="data.agree" /><span>同意协议并注册《尚品汇用户协议》</span><span class="error-msg">{{ errors.agree }}</span></div><div class="btn"><button @click="userRegister">完成注册</button></div></div></Form><!-- 此处省略部分代码 -->
</template>
import { Form, Field } from "vee-validate";export default {name: "Register",components: {Form,Field,},setup() {const schema = {phone: "required|phone",code: "required|code",password: "required|password",rePassword: "required|confirmed:password",agree: "agree",};return {// ...schema,};},
}

31:34 送出验证

首先给 Form 加上一个 ref registerForm

<Formref="registerForm"@submit="userRegister":validation-schema="schema"v-slot="{ errors }"
><!-- ... -->
</Form>

使用 registerForm.value.validate() 获取表单验证结果,若表单内容验证通过,则 registerForm.value.validate() 回传的 valid 属性为 true,否则为 false,所以我们可以判断 valid 是否为 true 再继续发送注册请求。

<script>
import { reactive, ref } from "vue";
export default {// ...setup() {// ...const registerForm = ref(null);// ...const userRegister = async () => {const form = await registerForm.value.validate();if (form.valid) {try {const { phone, code, password, rePassword } = data;if (phone && code && password === rePassword) {await store.dispatch("userRegister", {phone,code,password,rePassword,});router.push("/login");}} catch (error) {alert(error.message);}}};const schema = {phone: "required|phone",code: "required|code",password: "required|password",rePassword: "required|confirmed:password",agree: "agree",};return {data,getCode,userRegister,schema,registerForm};},
};
</script>

尚硅谷 VUE 尚品汇项目实战问题解决方式整理(Vue3 版)相关推荐

  1. 尚硅谷 VUE 尚品汇项目实战问题解决方式整理

    最近在自学大型vue项目,选中了尚硅谷的尚品汇,之前也学习过黑马程序员的一个电商后台管理项目,因为项目打包优化时候出了Bug,就闲置了,但是代码是一行一行敲完啦,也放到了码云上!学完这个尚品汇准备下一 ...

  2. 尚硅谷VUE项目-前端项目问题总结07--产品详情页【vuex-排他操作foreach-放大镜-轮播图-兄弟组件通信$bus-购物车-路由跳转传参-路由传参+会话存储】-游客身份-节流

    尚硅谷VUE项目-前端项目问题总结07---产品详情页 1.静态组件(详情页还未注册为路由组件) 2.发请求 3.vuex-获取产品详情信息 3.1放大镜 3.2 属性值[排他操作] 3.3轮播图[j ...

  3. 尚品汇项目笔记(持续更新中)

    项目网络教学视频链接:尚硅谷VUE项目实战,前端项目-尚品汇(大型\重磅)_哔哩哔哩_bilibili 目录 一. 使用vue-cli脚手架去初始化项目 二.项目的其他配置 三.项目路由分析 四.创建 ...

  4. 尚硅谷VUE课程的页签图标问题

    项目场景:尚硅谷VUE课程的页签图标问题 问题描述: 在没有添加页签图标favicon.icon时不报错. 解决方案: 给VScode添加Live Server插件,并右键选择open with Li ...

  5. 尚硅谷Vue技术全家桶(1)

    尚硅谷Vue技术全家桶 课程来源于b站尚硅谷教程:一套搞定Vue技术全家桶,轻松拿捏vue3.0(vue.js全网最新) 课程简介 在这个vue2到vue3的过渡时期,需要兼顾2.x和3版本.尚硅谷的 ...

  6. 尚硅谷 java基础第二个项目之客户关系管理系统

    尚硅谷 java基础第二个项目之客户关系管理系统. 做了一些完善,增加性别,电话,邮箱有效性验证.其中电话和邮箱验证直接"饮用"了网友的果汁. 在此感谢各位原著大佬们的分享. 具体 ...

  7. 尚硅谷-云尚办公-项目复盘

    尚硅谷-云尚办公-项目复盘 资料地址 本文介绍 问题汇总 问题1.knife4j无法下载 视频4 问题2.dev等含义 视频5 问题3.wrapper继承/实现图 视频8 问题4.修改统一返回结果 视 ...

  8. Vue数据代理+事件处理+事件修饰符的作用+计算属性的使用,尚硅谷Vue系列教程学习笔记(2)

    尚硅谷Vue系列教程学习笔记(2) 参考课程:<尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通> 参考链接:https://www.bilibili.com/video/ ...

  9. Vue 3.0 企业级项目实战

    目录 小册介绍 Vue 3.0 + Element Plus + Spring Boot 前后端分离实践,你不能错过! 小册须知 你会学到什么? 适宜人群 作者介绍 小册介绍 基于真实项目的实战开发, ...

最新文章

  1. oracle 日期计算
  2. 阿里云神龙团队拿下TPCx-BB排名第一的背后技术
  3. java中如何使用add方法_使用Java中的Calendar.add()方法将秒添加到当前日期
  4. Python自动化运维开发----基础(十二)函数
  5. linux rdma测试,硬件RDMA的驱动配置和测试
  6. MATLAB实现数图缩放:双线性内插法
  7. 3.7V锂电池升压到5V1A,FS2114升压转换芯片设计布局
  8. java审批流创建及代码流程
  9. java map.put map_java中map的put方法
  10. GDB 用法之查看内存
  11. 零基础也能快速上手的动画制作工具 | 万彩动画大师
  12. 2020腾讯、百度、华为Android面试题校招汇总(已拿offer
  13. 五猴分桃问题的数学解
  14. @kubernetes(k8s)数据持久化Volume存储卷(emptyDir、hostPath、NFS、StorageClass)
  15. 01-JVM(上)-JVM与JAVA体系结构
  16. Delphi XE10.4 TrayIcon托盘
  17. 多重比较你用对了吗?
  18. 【程序源代码】springboot开源OA系统
  19. 信度分析,如何设计考试卷
  20. LibreOffice office转pdf

热门文章

  1. MySQL 从 5.7 到 8.0
  2. Layui官网地址官网仓库
  3. 猿辅导2017 笔试题
  4. 区块链能否助力版权“突围”?
  5. 2021年深圳市专精特新中小企业遴选申报指南
  6. Unity中实现动态天空盒
  7. 英伟达显卡玩CF怎么提高帧数
  8. python降低图片分辨率怎么调_使用PIL调整图片分辨率
  9. 啤酒车间平面布置图、水厂平面布置图、厂房设备布置图、污水厂管道布置图、乳品厂平面布置图、水果罐头工厂厂区总平面布置图、煤矿开采工作面综合布置图、日产500吨石灰窑CAD工艺布置图……各种布置图汇总
  10. 某工厂配电线路及变电所设计