Skip to content

注册页面

Element-UI的使用

js
官网:https://element.eleme.cn/#/zh-CN
安装:npm i element-ui
引入入口文件:src->index.js
js
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

Element-UI按需引入

安装 npm install babel-plugin-component -D

  1. 修改 .babelrc配置文件,脚手架若没有.babelrc,可以修改 babel.config.js 文件
js
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}
  1. 在入口文件全局挂载所需要的组件:src->index.js
js
import {Button, Message, Row} from 'element-ui';
// Vue.component(Button.name, Button);
// Vue.component(Row.name, Row);
// 或者
Vue.use(Button);
Vue.use(Row);
Vue.prototype.$message = Message;
  1. 在组件中使用Element组件:src->pages->Register->index.vue
vue
<template>
    <div>
        <h3>注册界面</h3>
        <el-row>
            <el-button @click="fn">默认按钮</el-button>
            <el-button type="primary">主要按钮</el-button>
            <el-button type="success">成功按钮</el-button>
            <el-button type="info">信息按钮</el-button>
            <el-button type="warning">警告按钮</el-button>
            <el-button type="danger">危险按钮</el-button>
        </el-row>
    </div>
</template>

<script>
export default {
    name: "Register",
    methods:{
        fn(){
            this.$message.error('这是一条消息提示');
        }
    }
}
</script>

注册的静态页面

  1. 新建注册页面组件:src->pages->Register->index.vue
vue
<template lang="">
    <div>
        <div class="register">
            <h3>注册新用户
                <span class="go">我有账号,去 <router-link to="/login" target="_blank">登录</router-link>
            </h3>
            <div class="content">
                <label>手机号:</label>
                <input type="text" placeholder="请输入你的手机号">
                <span class="error-msg">错误提示信息</span>
            </div>
            <div class="content">
                <label>验证码:</label>
                <input type="text" placeholder="请输入验证码">
                <button class="getcode">获取验证码</button>
                <span class="error-msg">错误提示信息</span>
            </div>
            <div class="content">
                <label>登录密码:</label>
                <input type="text" placeholder="请输入你的登录密码">
                <span class="error-msg">错误提示信息</span>
            </div>
            <div class="content">
                <label>确认密码:</label>
                <input type="text" placeholder="请输入确认密码">
                <span class="error-msg">错误提示信息</span>
            </div>
            <div class="controls">
                <input name="m1" type="checkbox">
                <span>同意协议并注册《尚品汇用户协议》</span>
                <span class="error-msg">错误提示信息</span>
            </div>
            <div class="btn">
                <button>完成注册</button>
            </div>
        </div>
    </div>
</template>
<script>
export default {
    name: "Register",
}
</script>
<style lang="less" scoped>
    
</style>

发送验证码请求

  1. 封装验证码请求和注册用户请求:src->api->user.js
js
// 导入axios请求
import { sphRequest } from "@/request";

// 注册用户的接口: /api/user/passport/register  post
const postRegister = (data) =>  {
    return sphRequest.post(`/user/passport/register`,data);
}

// 发送验证码的接口: 获取验证码 /api/user/passport/sendCode/{phone} get
const getSendCode = (phone) => {
    return sphRequest.get(`/user/passport/sendCode/${phone}`);
}

// 暴漏数据
export {
    postRegister,
    getSendCode
}
  1. 封装用户数据仓库:src->store->user->index.js
js
import { postRegister } from "@/api/user"

const state = {
    // 用户列表
    usersList: []
}
const getters = {

}
const mutations = {

}

const actions = {
    // 发送注册用户的异步请求
    async postRegisterAsync(content,data){
        // 获取响应数据
        const resData = await postRegister(data);
        // 把响应数据返回
        return resData;
    }
    
}

export default {
    namespaced: true,
    state,
    mutations,
    actions,
    getters
}
  1. 双向绑定数据:src->pages->Register->index.vue
vue
<template lang="">
    <div>
        
    </div>
</template>
<script>
export default {
    data(){
        return{
            phone:'',
            code: "",
            password: "",
            rePassword: "",
            isAllow: false
        }
    }
}
</script>
<style lang="">
    
</style>
  1. 判断手机号是否合法,并发送验证码请求:src->pages->Register->index.vue
vue
<template lang="">
    <div>
        
    </div>
</template>
<script>
export default {
    data(){
        return{
            phone:'',
            code: "",
            password: "",
            rePassword: "",
            isAllow: false
        }
    }
    methods:{
        // 发送验证码
        async sendCode(){
            // 判断手机号是否合法
            if (phoneReg.test(this.phone)){
                // 调用异步请求,并接收
                this.code = await this.$store.dispatch("user/getSendCodeAsync",this.phone);
            }else{
                // 手机号不合法
                this.$message.error('手机号不合法!!!');
            }
        },
    }
}
</script>
<style lang="">
    
</style>
  1. 发送注册用户请求:src->pages->Register->index.vue
vue
<template lang="">
    <div>
        <div class="register">
            <h3>注册新用户
                <span class="go">我有账号,去 <a href="login.html" target="_blank">登录</a>
                </span>
            </h3>
            <div class="content">
                <label>手机号:</label>
                <input v-model="phone" type="text" placeholder="请输入你的手机号">
                
            </div>
            <div class="content">
                <label>验证码:</label>
                <input v-model="code" type="text" placeholder="请输入验证码">
                <button @click="sendCode" class="getcode">获取验证码</button>
                
            </div>
            <div class="content">
                <label>登录密码:</label>
                <input v-model="password" type="password" placeholder="请输入你的登录密码">
                
            </div>
            <div class="content">
                <label>确认密码:</label>
                <input v-model="rePassword" type="password" placeholder="请输入确认密码">
                
            </div>
            <div class="controls">
                <input v-model="isAllow" name="m1" type="checkbox">
                <span>同意协议并注册《尚品汇用户协议》</span>
                <span class="error-msg"></span>
            </div>
            <div class="btn">
                <button @click="goRegister">完成注册</button>
            </div>
        </div>
    </div>
