目录
这是我3月-5月单人负责的一个数藏项目,巅峰时刻同时在线3w人,也算我第一个uniapp综合项目,不够目前我做的已经停运了,当初学了很多,但是缺乏整理,项目缺点也很多。我想通过这段时间把代码重新写一遍,整理一下。我不会把所有功能都重新梳理,但是重要的几样我肯定都一一梳理,而且会有增加,例如:注册、登录、一键登录、短信、抢购、转赠、支付宝支付、版本热更新、公告等
当然里面涉及的上链操作我不是很会,你们可以把这个当作一个售卖东西的App,里面不会涉及上一个公司的信息,只是单纯的整理一下目前项目主要的技术和自学时候的壁垒。
我这里使用的是Mac电脑,不影响window电脑的学习,排版可能不好看
github上面每天都在更新,如果认可,不要吝啬正反馈:GitHub - chenqi13814529300/light-ship: 复习和加强uniapp/unicloud学习
点赞收藏超过100,我将录播视频
1 项目准备
1.1 腾讯云服务空间
开通空间
配置短信签名和模版
模版需要短信签名审核成功后才可以编辑模版
开通一键登录
1.2 准备阿里云域名
需要购买至少3个月的服务器,然后实名认证啥的才可以,这些自己弄吧,然后我再域名解析为如下本项目使用的域名
1.3 准备支付宝支付能力
绝大部分人都没有创建公司或者实体,所以这里的支付能力我将采用支付宝沙箱带你们一起如何使用支付宝。
1.3.1下载app支付宝助手
1.3.2支付宝开放平台
你需要自己设置账户密码啥的,这个自己弄。
最终你可以得到几个有用的信息
appid:20210001111111111
mchid:2088621911111111
商家账户:wtesjg2746@sandbox.com
商家登录密码:111111
买家账户:qhsfny9975@sandbox.com
买家登录密码:111111
买家支付密码:111111
准备关于支付能力的密钥和证书,打开之前下的软件,点击生成应用密钥和应用公钥
方法一用阿里云公钥(二选一):
点击查看
然后把应用公钥放到里面,生成支付宝公钥
方法一有应用私钥和支付宝公钥即可进行支付测试
方法二用证书:
获取csr文件,组织/公司和域名填写商家账户
上传csr文件,然后下载3个证书
ok,支付支付能力准备好了!
1.4新建项目
这里使用的是vue2,unicloud,以及默认空模版
并关联腾讯云服务器空间
2 注册
2.1验证码使用
首先需要开通验证码,签名和模版都审核通过,并充值些许钱。
需要先引入uni-id组件
本次项目采用的是老版uni-id这个用起来比较顺手
引入uni-id后需要配置uni-id/config.json
config.json的配置参考官网文档uni-app官网
发送验证码——前端代码
我这边是做了发送验证码前进行判断(是否已经注册),避免浪费验证码次数
async getCode() {
if (this.regsiterInfo.mobile && this.isAgree) {
this.settime()
const sms = uniCloud.importObject("sms")
// 正常查找到就说,该用户名已经被注册
const isRegister = await sms.getUserByMobile(this.regsiterInfo.mobile)
console.log(isRegister)
if (isRegister.code == 0) {
uni.showToast({
title: "该用户名已经被注册",
icon: "none"
})
this.countdown = 0
}
if (isRegister.code == -100) {
const res = await sms.sendSms(this.regsiterInfo.mobile, "register")
console.log(res)
if (res.code != 0) {
uni.showToast({
title: "验证码发送失败",
icon: "none"
})
return
}
uni.showToast({
title: "验证码发送成功",
icon: "none"
})
}
} else {
uni.showToast({
title: "请输入手机号并且勾选协议",
icon: "none"
})
}
}
验证码冷却时间——前端代码
也是节约成本,同时减少后端请求
settime() {
let smsTime = null
if (this.countdown == 0) {
this.isdisabledFn = false
this.isSms = "获取验证码"
this.countdown = 60;
clearInterval(smsTime)
} else {
this.isdisabledFn = true
this.isSms = "重新发送(" + this.countdown + ")"
this.countdown--;
smsTime = setTimeout(() => {
this.settime()
}, 1000)
}
},
校验验证码——前端代码
参数就是手机号和验证码
const sms = uniCloud.importObject("sms")
const smsInfo = await sms.verifySmsCode(this.regsiterInfo.mobile, this.regsiterInfo.code,
"register")
console.log(smsInfo)
验证码方法——后端代码
// 开发文档: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
const uniID = require('uni-id')
const db = uniCloud.database();
module.exports = {
// 发送验证码
async sendSms(mobile, type) {
// 生成验证码可以按自己的需求来,这里以生成6位数字为例
const randomStr = '00000' + Math.floor(Math.random() * 1000000)
const code = randomStr.substring(randomStr.length - 4)
const res = await uniID.sendSmsCode({
mobile,
templateId: "14408",
code,
type
})
return res
},
// 校验验证码
async verifySmsCode(mobile, code, type) {
const res = await uniID.verifyCode({
mobile,
code,
type
})
return res
},
// 根据电话号码查找个人信息
async getUserByMobile(mobile) {
let {
data
} = await db.collection("uni-id-users").where({
"mobile": mobile
}).get()
if (data[0]) {
return {
code: 0,
msg: '查询成功',
}
} else {
return {
code: -100,
}
}
}
// 手机+短信 登录
// 注册后的可以直接登录,如果没有注册过则去注册页面
async smsLogin(mobile, code) {
// 判断mobild是否注册了
const sms = uniCloud.importObject("sms")
try {
const res = await sms.getUserByMobile(mobile)
} catch (e) {
// 异常则显示未注册-100
return {
res: e
}
}
try {
const res = await uniID.loginBySms({
mobile,
code
})
return {
res: res
}
} catch (e) {
return {
res: e
}
}
}
}
2.2注册逻辑
手机号、密码、验证码、用户协议都填写才可以注册,上述完成后,便在云数据库中产生一条数据。
同时需要把注册的token保存下来,vuex保存下来。并跳转到登录页面
注册逻辑——前端代码
uniCloud.callFunction({
name: 'register',
data,
async success(res) {
console.log(res)
if (res.result.code === 0) {
uni.showToast({
title: '注册成功',
icon: 'none'
})
// 2.8.0版本起调整为蛇形uni_id_token(调整后在一段时间内兼容驼峰uniIdToken)
uni.setStorageSync('uni_id_token', res.result.token)
uni.setStorageSync('uni_id_token_expired', res.result.tokenExpired)
// 把邀请码清单归到邀请码数据库之中(存在就入库不存在不执行)
if (that.regsiterInfo.inviteCode) {
console.log("我有邀请码奥")
const userInfo = {
"registerUserId": res.result.uid,
"mobile": res.result.mobile,
"username": res.result.username
}
const invite = uniCloud.importObject("invite")
await invite.updateInvite(that.regsiterInfo.inviteCode,
userInfo)
}
// 跳转到登录页
setTimeout(function() {
uni.navigateTo({
url: "/pages/login/Login"
})
}, 1000)
} else if (res.result.code == -100) {
uni.showToast({
title: res.result.msg,
icon: "none"
})
} else {
uni.redirectTo({
content: res.result.message,
showCancel: false
})
}
},
fail(res) {
console.log(res)
uni.showModal({
content: '注册失败,请稍后再试',
showCancel: false
})
}
})
注册逻辑——后端代码
// 云函数register的代码
const uniID = require('uni-id')
exports.main = async function(event, context) {
const {
mobile,
password,
inviteCode
} = event
// 自动验证用户名是否与已经注册的用户名重复,如果重复会直接返回错误。否则会自动生成token并加密password存储username、password、token到数据表uni-id-users,并返回如上响应参数
const res = await uniID.register({ //支持传入任何值,比如可以直接传入mobile即可设置手机号码,切勿直接传入event否则这是一个极大的安全问题
"username":mobile,
"password": password,
"mobile": mobile,
"avatar": 'https://7463-tcb-d4ae9humf98e68-0dudeafb75fdc-1310755086.tcb.qcloud.la/head/good3.jpg',
"goods_count": 0,
"mobile_confirmed": 1,
"realname_auth": 0,
"invite_code": inviteCode,
"create_invite_code": '',
})
return res
}
注册成功!
2.3报错记录
Error: Method name required代表你创建的是云对象,不是云函数。云对象调用需要方法(例如一开始我创建的是register是云对象,一切操作符合文档结果报这个错误)
3 登录
3.1传统密码登录
密码登录——前端代码
一个是判断是否是传统登录方式,登录成功后执行登录后的逻辑,保存token,以及记录登录时长(用于3天后清空token,登录失效,需要重新登录)
if (this.isTradition) {
const login = uniCloud.importObject("login")
const loginInfo = await login.commonLogin(data)
console.log(loginInfo)
if (loginInfo.code == 0) {
// 执行登录成功后的逻辑
that.loginAfter(loginInfo)
}
}
// 登录成功后的逻辑
loginAfter(res) {
// uni.closeAuthView()
uni.showToast({
title: '登录成功',
icon: 'none',
})
// 保存token
uni.setStorageSync('uni_id_token', res.token)
uni.setStorageSync('uni_id_token_expired', res.tokenExpired)
var dayAdd1 = new Date();
dayAdd1 = dayAdd1.setDate(dayAdd1.getDate() + 1);
dayAdd1 = new Date(dayAdd1);
// 记入token终止日期,1天
uni.setStorageSync('uni_id_token_end_time', dayAdd1.getTime())
// 其他业务代码,如跳转到首页等
this.setUserInfo(res.userInfo)
setTimeout(function() {
uni.switchTab({
url: "/pages/index/Index"
})
}, 1000)
},
密码登录——后端代码
const uniID = require('uni-id')
module.exports = {
// 普通账号 密码登录
async commonLogin(userInfo) {
const {
mobile,
password
} = userInfo
// 自动完成mobile、password验证是否合法的逻辑
const res = await uniID.login({
username: mobile,
password,
})
return res
},
// 重置密码
async resetPwdBySms(resetInfo) {
const {
mobile,
code,
password
} = resetInfo
await uniID.resetPwdBySms({
mobile,
code,
password
})
return {
code:0
}
},
}
3.2短信密码登录
短信登录——前端代码
短信登录其实跟注册获取验证码逻辑类似
// 电话号码+验证码登录
// 1.是否填写验证码
if (that.loginInfo.code) {
const sms = uniCloud.importObject("sms")
const {
res
} = await sms.smsLogin(that.loginInfo.mobile, that.loginInfo.code)
// res.code为-100则res.errMsg 未注册
// 50202 errMsg "验证码错误或已失效"
// 0 是成功的
console.log(res)
switch (res.code) {
case 0:
// 执行登录成功后的逻辑
that.loginAfter(res)
break;
case -100:
uni.showToast({
title: res.errMsg,
icon: 'none'
});
break;
case 50202:
uni.showToast({
title: res.errMsg,
icon: 'none'
});
break;
case -300:
uni.showToast({
title: res.errMsg,
icon: 'none'
});
break;
}
} else {
uni.showToast({
title: "请填写验证码",
icon: 'none'
})
}
短信登录——后端代码
这部分代码,在注册的后端sms代码里面
3.3手机一键登录
一键登录——前端代码
目前有缺陷,ios一键登录uni.closeAuthView()不生效,是官方还没修复
// 判断是否支持一键登录
isAutoLogin() {
let _that = this
uni.getProvider({ //获取可用的服务提供商
service: 'oauth',
success: function(res) {
console.log(res.provider) // ['weixin', qq', 'univerify']
}
});
uni.preLogin({ //预登录
provider: 'univerify', //用手机号登录
success() {
_that.autoStatus = true
console.log('预登录成功')
_that.fasterLogin()
},
fail(err) { //预登录失败
_that.autoStatus = false
_that.error = err
console.log('错误码:' + err.errCode)
console.log(err.errMsg)
}
})
},
async fasterLogin() {
let that = this
uni.login({
provider: 'univerify',
async success(res) { // 登录成功
console.log(res.authResult); // {openid:'登录授权唯一标识',access_token:'接口返回的 token'}
const login = uniCloud.importObject("login")
const fastLoginRes = await login.fastLogin(res.authResult)
console.log(fastLoginRes)
if (fastLoginRes.code == -100) {
uni.showToast({
title: "请先注册",
icon: "none"
})
setTimeout(function() {
uni.closeAuthView()
uni.navigateTo({
url: "/pages/register/Register"
})
}, 1000)
}
if (fastLoginRes.code == 0) {
uni.closeAuthView()
that.loginAfter(fastLoginRes)
}
},
fail(res) { // 登录失败
console.log(res.errCode)
uni.closeAuthView()
}
})
},
一键登录——后端代码
type:指定操作类型,可选值为login
、register
,不传此参数时表现为手机号已注册则登录,手机号未注册则进行注册
// 手机一键登录
async fastLogin(userInfo) {
const {
access_token,
openid
} = userInfo
const res = await uniID.loginByUniverify({
access_token,
openid,
type:'login'
})
return res
},
4 登录与退出登录
登录成功跳转到首页,并且显示登录状态和用户名
点击退出后如下
完整代码如下——前端代码
<template>
<view>
<nav-bar>
<view slot="left" class="left">启航</view>
<view slot="right" v-if="isLogin" @click="goLogin">{{getUserInfo._id.slice(0,12)}} <text @click="outLogin"
class="outLogin">退出</text></view>
<view slot="right" v-else @click="goLogin">登录</view>
</nav-bar>
</view>
</template>
<script>
import NavBar from '../../components/NavBar.vue'
import {
mapGetters,
mapMutations
} from "vuex";
export default {
components: {
NavBar
},
data() {
return {}
},
computed: {
...mapGetters(["getUserInfo"]),
isLogin() {
console.log(this.getUserInfo)
if (Object.keys(this.getUserInfo).length > 0) {
return true;
} else {
return false;
}
},
},
methods: {
...mapMutations(['setUserInfo']),
// 去登录
goLogin() {
uni.navigateTo({
url: "/pages/login/Login"
})
},
// 退出登录
outLogin() {
this.setUserInfo({})
uni.removeStorageSync('uni_id_token')
uni.removeStorageSync('uni_id_token_expired')
uni.navigateTo({
url: "/pages/Login/Login"
})
},
}
}
</script>
<style>
</style>
NavBar组件——前端代码
<template>
<view class="nav-bar">
<view class="left"><slot name="left"></slot></view>
<view class="center"><slot name="center"></slot></view>
<view class="right"><slot name="right"></slot></view>
</view>
</template>
<script>
export default {
name: "NavBar"
}
</script>
<style scoped>
.nav-bar {
position: -webkit-sticky;
position: sticky;
top: var(--window-top);
z-index: 99;
height: 80rpx;
overflow: hidden;
line-height: 80rpx;
background-color: #242729;
color: white;
}
.left{
float: left;
}
.right{
float: right;
}
</style>
5 轮播图
效果如下
点击轮播图进入图片详情
轮播图——前端代码
<!-- 轮播图 -->
<uni-swiper-dot :info="info" :current="current" field="content" mode="round">
<swiper class="swiper-box" @change="change" autoplay="true">
<swiper-item v-for="(item ,index) in info" :key="index">
<image @click="bigImgDeatils(item)" :src="require(`@/static/lun/${item.content}`)" mode="widthFix">
</image>
</swiper-item>
</swiper>
</uni-swiper-dot>
// 轮播图片 这里就不从后端获取了,大家应该都会
info: [{
content: 'lun2.jpeg',
big: 'lun2-big.jpeg'
},
{
content: 'lun1.jpeg',
big: 'lun1-big.jpeg'
},
],
current: 0,
// 轮播图详情页面
bigImgDeatils(item) {
// 预览图片
let imgArr = [];
/* 这里可以使用网络路径,也可以使用图片的base64编码 */
imgArr.push(require(`@/static/lun/${item.big}`))
//预览图片
uni.previewImage({
urls: imgArr,
current: imgArr[0]
});
},
// 轮播图改变都时候的方法
change(e) {
this.current = e.detail.current;
},
6 公告栏
效果如下
这里使用的是插件,效果不错
公告栏——前端代码
<!-- 公告 -->
<view class="lwNoticeBox">
<lwNotice :showScale="true" @itemClick="newsItemDetails" :list="newsList"></lwNotice>
<uni-icons class="more" @click="oatoUrl" type="bars" size="30" color="white"></uni-icons>
</view>
// 新闻公告栏 也是写死,反正这个简单
newsList: [
"启航官网jrx.jrxtiejin.com",
"江河地笑CSDN",
]
// 公告详情
newsItemDetails(data) {
// 数据库中的新闻是包含title和id字段
// 通过title找到对应的新闻对象,然后通过新闻对象的id得到新闻详情,这里就不演示后端代码了
// const currentNews = this.newsList.find(item => item.title == data)
// uni.navigateTo({
// url: '/pages/activities/OfficialNewsDeatils?id=' + currentNews.id
// })
},
7 ThreeJs商品展示
商品展示环节很简单,做了几个组件,难点在于ThreeJS3d环节。然后点击进入到商品展示页面
然后点击购买进入付款环节,我们这次测试付款主要使用的是支付宝
皮肤设置数据库字段如下:
goods_type值得是皮肤类型,0是法师皮肤,1是战士皮肤,2是辅助皮肤
rank_index代表是皮肤展示前后顺序,0代表在最前面展示
sale_status代表是销售状态1:预销售,2:热销中,3:已售罄
首页展示单个皮肤组件——前端代码
<!-- 单个皮肤展示组件 -->
<digit-goods v-for="item in goodsList.data" :goodsItem="item" v-show="currentIndex==0"
@click.native="toGoodsDetails(item)"></digit-goods>
<template>
<view class="goodsItem">
<view class="goodsStatus">
{{goodsStatus}}
</view>
<image class="goodImg" :src="goodsItem.goods_img" mode="widthFix"></image>
<view class="content">
<view class="title">
{{goodsItem.goods_name}}
<text class="remain_count">库存:{{goodsItem.goods_count}}</text>
</view>
<view class="tag">
<text class="quality">限量:</text>
<text class="count">{{goodsItem.goods_count}}份</text>
<text class="classes">{{goodsItem.goods_desc}}</text>
</view>
<view class="price">
<text class="left">
<image :src="goodsItem.framer_img" mode="aspectFill"></image>{{goodsItem.framer}}
</text>
<text class="right">¥ {{goodsItem.goods_price.toFixed(2)}}</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: "DigitGoods",
props: {
goodsItem: {
type: Object,
default () {
return {
name: ''
}
}
}
},
computed: {
goodsStatus() {
switch (this.goodsItem.sale_status) {
case 1:
return '⌚️预销售'
case 2:
return '🔥热销中'
case 3:
return '⌚️已售罄'
}
}
},
data() {
return {
};
},
onLoad() {
console.log(2)
},
mounted() {}
}
</script>
<style lang="less" scoped>
.goodsItem {
width: 100%;
border-radius: 60rpx;
background-color: #212121;
position: relative;
margin-bottom: 30rpx;
image {
width: 100%;
height: 700rpx;
border-radius: 60rpx;
}
// 目前销售状态
.goodsStatus {
position: absolute;
left: 5%;
top: 5%;
text-align: center;
font-size: 27rpx;
line-height: 60rpx;
color: #C0C0C0;
width: 160rpx;
height: 60rpx;
background-color: black;
z-index: 9;
border-radius: 30rpx;
}
.content {
padding: 20rpx;
view{
margin: 20rpx 0;
}
.title {
color: white;
margin-bottom: 10rpx;
text{
float: right;
font-size: 25rpx;
color: #feac59;
line-height: 50rpx;
}
.remain_count{
margin: 0 20rpx;
// color: white;
}
}
.tag {
font-size: 27rpx;
color: #988f78;
margin-bottom: 15rpx;
text {
padding: 8rpx 12rpx;
background-color: #feac59;
}
.quality {
color: black;
}
.count {
background-color: #3a3a3b;
}
.classes {
margin-left: 20rpx;
background-color: #3a3a3b;
line-height: 60rpx;
}
}
.price {
overflow: hidden;
.left {
float: left;
color: #818484;
font-size: 25rpx;
image {
vertical-align: middle;
margin-right: 15rpx;
width: 50rpx;
height: 50rpx;
display: inline-block;
}
}
.right {
float: right;
color: white;
font-weight: 800;
font-size: 40rpx;
}
}
}
}
</style>
获取type对应的所有皮肤——后端代码
// 获取
async getGoodsBySkipAndtype(skip,goods_type) {
console.log(goods_type)
let {data} = await db.collection("digital-goods").where({
"is_on_sale": true,
goods_type
}).skip(skip).limit(10)
.orderBy("rank_index","desc")
.get()
return {
code: 0,
msg: '查询成功',
data
}
},
ThreeJS3D展示页面——前端代码
值得一说的就是,目前3d展示存在图片跨越问题,如果放本地图片是没问题的。这个还没解决,不过不是很重要,主要是学习购买逻辑
<template>
<view>
<nav-bar>
<view slot="left" class="left">
<uni-icons @click="goBack" type="back" size="30" color="white"></uni-icons>
</view>
</nav-bar>
<view class="showGoodItem">
<view class="center" id="center">
</view>
<view class="bottom">
</view>
</view>
<view class="introduce">
<text>{{goodsItemDeatils.goods_name}}</text>
</view>
<view class="tag">
<text class="quality">限量:</text>
<text class="count">{{goodsItemDeatils.goods_count}}份</text>
</view>
<!-- 作者 -->
<view class="author">
<view class="top">
<view class="headerImg">
<image :src="goodsItemDeatils.framer_img" mode="aspectFill"></image>
</view>
<view class="">
创作者
</view>
<view class="">
{{goodsItemDeatils.framer}}
</view>
</view>
<view class="center">
{{goodsItemDeatils.goods_desc}}
</view>
</view>
<view class="more" v-if="goodsItemDeatils.goods_bg">
<text class="tip">
购买后即可体验内容
</text>
<image :src="goodsItemDeatils.goods_bg" mode="widthFix"></image>
</view>
<view class="provision">
<view class="title">
购买须知
</view>
<view class="">
教学和学习使用
</view>
</view>
<view class="footer">
提供技术支持
</view>
<view class="buy">
<view class="left">
单价:¥{{goodsItemDeatils.goods_price}}
</view>
<view class="right" @click="$noMultipleClicks(goBuy)">
购买
</view>
</view>
</view>
</template>
<script>
import NavBar from '@/components/NavBar.vue'
import * as THREE from "@/static/js/three.js"
import "@/static/js/OrbitControls"
import {
mapGetters
} from 'vuex'
// const t1 = "2022/4/26 12:00:00"
// const t2 = "2022/4/27 12:00:00"
// const t3 = "2022/4/27 12:00:00"
// const t4 = "2022/4/27 18:00:00"
export default {
components: {
NavBar
},
onLoad: function(option) {
console.log(option.id)
this.goodsItemDeatilsId = option.id
},
data() {
return {
// 防抖挂载
noClick: true,
abc: null,
cube: undefined,
scene: undefined,
camera: undefined,
renderer: undefined,
floorTexture: undefined,
windowWidth: undefined,
windowHeight: undefined,
goodsItemDeatils: {},
goodsItemDeatilsId: null,
}
},
computed: {
...mapGetters(['getUserInfo'])
},
filters: {
hashFiter(value) {
if (value) {
return value.slice(0, 4) + '...' + value.slice(value.length - 4)
}
},
},
async mounted() {
let that = this
uni.getSystemInfo({
success: function(res) {
that.windowWidth = res.windowWidth
that.windowHeight = res.windowHeight
}
});
// 等下面这个执行完了才可以配置data
await this.getGoodsItemDeatils(this.goodsItemDeatilsId)
this.initGoodItem()
this.animate()
},
methods: {
initGoodItem() {
console.log(this.goodsItemDeatils)
//建立了场景
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, 1, 0.1, 2000);
// // 渲染器
this.renderer = new THREE.WebGLRenderer({
antialias: true //是否执行抗锯齿
});
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(0.9 * this.windowWidth, 300);
// 添加到页面中
document.getElementById('center').appendChild(this.renderer.domElement)
// BoxGeometry(立方体)对象
var geometry = new THREE.BoxBufferGeometry(10, 12, 0.3);
// 换成自己的图片路径
var loader = new THREE.TextureLoader();
let one = loader.load('../../static/basic/desk.png');
let two = loader.load('../../static/basic/desk.png');
let three = loader.load('../../static//basicdesk.png');
let four = loader.load('../../static/basic/desk.png');
// 这边会有跨域问题,我没有很好的解决方法,
// let five = loader.load(`../../static/goods/${this.goodsItemDeatils.goods_img}.png`);
let five = loader.load('../../static/basic/logo.png');
let six = loader.load('../../static/basic/logo.png');
const material1 = new THREE.MeshBasicMaterial({
color: 'rgb(248,248,255)'
});
const material2 = new THREE.MeshBasicMaterial({
color: 'rgb(248,248,255)'
});
const material3 = new THREE.MeshBasicMaterial({
color: 'rgb(248,248,255)'
});
const material4 = new THREE.MeshBasicMaterial({
color: 'rgb(248,248,255)'
});
const material5 = new THREE.MeshBasicMaterial({
map: five
});
const material6 = new THREE.MeshBasicMaterial({
map: six
});
const materials = [material1, material2, material3, material4, material5, material6]
// var materials = new THREE.MeshBasicMaterial()
this.cube = new THREE.Mesh(geometry, materials);
this.scene.add(this.cube);
//为了防止这种情况的发生,我们只需要将摄像机稍微向外移动一些即可。
this.camera.position.z = 15;
// light
var light = new THREE.PointLight("#F8F8FF");
light.position.set(10, 0, 100);
this.scene.add(light);
var controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
controls.update();
},
// 更新
animate() {
requestAnimationFrame(this.animate)
this.cube.rotation.y += 0.01
this.renderer.render(this.scene, this.camera)
},
// 购买
async goBuy() {
// 判断物品是否足够
await this.getGoodsItemDeatils(this.goodsItemDeatilsId)
console.log(this.goodsItemDeatils)
if (this.goodsItemDeatils.goods_count <= 0) {
uni.showToast({
title: "已售罄",
icon: "none",
duration: 1500
})
return
}
// 去购买物品
uni.navigateTo({
url: "/pages/index/digitGoodsDetails/BuyGoods?id=" + this.goodsItemDeatilsId,
})
},
goBack() {
uni.navigateBack({
delta: 1
});
},
// 通过id查询
async getGoodsItemDeatils(id) {
const goods = uniCloud.importObject('digital-goods')
const {
data
} = await goods.getGoodsById(id);
console.log(data)
this.goodsItemDeatils = data
this.goodsItemDeatilsId = data._id;
}
},
}
</script>
<style>
page {
background-color: black;
}
</style>
<style lang="less" scoped>
.nav-bar {
background-color: black;
}
.showGoodItem {
height: 800rpx;
display: flex;
flex-direction: column;
.center {
overflow: hidden;
flex: 6;
}
.bottom {
flex: 2;
background-image: url(../../../static/basic/desk.png);
background-repeat: no-repeat;
background-size: 100%;
z-index: 9999;
}
}
.introduce {
margin-top: 80rpx;
color: white;
text-align: center;
line-height: 30rpx;
font-size: 45rpx;
text:before {
background: url(@/static/basic/left.png);
content: '';
display: inline-block;
width: 80rpx;
height: 80rpx;
background-size: 100%;
background-repeat: no-repeat;
vertical-align: top;
}
text::after {
background: url(@/static/basic/right.png);
content: '';
display: inline-block;
width: 80rpx;
height: 80rpx;
background-size: 100%;
background-repeat: no-repeat;
vertical-align: top;
}
}
// 限量
.tag {
font-size: 27rpx;
color: #988f78;
margin-bottom: 15rpx;
text-align: center;
text {
padding: 8rpx 12rpx;
background-color: #feac59;
}
.quality {
color: black;
}
.count {
background-color: #3a3a3b;
}
}
.author {
// height: 450rpx;
margin-top: 60rpx;
display: flex;
flex-direction: column;
background-color: #141414;
color: white;
.top {
height: 100rpx;
font-size: 27rpx;
view {
margin-right: 20rpx;
}
view:nth-child(3) {
font-size: 30rpx;
}
.headerImg {
float: left;
height: 70rpx;
width: 70rpx;
border-radius: 50%;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
}
.center {
flex: 1;
margin: 20rpx 0;
font-size: 27rpx;
color: white;
line-height: 40rpx;
}
.bottom {
height: 60rpx;
overflow: hidden;
color: #C7C7C7;
.left {
float: left;
}
.right {
float: right;
}
}
}
// 购买须知
.provision {
margin-top: 40rpx;
padding-bottom: 50rpx;
background-color: #141414;
.title {
font-size: 40rpx;
color: white;
text-align: center;
padding-top: 20rpx;
}
view {
color: #C0C0C0;
font-size: 25rpx;
margin: 30rpx 0;
}
}
.footer {
height: 80rpx;
color: white;
text-align: center;
font-size: 25rpx;
}
.buy {
position: fixed;
bottom: 0;
left: 5%;
right: 5%;
padding: 20rpx 0;
height: 100rpx;
color: white;
background-color: black;
.left {
float: left;
font-size: 40rpx;
line-height: 70rpx;
}
.right {
float: right;
width: 180rpx;
height: 70rpx;
border-radius: 40rpx;
line-height: 70rpx;
text-align: center;
color: #ccc;
background-color: #141414;
}
}
</style>
8 商品购买
这里比较重要,算上一个入门的购买,不是很复杂,在如下界面进行支付宝沙箱购买,最近测试了一下。发现沙箱无法调用 沙箱支付宝app的,于是我就测试了PC扫码购买。
这里扫码支付需要用手机沙箱版的支付宝,而且登录的是买家信息
然后就购买完成了,后续进行库存减少一个,用户库存增加一个
开始支付——前端代码
async startPaymentUniPay() {
let that = this
// 1.新增一个订单表 0未完成 1已完成
// 前端不穿入私密信息参数,例如订单的总价,这些信息是在后端计算比较安全
const orderInfo = {
user_id: that.getUserInfo._id,
buy_count: that.buyCount,
goods_id: that.goodsItemDeatilsId,
goods_img: that.goodsItemDeatils.goods_img,
}
const userOrder = uniCloud.importObject('order')
const addOrderRes = await userOrder.addOrder(orderInfo)
console.log(addOrderRes)
// 2.订单表是否创建成功
if (addOrderRes.code == 0) {
const orderRecode = {
subject: addOrderRes.order.subject,
totalFee: addOrderRes.order.total * 100,
outTradeNo: addOrderRes.order.out_trade_no
}
// console.log(this.paymentArr[this.currentPayIndex].provider)
const pay = uniCloud.importObject("pay")
const res = await pay.createPayment(orderRecode)
console.log(res)
// pc支付 扫码注意``
this.pcCode=`${res.orderInfo.codeUrl}`
this.currentOutTradeNo = res.outTradeNo
// App支付沙箱不可以
// uni.getProvider({
// service: "payment",
// success: function(res) {
// if (~res.provider.indexOf('alipay')) {
// console.log(33)
// uni.requestPayment({
// // #ifdef APP-PLUS
// provider: 'alipay', // App端此参数必填,可以通过uni.getProvider获取
// // #endif
// // #ifdef MP-WEIXIN
// ...res.orderInfo,
// // #endif
// // #ifdef APP-PLUS || MP-ALIPAY
// orderInfo: res.orderInfo,
// // #endif
// ...res.orderInfo,
// success() {
// console.log(11)
// },
// fail(res) {
// console.log(res)
// console.log(22)
// }
// })
// }
// }
// })
} else {
// 2.2订单表创建失败
uni.showToast({
title: "订单创建失败",
icon: "none"
})
}
},
创建支付——后端代码
这里用的是原生的uni-pay大家可以下载这个插件
async createPayment(orderRecode) {
const {
subject,
totalFee,
outTradeNo
} = orderRecode
// 客户端IP
const clientInfo = this.getClientInfo()
const unipayIns = unipay.initAlipay({
appId: '2021000119653500',
mchId: '2088621958212266',
privateKey:'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCBSD5vl00ZVBozzaWN6ME9UkEeJlB9TJvPlHt+G3AWriGXx4zfCbOj94885qHx4k+DL2cz/WkSrc9UQrWb7rYHO5YMzt0LlfQaiqqOLH1zjc5eiLUyZgOtQgzmXUqEsyJ/uA8Oa+zbqMruHV/e5l3B5/x6ze9nQP2sCuzA9BW5GMW/iR8UhYltRFEH7ST4OgbNDMV9CsvSlJI+ncv3GnBGzoWoNIaE6sejL7HPtPWgwU3GB/a2UxXFsHs6M0ECxIb0iE1+EwW1g4mHXJAISQjXzC1mlWmUnyJDdb2Can9Yuw7Br8TMRRrSfjD8oDwzxK4+mgAOT7wIRXF0YMdRlBwnAgMBAAECggEANOuouy85HTQjIa99pHBxobNo+nl+mzvf0AIc5ws+P9p539KMpMQo5+avmKNkCUq1d6LQ4SRLfZzucLR5+OGnW6CVlJqdO1/fs3mtxVMK62FR57qYSZAe0y5lb2si1N0rMxLZ/vJI28Xlg8E8fFbabESVQoPICwE8lc9HCNkhh0m6a8Q7IRdhMWWcgStWiEIYwh4VWCXePrDX8vDcgck/avDLCdVxABC/g2TbM3blHMujuPi7RKHkCq1gNzeb+1nQ7B8KXKCpI/vV0x9umVXmsiLASjBYtarFcYdBeaNGzglILOKMtanVQbu9Ud/8rAXMWVIyGIc/Xk9YdNEJuDvA8QKBgQDCcWg62UWVOGWNRgaiy1tpUFCXRlpqEbOzL+7si6klty0Ea/3ar6s7pCDglNViRkGoW8SnTeuQ8CSE+RWDcB6/8sB5WNh+oFSV8tyktXXIDBJWl41ya+nNFBEoCwjkLt4/d/C/zKMCMy1IA8IDMW+5jx7qSk2mud/uvmDrDJrYvwKBgQCqNeS+z8327aVY/yMPLlLYHXaItrOG9G1EDv2CXQamhYOAxWwhbnUdFzAJ/vkuuE4TqizTUlSs4Q+JYu1nvMBmEGQEjH05KTuRk+VjfIpxTPUasg267QO+UqpSQjZOlywnlZpmF74CVbuf7VGMtRbgoqGYVwwTSZSJ88YQrznumQKBgDIAYdJITyl5UDwNFuXEL6ScX60XDWcbWD+rAuDXZU7qlOv/LU+QPxNeOIooG9tiv72go6h2VC54YpvDRfH/4hhwP2i1HS8q4bWRt4WvmDMyT4DvBhO3hm8TSvomJEoxdzuRB1r7L6wAJxPOy5RwoQ9xq3yKUP+f6uMFF/05x5QXAoGABTUS43eOEJDirx9XHj7HkA8Osk4MngNaWGuw+80GyA9rn4Pqs/ciJBqygIvJadF/qgBZ0LlDF+rmw8hABR10MWss9CqsJhSJ2wFQUkPcJG07Tm7GFsLri2YSCeQ2rx7W3fsotxjg2kdvLwxeo1Ri98ph8TwwhWKJ5dKMa7RArHkCgYEAmiLSxYKWOKOjYf0SvRju8XoAfmeY0jFdnmGxPIFQZMZuGEobaNikhkHO0z4A7pxmi3Z7+pLcS0PgfKaWGHeyjAzM8FN0o1+8vN0+jPS1weZT1h/UgwqmgXlxVrjdA0zq7cCxl4uhE3ke+UI43OVUO+MJbz5yBwS1C/DuCeOD+Y4=',
alipayPublicKey:'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgrX/exkQTqjQyg1fjiTBR8EVMRXrFKxaL1f+sIcFPHL47bgKIO1PxClCuUB5crAiZID1PiVYOaQwXU1pXD3AcUR4exXGlkhrC6ud3NiF51Ep4UKXfj+M6DfUXr+kp4qy0WOEhj78zHNwGhi7DLgSSd+n1mcfjP2ziUjWg8GmiIIPBVSq2UYYD+333f2UrRhg/cPIiCJ/BnCPfFssd0N+5xCV5u2sz91as5A4Yzm3N1pPW01l+VjdWFLVtCNEY+BaSdVqFaCSGHubK3Y4myzMvyHQZ3kdQDIqPZBkEjWcTZZdiF89FjmKbc/q/G+1mAFpk1GJeUzfYcTz+a6XPnYktwIDAQAB',
sandbox: true
})
// App支付沙箱不可以
// let orderInfo = await unipayIns.getOrderInfo({
// subject: subject, // 微信支付时不可填写此项
// spbillCreateIp: clientInfo.clientIP,
// outTradeNo: 'QiHang'+new Date().getTime(),
// tradeType: 'APP',
// totalFee: totalFee, // 金额,单位分
// notifyUrl: 'https://baidu.com' // 支付结果通知地址
// })
// PC扫码支付
let orderInfo = await unipayIns.getOrderInfo({
subject: subject, // 微信支付时不可填写此项
spbillCreateIp: clientInfo.clientIP,
outTradeNo: 'QiHang'+new Date().getTime(),
tradeType: 'NATIVE',
totalFee: totalFee, // 金额,单位分
notifyUrl: 'https://baidu.com' // 支付结果通知地址
})
console.log(orderInfo)
return {
orderInfo,
outTradeNo
}
},
地址生成二维码——前端代码
需要把后端返回的codeUrl变成二维码,让用户用手机去扫,然后付款
<uqrcode :class="{uqrcode_hide:pcCode==''}" cid="uQRCode" ref="uQRCode" :text="pcCode"
:corner="{ lt: { color: '#AA0000' }, rt: { color: '#ffff00,#ff5500' }, lb: { color: ['#00ffff', '#55aaff'] } }"
background-color="#FFFFFF" :size="130" :margin="10" foreground-image="/static/basic/logo.png">
</uqrcode>
这个插件直接复制我的文件夹放到你们的文件夹即可,跟目前网上的插件有一些差别