进行支付
01-提交订单
目标:提交订单,生成订单,跳转支付页面
大致步骤:
- 定义API函数
- 确认需要提交字段
- 组件内绑定事件进行提交
- 判断是否有收货地址
- 进行提交
- 成功,跳转支付页面
- 失败,错误提示
落地代码:
- 定义API函数
// 创建订单
export const createOrder = (data) => request("/member/order", "post", data);
- 组件内绑定事件进行提交
import { createOrder } from "@/api/order";
// 创建订单
const router = useRouter();
const submitOrder = async () => {
if (!defAddr.value.id) return Message({ text: "请选择收货地址" });
try {
const data = await createOrder({
goods: info.value.goods.map(({ skuId, count }) => ({ skuId, count })),
addressId: defAddr.value.id,
// 以下是默认信息
deliveryTimeType: 1,
payType: 1,
payChannel: 1,
buyerMessage: "",
});
router.push(`/user/pay?id=${data.result.id}`);
} catch (e) {
Message({
type: "error",
text: e.response.data.message || "确认订单失败",
});
}
};
// return { createOrder }
<!-- 提交订单 -->
<div class="submit">
<XtxButton @click="submitOrder" type="primary">提交订单</XtxButton>
</div>
02-支付-组件基础
目标:配置路由和组件,完成基础信息渲染
大致步骤:
- 配置路由信息和组件
- 定义API,完成基础信息渲染
落地代码:
- 配置路由信息和组件
views/pay/index.vue
<template>
<div class="xtx-pay-page">
<div class="container">
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem to="/cart">购物车</XtxBreadItem>
<XtxBreadItem>支付订单</XtxBreadItem>
</XtxBread>
<!-- 付款信息 -->
<div class="pay-info">
<span class="icon iconfont icon-queren2"></span>
<div class="tip">
<p>订单提交成功!请尽快完成支付。</p>
<p>支付还剩 <span>24分59秒</span>, 超时后将取消订单</p>
</div>
<div class="amount">
<span>应付总额:</span>
<span>¥5673.00</span>
</div>
</div>
<!-- 付款方式 -->
<div class="pay-type">
<p class="head">选择以下支付方式付款</p>
<div class="item">
<p>支付平台</p>
<a class="btn wx" href="javascript:;"></a>
<a class="btn alipay" href="javascript:;"></a>
</div>
<div class="item">
<p>支付方式</p>
<a class="btn" href="javascript:;">招商银行</a>
<a class="btn" href="javascript:;">工商银行</a>
<a class="btn" href="javascript:;">建设银行</a>
<a class="btn" href="javascript:;">农业银行</a>
<a class="btn" href="javascript:;">交通银行</a>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "XtxPayPage",
};
</script>
<style scoped lang="less">
.pay-info {
background: #fff;
display: flex;
align-items: center;
height: 240px;
padding: 0 80px;
.icon {
font-size: 80px;
color: #1dc779;
}
.tip {
padding-left: 10px;
flex: 1;
p {
&:first-child {
font-size: 20px;
margin-bottom: 5px;
}
&:last-child {
color: #999;
font-size: 16px;
}
}
}
.amount {
span {
&:first-child {
font-size: 16px;
color: #999;
}
&:last-child {
color: var(--price-color);
font-size: 20px;
}
}
}
}
.pay-type {
margin-top: 20px;
background-color: #fff;
padding-bottom: 70px;
p {
line-height: 70px;
height: 70px;
padding-left: 30px;
font-size: 16px;
&.head {
border-bottom: 1px solid #f5f5f5;
}
}
.btn {
width: 150px;
height: 50px;
border: 1px solid #e4e4e4;
text-align: center;
line-height: 48px;
margin-left: 30px;
color: #666666;
display: inline-block;
&.active,
&:hover {
border-color: var(--xtx-color);
}
&.alipay {
background: url(https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/7b6b02396368c9314528c0bbd85a2e06.png)
no-repeat center / contain;
}
&.wx {
background: url(https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/c66f98cff8649bd5ba722c2e8067c6ca.jpg)
no-repeat center / contain;
}
}
}
</style>
router/index.js
const Pay = () => import("@/views/pay/index");
{ path: "/cart", component: Cart },
{ path: "/user/checkout", component: Checkout },
+ { path: "/user/pay", component: Pay },
- 定义API
// 订单详情
export const getOrderInfo = (id) => request(`/member/order/${id}`, "get");
- 完成基础信息渲染
views/pay/index.vue
import { onMounted, ref } from "vue";
import { getOrderInfo } from "@/api/order";
import { useRoute } from "vue-router";
export default {
name: "XtxPayPage",
setup() {
const route = useRoute();
const order = ref(null);
onMounted(async () => {
const data = await getOrderInfo(route.query.id);
order.value = data.result;
});
return { order };
},
};
<!-- 付款信息 -->
<div class="pay-info">
<span class="icon iconfont icon-queren2"></span>
<div class="tip">
<p>订单提交成功!请尽快完成支付。</p>
<p v-if="order.countdown > 0">
支付还剩 <span>24分59秒</span>, 超时后将取消订单
</p>
<p v-else>订单已经超时</p>
</div>
<div class="amount">
<span>应付总额:</span>
<span>¥{{ order.payMoney }}</span>
</div>
</div>
03-支付-倒计时
目标:完成支付倒计时设置
大致步骤:
- 使用 useIntervalFn 进行倒计时
- 组件初始化开启
- 基于 dayjs 定义格式化时间的函数
落地代码:
- 使用 useIntervalFn 进行倒计时
import { useIntervalFn } from "@vueuse/core";
import { onMounted, onUnmounted, ref } from "vue";
// 倒计时操作
const { resume, pause } = useIntervalFn(
() => {
order.value.countdown--;
if (order.value.countdown <= 0) {
pause();
}
},
1000,
{
immediate: false,
}
);
onUnmounted(() => pause());
- 组件初始化开启
onMounted(async () => {
const data = await getOrderInfo(route.query.id);
order.value = data.result;
+ // 开启倒计时
+ resume();
});
- 基于 dayjs 定义格式化时间的函数
yarn add dayjs
import dayjs from "dayjs";
// 格式化时间
const formatTime = (time) => {
return dayjs.unix(time).format("mm分ss秒");
}; <p v-if="order.countdown > 0">
支付还剩 <span>{{ formatTime(order.countdown) }}</span>
, 超时后将取消订单
</p>
return { order, formatTime };
<p v-if="order.countdown > 0">
支付还剩 <span>{{ formatTime(order.countdown) }}</span>
, 超时后将取消订单
</p>
04-支付-后端流程
目标:知道小兔鲜儿支付流程
支付流程:
- PC端点击支付按钮,A标签设置新开页,按照后台支付地址携带:订单ID和回跳地址
- 后台服务跳转支付宝支付页面,支付成功后台回跳后台,后台重定向到PC端支付结果页
- PC端支付结果页面,根据回跳地址上的订单ID,查询订单的支付状态,展示支付结果
回跳地址:
# 当前的前端服务地址
http://localhost:8080/#/user/pay/result
测试账号:
- 如果使用扫码支付需要下载
沙箱支付宝,开放平台扫码下载(仅支持安卓)。
// 买家账号 jfjbwb4477@sandbox.com
// 登录密码 111111
// 支付密码 111111
05-支付-跳转支付
目标:支付打开新页,当前页打开提示框。
大致步骤:
- 和
request.js共用后台服务器地址 - 拼接地址给 支付宝 A链接使用
- 跳转的同时当前页打开等待对话窗口
具体代码:
- 和
request.js共用后台服务器地址
utils/request.js
+export const baseURL = "http://pcapi-xiaotuxian-front-devtest.itheima.net/";
const instance = axios.create({
+ baseURL,
timeout: 5000,
});
- 拼接地址给 支付宝 A链接使用
import { computed } from "vue";
const payUrl = computed(() => {
const redirectUrl = encodeURIComponent(
"http://localhost:8080/#/user/pay/result"
);
return `${baseURL}pay/aliPay?orderId=${order.value.id}&redirect=${redirectUrl}`;
});
return { order, formatTime, payUrl };
<a class="btn alipay" target="_blank" :href="payUrl"></a>
- 跳转的同时当前页打开等待对话窗口
<XtxDialog title="正在支付..." v-model:visible="visibleDialog">
<div class="pay-wait">
<img src="@/assets/load.gif" alt="" />
<div v-if="order">
<p>如果支付成功:</p>
<a href="javascript:;"> 查看订单详情> </a>
<p>如果支付失败:</p>
<a href="javascript:;">查看相关疑问></a>
</div>
</div>
</XtxDialog>
.pay-wait {
display: flex;
justify-content: space-around;
p {
margin-top: 30px;
font-size: 14px;
}
a {
color: var(--xtx-color);
}
}
<a
+ @click="visibleDialog = true"
class="btn alipay"
target="_blank"
:href="payUrl"
></a>
const visibleDialog = ref(false);
return { order, formatTime, payUrl, visibleDialog };
06-支付-结果展示
目标:配置路由和组件,展示支付结果
大致步骤:
- 路由与组件
- 渲染页面
落地代码:
- 路由与组件
views/pay/result.vue
<template>
<div class="xtx-pay-result-page">
<div class="container">
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem to="/cart">购物车</XtxBreadItem>
<XtxBreadItem>支付结果</XtxBreadItem>
</XtxBread>
<!-- 支付结果 -->
<div class="pay-result">
<span v-if="$route.query.payResult" class="iconfont icon-queren2 green">
</span>
<span v-else class="iconfont icon-shanchu red"></span>
<p class="tit">
订单支付{{ $route.query.payResult ? "成功" : "失败" }}
</p>
<p class="tip">我们将尽快为您发货,收货期间请保持手机畅通</p>
<p>支付方式:<span>微信支付</span></p>
<p>
支付金额:<span class="red">¥{{ order?.payMoney }}</span>
</p>
<div class="btn">
<XtxButton type="primary" style="margin-right: 20px">
查看订单
</XtxButton>
<XtxButton type="gray">进入首页</XtxButton>
</div>
<p class="alert">
<span class="iconfont icon-tip"></span>
温馨提示:小兔鲜儿不会以订单异常、系统升级为由要求您点击任何网址链接进行退款操作,保护资产、谨慎操作。
</p>
</div>
</div>
</div>
</template>
<script>
import { onMounted, ref } from "vue";
import { getOrderInfo } from "@/api/order";
import { useRoute } from "vue-router";
export default {
name: "XtxPayResultPage",
setup() {
const route = useRoute();
const order = ref(null);
onMounted(async () => {
const data = await getOrderInfo(route.query.orderId);
order.value = data.result;
});
return { order };
},
};
</script>
<style scoped lang="less">
.pay-result {
padding: 100px 0;
background: #fff;
text-align: center;
> .iconfont {
font-size: 100px;
}
.green {
color: #1dc779;
}
.red {
color: var(--price-color);
}
.tit {
font-size: 24px;
}
.tip {
color: #999;
}
p {
line-height: 40px;
font-size: 16px;
}
.btn {
margin-top: 50px;
}
.alert {
font-size: 12px;
color: #999;
margin-top: 50px;
}
}
</style>
router/index.js
const PayResult = () => import("@/views/pay/result");
{ path: "/user/checkout", component: Checkout },
{ path: "/user/pay", component: Pay },
+ { path: "/user/pay/result", component: PayResult },
- 渲染组件
<span v-if="$route.query.payResult" class="iconfont icon-queren2 green">
</span>
<span v-else class="iconfont icon-shanchu red"></span>
<p class="tit">
订单支付{{ $route.query.payResult ? "成功" : "失败" }}
</p>
<p class="tip">我们将尽快为您发货,收货期间请保持手机畅通</p>
<p>支付方式:<span>微信支付</span></p>
<p>
支付金额:<span class="red">¥{{ order?.payMoney }}</span>
</p>
import { onMounted, ref } from "vue";
import { getOrderInfo } from "@/api/order";
import { useRoute } from "vue-router";
export default {
name: "XtxPayResultPage",
setup() {
const route = useRoute();
const order = ref(null);
onMounted(async () => {
const data = await getOrderInfo(route.query.orderId);
order.value = data.result;
});
return { order };
},
};
一些扩展
07-vue2使用组合API
目标:在vue2项目中使用compositionAPI
大致步骤:
- 安装
@vue/composition-api - 注册插件
- 使用组合API
落地代码:
- 安装
yarn add @vue/composition-api
- 注册插件
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
- 使用组合API
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
+ {{count}} <button @click="count++">+1</button>
</div>
</template>
<script>
+import { ref } from '@vue/composition-api'
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
},
+ setup() {
+ const count = ref(0)
+ return { count}
+ }
}
</script>
08-vite脚手架
目标:了解vite是什么,使用vite创建vue项目
vite是什么:官方文档
- 它是一个更加轻量(热更新速度快,打包构建速度快)的vue项目脚手架工具。
- 相对于vue-cli它默认安装的插件非常少,随着开发过程依赖增多,需要自己额外配置。
- 所以: 在单纯学习vue3语法会使用它,后面做项目的时候我们还是使用vue-cli
vite基本使用:
- 创建项目
npm init vite-app 项目名称或者yarn create vite-app 项目名称 - 安装依赖
npm i或者yarn - 启动项目
npm run dev或者yarn dev
总结: vite是什么?
- 基于ES6模块化的脚手架,热更新和打包速度很快。
09-vue3简易状态共享
目标:通过响应式状态模块,实现简易状态共享
- 状态模块
import { reactive } from "vue";
export default reactive({
count: 0
})
- A组件
<template>
<h1>HelloWorld {{store.count}}</h1>
</template>
<script>
import store from "../store";
export default {
name: "HelloWorld",
setup() {
return { store };
},
};
</script>
- B组件
<template>
<div>Counter组件 {{store.count}} <button @click="store.count++">+1</button></div>
</template>
<script>
import store from "../store";
export default {
name: "Counter",
setup() {
return { store };
},
};
</script>
注意:
- 这种方式对应大型应用不友好,且不支持SSR
10-pinia体验
目的:体验一下pinia状态管理
1)安装pinia插件
yarn add pinia
2)注册pinia插件 main.js
import { createPinia } from 'pinia'
createApp(App).use(createPinia()).mount('#app')
3)创建store
store/counter.js
import { defineStore } from "pinia";
export default defineStore("counter", {
// 状态
state() {
return {
count: 0,
};
},
// mutations + actions 函数,可以执行异步和同步操作
actions: {
increment() {
this.count++;
},
},
// 计算属性
getters: {
doubleCount() {
return this.count * 2;
},
},
});
4)组件使用 app.vue
<template>
<div>
<p>{{store.count}}</p>
<p>{{store.doubleCount}}</p>
<button @click="store.increment">+1</button>
</div>
</template>
<script>
import useCounterStore from './store/counter'
export default {
name: 'App',
setup () {
const store = useCounterStore()
return { store }
}
}
</script>
总结:
- pinia是趋势还可以,vue3 + TS + pinia 组合开发是不错的选择。
11-前端mock
目的:体验前端mock接口数据
1)安装
yarn add axios mockjs
2)发送请求 App.vue
import axios from "axios";
export default {
name: "App",
setup() {
axios
.get("/list")
.then((data) => {
console.log(data);
})
.catch((e) => {
console.log(e);
});
},
};
3)mock数据 mock/index.js
import Mock from "mockjs";
Mock.mock("/list", () => {
const list = [];
for (let i = 0; i < 10; i++) {
list.push({
id: Mock.mock('@id'),
name: Mock.mock('@cname'),
age: Mock.mock('@integer(20,30)'),
city: Mock.mock('@city(true)'),
picture: Mock.mock('@image')
});
}
return {
message: "获取数据成功",
result: list,
};
});
4)使用mock main.js
import './mock'
总结:
- 前期开发后台接口没更上,可以先mock数据,开发完毕注释导入的mock文件即可。