likes
comments
collection
share

只因为给我打了0.1分,重新撸了个记账小程序

作者站长头像
站长
· 阅读数 18

2019年的时候和朋友出去旅行,因为需要A账单,所以前一天开发了一个记账小程序,时间匆忙,就随便完成基础记账和AA计算功能后就上线,旅行结束后也就没用过了,前几天无意登录,发现被打了1.0分。 叔能忍,婶婶不可忍,摸鱼两周升级完成新的记账小程序。

只因为给我打了0.1分,重新撸了个记账小程序
技术栈:uni-app + uniCloud + TM-vuetify
只因为给我打了0.1分,重新撸了个记账小程序

具体实现功能思路:

  1. 用户进入小程序自动登录,未注册自动注册,免去登录步骤
  2. 记账功能
    • 多种记账类型(ICON图标)
    • 可选记账日期
    • 计算器(因为小程序为了安全起见不支持eval,所以这块需要自己实现)
  3. 月度账单图表查看(收支排行榜、当月结余)
  4. 年度账单查看
  5. 共享协同账单(目前只实现了AA协同方式,因为太懒了,其他先不搞了)
  6. 个人中心
    • 用户设置(头像啊、昵称什么的,共享账单不得知道谁是谁)
    • 意见反馈(用于收集用户反馈bug等,ps:不得给用户留个发泄口,免得再给我打1.0分)
    • 分享功能
    • 在线客服

需求理清楚了,开干!(好吧,其实并没有,因为没有设计天赋,所以摆烂了两天,o(╥﹏╥)o)

只因为给我打了0.1分,重新撸了个记账小程序

用户登录/注册

因为微信小程序一直在变更用户信息获取方式 从# UserInfo => # wx.getUserInfo => # wx.getUserProfile 最后到现在的需要通过用户授权才能获取到头像和昵称,所以初始化注册无法自动补全用户信息 # 小程序用户头像昵称获取规则

前端通过 uni.login 获取用户code

uni.login({
    success: async (res) => {
        // 获取用户code
	const {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>
只因为给我打了0.1分,重新撸了个记账小程序

计算器

因为微信小程序因为安全策略不支持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] = key
        str = 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 - 1
        const 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
}
只因为给我打了0.1分,重新撸了个记账小程序

月度账单

引入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
}]
只因为给我打了0.1分,重新撸了个记账小程序

年度账单

这块需要判断查询年份是否为当年,如果为本年度则只展示到当前月份,如果为过去年份,则展示全年统计

const now = new Date().getFullYear()
const months = ((this.year === now) ? (new Date().getMonth() + 1) : 12)
只因为给我打了0.1分,重新撸了个记账小程序

协同账单

// 账单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)
    }
}
只因为给我打了0.1分,重新撸了个记账小程序

我的

这块主要说下个人设置,因为现在获取用户头像和昵称需要用户授权 官方文档说的是通过个人设置页面让用户授权获取,参考文档:头像昵称填写能力

注:如果需要存储用户手机号等敏感信息,务必在填写页面声明用户协议及隐私政策,否则不予通过

只因为给我打了0.1分,重新撸了个记账小程序

总结

记录下时隔好几年重新写小程序,官方生态很多都变了,现在官方越来越注重用户隐私这块了,基本上所以牵扯到用户的地方都需要用户授权后才可调用。代码大部分还是无脑梭出来的,毕竟时间有限,后续还需要优化代码,但是喜欢摆烂就这样吧。对了,我还接入了ChatGPT,但是被官方禁止了,不允许接入,那页面都做了能怎么办,接了个睿智机器人。