</template>
<script>
export default {
    data(){
        return{
            phone:'',
            code: "",
            password: "",
            rePassword: "",
            isAllow: false
        }
    }
    methods:{
        // 发送注册请求
        async goRegister(){
            // 验证表单
            if (!phoneReg.test(this.phone)) {
                this.$message.error("请输入正确的手机号");
                return;
            }
            if (this.code.length < 1) {
                this.$message.error("请输入验证码");
                return;
            }
            if (this.password.length < 1) {
                this.$message.error("请输入密码]");
                return;
            }
            if (this.password !== this.rePassword) {
                this.$message.error("两次密码不一致!");
                return;
            }
            if (!this.isAllow) {
                this.$message.error("您还未同意协议!");
                return;
            }
            // 从this中解构出
            const { phone, password, code } = this;
            // 发送注册异步请求
            const result = await this.$store.dispatch("user/postRegisterAsync", {
                phone, password, code
            })
            // 成功则跳转 登陆,否则提示
            if (result.code === 200) {
                console.log(result);
                this.$router.push("/login");
            } else {
                this.$message.error(result.message)
            }
        }
    }
}
</script>
<style lang="">
    
</style>

登陆页面

静态登陆组件

  1. 新建登陆组件:src->pages->Login->index.vue
vue
<template lang="">
    <div>
        <!-- 登录 -->
        <div class="login-wrap">
            <div class="login">
                <div class="loginform">
                    <div class="content">
                        <form @submit.prevent="login">
                            <div class="input-text clearFix">
                                <i></i>
                                <input type="text" name="phone" placeholder="手机号">
                                <!-- <span class="error-msg">错误提示信息</span> -->
                            </div>

                            <div class="input-text clearFix">
                                <i class="pwd"></i>
                                <input type="text" name="password" placeholder="请输入密码">
                                <!-- <span class="error-msg">错误提示信息</span> -->
                            </div>

                            <button class="btn">登&nbsp;&nbsp;录</button>
                        </form>
                        <div class="call clearFix">
                            <router-link to="/register" class="register">立即注册</router-link>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>

export default {
    name: "Login",
    methods: {
        // 登陆的方法
        async login(e){
            // 获取输入框中的数值
            const phone = e.target.phone.value.trim();
            const password = e.target.password.value.trim();
            // 验证是否为空
            if (!phone || !password) {
                this.$message.error("手机号与密码不允许为空");
                return;
            }
            // 调用异步请求
            await this.$store.dispatch('user/postLoginAsync',{
                phone,
                password
            })
        }
    },
}
</script>
<style lang="less" scoped>

</style>
  1. 封装登陆的请求:src->api->login.js
js
// 登陆接口:/api/user/passport/login post
const postLogin = (body) => {
    return sphRequest.post('/user/passport/login',body)
}
// 暴漏数据
export {
    postRegister,
    getSendCode,
    postLogin
}
  1. 封装数据仓库store:src->store->user->index.js
js
const actions = {
    // 发送注册用户的异步请求
    async postRegisterAsync(content,data){
        // 获取响应数据
        const resData = await postRegister(data);
        // 把响应数据返回
        return resData;
        
    },
    // 发送验证码的异步请求
    async getSendCodeAsync(content,phone){
        // 获取响应数据
        const result = await getSendCode(phone);
        // 把响应数据返回
        return result.data;
        
    },
    // 发送登陆的异步请求
    async postLoginAsync(content,body){
        // 调用登陆api,解构出响应的code和数据
        const { code, data } = await postLogin(body);
        // 判断响应结果
        if (code === 200){
            Message.success("恭喜您,登陆成功!");
            router.push("/");// 跳转至首页
        }else{
            Message.error("账号或密码错误!");
        }
    }
}

根据Token获取个人信息

token的流程:

1- 用户输入手机,密码,按下回车,在请求体中携带手机以及密码向后端接口发送AJAX请求。(前端)

2-接口接收到手机以及密码以后向数据库中查找是否有满足条件的用户。如果有满足条件的用户,会负责生成token并响应给前端。(服务端)

3- 前端接收到token以后,需要将token保存至localStorage。(前端)

4- 前端在访问一些隐私接口(针对用户)时需要携带上token.传递给服务端接口(前端)

5-服务端接收token以后,对token进行验证(1-验证token是否合法-是否可以解析 2- 验证是否过期),如果验证成功,返回针对于该用户的信息,如果失败返回失败信息(服务端)

6- 前端如果获取到的是失败信息的话需要清除用户个人信息(清除token)重新登陆,是成功信息该渲染渲染,该干嘛 干嘛!(前端)

由于后端接口需要得知请求方的用户标识,根据用户标识获取个人信息,然后响应给前端。所以需要有token.(标识)

  1. 封装获取个人信息的接口:src->api->user.js
js
// 获取用户信息:/api/user/passport/auth/getUserInfo  GET
const getUserInfo = () => {
    return sphRequest.get('/user/passport/auth/getUserInfo');
}
  1. 封装获取个人信息的异步请求:src->store->user->index.js
