# SAM-shop
**Repository Path**: sam9029/vue-shop
## Basic Information
- **Project Name**: SAM-shop
- **Description**: 预览地址: http://sam9029.gitee.io/vue-shop
- **Primary Language**: JavaScript
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2022-06-16
- **Last Updated**: 2022-12-06
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# vue-shop
- **PC端 -- 使用vue2构建基础电商项目**
## 前言
- vuex-state操作数据
- 页面负责页面操作,路由跳转等视图操作
- vue组件大写命名,页面小写命名
## 功能说明
- 菜单导航-动态渲染
(unfinished-!!首页其他部分,全部跳转搜索页)
- 轮播商品图-动态渲染-mock模拟数据
- 楼层商品-动态渲染-mock模拟数据
- 可能使用多个楼层,所以异步网络请求应该在父组件home身上请求
- 楼层组件引入轮播图
- apis请求库统一管理接口
- 封装axios--request.js
- 实际开发中,可能由多个不一样的接口地址
- 可以axios.create()创建多个axios实例
- 封装公共地址,
- 统一自行判断 param 与 data 的使用,直接调用使用 data 传
- 视频-P10
- Axios统一封装 请求拦截器 响应拦截器
- 请求拦截器
-`Request.interceptors.request.use(config=>{},err=>{})`
-统一为所有请求添加 头部信息,例如(token,游客的暂时id)
- 响应拦截器
- `Request.interceptors.response.use(res=>{},err=>{})`
- 统一处理网路响应返回的错误
- 响应成功
- 含各类http码,在处理,提出200码的响应结果
- 响应失败
- lodash 插件节流(节流:`一定时间内只触发一次`)
- 触发
- ⭐mockjs 插件使用 创建虚拟数据
- 在main.js中引入
~~~JS
// 引入mockjs
import '@/mock/mock'
~~~
- 创建mockRequest文件定义 新的封装请求 mockRequest
~~~js
import Axios from 'axios'
const mockRequest = Axios.create({
// 配置公共请求地址
baseURL:'/mock',
// 请求超时时间
timeout:'5000'
});
// Request // 直接暴露一个匿名函数, 三个重要参数
export default ({method,url,data}) => {
// 记得 return 也是 mockRequest
return mockRequest({
method,
url,
// 若 method为post 则用 data,否则用param
[method.toLowerCase() === "get"? 'param' : 'data' ]:data
})
}
~~~
- 创建mockjs文件(定义需要的模拟数据)接口,
~~~js
import Mock from 'mockjs'
// 导入模拟数据
import bannerMockData from './banner.json'
// 创建 虚拟接口 及 虚拟的接口返回数据
Mock.mock('/testMock',[1,2,3])
// 轮播图数据模拟
Mock.mock('/mock/banner',{
code:200,
message:'成功',
ok:'true',
data:bannerMockData
})
~~~
- 在api处引入模拟数据请求函数
~~~js
import mockRequest from '@/utils/mockRequest'
//--模拟的接口--------------------
// 暴露轮播图数据接口
export const getBanner = ()=>{
return mockRequest({
method:'GET',
url:'/banner',
})
}
~~~
- 组件中正常引入接口并使用模拟数据请求函数
~~~js
import api from '@/api/api'
~~~
- swiper插件 轮播封装全局组件 注意使用nextTick,和数据的初始化
- 轮播封装全局组件Slide,接受值,需要被监听
- 移入图片上方,图片停止轮播,移出时,轮播开始
- ⭐轮播图组件
- 面包屑的关闭处理全部逻辑一个人完成,为参考视频,用时大约6-7h
- ⭐面包屑处理
- 结合搜索函数,使用的搜索数据
- 搜索页面将搜索数据数据,传递给面包屑组件
- 面包屑组件读取搜索数据数据,同时按顺序(分类,搜索值,品牌,属性值)渲染
- 关闭面包屑处理
- 注意所有参数中(分类categoryName,搜索值keyword,品牌trademark,属性props)都存在于vuex的searchData中
- 注意!分类categoryName,搜索值keyword还存在于路由中,要对路由进行处理!!
- categoryName,keyword要处理路由信息,!方法重载路由(我选择this.$route.replace,不记录历史)
- 品牌直接在vuex的searchData中赋空值
- 属性直接在vuex的searchData中处理 !处理对应数组的对应值
- 排序完成
- 一个事件重复点击改变升降序规则
- 思路存储 前者,与 现在值 进行对比,来改变
- 待优化-综合与价格的升降序箭头同时改变
- 搜索逻辑处理
- 点击首页,清除所有搜索参数(防止返回后,搜索参数依旧存在,面包屑会渲染)
- 搜索框 输入搜索值 点击搜索后 清除所有(若之前有品牌,属性,除了分类)参数
- !!!进入商品页时,页面自动回到顶部!!!
- 利用导航守卫,dom页面滚动scrollTo
- 商品详情页
- 使用vuex储存网络方法和数据,并处理商品数据信息,供detail组件调用
- 全局路由导航-跳转新页面时,页面置顶
- 官方文档 [滚动行为](https://v3.router.vuejs.org/zh/guide/advanced/scroll-behavior.html#%E5%BC%82%E6%AD%A5%E6%BB%9A%E5%8A%A8 )
- (推荐)方法一:跳转页面,页面顶置,回退页面时,页面回退至相应位置
scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。
~~~js
const router = new VueRouter({
routes,
//scrollBehavior 函数接收 to和 from 路由对象,如 Navigation Guards。
//第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
//此处意思 即若回退页面,返回savedPosition,且页面不置顶
return savedPosition
} else {
//此处意思 若页面跳转新页面,则页面置顶
return { x: 0, y: 0 ,behavior:'smooth'}
}
}
})
~~~
- 方法二:(退回页面,不会记录历史位置)
~~~js
router.beforeEach((to,from,next) => {
window.scrollTo({
top:0,
behavior:'smooth'
})
next()
})
~~~
- 只要 大于零的 整数
~~~js
//排除字符串,undefined,NaN等非数字字符
//排除小数,小于零的数
if(isNaN(value) || value%1 !== 0 || value < 0){
//...code
}else{
//...code
}
~~~
- isNaN() 函数用于检查其参数是否是非数字值。
如果参数值为 NaN 或字符串、对象、undefined等非数字值则返回 true, 否则返回 false。
- ⭐UN游客模式下-添加购物车
- 写api 统一管理
- 使用uuid-v4作为游客的暂时id,存储游客的购物车信息
- axios统一头部添加暂时的uuid
- uuid v4 写成util 工具包调用,(存在浏览器本地内存中)
- uuidV4 工具包逻辑:先读取本地储存,若有userTempId,及return;若无,新建一个存进本地,并return
- (注意,不能使得每次调用都生成新的id,应该是本机生成后,不在改变本机的userTempId)
-
## 项目结构
~~~
- 首页
- 搜索页-商品分类
- 商品详情
- 购物车
- 登录-注册
- 订单确认页 》 支付订单页 》 支付成功页
- 我的订单页
~~~
### vue-组件结构
~~~
- layout
- 引入AppHeader、AppHeader(局部组件)
- home
- 引入TypeNav
- 引入 首页组成的 List、Rank、Like、Floor、Brand(局部组件)
- search
- TypeNav(全局组件-在home中使用)
~~~
# 总结
## router-link
~~~html
~~~
## button 使用路由的时候 , 绑定@click='$router.push("/xx")'
~~~html
~~~
## vue-router 路由方法的 push 重写--UN
- 解决 编程式`.$router.push()`写的 同一路由重复跳转中的 警告⚠
## vue-cli中的全局组件使用
- 定义全局组件(`GlobalName.vue`)文件后,需要在main.js 中注册
- 之后在其他 vue文件中直接使用
- 无需export default中 components中 注册
`
import GlobalName from './xxx/xxx'
Vue.component('GlobalName', GlobalName)
`
## UN--vue 的 transition 动画便签 ,配合css的tranition使用
## swiper图片 移入图片上方,图片停止轮播,移出时,轮播开始
是 mouse 不是 mounse
~~~js
@mouseenter="autoplaySwiper.autoplay.stop()"
@mouseleave="autoplaySwiper.autoplay.start()"
~~~
## 在渲染可跳转菜单时,(菜单项目多了-不要用router-link,会产生)----详情见TypeNav 渲染菜单导航
- 视频 [p8](https://www.bilibili.com/video/BV1Lg411R7WK?p=8&spm_id_from=333.851.header_right.history_list.click&vd_source=41f5451f27195a55e47c35f50a0e3772)
## 衍生--多次绑定事件-可换成事件委托-优化性能
## 设置在app组件里的网络请求,在页面加载时只会加载一次,此处适用于菜单项目导航数据的获取
## vuex
## TapeNav 全局使用时,首页和搜索页转换时,鼠标浮动的菜单动画展开
- 结合mouseenter mouseleave事件来使用 v-show 来(转换isShow的值)
- 通过直接在mounted中路由判断(this.$route.path)是否为首页,来修改isShow的值,确认是否初始隐藏菜单
- 优化处理首页 离开时,菜单消失,绑定一个函数(判断是否首页,是则中断函数)
## 使用this.$route.push({})传值
- 一:query this.$route.push({ path:'/xxx', query:{xxx:xxxx} })
- 二:params this.$route.push({ name:'xxx', params:{xxx:xxxx} })
- (注意!!)params传值最好使用name声明路径,使用path时this.$route对象中的params接收不到传递的值
## 使用组件内导航守卫时,beforeRouteUpdate(to,from,next){},注意this.$route.params.value获取的值不是实时的,是上一次的传进来的params.value
- 场景:使用在 search/index.vue中
- ~~~js
//已弃用-改用watch
// 组件路由导航改变时
beforeRouteUpdate(to,from,next){
// 在动态路由中,路由信息发生改变时,路由不会刷新,要检测路由变化,执行搜索函数
this.getCommdity({
//使用 to.params.searchValue来获取参数
keyword:to.params.searchValue
})
console.log('路由改变,搜索执行')
next()
},
~~~
## 购物车全选有三个细节功能---promise.all(数组对象)
(⭐注意--promise.all 本身也是promise,调用时要async和await处理)
- 1.全选按钮-自动匹配(是否全选):计算属性通过筛选 所有购物车 商品的isChecked ,全所有都是true ,则全选自动选中,不满足就不选中
- 按钮点击功能-提取未isChecked 为0/1的商品,取得skuId,并将其传入函数,再将函数传入数组-供promise.all()调用
- 2.全选:
- 3.取消全选:
## 购物车选中删除,
- 待优化-最后的商品选中后,点击无法网络请求后无法---更新页面,
- 已解决- 原本删除后,新的购物车数组数据没有值,报错
- 判断新返回的购物车数组数据是否有值,返回数据和空数组,避免删除所有商品后不刷新
- 有选中,有未选时,效果则可行
------------------------------------------------------------------------------------
------------------------------------------------------------------------------------
# 问题
## ⭐(特级)vuex 中state数据更新后,常规的属性调用 getter 读取state的值不更新 的解决
> [vuex getters 通过方法访问](https://vuex.vuejs.org/zh/guide/getters.html#%E9%80%9A%E8%BF%87%E5%B1%9E%E6%80%A7%E8%AE%BF%E9%97%AE)
- 一般情况下,getters(作用类似computed)通过属性来访问!!
- 此请况可以通过 方法来访问(实现调用getters方法时,每次调用都是调用方法,数据就会重新调用)
## 例子
### vuex文件写入
```js
state: {
// commdityData数据会由mutations的方法进行更新
commdityData:{}
},
getters:{
// 第一个参数是state
// 写入一个箭头函数
cateNav:(state) => () =>{ return state.commdityData.categoryView },
}
```
### 组件调用(方式都是默认使用vuex模块的)
#### 方式一: 使用辅助函数
```js
import{createNamespacedHelpers} from 'vuex'
const{mapGetters:detailMapGetters} = createNamespacedHelpers('detail')
export default{
methods:{
...detailMapGetters(['cateNav'])
}
mounted(){
this.cateNav()
}
}
```
#### 方式二 :
```js
import{mapGetters} from 'vuex'
export default{
methods:{
// ...mapGetters('模块名',['getters方法名'])
...mapGetters('detail',['cateNav'])
}
mounted(){
this.cateNav()
}
}
```
## eslint 命名的语法 报错 解决
~~~js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
// 关闭命名语法检测
lintOnSave: false
})
~~~
## 使用编程式导航:重复跳转同一个路径,会产生错误
- 可以正常使用,但是会有 log 报错

