2019年的时候和朋友出去旅行,因为需要A账单,所以前一天开发了一个记账小程序,时间匆忙,就随便完成基础记账和AA计算功能后就上线,旅行结束后也就没用过了,前几天无意登录,发现被打了1.0分。
叔能忍,婶婶不可忍,摸鱼两周升级完成新的记账小程序。
技术栈:uni-app + uniCloud + TM-vuetify
具体实现功能思路:
- 用户进入小程序自动登录,未注册自动注册,免去登录步骤
- 记账功能
- 多种记账类型(ICON图标)
- 可选记账日期
- 计算器(因为小程序为了安全起见不支持eval,所以这块需要自己实现)
- 月度账单图表查看(收支排行榜、当月结余)
- 年度账单查看
- 共享协同账单(目前只实现了AA协同方式,因为太懒了,其他先不搞了)
- 个人中心
- 用户设置(头像啊、昵称什么的,共享账单不得知道谁是谁)
- 意见反馈(用于收集用户反馈bug等,ps:不得给用户留个发泄口,免得再给我打1.0分)
- 分享功能
- 在线客服
需求理清楚了,开干!(好吧,其实并没有,因为没有设计天赋,所以摆烂了两天,o(╥﹏╥)o)
用户登录/注册
因为微信小程序一直在变更用户信息获取方式
从# UserInfo => # wx.getUserInfo => # wx.getUserProfile 最后到现在的需要通过用户授权才能获取到头像和昵称,所以初始化注册无法自动补全用户信息
# 小程序用户头像昵称获取规则
前端通过 uni.login 获取用户code
uni.login({success: async (res) => {// 获取用户codeconst {code} = res// 调用云函数登录/注册uniCloud.callFunction({name: 'user',data: {code}}).then(res => {// 存储返回的当前用户的信息uni.setStorageSync('userInfo', JSON.stringify(res.result))this.$store.dispatch('setUserInfo', res.result)})}
})
user云函数负责处理登录返回用户信息,未注册则自动注册后返回用户信息
/*
* 通过请求session接口获取用户openId(微信用户唯一标识)
* appid、secret在公众平台查看
*/
const res = await uniCloud.httpclient.request('https://api.weixin.qq.com/sns/jscode2session', {method: 'GET',data: {appid,secret,js_code,grant_type: 'authorization_code'},contentType: 'json',dataType: 'json'
})
/*
* 通过user集合的length可判断是否注册
*/
const {data:user} = await db.collection('user').where({openId}).get()
if(user.length > 0){// 已注册过直接返回用户信息return user[0]
}else{// 未注册则创建用户后返回用户信息const userObj = {nickname: `微信用户${str}`, // 因为初始化无法获取,所以给默认昵称,str:随机字符串avatar: null,openId,phone: ''}await db.collection('user').add(userObj)return userObj
}
记账
ICON图标
引用阿里字体图标,将资源包导入项目static目录,App.vue引入iconfont.css
创建icon组件,通过name参数生成图标
<text :class="'iconfont icon-' + name"></text>
计算器
因为微信小程序因为安全策略不支持eval函数,所以这块通过另类实现计算器功能
// 操作符
const code_symbol = ['.', '+', '-']
let str = (this.bill.money === '0' ? '0' : this.bill.money)const end = str[str.length - 1]
// 禁止触发多个操作符
if ((code_symbol.includes(key) && !code_symbol.includes(end)) || (!code_symbol.includes(key))
) str += key// 判断操作符变更
if (code_symbol.includes(end) && end !== key) {let key_arr = str.split('')key_arr[key_arr.length - 1] = keystr = key_arr.join('')
}/*
* 计算同理
*/
const code_symbol = ['+', '-']
// 格式化处理展示金额值
const arr = this.formatStr(this.bill.money)
let prev, result
for (let i = 0; i < arr.length - 1; i++) {const item = arr[i]const next = arr[i + 1]// 处理点位符,处理最后位数为操作符if (code_symbol.includes(item)) {item === '+' ? (result = parseFloat(prev || 0) + parseFloat(next || 0)) : (result =parseFloat( prev || 0) - parseFloat(next || 0))prev = result} else {prev = result || arr[i]}
}/** 格式化处理计算 string * return {Array}*/
formatStr(str) {let arr = []const code_symbol = ['+', '-']for (let i = 0; i < str.length; i++) {let len = arr.length === 0 ? 0 : arr.length - 1const v = str[i]if (code_symbol.includes(v)) {arr.push(v)} else {if (code_symbol.includes(arr[len])) {arr[len + 1] = (arr[len + 1] || '') + v} else {arr[len] = (arr[len] || '') + v}}}return arr
}
月度账单
引入echarts图表,实现收支饼状图
this.$refs.incomeChart.setOption({
tooltip: {trigger: 'item',formatter: '{a} <br/>{b} : {c} ({d}%)'
},
grid: {left: 20,
},
legend: {type: 'scroll',orient: 'vertical',right: 10,top: 10,bottom: 30,data: this.incomeData.map((v) => {return v.name}),formatter: function(name) {return name.length > 4 ? `${name.substring(0,4)}...` : name;}
},
toolbox: {show: true,feature: {mark: {show: true},dataView: {show: true,readOnly: false},restore: {show: true},saveAsImage: {show: true}}
},
series: [{type: 'pie',radius: ['40%', '70%'],center: ["30%", "50%"],label: {show: false},emphasis: {label: {show: true}},data: this.incomeData
}]
年度账单
这块需要判断查询年份是否为当年,如果为本年度则只展示到当前月份,如果为过去年份,则展示全年统计
const now = new Date().getFullYear()
const months = ((this.year === now) ? (new Date().getMonth() + 1) : 12)
协同账单
// 账单Schema
{name, // 账单名称img, // 缩略图users: [userId], // 成员、默认加入创建人createTime: Date.now(), bills: [], // 账单createUser: userId, audit, // 是否需要审核member, // 是否允许成员记账allocation // 计算方式
}// 邀请用户自动加入
if (!res.users.includes(this.userInfo.openId)) {// 判断是否开始审核if (!res.audit) {await uniCloud.callFunction({name: 'add-share-user',data: {_id: this.shareId,openId: this.userInfo.openId}})this.getDetail(this.shareId)} else {// 触发审核const params = {ownId: res.createUser,shareBillId: this.shareId,auditId: this.userInfo.openId}await uniCloud.callFunction({name: 'send-audit',data: params})setTimeout(() => {this.$refs.toast.show({model: 'warn',label: '创建者已开启审核,待审核通过后自动加入'})}, 1000)}
}
我的
这块主要说下个人设置,因为现在获取用户头像和昵称需要用户授权
官方文档说的是通过个人设置页面让用户授权获取,参考文档:头像昵称填写能力
注:如果需要存储用户手机号等敏感信息,务必在填写页面声明用户协议及隐私政策,否则不予通过
总结
记录下时隔好几年重新写小程序,官方生态很多都变了,现在官方越来越注重用户隐私这块了,基本上所以牵扯到用户的地方都需要用户授权后才可调用。代码大部分还是无脑梭出来的,毕竟时间有限,后续还需要优化代码,但是喜欢摆烂就这样吧。对了,我还接入了ChatGPT,但是被官方禁止了,不允许接入,那页面都做了能怎么办,接了个睿智机器人。
转载或者引用本文内容请注明来源及原作者:_元十七 (https://juejin.cn/post/7220262023472103485)