js
    // 发送登陆的异步请求
    async postLoginAsync(content,body){
        // 调用登陆api,解构出响应的code和数据
        const { code, data } = await postLogin(body);
        // 判断响应结果
        if (code === 200){
            Message.success("恭喜您,登陆成功!");
            // 存储token到localstorage
            saveToken(data.token);
            // 跳转至首页
            router.push("/");
        }else{
            Message.error("账号或密码错误!");
        }
    },
    // 发送获取个人信息的异步请求
    async getUserInfoAsync(){
        const result = await getUserInfo();
        console.log(result);
    }
  1. 封装token为一个工具类:src->utils->auth.js
js
// 从localStorage中获取token
const getToken = () => {
    // 从localStorage中获取token
    localStorage.getItem("token");
}
// 存储token
const saveToken = (token) => {
    localStorage.setItem("token", token);
}
// 删除token
const rmToken = () => {
    localStorage.removeItem("token");
}

// 暴漏数据
export {
    getUserTempId,
    getToken,
    saveToken,
    rmToken,
}
  1. 请求头设置token请求配置项:src->request->sphRequest.js
js
sphRequest.interceptors.request.use(config => {
	nprogress.start();// 开启进度条
	config.headers.userTempId = getUserTempId()
	const token = getToken();
	if(token)
		config.headers.token = token;
	return config;
});

保存个人信息

保存至localStorage不合适,为什么?

答:localStorage的特点会永久保存,除非个人删除或硬盘损坏。如果token过期了,localStorage也存在,这种情况不合理。

可以将个人信息保存至store当中,但刷新数据会丢失。我们可以解决丢失的问题。

  1. 将个人信息保存到store中:src->store->user->index.js
js
const state = {
	userInfo:null
}
const mutations = {
	SAVE_USER_INFO(state,userInfo){
		state.userInfo = userInfo;
	}
}
const actions = {
	// 获取个人信息
	async getUserInfoAsync({commit}){
		const result = await getUserInfo();
		commit("SAVE_USER_INFO",result.data);
	}
}
export default {
	namespaced:true,
	actions,
	state,
	mutations
}
  1. 增加路由前守卫,在每次路由跳转时调用获取个人信息异步请求:src->router->index.js
js
// 创建路由对象
const router = new VueRouter({
    mode:"history",
    routes,
    scrollBehavior(to, from) {
        if (to.meta.ScrollToHeader) {
            return {
                x: 0,// 横向
                y: 0// 纵向
            }
        }
    },
    
});
// 创建路由前守卫
router.beforeEach(async (to, from, next) => {
    // 当存在token但个人信息数据不存在时,即刷新了界面
    if(getToken() && !store.state.user.userInfo){
        // 调用异步请求
        await store.dispatch("user/getUserInfoAsync");
    }
    // 放行
    next();
})

// 导出
export default router;

个人信息的渲染

  1. 修改头部个人信息的渲染:src->components->Header->index.vue
vue
<template lang="">
    <div>
        <div class="loginList">
            <p v-if="userInfo">
                <span>尚品汇欢迎您!{{userInfo.nickName}} </span>
                <router-link to="/loginout" class="register">退出登陆</router-link>
            </p>
            <p v-else>
                <span>请</span>
                <router-link to="/login">登陆</router-link>
                <router-link to="/register" class="register" >免费注册</router-link>
            </p>
    	</div>
    </div>
</template>
<script>
import { mapState } from "vuex";
export default {
    computed:{
        ...mapState("user", ["userInfo"]),
    }
}
</script>
<style lang="">
    
</style>

退出登陆

  1. 封装退出登陆接口:src->api->user.js
js
// 退出登陆
const getLogout = () => {
    return sphRequest.get('/user/passport/logout');
}
  1. 封装退出登陆的store的mutations:src->store->user.js
js
// 退出登陆清空store数据
OUT_LOG(){
    // 把userInfo数据清空
    state.userInfo = null;
    // 调用清除token工具类
    rmToken();
    // 路由跳转到登陆界面
    router.push("/login");

}
  1. 在Header组件中调用:src->components->Header->index.vue
vue
<template lang="">
    <div>
         <p v-if="userInfo">
            <span>尚品汇欢迎您!{{userInfo.nickName}} </span>
            <a  @click.prevent="OUT_LOG" herf="" class="register">退出登陆</a>
        </p>
    </div>
</template>
<script>
export default {
    methods: {
        ...mapMutations("user", ["OUT_LOG"])
    },
}
</script>
<style lang="">
    
</style>

Token过期处理

  1. 修改axios请求:src->request->sphRequest.js
js
import store from "@/store";
import { Message } from "element-ui";

// 响应拦截器
sphRequest.interceptors.response.use(response => {
    // 结束进度条
    nprogress.done();
    // 处理token异常
    if (response.data.code === 208){
        // 清空token,跳转到登陆页面
        store.commit("user/OUT_LOG")
        // 提示
        Message.warning("身份已过期,请重新登陆")
    }
    // 返回响应体
    return response.data;
},err=>{
    // 结束进度条
    nprogress.done();
    //提示错误信息
    alert(err);
    //中断Promise
    return new Promise(() => { });
})

结算界面

静态结算界面

  1. 添加结算界面组件:src->pages->Trade->index.vue
vue
<template lang="">
    ...
</template>
<script>
export default {
    name: "Trade",
}
</script>
<style lang="less" scoped>
    ...
</style>
  1. 添加交易结算路由:src->router->index.vue
js
{
    path:"/trade",
    component:Trade,
    meta:{
        isAuth:true
    }
}

