文章目录
-
- 最好使用视频上的账号密码,13700000000 密:111111
- 最新服务端接口地址:http://gmall-h5-api.atguigu.cn
- 脚手架使用
- 一、项目路由分析
- 二、Header、Footer非路由组件完成
- 三、TypeNav三级联动组件完成
- 四、Home首页拆分静态组件完成
- 五、动态渲染三级联动部分
- 六、mockjs模拟数据 | Home首页完成
- 七、Search模块
- 八、分页器
- 九、Detail商品详情组件
- 十、添加到购物车成功 路由组件
- 十一、购物车组件
- 十二、登录注册组件
- 十三、Trade交易组件
- 十四、支付页面
- 十五、微信支付业务
- 十六、Center个人中心组件
- (17)用户登录后的导航守卫(路由独享守卫与组件内守卫)
- (18)未登录的导航守卫
- (19)图片懒加载
- (20)vee-validate插件 表单验证
- (21)路由懒加载
- (22)处理map文件
- (23)服务器
最好使用视频上的账号密码,13700000000 密:111111
最新服务端接口地址:http://gmall-h5-api.atguigu.cn
因为该账号数据库存有地址信息 。
但是注意,因为很多同学同时操作,所以可能出现:购物车商品被其他同学增加或减少,订单不能重复提交等问题。此时稍等一会,或多试几次即可。
学习尚硅谷的商城前台尚品汇项目的笔记:
脚手架使用
1、创建项目
vue create 项目名称
2、脚手架默认目录:
- node_modules:放置项目依赖的地方。
- public:一般放置一些共用的静态资源,打包上线的时候,public文件夹里面资源原封不动打包到dist文件夹里面。
- src:程序员源代码文件夹:
- assets:经常放置一些静态资源(公用的图片(即很多组件都用此图)),assets文件夹里面资源webpack会进行打包为一个模块(js文件夹里面)
- components:一般放置非路由组件(如共用的组件)
- App.vue:唯一的根组件
- main.js:入口文件【程序最先执行的文件】
- babel.config.js:babel配置文件
- package.json:项目描述、项目依赖、项目运行
- README.md:项目说明文件
注意:
(放public中的图片,组件引用:images/1.png
组件的less中引用:url(/images/9.png);
使用"/"作为根目录。 因为最后webpack打包,public中的图片原封不动打包到了images中,所以用绝对路径)
放assets中的图片,组件的html中引用:@/assets/2.png
组件的less中引用:~@/assets/7.png
因为assets文件夹里面资源webpack会进行打包为一个模块(js文件夹里面),所以用相对路径)
3、脚手架下载的项目稍微配置一下
-
1)浏览器自动打开
在 package.json 文件中
? "scripts": { ? "serve": "vue-cli-service serve --open", ? "build": "vue-cli-service build", ? "lint": "vue-cli-service lint" ? },
-
2)关闭 eslint 校验工具,以防写代码时没错也报错。
在根目录创建 vue.config.js 文件:需要对外暴露
module.exports = { lintOnSave: false, }
-
3)src文件夹的别名的设置
因为项目大的时候src(源代码文件夹):里面目录会很多,找文件不方便,设置src文件夹的别名的好处,找文件会方便一些。
就不用用那么多…/…/了。直接@代替。
(js用
@
代替src路径,css用~@
代替src路径)创建 jsconfig.json 文件
{ "compilerOptions": { "baseUrl": "./", "paths": { "@/*": [ "src/*" ] } }, "exclude": [ "node_modules", "dist" ] }
一、项目路由分析
路由组件:
Home首页、Search搜索、login登录、Refister注册
非路由组件:
Header头部、Footer底部 (有Home首页、Search搜索组件。无login登录、Refister注册组件)
二、Header、Footer非路由组件完成
在开发项目的时候:
1: 书写静态页面(HTML + CSS)
2: 拆分组件
3: 获取服务器的数据动态展示
4: 完成相应的动态业务逻辑
需要用到less ,安装低版本,高版本容易出错。
yarn add less less-loader@5
(组件中,需要加 <style scoped lang="less">
1、使用非路由组件步骤:
- 创建
- 引入
- 注册
- 用标签使用
1、创建在 components下
2、引入到(需要的组件中)App.vue中,并注册、使用。
<template>
<div id="app">
<Header></Header>
<Footer></Footer>
</div>
</template>
<script>
import Header from './components/Header'
import Footer from './components/Footer'
export default {
name: 'App',
components: {
Header,
Footer
}
}
</script>
2、使用路由组件步骤:
-
yarn add vue-router
3版本 -
在src 创建 pages | views文件夹:放置路由组件。
-
配置路由:
在src 创建 router文件夹,建 index.js
index.js:
// 配置路由
import Vue from ‘vue’;
import VueRouter from ‘vue-router’;
Vue.use(VueRouter);
// 引入路由组件
import Home from ‘@/pages/Home’
import Search from ‘@/pages/Search’
import Login from ‘@/pages/Login’
import Register from ‘@/pages/Register’export default new VueRouter({
routes: [
{
path: “/home”,
component: Home
},
{
path: “/Search”,
component: Search
},
{
path: “/Login”,
component: Login
},
{
path: “/Register”,
component: Register
},
]
})
还要去main.js引入和注册路由
main.js
import Vue from 'vue'
import App from './App.vue'
// 引入路由
import router from '@/router';
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router
}).$mount('#app')
路由组件出口,路由组件展示
App.vue
<template>
<div id="app">
<Header></Header>
<!-- 路由组件出口的地方、路由组件展示 -->
<router-view></router-view>
<Footer></Footer>
</div>
</template>
1) 路由的跳转有两种形式:
注册完路由,不管路由路由组件、还是非路由组件身上都有 r o u t e 、 route、 route、router属性。
$route:一般获取当前组件的路由信息 [路径、query、 params等等]
$router:一般进行编程式导航进行路由跳转 [push | replace]
1、声明式导航:router-link,可以进行路由的跳转
<router-link to="/login">登录</router-link>
2、编程式导航:利用组件实例的 $router.push | replace,可以进行路由跳转
// 搜索按钮的回调函数,点击按钮跳转至search路由
goSearch() {
this.$router.push("/search");
},
编程式导航:声明式导航能做的,编程式导航都能
但是编程式导航除了可以进行路由跳转,还可以做一些其他的业务逻辑。
2) 路由元信息:
将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过$route 的 meta属性 来实现,并且它可以在路由地址和导航守卫上都被访问到。
Footer组件的显示与隐藏
显示或隐藏组件: v-if | v-show
Footer组件 在Home、Search显示 在登录注册隐藏。
router中的index.js:
{
path: "/home",
component: Home,
meta: {show:true}
},
{
path: "/search",
component: Search,
meta: {show:true}
},
{
path: "/login",
component: Login,
meta: {show:false}
},
{
path: "/register",
component: Register,
meta: {show:false}
},
]
<!-- meta.show在路由配置中定义 -->
<Footer v-show="$route.meta.show"></Footer>
3) 路由传递参数
路由传参,参数有几种写法
params参数: 属于路径当中的一部分,需要注意,在配置路由的时候,需要占位
query参数: 不属于路径当中的一部分,类似于ajax中的queryString /homek=v&kv=,不需要占位
Header/index.vue
<input
type="text"
id="autocomplete"
class="input-error input-xxlarge"
v-model="keyword"
/> <!-- keyword数据绑定 -->
data() {
return {
keyword: "",
};
},
methods: {
// 搜索按钮的回调函数,点击按钮跳转至search路由
goSearch() {
// 路由传递参数:
// 第一种:字符串形式
// this.$router.push(
// "/search/" + this.keyword + "?k=" + this.keyword.toUpperCase() // toUpperCase()转为大写字母
// 第二种:模板字符串
// this.$router.push(
// `/search/${this.keyword}?k=${this.keyword.toUpperCase()}`
// );
// 第三种:对象(常用)
this.$router.push({
name: "search",
params: { keyword: this.keyword },
query: { k: this.keyword.toUpperCase() },
});
},
search/index.vue
<div>
<h1>params参数---{
{ $route.params.keyword }}</h1>
<h1>query参数---{
{ $route.query.k }}</h1>
</div>
router/index.js
{
// params参数在配置路由的时候,需要占位
path: "/search/:keyword",
component: Search,
meta: {show:true},
// 对象形式路由传递参数
name: "search",
},
路由传递参数面试题:
-
路由传递参数(对象写法) path是否可以结合 params参数一起使用? 即:
this.$router.push({ path: '/search', params: { keyword: this.keyword }, query: { k: this.keyword.toUpperCase() }, });
答:报错,不能。
-
如何指定 params参数 可传可不传? 即:
this.$router.push({ name: "search", query: { k: this.keyword.toUpperCase() }, });
答:配置路由时,path上加个 号,代表可传参数也可不传;若不加 ,则URL会出现问题。
{ path: "/search/:keyword?", component: Search, meta: {show:true}, // 对象形式路由传递参数 name: "search", },
-
params参数 可以传递也可以不传递,但是如果传递是空串,如何解决? 即:
this.$router.push({name:"search",params:{keyWord:''},query:{k:this.keyWord}})
答:可以使用 undefined 来解决params参数可以传递也可不传递(空的字符串)
this.$router.push({
name: "search",
params: { keyword: '' || undefined },
query: { k: this.keyword.toUpperCase() },
});
},
- 路由组件能不能传递 props数据?
答:可以。三种写法:
{
path: "/search/:keyword",
component: Search,
meta: { show: true },
// 对象形式路由传递参数
name: "search",
// 路由组件能不能传递 props数据?
// 1、布尔值写法,但是这种方法只能传递params参数
// props: true,
// 2、对象写法:额外给路由组件传递一些props
// props: { a: 1, b: 2 },
// 函数写法(常用):可以params参数、query参数,通过props传递给路由组件
props: ($route) => {
return {keyword: $route.params.keyword, k: $route.query.k};
}
},
<div>
<h1>params参数---{
{ $route.params.keyword }}</h1>
<h1>query参数---{
{ $route.query.k }}</h1>
<h1>props数据---{
{ keyword }}</h1>
<!-- <h1>props数据---{
{ a }}--{
{ b }}</h1> -->
</div>
</template>
<script>
export default {
name: '',
props: ['keyword', 'a', 'b'],
}
4) 重写push与repalce方法
编程式路由跳转 到当前路由(参数不变),多次执行跳转到当前路由,会抛出NavigationDuplicated的警告错误
– 路由跳转有两种形式:声明式导航、编程式导航
– 声明式导航 没有这类问题的,因为vue-router底层已经处理好了。“vue-router”: “^3.5.3” 最新的vue-router引入promise。
为什么 编程式导航 这时会有问题?
this.$router.push({
name: "search",
params: { keyword: this.keyword },
query: { k: this.keyword.toUpperCase() },
},
()=>{},()=>{});
这种写法:治标不治本,将来在别的组件当中push | replace,编程式导航还是有类似错误。
this:当前组件实例( search)
this.$router
:VueRouter的实例。当在入口文件注册路由的时候,给组件实例添加了$router | $route属性。
即: let $router = new VueRouter();
VueRouter是一个构造函数。
push:是VueRouter的一个方法
即: VueRouter.prototype.push = function() {}
$router 借用原型对象的方法:
$router.push(xxx)
router/index.js
// 保存原来的push函数
let originPush = VueRouter.prototype.push;
// 保存原来的replace函数
let originReplace = VueRouter.prototype.replace;
// 重写push函数,为解决相同路径跳转报错
// 第一个参数: 告诉原来push方法,你往哪里跳转(传递哪些参数)
// 第二个参数: 成功回调 第三个参数: 失败的回调
VueRouter.prototype.push = function(location, resolve, reject) {
if(resolve && reject) {
originPush.call(this, location, resolve, reject);
} else {
originPush.call(this, location, () => { }, () => { });
}
}
// 重写replace函数,为解决相同路径跳转报错
VueRouter.prototype.replace = function(location, resolve, reject) {
if(resolve && reject) {
originReplace.call(this, location, resolve, reject);
} else {
originReplace.call(this, location, () => { }, () => { });
}
}
三、TypeNav三级联动组件完成
因三级联动组件存在于Home、Search、Detail组件中,所以注册为全局组件。
main.js
// 三级联动组件--注册为全局组件(在各组件中使用就不需要引入了)
import TypeNav from '@/components/TypeNav'
// 第一个参数:全局组件的名字 第二个参数:哪一个组件
Vue.component(TypeNav.name, TypeNav)
components/TypeNav/index.vue
<template>
<div>
主页
<TypeNav></TypeNav>
</div>
</template>
<script>
</script>
<style lang="less" scoped>
</style>
四、Home首页拆分静态组件完成
Home/index.vue
<template>
<div>
<TypeNav/>
<ListContainer/>
<Recommend/>
<Rank/>
<Like/>
<Floor/>
<Floor/>
<Brand/>
</div>
</template>
<script>
import ListContainer from '../Home/ListContainer';
import Recommend from '../Home/Recommend';
import Rank from '../Home/Rank';
import Like from '../Home/Like';
import Floor from '../Home/Floor';
import Brand from '../Home/Brand';
export default {
name: '',
components: {
ListContainer,
Recommend,
Rank,
Like,
Floor,
Brand,
}
}
</script>
<style lang="less" scoped>
</style>
五、动态渲染三级联动部分
1、postman测试接口
填入服务器地址和请求地址,即
http://gmall-h5-api.atguigu.cn/api/product/getBaseCategoryList
200ok 说明成功。接口没问题。
2、axios 二次封装
为什么二次封装?
为了请求拦截器、响应拦截器。
请求拦截器:在发请求之前可以处理一些业务;
响应拦截器:当服务器数据返回以后,可以处理一些事情。
安装axios
yarn add axios
在项目中经常有 api文件夹,一般都是放 axios的
api/request.js
// 对于axios进行二次封装
import axios from "axios"
// 利用axios对象的方法create,去创建一个axios实例
// 这里的request 就是 axios,在这里配置一下
const request = axios.create({
// 配置对象
// 基础路径,发请求的时候,路径当中会默认有/api,不用自己写了
baseURL: "/api",
// 请求超时5s
timeout: 5000,
})
// 请求拦截器:在发请求之前,请求拦截器可以检测到,在请求发出之前做一些事情;
requests.interceptors.request.use((config) => {
// config:配置对象,其有一个重要属性:header请求头
})
// 响应拦截器:当服务器数据返回以后,可以处理一些事情。
requests.interceptors.response.use(((res) => {
// 服务器响应成功的回调函数
return res.data;
}, (error) => {
// 服务器响应失败的回调函数
return Promise.reject(new Error('faile'));
}))
// 对外暴露
export default requests;
3、API接口统一管理
若项目很小,可以在组件的生命周期函数中发请求
但项目大,组件多,若有更改,将麻烦。所以API接口统一管理。
什么是跨域?
同源就是指,域名、协议、端口均为相同。
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。
vue.config.js
// webpack中的代理跨域
devServer: {
proxy: {
'/api': {
// 服务器地址
target: 'http://gmall-h5-api.atguigu.cn',
},
},
},
api/index.js
import requests from "./request";
// 三级联动接口
export const reqCategoryList = () =>
// 发请求:axios发请求返回结果是Promise对象
requests({ url: "/product/getBaseCategoryList", method: "get" })
4、nprogress进度条的使用
安装: yarn add nprogress
在响应拦截器使用
api/request.js
// 引入进度条
import nprogress from 'nprogress'
// 引入进度条样式
import "nprogress/nprogress.css"
// 请求拦截器:
requests.interceptors.request.use((config) => {
// config:配置对象,其有一个重要属性:header请求头
// 进度条开始动
nprogress.start();
return config;
})
// 响应拦截器:
requests.interceptors.response.use((res) => {
// 服务器响应成功的回调函数
// 进度条结束
nprogress.done();
return res.data;
}, (err) => {
// 服务器响应失败的回调函数
return Promise.reject(new Error('faile'));
})
5、vuex 模块式开发
vuex 是官方提供的插件, 状态管理库,集中式管理项目中组件共用的数据 。
切记,并不是全部项目都需要 Vuex,如果项目很小,完全不需要Vuex,如果项目很大,组件很多、数据很多,数据维护很费劲,用Vuex
安装vuex yarn add vuex
main.js
// 引入仓库
import store from './store'
store/home/index.js
// home 模块的小仓库
// state:仓库存储数据的地方
const state = {}
// mutations:修改state的唯一手段
const mutations = {}
// actions:处理action,书写自己的业务逻辑、也可以处理异步
const actions = {}
// getters:计算属性,用于简化仓库数据,让组件获取仓库的数据更方便
const getters = {}
// 对外暴露
export default {
state,
mutations,
actions,
getters,
}
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 引入小仓库
import home from './home'
import search from './search'
export default new Vuex.Store({
modules: {
home,
search,
}
})
6、动态展示三级联动数据
api/index.js
import requests from "./request";
// 三级联动接口
export const reqCategoryList = () =>
// 发请求:axios发请求返回结果是Promise对象
requests({ url: "/product/getBaseCategoryList", method: "get" })
store/home/index.js
// home 模块的小仓库
import { reqCategoryList } from "@/api"
// state:仓库存储数据的地方
const state = {
categoryList: [],
}
// mutations:修改state(数据)的唯一手段
const mutations = {
CATEGORYLIST(state, categoryList) {
state.categoryList = categoryList
}
}
// actions:处理action,书写自己的业务逻辑、也可以处理异步
const actions = {
// {commit}是因为其中要用到commit,原本是categoryList(context,value){}, 用commit时需要context.commit,{commit}可以省略context. 若不需要value则可不写。
async categoryList({commit}) {
// 向服务器发请求
let result = await reqCategoryList();
// console.log(result)
// result.code == 200代表请求成功
if (result.code == 200) {
// 修改数据
commit("CATEGORYLIST", result.data)
}
},
}
// 对外暴露
export default {
state,
mutations,
actions,
getters,
}
components/TypeNav/index.vue
<script>
import { mapState } from "vuex";
export default {
name: "TypeNav",
// 组件挂载完毕,可以向服务器发请求
mounted() {
// 通知Vuex发请求,获取数据,存储于仓库当中
this.$store.dispatch("categoryList");
},
computed: {
...mapState({
// 从仓库拿数据
categoryList: (state) => state.home.categoryList,
}),
},
};
</script>
<div class="all-sort-list2">
<div
class="item"
v-for="(c1, index) in categoryList"
:key="c1.categoryId"
>
<h3>
<a href="#">{
{ c1.categoryName }}</a>
</h3>
<div class="item-list clearfix">
<div
class="subitem"
v-for="(c2, index) in c1.categoryChild"
:key="c2.categroyId"
>
<dl class="fore">
<dt>
<a href="#">{
{ c2.categoryName }}</a>
</dt>
<dd>
<em
v-for="(c3, index) in c2.categoryChild"
:key="c3.categroyId"
>
<a href="#">{
{ c3.categoryName }}</a>
</em>
</dd>
</dl>
</div>
</div>
</div>
</div>
7、动态一级菜单背景颜色
鼠标移出全部商品分类时,一级菜单背景消失
<div @mouseleave="leaveIndex">
<h2 class="all">全部商品分类</h2>
实现鼠标在当前标题,当前标题背景颜色设为蓝色,排他高亮显示
<div
class="item"
v-for="(c1, index) in categoryList"
:key="c1.categoryId"
>
<!-- 当鼠标进入当前的一级标题,则改变currentIndex为当前的index -->
<h3
@mouseenter="changeIndex(index)"
:class="{ cur: currentIndex == index }"
> <!-- 若currentIndex==index ,则加类名cur,实现鼠标在当前标题,当前标题背景颜色设为蓝色,排他高亮显示 -->
<a href="#">{
{ c1.categoryName }}</a>
</h3>
data() {
return {
// 存储用户移上哪一个一级分类
currentIndex: -1,
};
},
methods: {
// 鼠标进入则修改响应式数据currentIndex属性
changeIn