- 解决方法
~~~js
goSearch(){
this.$router.push('/search')
.then(res=>{console.log('跳转成功')})
.catch(res=>{console.log('跳转失败')})
}
~~~
~~~js
goSearch(){this.$router.push('/search',res=>{console.log('跳转成功')},res=>{console.log('跳转失败')})}
~~~
## 在mockRequest 暴露的函数中,也是return mockRequest()
- 之前还是 ,之前还是写的 原来的 return Request
- 报错 `Failed to construct 'Request': Please use the 'new' operator, this DOM object constructor cannot be called as a function.`
// 记得 return 也是 mockRequest
## ⭐⭐this.$nextTick()的使用
## swiper插件 轮播图拖不动的原因
- 在 list 组件中,使用异步获取的banner数据
- 初始化轮播图时,轮播图列表数据还没有渲染
- 使用nextTick() 来获取更新后 的数据
- ⭐异步方法只能用 async await 来获取数据
~~~js
async mounted(){
const res = await getBanner()
console.log(res);
this.bannerData = res.data.data;
console.log('list',this.bannerData)
// 初始化轮播图,轮播列表的数据还未渲染 this.$nextTick()等待页面渲染后
this.$nextTick( ()=>{
new Swiper(this.$refs.mySwiper,{
loop:true,
autoplay:true
});
})
}
~~~
promise.then的方式(下面这种方式)图片也拖不动
~~~js
mounted(){
getBanner().then(res => {
console.log(res);
this.bannerData = res.data.data;
console.log('list',this.bannerData)
})
// 初始化轮播图,轮播列表的数据还未渲染 this.$nextTick()等待页面渲染后
this.$nextTick( ()=>{
new Swiper(this.$refs.mySwiper,{
loop:true,
autoplay:true
});
})
}
~~~
## ?????vue 项目v-for动态渲染 时 绑定 本地图片-UN
- 目前投机取巧 绑了实例网站的公共域名地址来获取数据 `:src="'http://zhi.zeng.pub/zshop'+floorMockData.imgUrl"`
## 深度监听的写法,
~~~js
watch: {
// 是冒号啊。注意注意啊!!!
'person.name': {
handler(newVal,oldVal) {
// ...
},
deep: true,
immediate: true
}
}
~~~
## 传递路由 (route文件和使用路由的组件要同时改query,params的信息)
route--index.js
- 问号用于正则匹配(具体作用发挥不知)
` {path:'/search/:keyword?',name:'Search' ,component:Search}, `
-
组件使用
~~~js
this.$router.push({
//(注意!!)params传值最好使用name声明路径
// 使用path时this.$route对象中的params接收不到传递的值
name:'Search',
params:{keyword:this.keyword || undefined }
// query:this.$route.query
})
~~~
## 父子组件传方法使用,@xxFoo='xxFoo',
- 注意是@,不是:
- 子组件接收 @click='$emit('xxFoo',val)'
## 注意 组件使用data 时,在其他的地方(computed,methods)中操作时不要覆盖了原值
## !!在HTML5中添加了data-的方式来自定义属性,
⭐⭐ 命名可以用驼峰命名方式,但取值是必需全部使用小写(后面会说)
- !!!!驼峰命名取值时要使用小写
- `data-categoryName` 取值时 const {categoryname} = e.target.dataset
- !!!!单杠线命名取值才使用大写
- `data-category-name` 取值时 const {categoryName} = e.target.dataset
## 组件 传递 当前路由信息 给 vuex 注意的问题
- 组件使用了vuex的mutations方法 通过函数传值
- 目的在于 提取 当前路由信息中的 params和query信息 传递给vuex
- 不可传的写法:
- 写法一:params可传,!!但query始终为undefined:
- `this.CHANGE_SEARCHDATA(this.$route.params,this.$route.query)`
- 写法二:params,query 都不可传
- `this.CHANGE_SEARCHDATA( {params,query} = this.$route)`
- 可传的写法:params和query都可传:
- `this.CHANGE_SEARCHDATA({...this.$route})` 这样写可以接受到值,之后可在vuex做解构
## //对象中是 属性:值
// 不是 属性 = 值!!!
## 使用reduce报TypeError: Reduce of empty array with no initial value处理方法
- 增加初始值,0
`Array.reduce( Foo(){//code}, 0 )`
## 增加商品数量的api有逻辑问题,不能直接改变商品为指定数量
- 传递正数就添加(会累积)
- 指定数量传递只会被认为是正数传递
- 传递负数就减少(会累积)
--------------------------------------------------------------------
--------------------------------------------------------------------
# 拓展:
路由使用时,注意undefined 不会通过url 传递!!!
## 渲染时,渲染多个数据中包含绑定事件,可采用事件委托的方式,优化性能提升
## 获取data-xxx自定义属性要使用 e.target.data.xxx
--------------------------------------------------------------------
# 待优化:
## 待优化-综合与价格的升降序箭头同时改变
- 已解决:被点击的(选中的参数就显示),没有选中就不显示升降序箭头
## 面包屑,分页器可采用封装组件的思想