前端:Vue学习 - 智慧商城项目
-
- [1. vue组件库 => vant-ui](#1. vue组件库 => vant-ui)
- [2. postcss插件 => vw 适配](#2. postcss插件 => vw 适配)
- [3. 路由配置](#3. 路由配置)
- [4. 登录页面静态布局](#4. 登录页面静态布局)
-
- [4.1 封装axios实例访问验证码接口](#4.1 封装axios实例访问验证码接口)
- [4.2 vant 组件 => 轻提示](#4.2 vant 组件 => 轻提示)
- [4.3 短信验证倒计时](#4.3 短信验证倒计时)
- [4.4 登录功能](#4.4 登录功能)
- [4.5 响应拦截器 => 统一处理错误](#4.5 响应拦截器 => 统一处理错误)
- [4.6 登录权证信息存储](#4.6 登录权证信息存储)
- [4.7 storage存储模块 => vuex持久化处理](#4.7 storage存储模块 => vuex持久化处理)
- [4.8 添加请求loading效果](#4.8 添加请求loading效果)
- [5. 页面访问拦截](#5. 页面访问拦截)
- [6. 首页布局](#6. 首页布局)
- [7. 搜索历史管理](#7. 搜索历史管理)
- [8. 商品详情页](#8. 商品详情页)
- [9. 加入购物车](#9. 加入购物车)
1. vue组件库 => vant-ui
安装命令为:
js
npm i vant@latest-v2
vant官网链接为:vant,默认打开的是vant最新版本4,这里使用vant2,链接为:vant2
有两种导入方式,全部导入和按需导入,全部导入 直接在main.js加入下述代码即可。
js
import Vue from 'vue';
import Vant from 'vant';
import 'vant/lib/index.css';
Vue.use(Vant);
此时在vue文件中就可以添加相应的vant组件代码了,但是性能低。
按需导入 首先需要安装babel-plugin-import插件,插件下载命令如下:
js
//安装插件
npm i babel-plugin-import -D
然后在babel.config.js文件中添加下述代码:
js
module.exports = {
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
};
之后只需要在main.js中引入vant中对应组件,并进行注册即可。
js
import { Button } from 'vant';
Vue.use(Button)
随着项目逐渐增大,可以把对应的按需导入组件代码写到专门的js文件中,然后在main.js中引入即可。
2. postcss插件 => vw 适配
postcss插件相关内容链接为:postcss
postcss插件安装命令为:
js
npm install postcss-px-to-viewport@1.1.1 -D
然后在根项目下新建postcss.config.js文件,填入下述配置
js
module.exports = {
plugins:{
"postcss-px-to-viewport":{
viewportWidth: 375
// 标准屏幕宽度
}
}
}
上述设置了375,此时 80vw=300px,计算方式为 300/375*100 = 80。
3. 路由配置
凡是单个页面独立展示的,都是一级路由。
一级路由有登录页面、首页、搜索页、搜索列表页、商品详情页、结算支付页、订单管理页;二级路由在首页下有首页、分类、购物车、我的。
路由配置代码如下:
js
import Vue from 'vue'
import VueRouter from 'vue-router'
// 引入一级路由组件
import Login from "@/views/login/Login.vue"
import Layout from "@/views/layout/Index.vue"
import MyOrder from '@/views/myorder/MyOrder.vue'
import Pay from '@/views/pay/Pay.vue'
import ProDetail from '@/views/prodetail/ProDetail.vue'
import List from '@/views/search/List.vue'
import Index2 from '@/views/search/Index2.vue'
// 引入二级路由组件
import Home from '@/views/layout/Home.vue'
import Cart from '@/views/layout/Cart.vue'
import Category from '@/views/layout/Category.vue'
import User from '@/views/layout/User.vue'
Vue.use(VueRouter)
const routes = [
{path:"/login",component:Login},
{path:"/",component:Layout,
children:[
// 二级路由
{path:"/home",component:Home},
{path:"/category",component:Category},
{path:"/cart",component:Cart},
{path:"/user",component:User}
],
redirect:"/home"
// 重定向
},
{path:"/search",component:Index2},
{path:"/searchList",component:List},
{path:"/prodetail/:id",component:ProDetail},
// 动态路由
{path:"/pay",component:Pay},
{path:"/myorder",component:MyOrder}
]
const router = new VueRouter({
routes
})
export default router
运行结果:
4. 登录页面静态布局
使用vant的NavBar组件
js
import { NavBar } from 'vant';
// 导航栏
Vue.use(NavBar);
html
<template>
<div class="login">
<van-nav-bar title="登录" left-arrow @click-left="onClickLeft"/>
<div class="login-b">
<p class="login-info">手机号注册</p>
<p class="login-info-2">未注册的手机号登录后会自动注册</p>
<input type="text" placeholder="请输入手机号码" class="phone margin-b-20">
<div class="img-code margin-b-20">
<input type="text" placeholder="请输入图形验证码">
<img src="@/static/code.png" alt="">
</div>
<div class="xx-code margin-b-20">
<input type="text" placeholder="请输入短信验证码">
<button>获取验证码</button>
</div>
<button class="login-btn">登录</button>
</div>
</div>
</template>
<script>
export default {
name:"Login",
methods:{
onClickLeft(){
this.$router.back();
}
}
}
</script>
<style lang="less" scoped>
.login-b{
padding-top: 40px;
padding-left: 30px;
padding-right: 30px;
.login-info{
font-size: 35px;
font-weight: 500;
}
.login-info-2{
color: rgb(220, 223, 227);
padding: 9px 0;
font-size:14px;
}
input{
width: 100%;
border: none;
border-bottom: 1px solid rgb(220, 223, 227) !important;
height: 50px;
line-height: 50px;
font-size: 16px;
}
.margin-b-20{
margin-bottom: 20px;
}
.img-code{
position: relative;
input{
width: 220px;
}
img{
width: 75px;
position: absolute;
bottom: 5px;
right: 0;
}
}
.login-btn{
color: white;
background-color: rgb(233, 189, 32);
border: none ;
width: 100%;
height: 40px;
line-height: 40px;
border-radius: 20px;
margin-top: 14px;
}
.xx-code{
position: relative;
input{
width: 220px;
}
button{
position: absolute;
right: 0;
bottom: 5px;
border: none;
background-color: white;
color: rgb(233, 189, 32);
}
}
}
</style>
运行结果:
4.1 封装axios实例访问验证码接口
封装axios实例,js代码如下:
js
import axios from 'axios'
const instance = axios.create({
baseURL: 'http://localhost:9998/w',
timeout: 1000,
headers: { 'X-Custom-Header': 'foobar' }
});
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response.data;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
export default instance
// 导出配置
使用封装好的axios实例访问接口,如下:
js
import request from '@/utils/request.js'
export const getPicCode = function(){
return request.get('?str_1=/captcha/image')
}
按需导出
4.2 vant 组件 => 轻提示
使用Toast请提示,注册安装
js
import { Toast } from 'vant';
Vue.use(Toast);
// 轻提示
使用直接this.$toast('提示内容'),需要注意的是只能在组件内部使用。另外一种是在任何地方都可以使用。
js
import {Toast} from "vant"
Toast('提示内容')
运行结果:
4.3 短信验证倒计时
准备data数据,三个,分别为totalSecond、second、timer;
js
data(){
return{
totalSecond:60, // 总秒数
second:60, // 当前秒数
timer:null
}
}
点击发送验证码按钮代码逻辑,
js
getCode(){
// timer判断,防止重复点击
if(!this.timer && this.second === this.totalSecond){
this.timer = setInterval(()=>{
this.second --;
if(this.second <= 0){
clearInterval(timer);
this.timer = null;
this.second = totalSecond;
}
// 倒计时显示为0,关闭定时器
},1000);
}
}
页面点击按钮页面布局为:
html
<button @click="getCode">
{{second === totalSecond?"获取验证码": second + "秒之后重新发送"}}
</button>
运行结果:
另外还存在一个问题,就是离开这个页面时,定时器还在运行,需要在destroy函数中关闭当前定时器。
js
// 离开页面,清除定时器
destory(){
clearInterval(this.timer);
}
下述是对输入的手机号和图片验证码进行验证:
js
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;
}
请求短信验证码接口,只是演示效果而已
js
// 短信验证码
// 短信验证码
export const getMsgCode = function (captchaCode, captchaKey, mobile) {
return request.post('?type=code&str_1=/captcha/sendSmsCaptcha',
{
captchaCode,
captchaKey,
mobile
}
)
}
js
async getCode(){
if(!this.validFn()){
// 点击验证码按钮之后进行判断
return
}
// timer判断,防止重复点击
if(!this.timer && this.second === this.totalSecond){
await getMsgCode(this.picCode,this.picKey,this.mobile);
this.$toast("短信发送成功!");
this.timer = setInterval(()=>{
this.second --;
// console.log("正在倒计时。。。");
if(this.second <= 0){
clearInterval(this.timer);
this.timer = null;
this.second = this.totalSecond;
}
// 倒计时显示为0,关闭定时器
},1000);
}
}
实现步骤:
- 点击按钮,实现倒计时效果
- 倒计时之前进行校验(手机号、图片验证码)
- 请求短信验证码接口,添加相应提示
4.4 登录功能
登录之前仍需要对手机号、图片验证码和短信验证码进行校验,然后调用相应的请求登录接口的方法,发送请求,请求成功后添加相应的提示并跳转。
js
export const codeLogin = function(mobile,smsCode){
return request.post('/w3?type=codeLogin',{
mobile,
smsCode
})
}
js
if(!this.validFn()) return
if(!/^\d{6}$/.test(this.msgCode)){
this.$toast("请输入正确的短信验证码!")
return
}
const res = await codeLogin(this.mobile,this.msgCode);
console.log(res);
this.$toast("登录成功!")
this.$router.push("/")
4.5 响应拦截器 => 统一处理错误
上述请求对应接口只是在考虑请求结果正确的情况下。在封装的axios模块下的响应拦截器添加如下代码:
js
import {Toast} from 'vant';
// 添加响应拦截器
instance.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);
});
运行结果:
4.6 登录权证信息存储
vuex构建user模块存储登录权证。
构建user模块
js
export default{
namespaced:true,
state(){
return{
userInfo:{
token:"",
userId:""
}
}
},
mutations:{
},
actions:{
},
getters:{
}
}
挂载user到全局上去
js
import Vue from 'vue'
import Vuex from 'vuex'
import user from "@/store/modules/user"
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
user
}
})
mutations中设置state相应的值
js
mutations:{
setUserInfo(state,obj){
state.userInfo = obj;
}
},
页面中进行调用
js
this.$store.commit("user/setUserInfo",{token:res.token,userId:res.userId});
4.7 storage存储模块 => vuex持久化处理
封装storage模块,利用本地存储,进行vuex持久化处理。
js
const INFO_KEY = "lz_info"
export const getInfo = ()=>{
const defaultObj = {token:'',userId:''};
const res = localStorage.getItem(INFO_KEY);
return res ? JSON.parse(res) : defaultObj;
}
// 获取个人信息
export const setInfo = (obj)=>{
localStorage.setItem(INFO_KEY,JSON.stringify(obj));
}
// 设置个人信息
export const removeInfo = ()=>{
localStorage.removeItem(INFO_KEY);
}
// 移除个人信息
在user模块使用
js
import { getInfo, setInfo } from "@/utils/storage"
export default{
namespaced:true,
state(){
return{
userInfo:getInfo()
}
},
mutations:{
setUserInfo(state,obj){
state.userInfo = obj;
setInfo(obj);
}
},
actions:{
},
getters:{
}
}
运行结果:
即使刷新了页面,token信息也不会丢失。
4.8 添加请求loading效果
请求后台时,添加loading效果。实现:在请求拦截器中,每次请求,打开loading;在响应拦截器中,每次响应,关闭loading。
js
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
// 开启loading,禁止背景点击
Toast.loading({
message:"加载中...", // 设置轻提示内容
forbidClick:true, // 禁止背景点击
duration:0 // 不会自动消失
})
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 对响应数据做点什么
const res = response.data;
if(res.status != 200){
Toast(res.message);
return Promise.reject(res.message);
}else{
Toast.clear();
// 清除loading效果
}
return res;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
5. 页面访问拦截
有的页面只有当用户满足一定条件下才能访问,比如登录成功后才能查看购物车中商品信息。
这里可以考虑使用路由导航守卫,全局前置守卫。所有的路由一旦被匹配到,都会先经过全局前置守卫;只有全局前置守卫放行,才会真正解析渲染组件,才能看到页面内容。
具体而言,跳转路由后先经过全局前置守卫,在这里边进行判断要跳转的页面的是否存在权限问题,如果没有权限问题,直接放行;否则判断是否有token信息,有的话直接跳到对应页面进行渲染即可;否则,跳转到登录页面。
在路由配置文件添加如下配置:
js
import store from "@/store/index"
const urls = ["/pay","/myorder"];
router.beforeEach((to,from,next)=>{
// to 到哪个页面去的完整路由对象
// from 从哪个页面来的完整路由对象
// next() 是否放行
if(!urls.includes(to.path)){
next();
return
}
const token = store.getters.token;
if(token){
next();
}else{
next("/login");
}
})
export default router
token是全局的配置如下:
js
import Vue from 'vue'
import Vuex from 'vuex'
import user from "@/store/modules/user"
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
getters: {
token(state){
return state.user.userInfo.token;
}
},
mutations: {
},
actions: {
},
modules: {
user
}
})
运行结果:
6. 首页布局
首页数据获取模块的封装
js
import request from '@/utils/request'
export const getHomeData = ()=>{
return request.get('w4?pageId=0&str_1=/page/detail');
}
调用封装好的模块进行页面渲染
js
<template>
<div class="home">
<van-nav-bar title="智慧商城" fixed/>
<!-- 顶部 -->
<van-search v-model="value" placeholder="请输入搜索关键词" />
<!-- 搜索框 -->
<van-swipe :autoplay="3000" :height="200">
<van-swipe-item v-for="(image, index) in images" :key="index">
<img v-lazy="image.imgUrl" />
</van-swipe-item>
</van-swipe>
<!-- 轮播图 -->
<van-grid square icon-size="40" :column-num="5">
<van-grid-item v-for="(item,index) in images2" :key="index" :icon="item.imgUrl" :text="item.text" />
</van-grid>
<div class="middle-sec">
<img :src="totalImg" alt="">
</div>
<div class="goods-item">
<p class="goods-top">-猜你喜欢-</p>
<GoodItem v-for="item in proList" :key="item.goods_id" :pro="item"></GoodItem>
</div>
</div>
</template>
<script>
import GoodItem from '@/components/GoodItem.vue'
import {getHomeData} from '@/api/home'
export default {
name:'Home',
components:{
GoodItem
},
data(){
return{
value:"",
images:[],
// 轮播图
images2:[],
// 导航
proList:[],
totalImg:''
}
},
async created(){
const {data:{pageData}} = await getHomeData();
console.log(pageData);
this.images = pageData.items[1].data;
this.images2 = pageData.items[3].data;
this.proList = pageData.items[6].data;
this.totalImg = pageData.items[4].data[0].imgUrl;
}
}
</script>
<style lang="less" scoped>
.van-nav-bar{
background-color: red;
/deep/ .van-nav-bar__title{
color: white;
font-size: 16px;
}
}
.van-search{
margin-top:12.26667vw;;
}
.van-swipe{
width: 100%;
height: 200px;
img{
height: 200px;
}
/deep/ .van-swipe__indicator{
background-color: red;
}
}
/deep/ .van-icon__image{
border-radius: 8px;
}
.middle-sec{
width: 100%;
img{
width: 100%;
}
}
.goods-item{
margin-bottom: 40px;
}
.goods-top{
height: 30px;
width: 100%;
text-align: center;
line-height: 30px;
font-size: 16px;
}
</style>
7. 搜索历史管理
在搜索页面添加历史记录管理,便捷用户操作。
点击搜索按钮或底下历史记录,都能进行搜索,若之前没有相同搜索关键字,则直接追加到最前面;若之前已有相同搜索关键字,则该原有关键字移除,再追加,这样操作的话新搜索关键字可以在搜索历史记录中提前。
显示效果如下:
8. 商品详情页
商品详情页下需要通过该商品id获取对应信息及获取对应的用户评论数据。界面如下:
9. 加入购物车
只有登录的用户,才能加入购物车。
只需要判断token是否存在,就可以发送购物车的请求;如果token不存在,那么给个提示,引导用户登录,然后再跳回来这个页面。具体实现视频链接在这:加入购物车