// 创建路由前守卫
router.beforeEach(async (to, from, next) => {
    // 判断个人信息是否存在:当存在token但个人信息数据不存在时,即刷新了界面
    if(getToken() && !store.state.user.userInfo){
        // 调用异步请求
        await store.dispatch("user/getUserInfoAsync");
    }
    // 判断路由是否需要登陆认证
    if(to.meta.isAuth){
        // 判断是否存在token
        if(getToken()){
            next();
        }else{
            next({
                // 跳转到登陆页面
                path:"/login",
                // 把目的地址拼接到query中
                query:{
                    cb:to.path,
                }
            });
        }
    }
    // 默认放行
    next();
})
  1. 购物车页面添加到跳转:src->pages->Cart->index.vue
vue
<template lang="">
    <div class="sumbtn">
        <router-link class="sum-btn" to="/Trade">结算</router-link>
    </div>
</template>
<script>
export default {
    
}
</script>
<style lang="">
    
</style>
  1. 优化登陆后自动跳转到未登录前的页面:src->store->user.js
js
// 发送登陆的异步请求
    async postLoginAsync(content,body){
        // 调用登陆api,解构出响应的code和数据
        const { code, data } = await postLogin(body);
        // 判断响应结果
        if (code === 200){
            Message.success("恭喜您,登陆成功!");
            // 存储token到localstorage
            saveToken(data.token);
            // 跳转至未登录前的页面
            // 判断是否存在cb
            const { cb } = router.history.current.query;
            // 如果存在cb跳转cb,否则跳转到首页
            if (cb){
                router.push(cb);
            }else{
                router.push("/");
            }
        }else{
            Message.error("账号或密码错误!");
        }
    }

交易界面的渲染

  1. 封装获取用户地址信息的api接口:src->api->user.js
js
// 获取用户的地址信息:获取用户地址信息 /user/userAddress/auth/findUserAddressList
const getUserAddressList = () => {
    return sphRequest.get('/user/userAddress/auth/findUserAddressList');
}
  1. 封装获取用户交易信息的api接口:src->api->order.js
js
// 导入axios请求
import { sphRequest } from "@/request";

// 获取用户交易信息:
const getTradeList = () => {
    return sphRequest.get("/order/auth/trade");
}

// 暴漏数据
export {
    getTradeList
}
  1. 封装用户地址信息的store数据仓库:src->store->user.js
js
import {findUserAddressList} from "@/api/user";
const state = {
	addressList:[]
}
const mutations = {
	SAVE_ADDRESS_LIST(state,payload){
		state.addressList = payload;
	}
}
const actions = {
	// 获取地址列表
	async findUserAddressListAsync({commit}){
		const {data} = await findUserAddressList();
		commit("SAVE_ADDRESS_LIST",data);
	}
}
const getters = {
	addressDefault(state){
		return state.addressList.find(v=>v.isDefault==='1') || {}
	}
}
export default {
	namespaced:true,
	actions,
	state,
	mutations,
	getters
}
  1. 封装用户交易信息的store数据仓库:src->store->trade.js
js
import { getTradeList } from "@/api/order";

const state = {
    // 交易信息
    tradeInfo:{
        detailArrayList: [
            
        ]
    }
}

const mutations = {
    SAVE_TRADE_INFO(state, payload) {
        state.tradeInfo = payload;
    }
}

const actions = {
    // 发送交易信息的异步请求
    async getTradeListAsync({ commit }) {
        const result = await getTradeList();
        commit("SAVE_TRADE_INFO", result.data);
    }
}

export default {
    namespaced: true,
    actions,
    state,
    mutations
}
  1. 引入到主模块中:src->store->index.js
js
import Vue from "vue";
import Vuex from "vuex";
import order from "@/store/order";
Vue.use(Vuex);
const store = new Vuex.Store({
	modules:{
		order
	}
});
export default store;
  1. 渲染交易页面:src->pages->Trade->index.vue
vue
<template lang="">
    <div>
        <div>
            <!-- 买卖 -->
            <h3 class="title">填写并核对订单信息</h3>
            <div class="content">

                <h5 class="receive">收件人信息</h5>
                <div
                    v-for="item in $store.state.user.addressList"
                    :key="item.id"
                    @click="CHANGE_ADDRESS_DEFAULT_BY_ID(item.id)" 
                    class="address clearFix">
                    <span :class="'username'+(item.isDefault==='1'?' selected':'')">{{item.consignee}}</span>
                    <p>
                        <span class="s1">{{item.fullAddress}}</span>
                        <span class="s2">{{item.phoneNum}}</span>
                        <span v-show="item.isDefault==='1'" class="s3">默认地址</span>
                    </p>
                </div>
                
                <div class="line"></div>
                <h5 class="pay">支付方式</h5>
                <div class="address clearFix">
                    <span class="username selected">在线支付</span>
                    <span class="username" style="margin-left:5px;">货到付款</span>
                </div>
                <div class="line"></div>
                <h5 class="pay">送货清单</h5>
                <div class="way">
                    <h5>配送方式</h5>
                    <div class="info clearFix">
                        <span class="s1">天天快递</span>
                        <p>配送时间:预计8月10日(周三)09:00-15:00送达</p>
                    </div>
                </div>
                <div class="detail">
                    <h5>商品清单</h5>
                    <ul 
                        v-for="(item,index) in tradeInfo.detailArrayList"
                        :key=index
                        class="list clearFix">
                        <li>
                            <img width="82" :src="item.imgUrl" alt="">
                        </li>
                        <li>
                            <p>
                                {{item.skuName}}</p>
                            <h4>7天无理由退货</h4>
                        </li>
                        <li>
                            <h3>{{item.orderPrice|currency(2,"¥")}}</h3>
                        </li>
                        <li>X{{item.skuNum}}</li>
                        <li>有货</li>
                    </ul>
                </div>
            </div>
            <div class="money clearFix">
                <ul>
                    <li>
                        <b><i>{{tradeInfo.totalNum}}</i>件商品,总商品金额</b>
                        <span>{{tradeInfo.totalAmount| currency(2,"¥")}}</span>
                    </li>
                    <li>
                        <b>返现:</b>
                        <span>0.00</span>
                    </li>
                    <li>
                        <b>运费:</b>
                        <span>0.00</span>
                    </li>
                </ul>
            </div>
            <div class="trade">
                <div class="price">应付金额:<span>{{tradeInfo.totalAmount| currency(2,"¥")}}</span></div>
                <div class="receiveInfo">
                    寄送至:
                    <span>{{addressDefault.userAddress}}</span>
                    收货人:<span >{{addressDefault.consignee}}</span>
                    <span>{{addressDefault.phoneNum}}</span>
                </div>
            </div>
            <div class="sub clearFix">
                <a href="##" class="subBtn">提交订单</a>
            </div>
        </div>
    </div>
