效果
项目简介
这是一门使用uniapp,vk uniCloud框架,从0开发一个接近企业级商业级项目之云商城(仿小米商城),课程包含了基础内容,高级内容,项目封装,项目重构等知识;主要是讲解如何使用系统功能,流行的第三方框架,第三方服务完成一个接近企业级商业级项目,目的是通过对课程质量的苛刻要求,以达到学习完我们课程的同学能真真正正的学习到知识和经验,能让他们成为行业的高端人才,同时拥有更好的人生规划和职业发展前景。
功能点
预备知识:Git,Postman,HTML,CSS,Flex布局,JavaScript等
搭建环境:搭建HBuilderX,Android,iOS,微信和支付小程序环境等
基础知识:uniapp,uniCloud项目创建,开发和网络请求封装等
项目实战:实现轮播图,商品列表,商品分类,商品详情,订单等
登录注册:用户注册和登录,手机号登录,微信App登录,微信小程序登录等
支付功能:微信App支付,微信小程序支付,支付宝App支付等
应用打包:自定义基座,调试前面和云函数,应用升级,版本检测等
苹果开发:加入开发者计划,签名和设备,自定义基座打包,上架等
后台管理:轮播图管理,商品分类管理,商品管理,首页统计,权限和菜单
...
部分项目视频
b站搜索爱学啊,然后在专辑里面找仿小米商城。
发环境概述
2022年11月开发完成的,所以全部都是最新的,平均每3年会重新制作,云商城还是第一版,其他云音乐项目现在已经是第三版了。
HBuilder X 3.6
编译和运行
用最新HBuilder X打开mall,然后可运行到h5,微信小程序,Android,iOS等平台;后台管理系统运行到PC端,部分功能不兼容手机网页,不过管理后台兼容手机网页屏幕也太窄了,体验也不好。
首页
是一个多类型列表,最底部是标题栏,微信小程序自定义了,兼容小程序胶囊按钮,其他平台使用框架自带的;后面就是轮播图,使用官方的swiper组件,然后快捷按钮,使用flex布局实现九宫格效果;然后是左右大封面图,也是使用flex布局实现;接下来就是热门商品,最新商品,最后是推荐商品;并实现了下拉刷新,上拉加载更多。
<template>
<!-- #ifdef MP -->
<page-meta :page-style="'overflow:'+(isShowNewUserCoupon?'hidden':'visible')"></page-meta>
<!-- #endif -->
<view>
<!-- #ifdef MP -->
<view class="bg-surface fixed left-0 right-0 top-0" style="z-index: 999;">
<!-- 状态栏 -->
<StatusBar />
<!-- 导航栏 -->
<view :style="{height:navibarHeight+'px'}" class="flex items-center">
<image @click="scanClick" src="/static/images/navigation-logo.png" class="image-navigation ml-10r"
mode="heightFix"></image>
<view @click="searchClick" class="flex flex-1 bg rounded-50r items-center justify-center"
style="height: 32px;">
<text class="text-28r text-on-secondary">搜索商品</text>
</view>
<view :style="'width: '+menuButtonWidth+'px'">
</view>
</view>
</view>
<!-- 导航栏占位控件 -->
<view>
<StatusBar />
<view :style="{height:navibarHeight+'px'}">
</view>
</view>
<!-- #endif -->
<view v-if="data">
<!-- banner -->
<swiper class="banner" :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000">
<block v-for="(item,index) in data.banners" :key="index">
<swiper-item class="banner">
<view @click="bannerClick(item)" class="swiper-item banner">
<image :src="item.bannerfile" lazy-load="true" mode="aspectFill" class="banner"></image>
</view>
</swiper-item>
</block>
</swiper><!-- end banner -->
<!-- 按钮 -->
<view class="flex flex-wrap bg-surface">
<image @click="buttonClick(item)" v-for="(item,index) in data.buttons" :src="item.icon"
class="image-button">
</image>
</view>
<!-- end 按钮 -->
...
<!-- 最新商品 -->
<view class="flex items-center pl-23r pr-23r pt-23r mt-16r bg-surface">
<text class="text-30r font-bold flex-1">新上好货</text>
<text class="text-26r text-on-secondary">查看更多</text>
<fui-icon name="arrowright" :size="40" :color="theme.colorOnSecondary"></fui-icon>
</view>
<view class="bg-surface">
<sui-goods-list :datum="data.news" />
</view>
<image class="mt-16r mb-16r" :src="data.banner1" style="width: 750rpx;height: 291.6666666667rpx;"></image>
<sui-goods-list :datum="datum" :column="2" />
</view>
<fui-loadmore v-if="isShowLoadingStatus" :state="loadMoreState" :activeColor="theme.colorPrimary">
</fui-loadmore>
<!-- 添加到小程序提示 -->
<xzj-firsthint :isCustom="true" />
<!-- <fui-loading v-else type="row"></fui-loading> -->
<sui-new-user-coupon time="122800" @closeClick="isShowNewUserCoupon = false" v-if="isShowNewUserCoupon">
</sui-new-user-coupon>
</view>
</template>
<script>
// import GoodsList from '@/components/superui/sui-goods-list/sui-goods-list.vue'
import StatusBar from "@/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue";
var vk = uni.vk;
export default {
components: {
StatusBar
},
data() {
// 页面数据变量
return {
isShowNewUserCoupon: false,
navibarHeight: 0,
/**
* 胶囊按钮外部容器宽度
*/
menuButtonWidth: 0,
data: null,
datum: [],
isShowLoadingStatus: false,
//加载更多状态
loadMoreState: 1,
//分页
page: 1,
// 表单请求数据
form1: {
},
scrollTop: 0,
}
},
onPageScroll(e) {
this.scrollTop = e.scrollTop;
},
// 监听 - 页面每次【加载时】执行(如:前进)
onLoad(options = {}) {
vk = uni.vk;
this.options = options;
this.init(options);
},
// 监听 - 页面【首次渲染完成时】执行。注意如果渲染速度快,会在页面进入动画完成前触发
onReady() {
if (!this.config.DEBUG) {
setTimeout(() => {
// #ifdef APP
this.start('/components/superui/sui-new-user-coupon/sui-new-user-coupon-page');
// #endif
// #ifndef APP
this.isShowNewUserCoupon = true;
// #endif
}, 3000);
}
},
// 监听 - 页面每次【显示时】执行(如:前进和返回) (页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面)
onShow() {
},
// 监听 - 页面每次【隐藏时】执行(如:返回)
onHide() {
},
// 监听 - 点击右上角转发时
onShareAppMessage(options) {
},
// 函数
methods: {
// 页面数据初始化函数
init(options) {
// #ifdef MP
uni.getSystemInfo({
success: (r) => {
console.log(r);
//获取小程序右上角胶囊按钮位置信息,自定义的时候避开他
//https://uniapp.dcloud.net.cn/api/ui/menuButton.html#getmenubuttonboundingclientrect
let menuButtonInfo = uni.getMenuButtonBoundingClientRect();
console.log(menuButtonInfo);
//状态栏高度
let statusBarHeight = r.statusBarHeight;
this.navibarHeight = menuButtonInfo.height + (menuButtonInfo.top -
statusBarHeight) * 2;
console.log(this.navibarHeight);
this.menuButtonWidth = menuButtonInfo.width + (r.windowWidth - menuButtonInfo.right) *
2;
}
})
// #endif
// vk.reLaunch("/pages_template/uni-id/index/index");
uni.startPullDownRefresh();
// vk.myfn.test2();
},
pageTo(path) {
vk.navigateTo(path);
},
/**
* 轮播图点击
* @param {Object} data
*/
bannerClick(data) {
if (data.open_url.startsWith('http')) {
uni.navigateTo({
url: '/components/superui/webview/webview?url=' + data.open_url + "&title=" + (data
.title || '')
})
} else {
uni.navigateTo({
url: data.open_url
})
}
},
...
loadMore() {
vk.callFunction({
url: 'client/goods/pub/getList',
data: {
page: this.page
}
}).then((r) => {
//把两个数组加一起
this.datum = [...this.datum, ...r.rows];
//计算分页相关信息
let totalPage = this.util.page(r.total, r.pagination.pageSize);
if (this.page == totalPage) {
//没有数据了
this.loadMoreState = 3;
} else {
this.loadMoreState = 1;
this.page += 1;
}
}).catch(r => {
this.loadMoreState = 1;
});
},
scanClick() {
uni.scanCode({
success: function(res) {
console.log('条码类型:' + res.scanType);
console.log('条码内容:' + res.result);
}
});
},
searchClick() {
vk.navigateTo('/pages/goods/goods?style=10');
},
// #ifndef MP
openOverlay() {
let uniPlatform = uni.getSystemInfoSync().uniPlatform;
// H5禁止滚动
if (uniPlatform == 'web') {
var mo = function(e) {
e.preventDefault();
};
document.body.style.overflow = 'hidden';
document.addEventListener("touchmove", mo, false); //禁止页面滑动
}
},
//关闭遮罩
closeOverlay: function() {
let uniPlatform = uni.getSystemInfoSync().uniPlatform;
if (uniPlatform == 'web') {
var mo = function(e) {
e.preventDefault();
};
document.body.style.overflow = ''; //出现滚动条
document.removeEventListener("touchmove", mo, false);
}
},
// #endif
},
// 监听器
watch: {
isShowNewUserCoupon(newValue, oldValue) {
// #ifndef MP
if (newValue) {
this.openOverlay();
} else {
this.closeOverlay();
}
// #endif
}
},
}
</script>
<style lang="scss">
page {
background-color: #EDEDED !important;
}
.banner {
width: 750rpx;
height: 375rpx;
}
// 图片按钮
.image-button {
width: 150rpx;
height: 158rpx;
}
// 左侧大轮播图
.banner-left {
width: 351rpx;
height: 498.0670391109rpx;
border-radius: 10rpx;
}
.banner-right {
width: 351rpx;
height: 241.0335195555rpx;
border-radius: 10rpx;
}
.image-navigation {
height: 28px;
}
</style>
商品详情
顶部在所有平台自定义了标题栏,主要实现图片能显示到状态栏下,滚动列表,标题栏会有渐变效果,这种效果在市面上商城商品详情用的比较的;接下来就是轮播图,和首页实现方法差不多;然后是商品信息,优惠券;商品规格;卖家,以及推荐商品;然后是商品详情富文本;最后底部是快捷按钮。
<template>
<view class="" v-if="data">
<view class="fixed left-0 right-0 top-0" style="z-index: 999;"
:style="'background-color:rgba(255,255,255,'+naviBarAlpha+')'">
<!-- #ifndef MP -->
<StatusBar />
<!-- #endif -->
<!-- 状态栏 -->
<!-- 导航栏 -->
<view class="flex items-center navibar">
<!-- #ifndef MP -->
<image @click="backClick" :src="'/static/images/back'+naviBarIconFlag+'.png'" class="ml-10r"
mode="heightFix"></image>
<!-- #endif -->
<fui-tabs background="rgba(255,255,255,0)" :short="false" scale="1" :selectedColor="theme.colorPrimary"
:sliderBackground="theme.colorPrimary" class="flex flex-1" :tabs="tabs" @change="change"></fui-tabs>
<!-- #ifndef MP -->
<image @click="shareClick" :src="'/static/images/share'+naviBarIconFlag+'.png'" class="ml-10r"
mode="heightFix"></image>
<!-- #endif -->
</view>
</view>
<!-- banner -->
<swiper class="banner" :indicator-dots="true">
<block v-for="(item,index) in data.goods_banner_imgs" :key="index">
<swiper-item class="banner">
<view @click="bannerClick(item)" class="swiper-item banner">
<image :src="item" lazy-load="true" mode="aspectFill" class="banner"></image>
</view>
</swiper-item>
</block>
</swiper><!-- end banner -->
<!-- 商品信息 -->
<view class="p-25r bg-surface">
<text class="text-primary text-44r font-bold">¥{{this.util.price(price)}}</text>
<view class="flex mt-25r">
<view class="flex flex-wrap flex-1">
<fui-tag v-for="(item,index) in activitys" :background="theme.colorActivity"
:color="theme.colorPrimary" :text="item.title" margin-bottom="24" theme="light"
margin-right="24"></fui-tag>
</view>
<fui-button @click="showCouponPopup" type="primary" width="100rpx" height="55rpx" :size="27">领券
</fui-button>
</view>
<view class="mt-25r">
<text class="text-35r font-bold text-on-surface">{{data.name}}</text>
</view>
<view class="mt-25r">
<text class="text-27r text-on-surface">{{data.highlight}}</text>
</view>
</view>
<!--end 商品信息 -->
<!-- 规格信息 -->
<view class="mt-16r">
<sui-setting @click="skuClick" title="已选" :subTitle="skuText" :isShowMoreIcon="true" />
<sui-setting title="送至" subTitle="四川省成都市高新区" :isShowMoreIcon="true" />
<sui-setting title="服务" subTitle="假一赔十 · 退货运费险 · 急速退款" :isShowMoreIcon="true" />
</view>
<!-- 评论 -->
<view id="comment-container" class="mt-16r">
<sui-setting :titleSize="32" titleFontWeight="700" @click="commentMoreClick" title="用户评价(100)"
moreTitle="查看更多" :isShowMoreIcon="true" />
<!-- 标签 -->
<view class="pl-25r pr-25r bg-surface">
<fui-tag size="25" :text="item.content+'('+item.count+')'" margin-bottom="15" theme="plain"
margin-right="25" background="#FFEEEC" radius="40" v-for="(item,index) in tags" :key="index"
@click="commentTagClick(item)">
</fui-tag>
</view>
<!-- 一个评论 -->
<view class="pt-25r pb-25r bg-surface mb-2r" v-for="(item,index) in data.comments" :key="index"
v-if="data.comments">
<!-- 内容 -->
<view class="pl-25r pr-25r flex justify-between">
<view class="flex items-center">
<image :src="item.user.avatar || this.config.r(
'0024e600-e6eb-42af-8673-2ad66d75c0bb.jpg')" class="icon-comment rounded-10r" mode=""></image>
<view class="flex flex-col ml-16r">
<fui-text :text="item.user.nickname" text-type="name" format size="29">
</fui-text>
<fui-text color="#aaa" text="6天前" format size="26">
</fui-text>
</view>
</view>
<uni-rate :readonly="true" :value="item.score" :size="18" />
</view>
<view class="pl-25r pr-25r flex justify-between items-center pt-16r">
<fui-text :text="item.content" format size="28"></fui-text>
</view>
<!-- 图片 -->
<view v-if="item.medias" class="pl-25r bg-surface pt-16r flex flex-wrap">
<image :src="it.url" class="rounded-10r mr-16r mb-16r"
:style="'width:'+imageWidth+'rpx;height:'+imageWidth+'rpx;'" mode="aspectFill"
v-for="(it,index) in item.medias" :key="index" @click="commentImageClick(item.medias,index)">
</image>
</view>
<view class="pl-25r pr-25r flex justify-between items-center pt-16r" v-if="item.spec_value">
<text class="text-on-secondary text-27r">已买: {{item.spec_value}}</text>
</view>
</view>
<!--/ 一个评论 -->
</view>
...
<!-- 底部按钮 -->
<view class="fixed bottom-0 left-0 right-0 bg-surface">
<DividerSmall />
<view class="flex pr-35r bottombar items-center ">
<view class="flex flex-col ml-35r">
<fui-icon name="kefu" :size="40"></fui-icon>
<text class="text-on-surface text-26r">客服</text>
</view>
<view class="flex flex-col ml-35r mr-35r">
<fui-icon name="cart" :size="40"></fui-icon>
<text class="text-on-surface text-26r">购物车</text>
</view>
<fui-button @click="addCartClick" class="flex-1" text="加入购物车" radius="96rpx 0rpx 0rpx 96rpx"
height="80rpx" :size="28" type="warning"></fui-button>
<fui-button @click="primaryClick" class="flex-1" text="立即购买" radius="0rpx 96rpx 96rpx 0rpx"
height="80rpx" :size="28" type="primary"></fui-button>
</view>
</view>
<view style="height: 102rpx;">
</view>
<!-- end 底部按钮 -->
<sui-coupon-popup @primaryClick="couponPrimaryClick" :datum="activitys" ref="couponPopup" />
<!-- sku选择组件 -->
<vk-data-goods-sku-popup ref="skuPopup" v-model="skuKey" border-radius="20" :localdata="data" :mode="skuMode"
@open="onOpenSkuPopup" @close="onCloseSkuPopup" @add-cart="addCart" @buy-now="buyNow">
</vk-data-goods-sku-popup>
</view>
</template>
<script>
import StatusBar from "@/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue";
import mpHtml from '@/uni_modules/mp-html/components/mp-html/mp-html.vue'
import UniShare from '@/uni_modules/uni-share/js_sdk/uni-share.js';
const uniShare = new UniShare();
export default {
components: {
StatusBar,
mpHtml
},
data() {
return {
// (750-(25*2)-(18*2))/3
imageWidth: 222.6666666667,
tabs: ['商品', '评价', '推荐', '详情'],
naviBarIconFlag: 1,
naviBarAlpha: 0,
data: null,
activitys: [],
coupon_id: null,
price: 0,
skuText:'',
tags: [{
"id": "12312",
"content": "质量好",
"count": 175
}, {
"id": "12312",
"content": "描述真实",
"count": 50
}, {
"id": "12312",
"content": "客服好",
"count": 25
}, {
"id": "12312",
"content": "老板人好",
"count": 38
}, {
"id": "12312",
"content": "课程很好",
"count": 150
}],
icon: "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dce8d661-99e9-4c7f-a4d9-df08f9c8f4e4/8ed9516f-1199-44fb-bbaa-5c4a11c3a637.jpg",
id: null,
// 是否打开SKU弹窗
skuKey: false,
// SKU弹窗模式
skuMode: 1,
}
},
onLoad({
id
}) {
this.id = id;
vk.callFunction({
url: 'client/goods/pub/detail',
data: {
id: id
},
success: (r) => {
this.data = r.data;
this.data.goods_thumb = this.data.goods_banner_imgs[0];
this.defaultSKU();
this.loadActivity();
}
});
// #ifdef MP
//设置小程序内分享菜单可点击
//https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share.html
wx.showShareMenu({
withShareTicket: true,
menus: ["shareAppMessage", "shareTimeline"]
});
// #endif
},
/**
* 小程序分享到消息点击
* @param {Object} res
*/
onShareAppMessage(res) {
if (res.from === 'button') { // 来自页面内分享按钮
console.log(res.target)
}
return {
title: this.shareTitle, //分享的名称
path: '/pages/goods/goods-detail?id=' + this.id,
imageUrl: this.icon
}
},
/**
* 小程序分享到朋友圈点击
* @param {Object} res
*/
onShareTimeline(res) {
return {
title: this.shareTitle,
imageUrl: this.icon
}
},
computed: {
shareTitle() {
return '这个商品不错,' + this.data.name;
},
shareSummary() {
return '我们是一家专注于IT职业教育的在线教育企业。目的是通过对课程质量的苛刻要求,以达到学习完我们课程的同学能真真正正的学习到知识和经验,能让他们成为行业的高端人才,同时拥有更好的人生规划和职业发展前景。';
},
},
methods: {
defaultSKU(){
this.price = this.data.price;
this.skuText = "请选择商品规格";
},
backClick() {
uni.navigateBack();
},
shareClick() {
uniShare.show({
content: { //公共的分享参数配置 类型(type)、链接(herf)、标题(title)、summary(描述)、imageUrl(缩略图)
type: 0,
href: this.config.H5_ENDPOINT + '/#/pages/goods/goods-detail?id=' + this.data._id,
title: this.shareTitle,
summary: this.shareSummary,
imageUrl: this.icon
},
menus: [{
"img": this.config.r('23913917-f867-4f4e-a376-198ffb4c7b19.png'),
"text": "微信好友",
"share": { //当前项的分享参数配置。可覆盖公共的配置如下:分享到微信小程序,配置了type=5
"provider": "weixin",
"scene": "WXSceneSession"
}
},
{
"img": this.config.r('64f1ca72-9e10-4b93-b982-6e9445dcb4b9.png'),
"text": "微信朋友圈",
"share": {
"provider": "weixin",
"scene": "WXSceneTimeline"
}
},
{
"img": this.config.r('1a9e835d-1547-444a-8342-353ad0c4a5d2.png'),
"text": "微信小程序",
"share": {
provider: "weixin",
scene: "WXSceneSession",
type: 5,
miniProgram: {
id: '123',
path: '/pages/list/detail',
webUrl: '/#/pages/list/detail',
type: 0
},
}
},
{
"img": this.config.r('76c1d6f4-62cd-4538-8f7b-45baaa4ad4ad.png'),
"text": "微博",
"share": {
"provider": "sinaweibo"
}
},
{
"img": this.config.r('a3d1536a-9c65-4c54-906a-1e3d82c45ce8.png'),
"text": "QQ",
"share": {
"provider": "qq"
}
},
{
"img": this.config.r('c3793a70-61c0-4dec-816b-adec2652f7a1.png'),
"text": "复制",
"share": "copyurl"
},
{
"img": this.config.r('af7fe0c0-42d9-4d4d-b260-513b90e97bf2.png'),
"text": "更多",
"share": "shareSystem"
}
],
cancelText: "取消分享",
}, e => { //callback
console.log(uniShare.isShow);
console.log(e);
})
},
}
...
/**
* 页面滚动
* @param {Object} data
*/
onPageScroll(data) {
this.naviBarAlpha = data.scrollTop / 255;
if (this.naviBarAlpha > 1) {
this.naviBarAlpha = 1;
}
if (this.naviBarAlpha > 0.5) {
this.naviBarIconFlag = 2;
} else {
this.naviBarIconFlag = 1;
}
}
}
</script>
<style lang="scss">
page {
background-color: #EDEDED !important;
}
.icon-comment {
width: 80rpx;
height: 80rpx;
}
.banner {
width: 750rpx;
height: 750rpx;
}
.navibar {
height: 44px;
image {
height: 81rpx;
}
}
</style>
规格选择
规格选择简单的功能不算复杂,但细节还是很多的,这里使用第三方sku选择组件。
确认订单
大体效果仿照小米商品确认订单。
<template>
<view v-if="data">
<!-- 收货地址 -->
<sui-address :data="vk.getVuex('$order.address')" class="mt-16r" @click="selectAddressClick"></sui-address>
<!-- 商品 -->
<view class="bg-surface mt-16r">
<sui-goods-list-small :datum="data.carts" />
</view>
<sui-setting title="商品总价" :moreTitle="'¥'+ this.util.price(data.total_price)" :marginTop="16" />
<sui-setting title="运费" moreTitle="包邮" />
<sui-setting @click="couponClick" title="优惠券" :moreTitle="couponText" :moreTitleColor="couponColor"
:isShowMoreIcon="true" />
<!-- 底部按钮 -->
<view class="fixed bottom-0 left-0 right-0 bg-surface">
<DividerSmall />
<view class="flex pr-35r pl-35r bottombar items-center">
<text class="text-28r">共{{data.carts.length}}件 合计:</text>
<text class="text-28r text-primary mr-35r ml-10r">{{this.util.price(data.price)}}</text>
<fui-button @click="primaryClick" class="flex-1" text="去支付" height="80rpx" :size="28" type="primary"
radius="96rpx"></fui-button>
</view>
</view>
<view style="height: 102rpx;">
</view>
<!-- end 底部按钮 -->
<sui-coupon-popup @itemClick="couponItemClick" :selectId="couponId" :selectModel="true" :datum="coupons"
ref="couponPopup" />
</view>
</template>
<script>
export default {
data() {
return {
goodsId: null,
couponId: null,
data: null,
coupons: null,
couponText: '',
couponColor: ''
}
},
onLoad({
goods_id,
coupon_id
}) {
this.goodsId = goods_id;
this.couponId = coupon_id;
this.loadData();
},
methods: {
loadData() {
vk.callFunction({
url: 'client/order/kh/confirm-order',
data: {
goods_id: this.goodsId,
coupon_id: this.couponId,
address_id: vk.getVuex('$order.address._id'),
source: vk.myfn.source(),
sku:vk.getVuex('$order.sku'),
carts:vk.getVuex('$order.carts')
},
success: (r) => {
this.data = r.data;
//收货地址
if (r.data.address) {
vk.setVuex('$order.address', r.data.address);
} else {
vk.setVuex('$order.address', null);
}
if (this.coupons) {
this.showCoupon();
} else {
this.loadActivity();
}
}
});
},
loadActivity() {
vk.callFunction({
url: 'client/activity/pub/getList',
data: {
need_user_info: true, // 如果pub下的云函数需要用到 `userInfo`,则传true
goods_id: this.goodsId,
style: 10
},
success: (r) => {
this.coupons = r.data;
this.showCoupon();
}
});
},
showCoupon() {
this.couponColor = this.theme.colorPrimary;
if (this.data.coupon) {
//使用优惠券
this.couponText = '-¥' + this.util.price2(this.data.coupon.activity.price);
} else if (this.coupons) {
//有优惠券
this.couponText = this.coupons.filter((it) => {
return it.coupon != null;
}).length + '张可用';
} else {
//没有优惠券可用
this.couponText = "没有可用优惠券";
this.moreTitleColor = this.theme.colorOnSecondary;
}
},
primaryClick() {
if (!vk.getVuex('$order.address')) {
vk.toast("请选择收货地址");
return;
}
vk.callFunction({
url: 'client/order/kh/create',
data: {
goods_id: this.goodsId,
coupon_id: this.couponId,
address_id: vk.getVuex('$order.address._id'),
source: vk.myfn.source(),
sku:vk.getVuex('$order.sku'),
carts:vk.getVuex('$order.carts')
},
success: (r) => {
vk.setVuex('$order.sku',null);
vk.setVuex('$order.carts',null);
vk.redirectTo("/pages/pay/pay?style=10&id=" + r.data._id);
}
});
},
showCouponPopup() {
this.$refs.couponPopup.open();
},
closeCouponPopup() {
this.$refs.couponPopup.close();
},
/**
* 优惠券文本点击
*/
couponClick() {
if (this.coupons) {
//显示优惠券列表
this.showCouponPopup();
}
},
couponItemClick({
data,
index
}) {
this.closeCouponPopup();
this.couponId = data._id;
this.loadData();
},
selectAddressClick() {
vk.navigateTo("/pages/address/address?style=10");
}
}
}
</script>
<style>
page {
background-color: #EDEDED !important;
}
</style>
支付界面
参考小米商城,实现为单独的界面,好处是应用中要支付的统一跳转到这个界面支付,更好的复用;这里只实现了微信,支付宝支付,真实大部分项目最常用的也就是这两个了;实现方法用的vk-uni-pay插件在云函数里面生成参数,然后客户端使用uni-pay组件调用支付组件。
<template>
<view v-if="data" class="flex flex-col">
<view class="flex flex-col items-center mt-90r mb-90r">
<!-- 价格信息 -->
<text class="text-primary text-50r font-bold">¥{{this.util.price(data.price)}}</text>
<text style="background-color: #ddd;"
class="text-on-secondary rounded-60r mt-30r text-24r pt-10r pb-10r pl-16r pr-16r">请在{{timeString}}前完成支付</text>
</view>
<!-- 支付渠道 -->
<fui-radio-group @change="payChannelChange">
<fui-label v-for="(item,index) in payChannels" :key="index">
<fui-list-cell>
<view class="fui-list__cell">
<image class="h-60r w-60r" :src="item.icon"></image>
<text class="flex-1 ml-20r">{{item.name}}</text>
<fui-radio :checked="item.checked" :value="item.value">
</fui-radio>
</view>
</fui-list-cell>
</fui-label>
</fui-radio-group>
<!-- end 支付渠道 -->
<view class="mt-100r pl-25r pr-25r">
<fui-button @click="primaryClick" text="立即支付" radius="96rpx"></fui-button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
timeoutFlag: null,
time: 0,
style: 0,
loading: false,
timeout: false,
data: null,
payChannel: '',
payChannels: []
}
},
onLoad({
id,
style
}) {
this.id = id;
this.style = style;
var temps = [];
// #ifndef MP-WEIXIN
temps.push({
icon: this.config.r('a68f9f24-cfd2-4a99-a3aa-589c79c714fe.png'),
name: '支付宝',
value: "alipay",
checked: true,
});
this.payChannel="alipay";
// #endif
// #ifndef MP-ALIPAY
temps.push(this.wechatPay);
// #endif
// #ifdef MP-WEIXIN
this.payChannel="wxpay";
this.wechatPay.checked = true;
// #endif
this.payChannels = temps;
this.loadData();
},
computed: {
timeString() {
return vk.pubfn.timeFormat(this.time, "mm分ss秒");
},
wechatPay(){
return {
icon: this.config.r('d2463164-ec0e-407b-92a2-cd9df58c0259.png'),
name: '微信支付',
value: "wxpay",
checked: false,
};
}
},
methods: {
loadData() {
vk.callFunction({
url: 'client/order/kh/detail',
data: {
id: this.id || "634ad720702e9d000164f8f2"
},
success: (r) => {
this.data = r.data;
if (this.loading) {
uni.hideLoading();
}
if (this.constant.orderPaid(this.data.status)) {
this.next();
}
if (!this.timeoutFlag) {
this.startCountDown();
}
}
});
},
payChannelChange(data) {
this.payChannel = data.detail.value;
},
primaryClick() {
vk.callFunction({
url: 'client/pay/kh/pay',
title: '',
data: {
provider: this.payChannel,
id: this.data._id
},
success: (r) => {
let orderInfo = r.orderInfo;
uni.requestPayment({
// #ifdef APP-PLUS
provider: this.payChannel, // App端此参数必填,可以通过uni.getProvider获取
// #endif
// #ifdef MP-WEIXIN
...orderInfo,
// #endif
// #ifdef APP-PLUS || MP-ALIPAY
orderInfo: orderInfo,
// #endif
...orderInfo,
success: (data) => {
this.checkOrderStatus();
},
fail: (data) => {
vk.toast('支付失败,请稍后再试或联系客服');
}
})
}
});
// this.checkOrderStatus();
},
checkOrderStatus() {
this.loading = true;
uni.showLoading({
title: '支付确认中.',
mask: true
});
setTimeout(() => {
this.loadData();
}, 2000);
},
/**
* 显示返回确认对话框
*/
showBackConfirmDialog() {
uni.showModal({
title: "确认放弃付款吗?",
content: "超时后,订单自动关闭",
cancelText: "继续支付",
confirmText: "确认离开",
success: (r) => {
if (r.confirm) {
this.next();
} else {
}
}
})
},
next() {
if (this.style == 10) {
// 只有从确认订单跳转过来,才需要跳转到订单详情
// 关闭当前页面,跳转到应用内的某个页面。
uni.redirectTo({
url: '/pages/order/order-detail?id=' + this.id
})
} else {
this.finish();
}
},
startCountDown() {
this.cancelTimer();
//订单创建时间+15分钟
var t = vk.pubfn.getOffsetTime(new Date(this.data._add_time), {
minutes: 15,
mode: "after", // after 之后 before 之前
});
this.time = t - new Date().getTime();
if (this.time <= 0) {
this.showTimeoutDialog();
return;
}
this.timeoutFlag = setInterval(() => {
if (this.time <= 0) {
this.cancelTimer();
this.showTimeoutDialog();
return;
}
this.time = this.time - 1000;
}, 1000);
},
cancelTimer() {
if (this.timeoutFlag) {
clearInterval(this.timeoutFlag);
}
},
showTimeoutDialog() {
console.log('showTimeoutDialog');
this.timeout = true;
uni.showModal({
title: "支付超时",
content: "订单已自动关闭,请重新下单",
confirmText: "确认",
showCancel: false,
success: (r) => {
if (r.confirm) {
this.next();
}
}
})
}
},
/**
* 返回按钮点击
* https://ask.dcloud.net.cn/article/35120
* @param {Object} data
*/
onBackPress(data) {
if (this.timeout) {
return false;
}
if (!this.loading) {
//没有显示loading,才显示对话框
this.showBackConfirmDialog();
}
//取消关闭界面
return true;
},
onUnload() {
this.cancelTimer();
}
}
</script>
<style>
page {
background-color: #EDEDED !important;
}
.fui-section__title {
margin-left: 32rpx;
}
.fui-list__item {
width: 100%;
display: flex;
align-items: center;
background-color: #FFFFFF;
padding: 28rpx 32rpx;
box-sizing: border-box;
}
.fui-text {
font-size: 30rpx;
}
.fui-list__cell {
width: 100%;
display: flex;
align-items: center;
}
</style>