最近研究了uniCloud,并用uniCloud开发了一个性格测试小程序,已经发布到服务器,完美的发布到抖音,在抖音可以搜索到该小程序。源码和视频教程都开源给大家了。
视频的教程放在b站了,希望下坡伙伴,给个三连:
https://www.bilibili.com/video/BV19u411z7Jr?spm_id_from=333.999.0.0
一,什么是uniCloud:
uniCloud是uni-app的云开发库,是 DCloud 联合阿里云、腾讯云,为开发者提供的基于 serverless 模式和 js 编程的云开发平台。
uniCloud基本抹平了不同云厂商的差异,如果你熟悉mongoDB,那么就很容易上手了!
现在用uniCloud可以大大的提高开发的效率,而且可以降低运维方面的成本,因为现在serverless还处于免费阶段。
当然也有很多的缺点,新手上手的话不是很快,很多东西也不支持,例如删除数据,竟然还要去调api。
最近小孟和老王研究了下,感觉还是可以搞的,于是就测试并发布了整个通用的版本,发布到了抖音,这个大家可以搞一下,抖音的流量你懂的,但是我们现在对赚钱不感兴趣。所以有熊伙伴想搞的,可以拿去源码学习,但是不要商用和贩卖!
二,系统界面截图:
三,核心代码演示:
<template> <view class="center"> <view class="userInfo" @click.capture="toUserInfo"> <uni-file-picker v-if="userInfo.avatar_file" v-model="userInfo.avatar_file" fileMediatype="image" :del-icon="false" return-type="object" :image-styles="listStyles" disablePreview disabled /> <image v-else class="logo-img" src="/static/uni-center/defaultAvatarUrl.png"></image> <view class="logo-title"> <text class="uer-name">{{userInfo.nickname||userInfo.username||userInfo.mobile||'未登录'}}</text> </view> </view> <uni-list class="center-list" v-for="(sublist , index) in ucenterList" :key="index"> <uni-list-item v-for="(item,i) in sublist" :title="item.title" link :rightText="item.rightText" :key="i" :clickable="true" :to="item.to" @click="ucenterListClick(item)" :show-extra-icon="true" :extraIcon="{type:item.icon,color:'#999'}"> <view v-if="item.showBadge" class="item-footer" slot="footer"> <text class="item-footer-text">{{item.rightText}}</text> <view class="item-footer-badge"></view> </view> </uni-list-item> </uni-list> </view> </template> <script> import { mapGetters, mapMutations } from 'vuex'; import checkUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update'; import callCheckVersion from '@/uni_modules/uni-upgrade-center-app/utils/call-check-version'; import uniShare from 'uni_modules/uni-share/js_sdk/uni-share.js'; const db = uniCloud.database(); export default { data() { return { ucenterList: [ [{ "title": '问题与反馈', "to": '/uni_modules/uni-feedback/pages/uni-feedback/uni-feedback', "icon": "help" }, { "title": '设置', "to": '/pages/ucenter/settings/settings', "icon": "gear" }], [{ "title": '关于', "to": '/pages/ucenter/about/about', "icon": "info" }] ], listStyles: { "height": "150rpx", // 边框高度 "width": "150rpx", // 边框宽度 "border": { // 如果为 Boolean 值,可以控制边框显示与否 "color": "#eee", // 边框颜色 "width": "1px", // 边框宽度 "style": "solid", // 边框样式 "radius": "100%" // 边框圆角,支持百分比 } } } }, onLoad() { //#ifdef APP-PLUS this.ucenterList[this.ucenterList.length - 2].unshift({ title: '检查更新', rightText: this.appVersion.version + '-' + this.appVersion.versionCode, event: 'checkVersion', icon: 'loop', showBadge: this.appVersion.hasNew }) //#endif }, computed: { ...mapGetters({ userInfo: 'user/info', login: 'user/hasLogin' }) // #ifdef APP-PLUS , appVersion() { return getApp().appVersion } // #endif , appConfig() { return getApp().globalData.config } }, methods: { ...mapMutations({ setUserInfo: 'user/login' }), toSettings() { uni.navigateTo({ url: "/pages/ucenter/settings/settings" }) }, /** * 个人中心项目列表点击事件 */ ucenterListClick(item) { if (!item.to && item.event) { this[item.event](); } }, async checkVersion() { let res = await callCheckVersion() console.log(res); if (res.result.code > 0) { checkUpdate() } else { uni.showToast({ title: res.result.message, icon: 'none' }); } }, toUserInfo() { uni.navigateTo({ url: '/pages/ucenter/userinfo/userinfo' }) }, /** * 去应用市场评分 */ gotoMarket() { // #ifdef APP-PLUS if (uni.getSystemInfoSync().platform == "ios") { // 这里填写appstore应用id let appstoreid = this.appConfig.marketId.ios; // 'id1417078253'; plus.runtime.openURL("itms-apps://" + 'itunes.apple.com/cn/app/wechat/' + appstoreid + '?mt=8'); } if (uni.getSystemInfoSync().platform == "android") { var Uri = plus.android.importClass("android.net.Uri"); var uri = Uri.parse("market://details?id=" + this.appConfig.marketId.android); var Intent = plus.android.importClass('android.content.Intent'); var intent = new Intent(Intent.ACTION_VIEW, uri); var main = plus.android.runtimeMainActivity(); main.startActivity(intent); } // #endif }, async share() { let {result} = await uniCloud.callFunction({ name: 'uni-id-cf', data: { action: 'getUserInviteCode' } }) console.log(result); let myInviteCode = result.myInviteCode || result.userInfo.my_invite_code console.log(myInviteCode); let { appName, logo, company, slogan } = this.appConfig.about // #ifdef APP-PLUS uniShare({ content: { //公共的分享类型(type)、链接(herf)、标题(title)、summary(描述)、imageUrl(缩略图) type: 0, href: this.appConfig.h5.url + `/#/pages/ucenter/invite/invite?code=${myInviteCode}`, title: appName, summary: slogan, imageUrl: logo + '?x-oss-process=image/resize,m_fill,h_100,w_100' //压缩图片解决,在ios端分享图过大导致的图片失效问题 }, menus: [{ "img": "/static/app-plus/sharemenu/wechatfriend.png", "text": "微信好友", "share": { "provider": "weixin", "scene": "WXSceneSession" } }, { "img": "/static/app-plus/sharemenu/wechatmoments.png", "text": "", "share": { "provider": "weixin", "scene": "WXSenceTimeline" } }, { "img": "/static/app-plus/sharemenu/weibo.png", "text": "微博", "share": { "provider": "sinaweibo" } }, { "img": "/static/app-plus/sharemenu/qq.png", "text": "QQ", "share": { "provider": "qq" } }, { "img": "/static/app-plus/sharemenu/copyurl.png", "text": "复制", "share": "copyurl" }, { "img": "/static/app-plus/sharemenu/more.png", "text": "更多", "share": "shareSystem" } ], cancelText: "取消分享", }, e => { //callback console.log(e); }) // #endif } } } </script> <style> /* #ifndef APP-PLUS-NVUE */ page { background-color: #f8f8f8; } /* #endif*/ .center { flex: 1; flex-direction: column; background-color: #f8f8f8; } .userInfo { width: 750rpx; padding: 20rpx; padding-top: 50px; background-image: url(../../static/uni-center/headers.png); flex-direction: column; align-items: center; } .logo-img { width: 150rpx; height: 150rpx; border-radius: 150rpx; } .logo-title { flex: 1; align-items: center; justify-content: space-between; flex-direction: row; } .uer-name { height: 100rpx; line-height: 100rpx; font-size: 38rpx; color: #FFFFFF; } .center-list { margin-bottom: 30rpx; background-color: #f9f9f9; } .center-list-cell { width: 750rpx; background-color: #007AFF; height: 40rpx; } .grid { background-color: #FFFFFF; margin-bottom: 6px; } .uni-grid .text { font-size: 30rpx; height: 25px; line-height: 25px; color: #817f82; } .uni-grid .item /deep/ .uni-grid-item__box { justify-content: center; align-items: center; } /*修改边线粗细示例*/ /* #ifndef APP-NVUE */ .center-list /deep/ .uni-list--border:after { -webkit-transform: scaleY(0.2); transform: scaleY(0.2); margin-left: 80rpx; } .center-list /deep/ .uni-list--border-top, .center-list /deep/ .uni-list--border-bottom { display: none; } /* #endif */ .item-footer { flex-direction: row; align-items: center; } .item-footer-text { color: #999; font-size: 24rpx; padding-right: 10rpx; } .item-footer-badge { width: 20rpx; height: 20rpx; /* #ifndef APP-NVUE */ border-radius: 50%; /* #endif */ /* #ifdef APP-NVUE */ border-radius: 10rpx; /* #endif */ background-color: #DD524D; } </style>
<script> import uQRCode from './uqrcode.js' export default { props: { cid: { type: String, default(){ return Date.now()+Math.random()+''; } }, text: { type: String, required: true }, size: { type: Number, default: uni.upx2px(200) }, margin: { type: Number, default: 0 }, backgroundColor: { type: String, default: '#ffffff' }, foregroundColor: { type: String, default: '#000000' }, backgroundImage: { type: String }, logo: { type: String }, makeOnLoad: { type: Boolean, default: false } }, data() { return { } }, mounted() { if (this.makeOnLoad) { this.make() } }, methods: { async make() { var options = { canvasId: this.cid, componentInstance: this, text: this.text, size: this.size, margin: this.margin, backgroundColor: this.backgroundImage ? 'rgba(255,255,255,0)' : this.backgroundColor, foregroundColor: this.foregroundColor } var filePath = await this.makeSync(options) if (this.backgroundImage) { filePath = await this.drawBackgroundImageSync(filePath) } if (this.logo) { filePath = await this.drawLogoSync(filePath) } this.makeComplete(filePath) }, makeComplete(filePath) { this.$emit('makeComplete', filePath) }, drawBackgroundImage(options) { var ctx = uni.createCanvasContext(this.cid, this) ctx.drawImage(this.backgroundImage, 0, 0, this.size, this.size) ctx.drawImage(options.filePath, 0, 0, this.size, this.size) ctx.draw(false, () => { uni.canvasToTempFilePath({ canvasId: this.cid, success: res => { options.success && options.success(res.tempFilePath) }, fail: error => { options.fail && options.fail(error) } }, this) }) }, async drawBackgroundImageSync(filePath) { return new Promise((resolve, reject) => { this.drawBackgroundImage({ filePath: filePath, success: res => { resolve(res) }, fail: error => { reject(error) } }) }) }, fillRoundRect(ctx, r, x, y, w, h) { ctx.save() ctx.translate(x, y) ctx.beginPath() ctx.arc(w - r, h - r, r, 0, Math.PI / 2) ctx.lineTo(r, h) ctx.arc(r, h - r, r, Math.PI / 2, Math.PI) ctx.lineTo(0, r) ctx.arc(r, r, r, Math.PI, Math.PI * 3 / 2) ctx.lineTo(w - r, 0) ctx.arc(w - r, r, r, Math.PI * 3 / 2, Math.PI * 2) ctx.lineTo(w, h - r) ctx.closePath() ctx.setFillStyle('#ffffff') ctx.fill() ctx.restore() }, drawLogo(options) { var ctx = uni.createCanvasContext(this.cid, this) ctx.drawImage(options.filePath, 0, 0, this.size, this.size) var logoSize = this.size / 4 var logoX = this.size / 2 - logoSize / 2 var logoY = logoX var borderSize = logoSize + 10 var borderX = this.size / 2 - borderSize / 2 var borderY = borderX var borderRadius = 5 this.fillRoundRect(ctx, borderRadius, borderX, borderY, borderSize, borderSize) ctx.drawImage(this.logo, logoX, logoY, logoSize, logoSize) ctx.draw(false, () => { uni.canvasToTempFilePath({ canvasId: this.cid, success: res => { options.success && options.success(res.tempFilePath) }, fail: error => { options.fail && options.fail(error) } }, this) }) }, async drawLogoSync(filePath) { return new Promise((resolve, reject) => { this.drawLogo({ filePath: filePath, success: res => { resolve(res) }, fail: error => { reject(error) } }) }) }, async makeSync(options) { return new Promise((resolve, reject) => { uQRCode.make({ canvasId: options.canvasId, componentInstance: options.componentInstance, text: options.text, size: options.size, margin: options.margin, backgroundColor: options.backgroundColor, foregroundColor: options.foregroundColor, success: res => { resolve(res) }, fail: error => { reject(error) } }) }) } } } </script>