</template>

<script>
import { mapGetters, mapMutations, mapState } from 'vuex';

export default {
    name: "Trade",
    computed:{
        // 获取用户的默认地址
        ...mapGetters("user", ["addressDefault"]),
        ...mapState("trade", ["tradeInfo"])
    },
    methods:{
        ...mapMutations("user", ["CHANGE_ADDRESS_DEFAULT_BY_ID"])
    },
    async mounted(){
        // 发送获取用户交易异步请求
        await this.$store.dispatch("trade/getTradeListAsync");
        // 发送获取用户地址异步请求
        await this.$store.dispatch("user/findUserAddressListAsync");
    }
}
</script>

支付页面

思路:(目标:点击提交订单按钮完成订单的操作)

  • 应该为订单按钮增加单击事件
  • 在单击事件中收集订单相关的数据,调用接口
  • 接口调用成功后进入到订单成功支付界面。

发送提交请求

  1. 封装发送提交订单的api接口:src->api->order.js
js
// 提交订单的接口 
export const postSubmitOrder = (tradeNo,body) => {
    return sphRequest.post(`/order/auth/submitOrder?tradeNo=${tradeNo}`, body)
}
  1. 封装订单页面的store数据:src->store->order.js
js
import {getTradeList, postSubmitOrder} from "@/api/order";
const actions = {
	// 提交订单
	async postSubmitOrderAsync(context,{tradeNo,body}){
		const result = await postSubmitOrder(tradeNo,body);
		return result;
	}
}
export default {
	namespaced:true,
	actions,
	state,
	mutations
}
  1. 在交易页面调用异步请求:src->pages->Trade->index.vue
vue
<div class="bbs">
	<h5>买家留言:</h5>
	<textarea v-model.trim="orderComment" placeholder="建议留言前先与商家沟通确认" class="remarks-cont"></textarea>
</div>

<script>
export default {
    data(){
        return{
            orderComment:'',
        }
    },
    methods:{
        ...mapMutations("user", ["CHANGE_ADDRESS_DEFAULT_BY_ID"]),
        // 提交订单
        async submitOrder(){
            
            // 从默认地址中解构出 名字 手机号 地址
            const { consignee, phoneNum: consigneeTel, fullAddress: deliveryAddress } = this.addressDefault;
            // 获取买家留言
            const { orderComment } = this;
            // 发送异步提交请求
            await this.$store.dispatch("trade/postSubmitOrderAsync",{
                // 订单编号
                tradeNo: this.tradeInfo.tradeNo,
                body: {
                    // 收件人姓名
                    consignee,
                    // 收件人电话
                    consigneeTel,
                    // 收件地址
                    deliveryAddress,
                    // 支付方式
                    paymentWay: "ONLINE",
                    // 订单备注
                    orderComment,
                    // 存储多个商品对象的数组
                    orderDetailList:this.tradeInfo.detailArrayList
                }
            })
        }
    },
}
	
        
</script>

支付订单页面

  1. 添加静态订单页面:src->pages->Pay-index.vue
