项目概述
1.创建项目
目标:基于VueCli自定义创建项目架子,将目录调整成符合企业规范的目录
-
删除多余的文件
-
修改路由配置 和App.vue
-
新增两个目录 api/utils
①api接口模块:发送ajax请求的接口模块
②utils工具模块:自己封装的一些工具方法模块
2.vant组件库
目标:认识第三方Vue组件库vant-ui
组件库:第三方封装好了很多的组件,整合到一起就是一个组件库
https://vant-contrib.gitee.io/vant/v2/#/zh-CN/
Vue的组件库并不是唯一的,vant-ui也仅仅是组件库的一种
一般会按照不同的平台进行分类
① PC端:element-ui(element-plus) ,ant-design-vue
② 移动端:vant-ui ; Mint UI(饿了么) ; Cube UI(滴滴)
vant全部导入 和 按需导入
目标:明确 全部导入 和 按需导入 的区别
全部导入就相当于把整个工具箱全都放在了项目当中,好处是方便,就是我在任何页面中想要加一个密码框等效果,由于是导入整个,所以在那用都可以.缺点是会把所有组件都导入项目中,会使项目体积非常大
按需导入就相当于有vant这么一个工具箱,但是在项目当中要用的时候,并不是把整个工具箱丢到项目中,而是需要用到哪一个,就导入哪一个来使用
组件库的使用
1.全部导入
① 安装vant-ui
yarn add vant@latest-v2
②main.js中注册
import Vant from 'vant'
import 'vant/lib/index.css'
//把vant中所有组件都导入了
Vue.use(Vant)
③测试使用
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
2.按需导入
①安装 vant-ui(已安装)
②安装插件
npm i babel-plugin-import -D
③babel.config.js中配置
module.exports = {
presets: [
@vue/cli-plugin-babel/preset'
],
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
}
}
④main.js按需导入注册
import Vue from 'vue';
import { Button } from 'vant';
Vue.use(Button);
⑤测试使用
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
为了将来不在main.js中到人很多,然后写的很长👇
可以在utils文件中建一个vant-ui.js
,把要的东西放在这里面👇
然后再在main.js中导入这个文件👇
3.项目中的vw适配
目标:基于postcss插件,实现项目vw适配
Vant 默认使用 px 作为样式单位,如果需要使用 viewport 单位 (vw, vh, vmin, vmax),推荐使用 postcss-px-to-viewport 进行转换。
postcss-px-to-viewport 是一款 PostCSS 插件,用于将 px 单位转化为 vw/vh 单位。
视图中的长度是px,那么就需要把px转换成vw(vant2进阶用法)
①安装插件
yarn add postcss-px-to-viewport@1.1.1 -D
②根据目录新建postcss.comfig.js
// postcss.config.js
module.exports = {
plugins: {
'postcss-px-to-viewport': {
viewportWidth: 375,
},
},
};
4.路由设计配置
①目标分析项目页面,设计路由,配置一级路由
但凡是单个页面,独立展示的,都是一级路由
准备好views中的各个页面,然后开始在index.js中配置路由
配对应的路由规则,路由规则就是数组里面包对象import Login from '@/views/login' import Layout from '@/views/layout' import Search from '@/views/search' import SearchList from '@/views/search/list' import ProDetail from '@/views/prodetail' import Pay from '@/views/pay' import MyOrder from '@/views/myorder' import Home from '@/views/layout/home' import Category from '@/views/layout/category' import Cart from '@/views/layout/cart' import User from '@/views/layout/user' import store from '@/store' Vue.use(VueRouter) const router = new VueRouter({ routes: [ { path: '/login', component: Login }, { path: '/', component: Layout,// 首页 redirect: '/home', children: [ { path: '/home', component: Home }, { path: '/category', component: Category }, { path: '/cart', component: Cart }, { path: '/user', component: User } ] }, { path: '/search', component: Search }, { path: '/searchlist', component: SearchList }, // 动态路由传参,确认将来是哪个商品,路由参数中携带 id 这个是商品详情,所以得知道是哪一个商品,这就需要用到动态路由传参 { path: '/prodetail/:id', component: ProDetail }, { path: '/pay', component: Pay }, { path: '/myorder', component: MyOrder } ] })
基础用法
Icon 的 name 属性支持传入图标名称或图片链接,所有可用的图标名称见右侧示例。
<van-icon name="chat-o" />
<van-icon name="https://b.yzcdn.cn/vant/icon-demo-1126.png" />
徽标提示
设置 dot 属性后,会在图标右上角展示一个小红点;设置 badge 属性后,会在图标右上角展示相应的徽标。
<van-icon name="chat-o" dot />
<van-icon name="chat-o" badge="9" />
<van-icon name="chat-o" badge="99+" />
图标颜色
Icon 的 color 属性用来设置图标的颜色。
<van-icon name="cart-o" color="#1989fa" />
<van-icon name="fire-o" color="#ee0a24" />
图标大小
Icon 的 size 属性用来设置图标的尺寸大小,默认单位为 px。
<van-icon name="chat-o" size="40" /> <van-icon name="chat-o" size="3rem" />
自定义颜色
<van-tabbar v-model="active" active-color="#ee0a24" inactive-color="#000">
<van-tabbar-item icon="home-o">标签</van-tabbar-item>
<van-tabbar-item icon="search">标签</van-tabbar-item>
<van-tabbar-item icon="friends-o">标签</van-tabbar-item>
<van-tabbar-item icon="setting-o">标签</van-tabbar-item>
</van-tabbar>
//active-color是激活时的颜色,inactive是未激活
②基于底部导航,完成二级路由配置
5.登录静态页面布局
1.准备工作
(1)新建‘styles/common.less’重置默认样式
// 重置默认样式
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
// 文字溢出省略号
.text-ellipsis-2 {
overflow: hidden;
-webkit-line-clamp: 2;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
}
(2) main.js 导入common.less
import '@/styles/common.less'
(3)图片素材拷贝到assets目录【备用】
2.登录页静态布局编写
(1)头部组件说明(NavBar)
<van-nav-bar title="会员登录" left-arrow @click-left="$router.go(-1)" />
这里的这个小箭头是蓝色的,希望以后所有的都是蓝色的,就可以把他写到通用样式里👇
先在检查里找到它的名字👇
然后在👇里面写
// 添加导航的通用样式
.van-nav-bar {
.van-nav-bar__arrow {
color: #333;
}
}
(2)通用样式覆盖
(3)其他静态结构编写
6.request模块 - axios封装
目标:将axios请求方法,封装到request模块
使用axios来请求后端接口,一般都会对axios进行 一些配置(比如:配置基础地址,请求响应拦截器等)
所以项目开发中,都会对axios进行基本的二次封装,单独封装到一个request模块中,便于维护使用
- 安装 axios
npm i axios
-
新建
utils/request.js
封装 axios 模块利用 axios.create 创建一个自定义的 axios 来使用
http://www.axios-js.com/zh-cn/docs/#axios-create-config
/* 封装axios用于发送请求 */
import axios from 'axios'
// 创建一个新的axios实例,将来对创建出来的实例,进行自定义配置
// 好处:不会污染原始的axios实例
const request = axios.create({
baseURL: 'http://cba.itlike.com/public/index.php?s=/api/',
timeout: 5000
})
// 添加请求拦截器
request.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 添加响应拦截器
request.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response.data
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error)
})
export default request
- 获取图形验证码,请求测试
import request from '@/utils/request'
export default {
name: 'LoginPage',
async created () {
const res = await request.get('/captcha/image')
console.log(res)
}
}
7.图形验证码功能完成
目标:基于请求回来的base64图片,实现图形验证码功能
说明:
- 图形验证码,本质就是一个从后台请求回来的一个图片
- 用户将来输入图形验证码,用于强制人机交互,可以抵御机器自动化攻击(例如:避免批量请求获取短信)
需求:
- 动态将请求回来的base64图片,解析渲染出来
- 点击验证码图片盒子,要刷新验证码
- 准备数据,获取图形验证码后存储图片路径,存储图片唯一标识
<img v-if="picUrl" :src="picUrl" @click="getPicCode" alt="">
<script>
import { codeLogin, getMsgCode, getPicCode } from '@/api/login'
// import { Toast } from 'vant'
export default {
name: 'LoginPage',
data () {
return {
picKey: '', // 将来请求传递的图形验证码唯一标识
picUrl: '', // 存储请求渲染的图片地址
picCode: '', // 用户输入的图形验证码
}
},
async created () {
this.getPicCode()
},
methods: {
// 获取图形验证码
async getPicCode () {
const { data: { base64, key } } = await getPicCode()
this.picUrl = base64 // 存储地址
this.picKey = key // 存储唯一标识
},
}
- 动态渲染图形验证码,并且点击时要重新刷新验证码
<img v-if="picUrl" :src="picUrl" @click="getPicCode">
8.api接口模块 - 封装图片验证码接口
目标:将请求封装成方法,统一存放到aip模块,与页面分离
//😄login/index.vue
import { getPicCode } from '@/api/login'
methods: {
// 获取图形验证码
async getPicCode () {
const { data: { base64, key } } = await getPicCode()
this.picUrl = base64 // 存储地址
this.picKey = key // 存储唯一标识
// Toast('获取图形验证码成功')
// this.$toast('获取成功')
// this.$toast.success('成功文案')
},
//😄api/login.js
// 此处用于存放所有登录相关的接口请求
import request from '@/utils/request'
// 1. 获取图形验证码
export const getPicCode = () => {
return request.get('/captcha/image')
}
9.Toast轻提示
①导入调用
引入
import Vue from 'vue';
import { Toast } from 'vant';
Vue.use(Toast);
文字提示
Toast('提示内容');
②通过this直接调用
全局方法
引入 Toast 组件后,会自动在 Vue 的 prototype 上挂载 $toast 方法,便于在组件内调用。
export default {
mounted() {
this.$toast('提示文案');
},
};
😘方法①
在login/index.vue中导入调用import { Toast } from 'vant'
那么在合适的位置就可以调用了(在图形验证码的地方)
Toast('获取图形验证码成功')
这种方式组件内或非组件内均可使用(在main.js中也可以)
😘方法②(this)
//在组件内直接使用 this.$toast('获取成功')
10.短信验证倒计时
步骤分析
-
点击按钮,实现倒计时效果
-
倒计时之前的校验处理(手机号,验证码)
-
封装短信验证请求接口,发送请求添加提示
1.login/index.vue
export default {
name: 'LoginPage',
data () {
return {
totalSecond: 60, // 总秒数
second: 60, // 当前秒数,开定时器对 second--
timer: null, // 定时器 id
}
},
}
2.准备好了就可以注册点击事件了
<button @click="getCode">
{{ second === totalSecond ? '获取验证码' : second + '秒后重新发送'}}
</button>
3.getCode还要写一个methods
async getCode () {
if (!this.validFn()) {
// 如果没通过校验,没必要往下走了
return
}
😊 // 当前目前没有定时器开着,且 totalSecond 和 second 一致 (秒数归位) 才可以倒计时
if (!this.timer && this.second === this.totalSecond) {
// 发送请求
// 预期:希望如果响应的status非200,最好抛出一个promise错误,await只会等待成功的promise
await getMsgCode(this.picCode, this.picKey, this.mobile)
this.$toast('短信发送成功,注意查收')
// 开启倒计时
this.timer = setInterval(() => {
this.second--
if (this.second <= 0) {
clearInterval(this.timer)
this.timer = null // 重置定时器 id
this.second = this.totalSecond // 归位
}
}, 1000)
}
},
4.应为这些代码,离开页面时还在计时,所以要写一个离开页面清除定时器
// 离开页面清除定时器
destroyed () {
clearInterval(this.timer)
}
5.还要校验手机号和图形验证码是否合法
先在data中提供两个变量
export default {
name: 'LoginPage',
data () {
return {
mobile: '', // 手机号
picCode: '', // 用户输入的图形验证码
}
},
}
6.然后给手机号和验证码进行绑定
<div class="form-item">
<input v-model="mobile" class="inp" maxlength="11" placeholder="请输入手机号码" type="text">
</div>
<div class="form-item">
<input v-model="picCode" class="inp" maxlength="5" placeholder="请输入图形验证码" type="text">
<img v-if="picUrl" :src="picUrl" @click="getPicCode" alt="">
</div>
7.校验 手机号 和 图形验证码 是否合法
// 校验 手机号 和 图形验证码 是否合法
// 通过校验,返回true
// 不通过校验,返回false
validFn () {
if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
this.$toast('请输入正确的手机号')
return false
}
if (!/^\w{4}$/.test(this.picCode)) {
this.$toast('请输入正确的图形验证码')
return false
}
return true
},
8.还需要再在上面的倒计时前面加个判断👇
- 输入框 v-model 绑定变量
data () { return { mobile: '', // 手机号 picCode: '' // 图形验证码 } }, <input v-model="mobile" class="inp" maxlength="11" placeholder="请>输入手机号码" type="text"> <input v-model="picCode" class="inp" maxlength="5" placeholder="请输入图形验证码" type="text">
- methods中封装校验方法
// 校验输入框内容 validFn () { if (!/^1[3-9]\d{9}$/.test(this.mobile)) { this.$toast('请输入正确的手机号') return false } if (!/^\w{4}$/.test(this.picCode)) { this.$toast('请输入正确的图形验证码') return false } return true },
- 请求倒计时前进行校验
// 获取短信验证码 async getCode () { if (!this.validFn()) { return } ... }
9.封装短信验证请求接口,发送请求添加提示
api/login.js
// 2. 获取短信验证码
export const getMsgCode = (captchaCode, captchaKey, mobile) => {
return request.post('/captcha/sendSmsCaptcha', {
form: {
captchaCode,
captchaKey,
mobile
}
})
}
加这个👇
11.封装api登录接口,实现登录功能
1.阅读文档,封装登录接口
2.登录前的校验
3.调用方法,发送请求,成功添加提示并跳转
api/login.js
// 3. 登录接口
export const codeLogin = (mobile, smsCode) => {
return request.post('/passport/login', {
form: {
isParty: false,
partyData: {},
mobile,
smsCode
}
})
}
登录界面的校验,校验通过后,调用上面的接口
login/index.vue
// 登录
async login () {
if (!this.validFn()) {
return
}
if (!/^\d{6}$/.test(this.msgCode)) {
this.$toast('请输入正确的手机验证码')
return
}
console.log('发送登录请求')
const res = await codeLogin(this.mobile, this.msgCode)
this.$store.commit('user/setUserInfo', res.data)
this.$toast('登录成功')
// 进行判断,看地址栏有无回跳地址
// 1. 如果有 => 说明是其他页面,拦截到登录来的,需要回跳
// 2. 如果没有 => 正常去首页
const url = this.$route.query.backUrl || '/'
this.$router.replace(url)
}
在data中添加
msgCode: '' // 短信验证码
12.响应拦截器统一处理错误提示
问题:每次请求,都会有可能会有错误,就都需要错误提示
说明:响应拦截器是咱们拿到数据的 第一个 数据流转站,可以在里面统一处理错误
utils/request.js
import { Toast } from 'vant'
...
// 添加响应拦截器
request.interceptors.response.use(function (response) {
const res = response.data
if (res.status !== 200) {
Toast(res.message)
return Promise.reject(res.message)
}
// 对响应数据做点什么
return res
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error)
})
13.登录权证信息存储
目标:vuex构建user模块存储登录权证(token & userld)
补充说明:
1.token存入vuex的好处,易获取,响应式
2.vuex需要分模块 => user模块
构建user模块 → 挂载到vuex → 提供mutations → 页面中commit调用
1. 新建 vuex user 模块 store/modules/user.js
export default {
namespaced: true,
state () {
return {
userInfo: {
token: '',
userId: ''
},
}
},
mutations: {},
actions: {}
}
2. 挂载到 vuex 上
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
user,
}
})
3. 提供 mutations
mutations: {
setUserInfo (state, obj) {
state.userInfo = obj
},
},
4. 页面中 commit 调用
// 登录按钮(校验 & 提交)
async login () {
if (!this.validFn()) {
return
}
...
const res = await codeLogin(this.mobile, this.msgCode)
this.$store.commit('user/setUserInfo', res.data)
this.$router.push('/')
this.$toast('登录成功')
}
14.storage存储模块 - vuex持久化处理
目标:封装storage存储模块,利用本地储存,进行buex持久化处理
问题1:vuex刷新多久会丢失,怎么办?
//将token存入本地
localStorage.setItem('hm_shopping_info',JSON.stringify(xxx))
问题2么此存取操作太长怎么办?
把它封装成专门的存储模块storage,把一个一个存储方法封装成函数,到时候页面中只要调用对应的函数就可以了
- 新建
utils/storage.js
封装方法
const INFO_KEY = 'hm_shopping_info'
// 获取个人信息
export const getInfo = () => {
const result = localStorage.getItem(INFO_KEY)
return result ? JSON.parse(result) : {
token: '',
userId: ''
}
}
// 设置个人信息
export const setInfo = (info) => {
localStorage.setItem(INFO_KEY, JSON.stringify(info))
}
// 移除个人信息
export const removeInfo = () => {
localStorage.removeItem(INFO_KEY)
}
- vuex user 模块持久化处理
import { getInfo, setInfo } from '@/utils/storage'
export default {
namespaced: true,
state () {
return {
userInfo: getInfo()
}
},
mutations: {
setUserInfo (state, obj) {
state.userInfo = obj
setInfo(obj)
}
},
actions: {}
}
15.添加请求loading效果
背景:有时候因为网络原因,一次请求的结果可能需要一段时间后才能回来,
此时,需要给用户 添加loading效果
实操步骤:
- 请求拦截器中,每次请求,打开loading
- 响应拦截器中,每次响应,关闭loading
1. 请求时,打开 loading
// 添加请求拦截器
request.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
Toast.loading({
message: '请求中...',
forbidClick: true,
loadingType: 'spinner',
duration: 0
})
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
2. 响应时,关闭 loading
// 添加响应拦截器
request.interceptors.response.use(function (response) {
const res = response.data
if (res.status !== 200) {
Toast(res.message)
return Promise.reject(res.message)
} else {
// 清除 loading 中的效果
Toast.clear()
}
// 对响应数据做点什么
return res
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error)
})
16. 登录访问拦截 - 路由前置守卫
目标:基于全局前置守卫,进行页面访问拦截处理
说明:智慧商城项目,大部分页面,游客都可以直接访问, 如遇到需要登录才能进行的操作,提示并跳转到登录
但是:对于支付页,订单页等,必须是登录的用户才能访问的,游客不能进入该页面,需要做拦截处理
路由导航守卫 - 全局前置守卫
1.所有的路由一旦被匹配到,都会先经过全局前置守卫
2.只有全局前置守卫放行,才会真正解析渲染组件,才能看到页面内容
router.beforeEach((to, from, next) => {
// 1. to 往哪里去, 到哪去的路由信息对象
// 2. from 从哪里来, 从哪来的路由信息对象
// 3. next() 是否放行
// 如果next()调用,就是放行
// next(路径) 拦截到某个路径页面
})
const authUrl = ['/pay', '/myorder']
router.beforeEach((to, from, next) => {
const token = store.getters.token
if (!authUrl.includes(to.path)) {
next()
return
}
if (token) {
next()
} else {
next('/login')
}
})
17. 首页 - 静态结构准备
- 静态结构和样式
layout/home.vue
<template>
<div class="home">
<!-- 导航条 -->
<van-nav-bar title="智慧商城" fixed />
<!-- 搜索框 -->
<van-search
readonly
shape="round"
background="#f1f1f2"
placeholder="请在此输入搜索关键词"
@click="$router.push('/search')"
/>
<!-- 轮播图 -->
<van-swipe class="my-swipe" :autoplay="3000" indicator-color="white">
<van-swipe-item>
<img src="@/assets/banner1.jpg" alt="">
</van-swipe-item>
<van-swipe-item>
<img src="@/assets/banner2.jpg" alt="">
</van-swipe-item>
<van-swipe-item>
<img src="@/assets/banner3.jpg" alt="">
</van-swipe-item>
</van-swipe>
<!-- 导航 -->
<van-grid column-num="5" icon-size="40">
<van-grid-item
v-for="item in 10" :key="item"
icon="http://cba.itlike.com/public/uploads/10001/20230320/58a7c1f62df4cb1eb47fe83ff0e566e6.png"
text="新品首发"
@click="$router.push('/category')"
/>
</van-grid>
<!-- 主会场 -->
<div class="main">
<img src="@/assets/main.png" alt="">
</div>
<!-- 猜你喜欢 -->
<div class="guess">
<p class="guess-title">—— 猜你喜欢 ——</p>
<div class="goods-list">
<GoodsItem v-for="item in 10" :key="item"></GoodsItem>
</div>
</div>
</div>
</template>
<script>
import GoodsItem from '@/components/GoodsItem.vue'
export default {
name: 'HomePage',
components: {
GoodsItem
}
}
</script>
<style lang="less" scoped>
// 主题 padding
.home {
padding-top: 100px;
padding-bottom: 50px;
}
// 导航条样式定制
.van-nav-bar {
z-index: 999;
background-color: #c21401;
::v-deep .van-nav-bar__title {
color: #fff;
}
}
// 搜索框样式定制
.van-search {
position: fixed;
width: 100%;
top: 46px;
z-index: 999;
}
// 分类导航部分
.my-swipe .van-swipe-item {
height: 185px;
color: #fff;
font-size: 20px;
text-align: center;
background-color: #39a9ed;
}
.my-swipe .van-swipe-item img {
width: 100%;
height: 185px;
}
// 主会场
.main img {
display: block;
width: 100%;
}
// 猜你喜欢
.guess .guess-title {
height: 40px;
line-height: 40px;
text-align: center;
}
// 商品样式
.goods-list {
background-color: #f6f6f6;
}
</style>
- 新建
components/GoodsItem.vue
<template>
<div class="goods-item" @click="$router.push('/prodetail')">
<div class="left">
<img src="@/assets/product.jpg" alt="" />
</div>
<div class="right">
<p class="tit text-ellipsis-2">
三星手机 SAMSUNG Galaxy S23 8GB+256GB 超视觉夜拍系统 超清夜景 悠雾紫
5G手机 游戏拍照旗舰机s23
</p>
<p class="count">已售104件</p>
<p class="price">
<span class="new">¥3999.00</span>
<span class="old">¥6699.00</span>
</p>
</div>
</div>
</template>
<script>
export default {}
</script>
<style lang="less" scoped>
.goods-item {
height: 148px;
margin-bottom: 6px;
padding: 10px;
background-color: #fff;
display: flex;
.left {
width: 127px;
img {
display: block;
width: 100%;
}
}
.right {
flex: 1;
font-size: 14px;
line-height: 1.3;
padding: 10px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
.count {
color: #999;
font-size: 12px;
}
.price {
color: #999;
font-size: 16px;
.new {
color: #f03c3c;
margin-right: 10px;
}
.old {
text-decoration: line-through;
font-size: 12px;
}
}
}
}
</style>
- 组件按需引入
import { Search, Swipe, SwipeItem, Grid, GridItem } from 'vant'
Vue.use(GridItem)
Vue.use(Search)
Vue.use(Swipe)
Vue.use(SwipeItem)
Vue.use(Grid)
18. 首页 - 动态渲染
- 封装准备接口
api/home.js
import request from '@/utils/request'
// 获取首页数据
export const getHomeData = () => {
return request.get('/page/detail', {
params: {
pageId: 0
}
})
}
- 页面中请求调用
import GoodsItem from '@/components/GoodsItem.vue'
import { getHomeData } from '@/api/home'
export default {
name: 'HomePage',
components: {
GoodsItem
},
data () {
return {
bannerList: [],
navList: [],
proList: []
}
},
async created () {
const { data: { pageData } } = await getHomeData()
this.bannerList = pageData.items[1].data
this.navList = pageData.items[3].data
this.proList = pageData.items[6].data
}
}
- 轮播图、导航、猜你喜欢渲染
<!-- 轮播图 -->
<van-swipe class="my-swipe" :autoplay="3000" indicator-color="white">
<van-swipe-item v-for="item in bannerList" :key="item.imgUrl">
<img :src="item.imgUrl" alt="">
</van-swipe-item>
</van-swipe>
<!-- 导航 -->
<van-grid column-num="5" icon-size="40">
<van-grid-item
v-for="item in navList" :key="item.imgUrl"
:icon="item.imgUrl"
:text="item.text"
@click="$router.push('/category')"
/>
</van-grid>
<!-- 猜你喜欢 -->
<div class="guess">
<p class="guess-title">—— 猜你喜欢 ——</p>
<div class="goods-list">
<GoodsItem v-for="item in proList" :item="item" :key="item.goods_id"></GoodsItem>
</div>
</div>
- 商品组件内,动态渲染
<template>
<div v-if="item.goods_name" class="goods-item" @click="$router.push(`/prodetail/${item.goods_id}`)">
<div class="left">
<img :src="item.goods_image" alt="" />
</div>
<div class="right">
<p class="tit text-ellipsis-2">
{{ item.goods_name }}
</p>
<p class="count">已售 {{ item.goods_sales }}件</p>
<p class="price">
<span class="new">¥{{ item.goods_price_min }}</span>
<span class="old">¥{{ item.goods_price_max }}</span>
</p>
</div>
</div>
</template>
<script>
export default {
props: {
item: {
type: Object,
default: () => {
return {}
}
}
}
}
</script>