前端:Vue学习 - 智慧商城项目

前端: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);
    }
}

实现步骤:

  1. 点击按钮,实现倒计时效果
  2. 倒计时之前进行校验(手机号、图片验证码)
  3. 请求短信验证码接口,添加相应提示

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不存在,那么给个提示,引导用户登录,然后再跳回来这个页面。具体实现视频链接在这:加入购物车

相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
执笔论英雄3 小时前
【大模型学习cuda】入们第一个例子-向量和
学习
wdfk_prog3 小时前
[Linux]学习笔记系列 -- [drivers][input]input
linux·笔记·学习
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端