vue
<template>
    <!--中间内容-->
    <div class="pay-main">
        <div class="pay-container">
            <div class="checkout-tit">
                <h4 class="tit-txt">
                    <span class="success-icon"></span>
                    <span class="success-info">订单提交成功,请您及时付款,以便尽快为您发货~~</span>
                </h4>
                <div class="paymark">
                    <span class="fl">请您在提交订单<em class="orange time">4小时</em>之内完成支付,超时订单会自动取消。
                        订单号:<em>{{payInfo.orderId}}</em>
                    </span>
                    <span class="fr"><em class="lead">应付金额:</em><em class="orange money">¥{{payInfo.totalFee}}</em></span>
                </div>
            </div>
            <div class="checkout-info">
                <h4>重要说明:</h4>
                <ol>
                    <li>尚品汇商城支付平台目前支持<span class="zfb">支付宝</span>支付方式。</li>
                    <li>其它支付渠道正在调试中,敬请期待。</li>
                    <li>为了保证您的购物支付流程顺利完成,请保存以下支付宝信息。</li>
                </ol>
                <h4>支付宝账户信息:(很重要,<span class="save">请保存!!!</span>)</h4>
                <ul>
                    <li>支付帐号:11111111</li>
                    <li>密码:111111</li>
                    <li>支付密码:111111</li>
                </ul>
            </div>
            <div class="checkout-steps">
                <div class="step-tit">
                    <h5>支付平台</h5>
                </div>
                <div class="step-cont">
                    <ul class="payType">
                        <li><img src="./images/pay2.jpg"></li>
                        <li><img src="./images/pay3.jpg"></li>
                    </ul>

                </div>
                <div class="hr"></div>

                <div class="payshipInfo">
                    <div class="step-tit">
                        <h5>支付网银</h5>
                    </div>
                    <div class="step-cont">
                        <ul class="payType">
                            <li><img src="./images/pay10.jpg"></li>
                            <li><img src="./images/pay11.jpg"></li>
                            <li><img src="./images/pay12.jpg"></li>
                            <li><img src="./images/pay13.jpg"></li>
                            <li><img src="./images/pay14.jpg"></li>
                            <li><img src="./images/pay15.jpg"></li>
                            <li><img src="./images/pay16.jpg"></li>
                            <li><img src="./images/pay17.jpg"></li>
                            <li><img src="./images/pay18.jpg"></li>
                            <li><img src="./images/pay19.jpg"></li>
                            <li><img src="./images/pay20.jpg"></li>
                            <li><img src="./images/pay21.jpg"></li>
                            <li><img src="./images/pay22.jpg"></li>

                        </ul>
                    </div>

                </div>
                <div class="hr"></div>

                <div class="submit">
                    <a class="btn" href="paysuccess.html" target="_blank">立即支付</a>
                </div>
                <div class="otherpay">
                    <div class="step-tit">
                        <h5>其他支付方式</h5>
                    </div>
                    <div class="step-cont">
                        <span><a href="weixinpay.html" target="_blank">微信支付</a></span>
                        <span>中国银联</span>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
import {getCreateNative} from "@/api/payment";

export default {
    name: "Pay",
    data(){
        return {
            payInfo:{}
        }
    },
    mounted(){
        getCreateNative(this.$route.params.orderId).then(result=>{
            console.log(result)
            this.payInfo = result.data;
        })
    }
}
</script>
  1. 添加获取订单支付信息的api:src->api->payment.js
js
import { sphRequest } from "@/request";

// 获取订单支付信息:/payment/weixin/createNative/{orderId}
export const getCreateNative = (orderId) => sphRequest.get(`/payment/weixin/createNative/${orderId}`);
  1. 添加订单页面的路由:src->router->index.js
js
{
    path:"/pay/:orderId.html",
        component:Pay
}
  1. 修改结算页面的路由跳转:src->pages->Trade->index.vue
js
async submitOrder(){
    const {consignee,phoneNum:consigneeTel,fullAddress:deliveryAddress} = this.addressDefault;
    const {orderComment} = this;
    const result = await postSubmitOrder({
        tradeNo:this.$store.state.order.tradeInfo.tradeNo,
        // 收件人姓名
        consignee,
        // 收件人电话
        consigneeTel,
        // 收件地址
        deliveryAddress,
        // 支付方式
        paymentWay:"ONLINE",
        // 订单备注
        orderComment,
        // 存储多个商品对象的数组
        orderDetailList:this.$store.state.order.tradeInfo.detailArrayList
    });
    this.$router.push("/pay/"+result.data+".html")
}

弹出支付窗口

  1. 在入口文件中引入Element-UI组件:src->index.js
js
import {Button, Message,MessageBox, Row} from 'element-ui';
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$alert = MessageBox.alert;
Vue.prototype.$message = Message;
  1. 在订单页面使用Element组件:src->pages->Pay->index.vue
vue
<template>
    <div class="submit">
        <a @click.prevent="payHandler" class="btn" href="" target="_blank">立即支付</a>
    </div>
</template>
<script>
        methods:{
        payHandler(){
            this.$alert('<img width="200" src="/images/1.jpg"/>', '用微信扫描下方二维码完成支付!', {
                // 是否将 message 属性作为 HTML 片段处理
                dangerouslyUseHTMLString: true,
                // 是否居中布局
                center:true,
                // 是否显示取消按钮
                showCancelButton:true,
                // 完成按钮的文本内容
                confirmButtonText:"已完成微信支付",
                // 取消按钮的文本内容
                cancelButtonText:"支付遇到问题",
                // MessageBox 是否显示右上角关闭按钮
                showClose:false,
                callback:(action) => {
                    if (action === "confirm") {
                        console.log("已完成支付"),
                        clearTimeout(this.timer);
                        this.$router.push("/paySuccess")
                    } else {
                        console.log("支付遇到问题")
                        clearTimeout(this.timer);
                        this.$message.warning("如果遇到问题,可以联系客服!")
                    }
                }
            });
        }
    },
</script>

生成二维码

使用第三方插件:qrcode

  1. 下载并挂载:src->index.js
js
npm install qrcode
  1. 在订单页面中使用:src->page->Pay->index.vue
vue
<script>
import QRCode from 'qrcode'
	  async payHandler(){
            const base64Url = await QRCode.toDataURL(this.payInfo.codeUrl);
            this.$alert(`<img width="200" src="${base64Url}"/>`, '用微信扫描下方二维码完成支付!', {
                // 是否将 message 属性作为 HTML 片段处理
                dangerouslyUseHTMLString: true,
                // 是否居中布局
                center:true,
                // 是否显示取消按钮
                showCancelButton:true,
                // 完成按钮的文本内容
                confirmButtonText:"已完成微信支付",
                // 取消按钮的文本内容
                cancelButtonText:"支付遇到问题",
                // MessageBox 是否显示右上角关闭按钮
                showClose:false,
                callback(action){
                    if(action === "confirm"){
                        console.log("已完成支付")
                    }else{
                        console.log("支付遇到问题")
                    }
                }
            });
        }
