尚品汇笔记
前端核心:
1、构建页面Html+CSS+…
2、接口传来数据
3、vuex接收并处理数据
4、组件接收数据渲染到页面
5、交互
1、Vue的目录分析
node_modules:
是安装node后用来存放用包管理工具下载安装的包的文件夹。
public:
一般用于存放一些静态资源文件,例如图片,视频,音频等资源文件。需要特别注意的是webpack在进行打包的时候,会将public中的所有静态资源原封不动的进行打包。
src:
asset:也是用于存放一些静态资源文件
components:公共组件,非公共放在page
App.vue:是整个项目的根组件,所有组件的后缀名均为·vue
main.js:是文件的入口文件,程序执行先从该文件开始
babel.config.js:
: 配置文件(babel相关)
package.json
: 项目的详细信息记录
package-lock.json
: 缓存性文件(各种包的来源)
2、项目配置
2.1:项目的基本运行指令,自动打开浏览器,打包文件,自动修复
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
2.2:关闭eslint工具(不关闭严重影响,不按照eslint语法就报错)
module.exports = {
//关闭eslint
lintOnSave: false
}
2.3:2.3 src文件夹配置别名,创建jsconfig.json,用@/代替src/
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
//文件运行后产生的
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}
3、注册路由
3.1:
import VueRouter from "vue-router";
import Vue from "vue";
// 引用路由文件
import routes from './routes'
// 使用路由
Vue.use(VueRouter)
// 对外包括,方便在mian.js中应用
export default new VueRouter({
routes
})
3.2 routes文件夹统一管理路由
import Home from '../pages/Home/'
const routes=
[
{
//query参数的跳转是path的路径,params的跳转参数是name
path:'/Home',
name:'/Home',
component:Home
}
]
export default routes
3.3 main.js中注册路由,一定不能忘记在Vue中注册路由
import Vue from 'vue'
import router from './router'
new Vue({
router,
render: h => h(App),
}).$mount('#app')
4、注册全局组件
在main.js中注册过的组件,在全局都是有效的,不需要在任何vue文件中引用,直接用就好
// 引入公共组件中的TypeNav
import TypeNav from './components/TypeNav.vue'
// 注册全局组件 第一个参数就是组件的名字
Vue.component("TypeNav",TypeNav)
直接全局组件TypeNav,注册过
<template>
<div id="app">
<Header/>
<TypeNav/>
<router-view/>
<Footer/>
</div>
</template>
5.重定向:
在初始化项目时,路由自动跳转到设置的路由
// 重定向
{
path: '/',
redirect: '/Home'
},
6. 二次封装axios
主要是要用到请求拦截器和响应拦截器;
请求拦截器:可以在发请求之前可以处理一些业务,配置下baseURL路径
响应拦截器:当服务器数据返回以后,可以处理一些事情
// 封装axios
import axios from "axios";
//
const requests =axios.create({
// 配置基础路径,发请求的时候,会直接带上基本路径,不需要重复书写
baseURL:'/api',
// 相应事件5s,超过则失败
timeout:5000
})
requests.interceptors.request.use((config)=>{
// config是一个非常重要的属性,包含着发送的请求头,许多信息可以写在里里面
return config
})
export default requests
在index.js文件中书写相对应的地址
import requests from "./requests";
// /api/product/getBaseCategoryList 三级菜单
export const reqgetCategoryList=()=>requests({
url:`/product/getBaseCategoryList`,
methods:'get'
})
7.代理跨域问题
3.1:先放在这里,之后来填
跨域
的坑
vue.config.json
module.exports = ({
// 代理跨域
devServer:{
proxy:{
'/api':{
target:'http://gmall-h5-api.atguigu.cn',
}
}
}
})
8.Mock
8.1:封装一个mockrequest的二次封装,
和之前封装的相同,不过baseURL不同,在这里封装了,在index.js中就不需要再次书写
// 封装axios
import axios from "axios";
//
const MockRequests =axios.create({
// 配置基础路径,发请求的时候,会直接带上基本路径,不需要重复书写
baseURL:'/mock',
// 相应事件5s,超过则失败
timeout:5000
})
MockRequests.interceptors.request.use((config)=>{
// config是一个非常重要的属性,包含着发送的请求头,许多信息可以写在里里面
return config
})
export default MockRequests
8.2 创建mock响应内容
在src下创建mock文件夹,创建mockServer.js,同时在文件下下创建
其他的json数据文件
import Mock from "mockjs";
// 引入json文件
import banner from './banner.json'
import floor from './floor.json'
//mock数据:第一个参数请求地址、第二个参:请求数据
Mock.mock('/mock/banner',{code:200,data:banner})
Mock.mock('/mock/floor',{code:200,data:floor})
//记得要在main.js中引入一下
//import ''@/mock/mockServer
8.3 在api中发送请求,注意请求变了
import MockRequests from "./MockRequests";
// banner轮播图mock
export const reqBannerList =()=>MockRequests({
url:`/banner`,
methods:'get'
})
// floor页面 mock
export const reqFloorList =()=>MockRequests({
url:`/floor`,
methods:'get'
})
9.接口数据被分割成两个模块
9.1 可以使用v-show配合v-for一起使用,最好不用v-if,会显示报错
vue2中,v-for的优先级比v-if高,所以警告,并不影响运行
vue3中,v-if的优先级比v-for高
但是如果一起用,还是会浪费性能,所以项目还是老老实实的computed计算属性
v-for="(img,index) in item.recommendList" :key="index" v-show="index<2"
10、轮播图swiper的封装
挖坑
11、Vuex
11.1 安装vuex npm i vuex@3
注意:vueRouter和vuex
"vue": "^2.6.14",
"vue-router": "^3.6.5",
"vuex": "^3.6.2"
12.编程式导航+事件委托解决路由跳转
12.1 事件委托:
概念:
事件委托也叫事件代理,“事件代理”即是把原本需要绑定在子元素的响应事件(click keydown…)委托给
父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。
举个例子
- 英语老师要受英语作业,老师让学习委员统一收取作业,再交给她
学习委员就充当了委托中的父元素
好处:不需要对任何的一个子元素进行单独处理,而是汇总起来批量处理,可以大量节省内存占用,减少事件注册,比如在ul上代理所有li的click事件。
<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li
12.2
实现点击任何标题都可以跳转到Search页面
实现思路:
<div class="all-sort-list2" v-for="(nav) in categoryList" :key="nav.categoryId">
<div class="item bo">
<h3>
<!-- 一级菜单 -->
<a @click="goSearch" :data-categoryName='nav.categoryName' :data- categoryId1='nav.categoryId'>{{nav.categoryName}}</a>
</h3>
<div class="item-list clearfix" >
<div class="subitem" v-for="nav1 in nav.categoryChild" :key='nav1.categoryId'>
<dl class="fore">
<dt>
<!-- 二级菜单 -->
<a @click="goSearch" :data-categoryName='nav1.categoryName' :data-categoryId2='nav1.categoryId'>{{nav1.categoryName}}</a>
</dt>
<dd >
<em v-for=" nav2 in nav1.categoryChild " :key="nav2.categoryId">
<!-- 三级菜单 -->
<a @click="goSearch" :data-categoryName='nav2.categoryName' :data-categoryId3='nav2.categoryId'>{{nav2.categoryName}}</a>
</em>
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
1、需要在点击获取点击时的数据,自定义属性就登上帷幕,在event.target.dataset属性中可以获取
2、利用es6新属性,解构赋值,取出属性,判断
3、跳转路由,这个地方写的特别好,分别定义location和query,随后赋值
goSearch(event){
// 获取当前点击的数据
let element =event.target
console.log(element)
let {categoryid1,categoryid2,categoryid3,categoryname}=element.dataset
console.log(categoryid1,categoryid2,categoryid3,categoryname )
// 如果有categoryname的话那就是a标签里面的
if(categoryname){
// 创建跳转路由
let location={name:'Search'}
// 跳转参数
let query ={categoryName:categoryname}
if(categoryid1){
query.categoryid1=categoryid1
}else if(categoryid2){
query.categoryid2=categoryid2
}else if(categoryid3){
query.categoryid3=categoryid3
}
location.query=query
this.$router.push(location)
}
},
13.transition过度效果
14.父子组件通信面包屑
Vue组件间通信的几种常见方式:
- props / $emit
-
em
i
t
/
emit/
e
mi
t
/
on 全局时间总线$bus - ref / $refs 父子
- 依赖注入(provide / inject) 祖孙子
- Vuex 全局(用的多)
这里我们用的是props/$emit子父通信
需求:点击子组件,在index页面上呈现面包屑的效果
分析:
1、绑定点击事件
2、绑定emit的事件 this.$emit('事件名',传参)
3、父组件中写自定义事件
4、渲染页面
<template>
//简化代码,方便查看
<li v-for="mark in trademarkList " :key="mark.id" @click="BrandHandler(mark)">{{mark.tmName}}</li>
<a @click="attrHandler(attr2,attr)">{{attr2}}</a>
</template>
<script>
export default {
name: 'SearchSelector'
methods:{
// 点击品牌,出现面包屑
BrandHandler(Brand){
this.$emit('BrandHandler',Brand)
},
// 属性的面包屑
attrHandler(attr2,attr){
this.$emit('attrHandler',attr2,attr.attrId)
}
}
}
</script>
//传参props or 子传父事件
< template>
<SearchSelector :attrsList='attrsList' :trademarkList='trademarkList' @BrandHandler='BrandHandler' @attrHandler='attrHandler'/>
</template>
<script>
methods:{
// 品牌的面包屑 添加props
BrandHandler(Brand){
// es6 解构
let props=`${Brand.tmId}:${Brand.tmName}`
if( this.searchParams.props.indexOf(props)==-1){
this.searchParams.props.push(props)
}
},
// 属性的面包屑同样添加props
attrHandler(attr,attrId){
let props =`${attrId}:${attr}`
if( this.searchParams.props.indexOf(props)==-1){
this.searchParams.props.push(props)
}
},
</script>
15.分页器
这里我懒了,不太像手写分页器了,用了element-ui就当是锻炼下插件能力了
14.1 安装element-ui (npm i element-ui)
14.2main.js中注册(官网有安装教程)
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI)
new Vue({
render: h => h(App),
}).$mount('#app')
14.3 在element-ui中取分页器代码,自己改下
<div class="block page">
<el-pagination
:current-page.sync="searchParams.pageNo"
:page-size="searchParams.pageSize"
layout="prev, pager, next, jumper"
:total="searchParams.total">
</el-pagination>
</div>
第三个页面 /detail
五个需求点:
- 左上方的面包屑
- 左边图片的轮播图和图片
- 中间的属性选择
- 数量的增减和改变
- 加入购物车的跳转
左上方的面包屑 :
1、 分析:参数是由上一层Search传输而来的路由参数,在向服务器发送数据的时候带上这个参数
routes.js
Detail.js
控制台的数据传回
渲染上页面:
面包屑效果图:
2、左边图片的轮播图和图片:
分析:父子组件通信、ImageList子组件,利用swiper轮播官网提供组件
效果图:
实现大图和轮播图的兄弟通信功能:
imageList:
Zoom兄弟组件:
这里特别说明一下使用全局$bus的时候记得main.js中注册,否则就是报错emit/on
反思:这个案例还是非常有代表性的组件通信,动态改变页面功能,剖析本质,组件通信也就这回事
-
中间的属性选择 :
分析:选择属性只能选择一种,所以可以考虑到排他算法,这是最优解
item是单独的属性值 arr是item的父级,把父级的所有置空,然后一个子级脱颖而出,写好active就好 -
数量的增减和改变:
mouseout:鼠标移除的时候触发校正
-
加入购物车的跳转
跳转路由的方法有编程式路由和。。。。,这里就是编程式路由,比较直观的带参
第四个页面 AddCartSuccess
三个需求:
- 图片渲染上页面
- 文字和带参skuNum渲染页面
- 两个跳转页面(原路由,新路由)
思路:这是一个小页面。所以在数据上,接口并未提供返回数据,仅提供向服务器增添数据的接口,这时候选择用sessionStorage会话储存来保存上个页面的数据,也可以做到页面间的数据共享,不过劣势在于仅单次使用有效。
图片渲染上页面:
通过会话储存来存储Detail页面的数据,注意要更改成Json格式,否则就是object报错,再在下个页面取出
第五个页面:shopcart
七个需求:
- 商品的基础渲染
- 商品的增减、保证输入框是整数
- 删除商品
- 选择商品
- 全选全部商品
- 选择商品件数和总金额呜
- 结算页面的选择
UUID
接口: url:
/cart/cartList
,在接口文档中,是一个get请求,无法在参数中传递身份信息,只能在requsts 分装函数中定义
先在src下创建utils工具,创建uuid的js文件,对外暴露函数(记得 npm install uuid)
import {v4 as uuidv4} from 'uuid'
// 生成临时游客的uuid,不能发生变化,耗能持久储存
export const getUUID =()=>{
// 1、判断本地储存是否uuid
let uuid_token =localStorage.getItem('UUIDTOKEN')
// 2、本地没有uuid
if(!uuid_token){
// 2.1 生成uuid
uuid_token =uuidv4()
//2.2储存本地
localStorage.setItem('UUIDTOKEN',uuid_token)
}
// 当用户有uuid时就不会生成
return uuid_token
}
用户的uuid_token定义在Detail.js中
import { getUUID } from "@/utils/uuid";
const state ={
goodList:[],
// 游客身份
uuid_token:getUUID()
}
requests.js中去发送uuid的参数信息给服务器,返回数据
import store from "@/store";
// 请求拦截器:在请求发出去之前会作出一些操作
requests.interceptors.request.use((config)=>{
// 1、先判断uuid是否为空
if(store.state.Detail.uuid_token){
// 2、userTempId字段和后端统一
config.headers.userTempId = store.state.Detail.uuid_token
}
nProgress.start();
nProgress.done()
return config
})
输入框的加减以及数字的取整
temlple部分
<!-- 减号 -->
<li class="cart-list-con5">
<a class="mins" @click="handler('minus',-1,cart)">-</a>
<!-- 显示框 -->
<input
autocomplete="off"
type="text"
:value="cart.skuNum"
minnum="1"
class="itxt"
@change="handler('input',$event.target.value,cart)"
>
<!-- 加号 -->
<a class="plus" @click="handler('add',1,cart)" >+</a>
</li>
逻辑部分
// 通过服务器更改产品数量
async handler(type,disNum,cart){
// 如果type是minus的
if(type=='minus'){
// 因为值的本身不能低于1
disNum =cart.skuNum <1 ? 0:-1
}
// 如果type是add的话
if(type=='add'){
disNum=1
}
// 如果type==change
if(type=='input'){
if(isNaN(disNum)&&disNum>0){
disNum=0
}else{
disNum=parseInt(disNum)-cart.skuNum
}
}
try {
this.$store.dispatch('getAddorReduceShopCart',{skuId:cart.skuId,skuNum:disNum})
this.getData();
} catch (error) {
alert(error)
}
}