</script>

支付完成后跳转

  • 用户提交订单进入到支付页面。
    • 用户提交订单会向服务端发送请求,服务端接收到请求以后,会在数据库中生成一条订单记录(状态为未支付)
  • 在支付页面中可以获取基本的支付信息(订单号,总价格等)
    • 在支付页面中调用服务端接口,该接口将生成的订单信息响应给前端。
  • 用户进行支付。用心脏请求不必向服务端发送请求(订单的状态)
    • 用户扫描二维码后,会进行支付。不管成功还是失败均会调用接口(微信调用接口)。服务端接口接收到微信的调用后,会更改支付状态。(未支付-----》支付成功,未支付-----》支付失败)
  1. 添加静态支付成功页面:src->pages->PaySuccess->index.vue
vue
<template lang="">
    <div>
        <div class="paysuccess">
            <div class="success">
                <h3>
                    
                    <svg t="1723690508049" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2632" width="24" height="24"><path d="M501.01 0C224.501 0 0.395 229.199 0.395 512s224.106 512 500.627 512c276.514 0 500.62-229.199 500.62-512S777.434 0 501.016 0z m-39.104 733L257.939 514.3l52.21-44.304 118.02 97.41c48.011-60.3 155.566-180.6 303.506-276.399l12.418 30.4c-135.817 131.501-247.187 316.798-282.187 411.6z" fill="#06B464" p-id="2633"></path></svg>
                    恭喜您,支付成功啦!
                </h3>
                <div class="paydetail">
                    <p class="button">
                        <a href="myOrder.html" class="btn-look">查看订单</a>
                        <a href="index.html" class="btn-goshop">继续购物</a>
                    </p>
                </div>
            </div>
        </div>
    </div>
</template>
  1. 封装查询支付状态的api请求:src->api->payment.js
js
// 获取订单的支付状态:/payment/weixin/queryPayStatus/{orderId}
export const getQueryPayStatus = orderId => sphRequest.get(`/payment/weixin/queryPayStatus/${orderId}`);
  1. 修改支付页面组件:src->pages->Pay->index.vue
js
methods:{
        payTimeOut(){
            this.timer = setTimeout(async ()=>{
                const status =await  getQueryPayStatus(this.$route.params.orderId);
                if(status.code === 205){
                    this.payTimeOut();
                }else{
                    console.log("status",status);
                    this.$message.success("支付成功!");
                    this.$msgbox.close();// 关闭弹窗
                    clearTimeout(this.timer);
                    this.$router.push("/paySuccess");
                }

            },1000);
        },
        async payHandler(){
            const base64Url = await QRCode.toDataURL(this.payInfo.codeUrl);
            this.$alert(`<img width="200" src="${base64Url}"/>`, '用微信扫描下方二维码完成支付!', {
                // 是否将 message 属性作为 HTML 片段处理
                dangerouslyUseHTMLString: true,
                // 是否居中布局
                center:true,
                // 是否显示取消按钮
                showCancelButton:true,
                // 完成按钮的文本内容
                confirmButtonText:"已完成微信支付",
                // 取消按钮的文本内容
                cancelButtonText:"支付遇到问题",
                // MessageBox 是否显示右上角关闭按钮
                showClose:false,
                callback:(action)=>{
                    clearTimeout(this.timer);
                    if(action === "confirm"){
                        console.log("已完成支付");
                        this.$router.push("/paySuccess")
                    }else{
                        console.log("支付遇到问题");
                        this.$message.warning("如果遇到问题,可以联系客服!")
                    }
                }
            });

            this.payTimeOut();
            // this.timer = setInterval(async ()=>{
            //     const status =await  getQueryPayStatus(this.$route.params.orderId);
            //     console.log("status",status);
            //     this.$message.success("支付成功!");
            //     this.$msgbox.close();// 关闭弹窗
            //     clearInterval(this.timer);
            //     this.$router.push("/paySuccess");
            // },200);
        }
    },
  1. 修改axios请求拦截器:src->request->sphRequest.js
js
暂时不动
sphRequest.interceptors.response.use(response => {
	// 1- 增加判断code是否为205或200正常现象
	if(response.data.code === 200 || response.data.code === 205){
        // 返回响应体
		return response.data;
	}else{
        Message.error("异常:"+response.data.message);
        // 停止请求和后续动作
		return new Promise(()=>{});
    }
}
  1. 添加支付成功的路由:src->router->index.js
js
// 支付成功页面
    {
        path: "/paySuccess",
        component: PaySuccess
    }

订单页面

优化

懒加载

  1. 路由懒加载,修改路由导入方式:src->router->index.js
js
const Home = ()=>import("@/pages/Home");
const Login = ()=>import("@/pages/Login");
const Register = ()=>import("@/pages/Register");
const Search = ()=>import("@/pages/Search");
const Detail = ()=>import("@/pages/Detail");
const AddCartSuccess = ()=>import("@/pages/AddCartSuccess");
const Cart = ()=>import("@/pages/Cart");
const Trade = ()=>import("@/pages/Trade");
const Pay = ()=>import("@/pages/Pay");
const PaySuccess = ()=>import("@/pages/PaySuccess");
  1. 图片懒加载:
js
下载:npm install vue-lazyload@1
  1. 使用图片懒加载:src->index.js
js
import VueLazyLoad from "vue-lazyload";
import loading from "@/assets/images/loading.gif"
Vue.use(VueLazyLoad,{
	// 图片未加载前给予呈现的图片
	loading
})
  1. 需要实现图片懒加载的图片上增加指令v-lazy:src->pages->Search->index.vue
js
 <img v-lazy="item.defaultImg" />

路由权限

1- 加入购物车(addCartSuccess)成功界面(指定:搜索(search),详情(detail))

2- 提交订单()进入到的支付页面(指定:购物车界面(cart))

3- 支付成功页面(指定:支付界面(pay))

方式1:在购物车页面进入路由前进行判断:src->pages->AddCartSuccess->index.vue

js
// 路由前的钩子函数
beforeRouteEnter(to,from,next){
    // 只有来自于搜索页以及详情页才可以允许进入到该路由
    // if(["detail","search"].includes(from.name)){
    //     next();
    // }else next("/")

    if(from.meta.goCartSuccess) next();
    else next("/")
}

方式2:在对应路由上加上路由前进行判断:src->router->index.js

js
{
    // 成功加入购物车界面
    path:"/addCartSuccess",
        component:AddCartSuccess,
            beforeEnter(to,from,next){
            if(from.meta.goCartSuccess) next();
            else next("/")
        }
}

表单验证

使用第三方插件进行表单验证:vee-validate@2

  1. 下载,安装
js
npm i vee-validate@2

https://vee-validate.logaretm.com/v3/guide/basics.html#adding-rules
  1. 封装使用:src->utils->validate.js
js
import Vue from 'vue';

import VeeValidate, { Validator } from "vee-validate"
import { phoneReg, pwdReg } from "@/utils/reg";
Vue.use(VeeValidate);

// 重写规则:
Validator.extend("required", {
    // 该函数的返回值是一个布尔,为true说明符合规则,false不符合规则
    validate: value => value.trim().length > 0,
    // 当规则为false时,返回的值即是错误显示信息
    // file指向的是input标签中name设置的属性值
    getMessage(file) {
        return file + "不允许为空!"
    }
});

// 验证手机号:
Validator.extend("phone_rule", {
    // 该函数的返回值是一个布尔,为true说明符合规则,false不符合规则
    validate: value => phoneReg.test(value),
    // 当规则为false时,返回的值即是错误显示信息
    // file指向的是input标签中name设置的属性值
    getMessage: () => "请输入正确的手机号!"
});

// 验证密码:
Validator.extend("pwd_rule", {
    // 该函数的返回值是一个布尔,为true说明符合规则,false不符合规则
    validate: value => pwdReg.test(value),
    // 当规则为false时,返回的值即是错误显示信息
    // file指向的是input标签中name设置的属性值
    getMessage: () => "请输入6至21位密码!"
});
  1. 引入:src->index.js
js
import "./utils/validate";
  1. 在登陆页面中使用:src->pages->Login->index.vue
vue
<template>
    <!-- 登录 -->
    <div class="login-wrap">
        <div class="login">
            <div class="loginform">
                <div class="content">
                    <form @submit.prevent="login">
                        <div class="input-text clearFix">
                            <i></i>
                            <input
                                    v-validate="{required:true,phone_rule:true}"
                                    type="text"
                                    name="phone"
                                    placeholder="手机号">
                            <span class="error-msg">{{errors.first("phone")}}</span>
                        </div>

                        <div class="input-text clearFix">
                            <i class="pwd"></i>
                            <input v-validate="{required:true,pwd_rule:true}" type="text" name="password" placeholder="请输入密码">
                            <span class="error-msg">{{errors.first("password")}}</span>
                        </div>

                        <button class="btn">登&nbsp;&nbsp;录</button>
                    </form>
                    <div class="call clearFix">
                        <router-link to="/register" class="register">立即注册</router-link>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    name: "Login",
    methods:{
        // 登陆的方法
        async login(e){
            // 所有的规则全部满足为true,有一个不满足为false
            const result = await this.$validator.validateAll();
            // 获取输入框中的数值
            const phone = e.target.phone.value.trim();
            const password = e.target.password.value.trim();
            
            // 验证是否为空
            if (result) {
                // 调用异步请求
                await this.$store.dispatch('user/postLoginAsync', {
                    phone,
                    password
                })
            } else if (!phone || !password){
                Message.warning("用户名或密码不能为空")
            }
        }
    },
    mounted(){
       
    }
}
</script>

打包

打包项目

  1. 输入打包命令进行打包:
js
npm run build
  1. 复制dist目录的文件到目录site
js
  1. 上线:site->server.js
js
const express = require("express");
// 解决路由404问题第一步下载:cnpm install connect-history-api-fallback
// 解决路由404问题第二步引入:
const history = require("connect-history-api-fallback");
// 解决代理问题第一步下载:cnpm i http-proxy-middleware
// 解决代理问题第二步引入:
const {createProxyMiddleware} = require("http-proxy-middleware")

const app = express();
// 解决路由404问题第三步:将history当作中间件使用,当找不到指定的路由时,会将index.html作为渲染的界面
app.use(history());
// 将当前目录设置为静态资源
app.use(express.static(__dirname));

// 解决代理问题第三步:配置代理
app.use("/api",createProxyMiddleware({
	target:"http://sph-h5-api.atguigu.cn/api",
	changeOrigin:true
}))
app.use("/my",createProxyMiddleware({
	target:"http://zhangpeiyue.com:8082",
	changeOrigin:true,
	pathRewrite:{
		"^/my":""
	}
}))
app.listen(80,()=>{
	console.log("success");
})