├── miniprogram ├── pages │ ├── components │ │ ├── nav │ │ │ ├── nav.json │ │ │ ├── nav.js │ │ │ ├── nav.wxml │ │ │ └── nav.wxss │ │ ├── dialog │ │ │ ├── dialog.json │ │ │ ├── dialog.wxml │ │ │ ├── dialog.js │ │ │ └── dialog.wxss │ │ ├── relate │ │ │ ├── relate.json │ │ │ ├── relate.js │ │ │ ├── relate.wxss │ │ │ └── relate.wxml │ │ ├── calendar │ │ │ ├── calendar.json │ │ │ ├── .DS_Store │ │ │ ├── calendar.wxss │ │ │ └── calendar.wxml │ │ ├── .DS_Store │ │ ├── bill-list │ │ │ ├── bill-list.json │ │ │ ├── bill-list.wxss │ │ │ ├── bill-list.wxml │ │ │ └── bill-list.js │ │ ├── index │ │ │ ├── index.json │ │ │ └── index.wxss │ │ ├── chart │ │ │ ├── chart.json │ │ │ ├── chart.wxss │ │ │ └── chart.wxml │ │ └── list │ │ │ ├── list.json │ │ │ ├── list.wxml │ │ │ ├── list.wxss │ │ │ └── list.js │ ├── onboarding │ │ ├── onboarding.json │ │ ├── onboarding.js │ │ ├── onboarding.wxss │ │ └── onboarding.wxml │ ├── .DS_Store │ ├── morechart │ │ ├── morechart.json │ │ ├── morechart.wxss │ │ ├── morechart.wxml │ │ └── morechart.js │ ├── group │ │ ├── group.json │ │ ├── group.wxss │ │ └── group.wxml │ ├── category │ │ ├── category.json │ │ └── category.wxss │ ├── group-bill-set │ │ ├── group-bill-set.json │ │ ├── group-bill-set.wxss │ │ ├── group-bill-set.js │ │ └── group-bill-set.wxml │ ├── target │ │ ├── target.json │ │ └── target.wxss │ ├── target-set │ │ ├── target-set.json │ │ ├── target-set.wxss │ │ ├── target-set.wxml │ │ └── target-set.js │ ├── wxs │ │ └── index.wxs │ ├── setting │ │ ├── setting.json │ │ ├── setting.wxss │ │ ├── setting.js │ │ └── setting.wxml │ ├── search │ │ ├── search.json │ │ ├── search.wxml │ │ ├── search.js │ │ └── search.wxss │ └── tab │ │ ├── tab.json │ │ ├── tab.wxss │ │ └── tab.wxml ├── .DS_Store ├── images │ ├── 图表.png │ ├── 客服.png │ ├── 搜索.png │ ├── 目标.png │ ├── 设置.png │ ├── .DS_Store │ ├── add.png │ ├── app.png │ ├── cool.png │ ├── dices.png │ ├── greed.png │ ├── kiss.png │ ├── menu.png │ ├── plus.png │ ├── puke.png │ ├── quill.png │ ├── sad.png │ ├── smile.png │ ├── user.png │ ├── cancel.png │ ├── group1.png │ ├── group2.png │ ├── message.png │ ├── pencil.png │ ├── search.png │ ├── tongue.png │ ├── account.jpeg │ ├── add-white.png │ ├── arrow-left.png │ ├── calendar.png │ ├── grinning.png │ ├── jielong.jpeg │ ├── pie-chart.png │ ├── dandan-cover.png │ ├── backspace-arrow.png │ ├── calendar-dark.png │ ├── pie-chart-dark.png │ └── right-chevron.png ├── miniprogram_npm │ └── @antv │ │ └── wx-f2 │ │ ├── index.json │ │ ├── index.wxss │ │ └── index.wxml ├── sitemap.json ├── package.json ├── store │ ├── index.js │ └── omix │ │ └── path.js ├── app.json ├── iconfont.wxss ├── reset.wxss ├── app.wxss ├── .pnpm-debug.log ├── util.js ├── app.js └── pnpm-lock.yaml ├── .DS_Store ├── .eslintignore ├── cloudfunctions ├── donate │ ├── config.json │ ├── package.json │ └── index.js ├── user │ ├── config.json │ ├── package.json │ └── index.js ├── stat │ ├── config.json │ ├── package.json │ └── index.js ├── sendMessage │ ├── config.json │ ├── package.json │ └── index.js ├── word │ ├── package.json │ └── index.js ├── target │ ├── package.json │ └── index.js ├── category │ ├── package.json │ └── index.js ├── getCategory │ ├── package.json │ └── index.js ├── checkSubscribe │ ├── package.json │ └── index.js ├── accountAggregate │ ├── package.json │ ├── README.md │ └── index.js ├── search │ ├── package.json │ └── index.js ├── account │ ├── package.json │ └── index.js ├── getAccountList │ ├── package.json │ ├── README.md │ └── index.js ├── getAccountChart │ └── package.json └── exportFile │ ├── package.json │ ├── styles.xml │ └── index.js ├── .gitignore ├── cloudfunctionTemplate ├── getCategory.json ├── getAccountList.json ├── accountAggregate.json ├── account.json └── category.json ├── comm └── comm.js ├── .editorconfig ├── README.md ├── .eslintrc.js ├── LICENSE ├── package.json └── project.config.json /miniprogram/pages/components/nav/nav.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true 3 | } -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/.DS_Store -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | u-charts.js 2 | miniprogram/store/** 3 | node_modules 4 | *npm -------------------------------------------------------------------------------- /miniprogram/pages/components/dialog/dialog.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true 3 | } -------------------------------------------------------------------------------- /miniprogram/pages/onboarding/onboarding.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /cloudfunctions/donate/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "openapi": [ 4 | ] 5 | } 6 | } -------------------------------------------------------------------------------- /cloudfunctions/user/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "openapi": [ 4 | ] 5 | } 6 | } -------------------------------------------------------------------------------- /miniprogram/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/.DS_Store -------------------------------------------------------------------------------- /miniprogram/images/图表.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/图表.png -------------------------------------------------------------------------------- /miniprogram/images/客服.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/客服.png -------------------------------------------------------------------------------- /miniprogram/images/搜索.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/搜索.png -------------------------------------------------------------------------------- /miniprogram/images/目标.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/目标.png -------------------------------------------------------------------------------- /miniprogram/images/设置.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/设置.png -------------------------------------------------------------------------------- /miniprogram/pages/components/relate/relate.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /miniprogram/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/.DS_Store -------------------------------------------------------------------------------- /miniprogram/images/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/add.png -------------------------------------------------------------------------------- /miniprogram/images/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/app.png -------------------------------------------------------------------------------- /miniprogram/images/cool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/cool.png -------------------------------------------------------------------------------- /miniprogram/images/dices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/dices.png -------------------------------------------------------------------------------- /miniprogram/images/greed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/greed.png -------------------------------------------------------------------------------- /miniprogram/images/kiss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/kiss.png -------------------------------------------------------------------------------- /miniprogram/images/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/menu.png -------------------------------------------------------------------------------- /miniprogram/images/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/plus.png -------------------------------------------------------------------------------- /miniprogram/images/puke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/puke.png -------------------------------------------------------------------------------- /miniprogram/images/quill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/quill.png -------------------------------------------------------------------------------- /miniprogram/images/sad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/sad.png -------------------------------------------------------------------------------- /miniprogram/images/smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/smile.png -------------------------------------------------------------------------------- /miniprogram/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/user.png -------------------------------------------------------------------------------- /miniprogram/miniprogram_npm/@antv/wx-f2/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /miniprogram/miniprogram_npm/@antv/wx-f2/index.wxss: -------------------------------------------------------------------------------- 1 | .f2-canvas { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /miniprogram/pages/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/pages/.DS_Store -------------------------------------------------------------------------------- /miniprogram/pages/components/calendar/calendar.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /miniprogram/images/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/cancel.png -------------------------------------------------------------------------------- /miniprogram/images/group1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/group1.png -------------------------------------------------------------------------------- /miniprogram/images/group2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/group2.png -------------------------------------------------------------------------------- /miniprogram/images/message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/message.png -------------------------------------------------------------------------------- /miniprogram/images/pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/pencil.png -------------------------------------------------------------------------------- /miniprogram/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/search.png -------------------------------------------------------------------------------- /miniprogram/images/tongue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/tongue.png -------------------------------------------------------------------------------- /miniprogram/images/account.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/account.jpeg -------------------------------------------------------------------------------- /miniprogram/images/add-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/add-white.png -------------------------------------------------------------------------------- /miniprogram/images/arrow-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/arrow-left.png -------------------------------------------------------------------------------- /miniprogram/images/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/calendar.png -------------------------------------------------------------------------------- /miniprogram/images/grinning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/grinning.png -------------------------------------------------------------------------------- /miniprogram/images/jielong.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/jielong.jpeg -------------------------------------------------------------------------------- /miniprogram/images/pie-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/pie-chart.png -------------------------------------------------------------------------------- /miniprogram/images/dandan-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/dandan-cover.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | cloudfunctions/backup/wxInfo.js 3 | cloudfunctions/**/package-lock.json 4 | cloudfunctions/**/token.js -------------------------------------------------------------------------------- /miniprogram/images/backspace-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/backspace-arrow.png -------------------------------------------------------------------------------- /miniprogram/images/calendar-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/calendar-dark.png -------------------------------------------------------------------------------- /miniprogram/images/pie-chart-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/pie-chart-dark.png -------------------------------------------------------------------------------- /miniprogram/images/right-chevron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/images/right-chevron.png -------------------------------------------------------------------------------- /miniprogram/pages/components/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/pages/components/.DS_Store -------------------------------------------------------------------------------- /cloudfunctionTemplate/getCategory.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "获取分类列表", 4 | "value": { 5 | "flow": "1" 6 | } 7 | } 8 | ] -------------------------------------------------------------------------------- /comm/comm.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | SUCCESS_CODE: 1, 3 | ERROR_CODE: -1, 4 | SUCCESS_MESSAGE: '操作成功', 5 | ERROR_MESSAGE: '操作失败', 6 | } 7 | -------------------------------------------------------------------------------- /miniprogram/pages/components/calendar/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GzhiYi/dandan-account/HEAD/miniprogram/pages/components/calendar/.DS_Store -------------------------------------------------------------------------------- /miniprogram/pages/morechart/morechart.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "nav": "../components/nav/nav", 4 | "f2": "@antv/wx-f2" 5 | } 6 | } -------------------------------------------------------------------------------- /miniprogram/pages/components/bill-list/bill-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": { 4 | "dialog": "../dialog/dialog" 5 | } 6 | } -------------------------------------------------------------------------------- /miniprogram/pages/group/group.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "nav": "../components/nav/nav", 4 | "dialog": "../components/dialog/dialog" 5 | } 6 | } -------------------------------------------------------------------------------- /miniprogram/pages/category/category.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "nav": "../components/nav/nav", 4 | "dialog": "../components/dialog/dialog" 5 | } 6 | } -------------------------------------------------------------------------------- /miniprogram/pages/components/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": { 4 | "list": "../list/list", 5 | "dialog": "../dialog/dialog" 6 | } 7 | } -------------------------------------------------------------------------------- /miniprogram/pages/group-bill-set/group-bill-set.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "nav": "../components/nav/nav", 4 | "dialog": "../components/dialog/dialog" 5 | } 6 | } -------------------------------------------------------------------------------- /miniprogram/pages/target/target.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "nav": "../components/nav/nav", 4 | "dialog": "../components/dialog/dialog", 5 | "f2": "@antv/wx-f2" 6 | } 7 | } -------------------------------------------------------------------------------- /miniprogram/pages/target-set/target-set.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "nav": "../components/nav/nav", 4 | "dialog": "../components/dialog/dialog" 5 | }, 6 | "disableScroll": true 7 | } -------------------------------------------------------------------------------- /miniprogram/pages/wxs/index.wxs: -------------------------------------------------------------------------------- 1 | var fmtNum = function(number) { 2 | if (typeof number === 'number') return number.toLocaleString() 3 | return number 4 | } 5 | module.exports = { 6 | fmtNum: fmtNum 7 | } -------------------------------------------------------------------------------- /miniprogram/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /miniprogram/miniprogram_npm/@antv/wx-f2/index.wxml: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /miniprogram/pages/setting/setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "nav": "../components/nav/nav", 4 | "dialog": "../components/dialog/dialog", 5 | "relate": "../components/relate/relate" 6 | } 7 | } -------------------------------------------------------------------------------- /miniprogram/pages/components/chart/chart.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": { 4 | "dialog": "../dialog/dialog", 5 | "f2": "@antv/wx-f2", 6 | "bill-list": "../bill-list/bill-list" 7 | } 8 | } -------------------------------------------------------------------------------- /miniprogram/pages/components/dialog/dialog.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /miniprogram/pages/search/search.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "nav": "../components/nav/nav", 4 | "dialog": "../components/dialog/dialog", 5 | "bill-list": "../components/bill-list/bill-list" 6 | } 7 | } -------------------------------------------------------------------------------- /cloudfunctions/stat/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "triggers": [{ 3 | "name": "timeToStat", 4 | "type": "timer", 5 | "config": "0 0 4 * * * *" 6 | }], 7 | "permissions": { 8 | "openapi": [ 9 | ] 10 | } 11 | } -------------------------------------------------------------------------------- /miniprogram/pages/components/list/list.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": { 4 | "dialog": "../dialog/dialog", 5 | "calendar": "../calendar/calendar", 6 | "bill-list": "../bill-list/bill-list" 7 | } 8 | } -------------------------------------------------------------------------------- /cloudfunctions/sendMessage/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "triggers": [{ 3 | "name": "timeToSendMessage", 4 | "type": "timer", 5 | "config": "0 0 22 * * * *" 6 | }], 7 | "permissions": { 8 | "openapi": [ 9 | "subscribeMessage.send" 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = crlf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = false -------------------------------------------------------------------------------- /miniprogram/pages/tab/tab.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": { 4 | "list": "../components/list/list", 5 | "index": "../components/index/index", 6 | "chart": "../components/chart/chart", 7 | "nav": "../components/nav/nav", 8 | "f2": "@antv/wx-f2" 9 | } 10 | } -------------------------------------------------------------------------------- /miniprogram/pages/components/relate/relate.js: -------------------------------------------------------------------------------- 1 | // pages/components/relate/relate.js 2 | Component({ 3 | /** 4 | * 组件的属性列表 5 | */ 6 | properties: { 7 | 8 | }, 9 | 10 | /** 11 | * 组件的初始数据 12 | */ 13 | data: { 14 | 15 | }, 16 | 17 | /** 18 | * 组件的方法列表 19 | */ 20 | methods: { 21 | 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /cloudfunctions/user/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~2.4.0" 13 | } 14 | } -------------------------------------------------------------------------------- /cloudfunctions/word/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "word", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "latest" 13 | } 14 | } -------------------------------------------------------------------------------- /cloudfunctions/donate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "donate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~2.4.0" 13 | } 14 | } -------------------------------------------------------------------------------- /cloudfunctions/target/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "target", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "latest" 13 | } 14 | } -------------------------------------------------------------------------------- /cloudfunctions/category/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "category", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "^0.8.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cloudfunctions/getCategory/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getCategory", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "^0.8.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cloudfunctions/checkSubscribe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sendMessage", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cloudfunctions/accountAggregate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "accountAggregate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /miniprogram/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "miniprogram", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@antv/wx-f2": "2.1.1", 13 | "dayjs": "^1.10.7" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cloudfunctions/search/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "search", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dayjs": "^1.10.7", 13 | "wx-server-sdk": "latest" 14 | } 15 | } -------------------------------------------------------------------------------- /cloudfunctions/stat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stat", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dayjs": "^1.10.7", 13 | "wx-server-sdk": "~2.4.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cloudfunctions/account/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "account", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dayjs": "^1.10.7", 13 | "wx-server-sdk": "^0.8.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cloudfunctions/sendMessage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sendMessage", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dayjs": "^1.10.7", 13 | "wx-server-sdk": "latest" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cloudfunctions/getAccountList/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getAccountList", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dayjs": "^1.10.7", 13 | "wx-server-sdk": "latest" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cloudfunctions/getAccountChart/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getAccountChart", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dayjs": "^1.10.7", 13 | "wx-server-sdk": "^1.5.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cloudfunctions/exportFile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exportFile", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "latest" 13 | }, 14 | "devDependencies": { 15 | "excel-export": "^0.5.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /miniprogram/pages/components/dialog/dialog.js: -------------------------------------------------------------------------------- 1 | Component({ 2 | options: { 3 | multipleSlots: true 4 | }, 5 | properties: { 6 | visible: { 7 | type: Boolean, 8 | value: false 9 | } 10 | }, 11 | data: { 12 | }, 13 | ready() { 14 | }, 15 | attached() { 16 | }, 17 | methods: { 18 | closeDialog() { 19 | this.setData({ 20 | visible: false 21 | }) 22 | this.triggerEvent('closeDialog') 23 | } 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /miniprogram/pages/onboarding/onboarding.js: -------------------------------------------------------------------------------- 1 | // miniprogram/pages/onboarding/onboarding.js 2 | Page({ 3 | data: { 4 | step: 1 5 | }, 6 | next() { 7 | const { step } = this.data 8 | if (Number(step) === 6) { 9 | wx.redirectTo({ 10 | url: '/pages/tab/tab' 11 | }) 12 | wx.setStorageSync('isOnboarding', 'v3.5.0') 13 | } else { 14 | wx.vibrateShort() 15 | this.setData({ 16 | step: step + 1 17 | }) 18 | } 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /miniprogram/store/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data: { 3 | sysInfo: {}, 4 | categoryList: [], 5 | defaultCategoryList: [], 6 | myTarget: {}, 7 | myGroup: {}, 8 | plainCategoryList: [], 9 | selectedCategory: '', // 选择的分类 10 | mapCategoryName: {}, 11 | currentMonthData: {}, 12 | loadingRightIcon: false, 13 | pickDateListSumResult: [0, 0], 14 | editBill: {}, 15 | showTabbar: true, 16 | activeTab: 'index', 17 | isEdit: false, // 是否正在编辑账单 18 | shouldFetchList: false 19 | }, 20 | // 无脑全部更新,组件或页面不需要声明 use 21 | // updateAll: true, 22 | debug: true, 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 单单记账 2 |

3 |

4 |
5 |


6 | 无任何信息授权
7 | 持续更新
8 | 🎉欢迎使用🎉
9 | 🎉欢迎star && fork🎉
10 |


11 |

12 | 13 | ![扫码_搜索联合传播样式-标准色版.png](https://i.loli.net/2019/09/12/7L5QH9Pk2aODtJb.jpg) 14 | 15 | 16 | 17 | ![home.jpg](https://i.loli.net/2021/08/13/6QIRmlMd5thEpwb.png) 18 | 19 | # 开发/运行 20 | 21 | 详情查看文档-[开发说明](https://github.com/GzhiYi/dandan-account/blob/master/INSTALL.md) 22 | 23 | # 贡献 24 | [@yunnet](https://github.com/yunnet) 25 | -------------------------------------------------------------------------------- /miniprogram/pages/components/list/list.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /cloudfunctionTemplate/getAccountList.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "分页查询", 4 | "value": { 5 | "page": "1", 6 | "limit": "5" 7 | } 8 | }, 9 | { 10 | "name": "获取记录列表", 11 | "value": { 12 | "page": "1", 13 | "limit": "3" 14 | } 15 | }, 16 | { 17 | "name": "按照时间 查询", 18 | "value": { 19 | "mode": "getAccountListByTime", 20 | "page": "1", 21 | "limit": "5", 22 | "startDate": "2019-09-01", 23 | "endDate": "2019-09-30" 24 | } 25 | }, 26 | { 27 | "name": "根据父分类ID获取列表", 28 | "value": { 29 | "mode": "getAccountListByParentCID", 30 | "categoryId": "others", 31 | "page": "1", 32 | "limit": "50" 33 | } 34 | } 35 | ] -------------------------------------------------------------------------------- /miniprogram/pages/components/dialog/dialog.wxss: -------------------------------------------------------------------------------- 1 | .dialog { 2 | margin:0 40rpx 40rpx; 3 | position:fixed; 4 | left:0; 5 | right:0; 6 | bottom: 0; 7 | border-radius: 38rpx; 8 | padding: 15rpx; 9 | background:#fff; 10 | z-index:99; 11 | animation-duration: .4s; 12 | animation-name: slideUp; 13 | } 14 | .dialog-bg { 15 | background:rgba(0, 0, 0, 0.705); 16 | height:100%; 17 | width:100%; 18 | position:fixed; 19 | top:0; 20 | z-index: 98; 21 | } 22 | @keyframes slideUp { 23 | from { 24 | transform: translateY(100%); 25 | } 26 | 27 | to { 28 | transform: translateY(0%); 29 | } 30 | } 31 | 32 | @keyframes slideDown { 33 | from { 34 | transform: translateY(0%); 35 | } 36 | 37 | to { 38 | transform: translateY(100%); 39 | } 40 | } -------------------------------------------------------------------------------- /miniprogram/pages/morechart/morechart.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | background-color: #F9F9F9; 3 | height: 100%; 4 | } 5 | .charts { 6 | margin: 0 auto; 7 | height: 500rpx; 8 | background-color: #F9F9F9; 9 | } 10 | .desc { 11 | text-align: center; 12 | font-size: 28rpx; 13 | color: #777; 14 | margin: 40rpx 0 60rpx; 15 | } 16 | .pick-date { 17 | text-align: center; 18 | border: 1px solid #eee; 19 | width: 50%; 20 | margin: 10rpx auto 80rpx; 21 | border-radius: 15rpx; 22 | font-size: 28rpx; 23 | padding: 10rpx; 24 | color: #555; 25 | height: 46rpx; 26 | line-height: 46rpx; 27 | background-color: #EBEBEB; 28 | } 29 | .date-tip { 30 | text-align: center; 31 | font-size: 25rpx; 32 | color: grey; 33 | } 34 | .f2-chart { 35 | width: 100%; 36 | height: 500rpx; 37 | } -------------------------------------------------------------------------------- /miniprogram/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/tab/tab", 4 | "pages/target/target", 5 | "pages/morechart/morechart", 6 | "pages/setting/setting", 7 | "pages/group/group", 8 | "pages/group-bill-set/group-bill-set", 9 | "pages/target-set/target-set", 10 | "pages/category/category", 11 | "pages/search/search", 12 | "pages/onboarding/onboarding" 13 | ], 14 | "window": { 15 | "backgroundColor": "#F6F6F6", 16 | "backgroundTextStyle": "light", 17 | "navigationBarBackgroundColor": "#F6F6F6", 18 | "navigationBarTitleText": "单单记账", 19 | "navigationBarTextStyle": "black", 20 | "navigationStyle": "custom" 21 | }, 22 | "sitemapLocation": "sitemap.json", 23 | "navigateToMiniProgramAppIdList": ["wx083a90f5ac602053", "wxf3515fa60b618cd5"] 24 | } -------------------------------------------------------------------------------- /cloudfunctionTemplate/accountAggregate.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "按时间范围聚合数据", 4 | "value": { 5 | "mode": "aggregateAccountByDateRange", 6 | "startDate": "2019-09-07", 7 | "endDate": "2019-09-07" 8 | } 9 | }, 10 | { 11 | "name": "详细聚合数据", 12 | "value": { 13 | "mode": "aggregateAccountInDetail", 14 | "flow": "1", 15 | "startDate": "2019-09-07", 16 | "endDate": "2019-09-07" 17 | } 18 | }, 19 | { 20 | "name": "test", 21 | "value": { 22 | "mode": "test", 23 | "flow": "1", 24 | "startDate": "2019-09-07", 25 | "endDate": "2019-09-07" 26 | } 27 | }, 28 | { 29 | "name": "饼图数据", 30 | "value": { 31 | "mode": "getPieChartData", 32 | "startDate": "2019-09-07", 33 | "endDate": "2019-09-07" 34 | } 35 | } 36 | ] -------------------------------------------------------------------------------- /miniprogram/iconfont.wxss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'dan-icon'; /* Project id 1760677 */ 3 | src: url('//at.alicdn.com/t/font_1760677_cdxmhvrt9jq.woff2?t=1640274580230') format('woff2'), 4 | url('//at.alicdn.com/t/font_1760677_cdxmhvrt9jq.woff?t=1640274580230') format('woff'), 5 | url('//at.alicdn.com/t/font_1760677_cdxmhvrt9jq.ttf?t=1640274580230') format('truetype'); 6 | } 7 | .dan-icon { 8 | font-family: "dan-icon" !important; 9 | font-size: 32rpx; 10 | width: fit-content; 11 | font-style: normal; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | color: #a1a1a1; 15 | display: inline-block; 16 | position: relative; 17 | top: 2rpx; 18 | } 19 | .dan-icon-exchange:before { 20 | content: '\e772'; 21 | } 22 | .dan-icon-delete:before { 23 | content: '\e7ec'; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /cloudfunctions/getAccountList/README.md: -------------------------------------------------------------------------------- 1 | 获取记账记录列表(分页) 2 | 3 | ### 普通获取记账记录列表(函数名:getAccountList, mode: normal) 4 | 5 | | key| 说明 | 是否必填 | 6 | | -------- | ----- | ---- | 7 | | page|当前页数, 大于等于1|是| 8 | | limit|一次显示多少条, 大于0, 小于50|是| 9 | 10 | 11 | ### 普通获取记账记录列表(函数名:getAccountList, mode: getAccountListByTime) 12 | 13 | | key| 说明 | 是否必填 | 14 | | -------- | ----- | ---- | 15 | | page|当前页数, 大于等于1|是| 16 | | limit|一次显示多少条, 大于0, 小于50|是| 17 | | startDate|开始时间|是| 18 | | endDate|结束时间|是| 19 | 20 | 21 | ### 根据父分类ID与时间范围获取记账记录列表(函数名:getAccountList, mode: getAccountListByParentCID) 22 | 23 | | key| 说明 | 是否必填 | 24 | | -------- | ----- | ---- | 25 | | page|当前页数, 大于等于1|是| 26 | | limit|一次显示多少条, 大于0, 小于50|是| 27 | | categoryId|父分类ID|是| 28 | | startDate|开始时间|是| 29 | | endDate|结束时间|是| 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /miniprogram/pages/morechart/morechart.wxml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 切换时间 7 | 14 | {{date}} ▾ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {{month}}月记账折线图 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {{year}}年记账折线图 33 | 34 | 35 | -------------------------------------------------------------------------------- /cloudfunctions/user/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 该云函数只做用户openId的收集,不关联用户账单。 3 | */ 4 | const cloud = require('wx-server-sdk') 5 | 6 | cloud.init() 7 | 8 | // 云函数入口函数 9 | exports.main = async (event) => { 10 | const wxContext = cloud.getWXContext() 11 | cloud.updateConfig({ 12 | env: wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 13 | }) 14 | // 初始化数据库 15 | const db = cloud.database({ 16 | env: wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 17 | }) 18 | if (event.mode === 'add') { 19 | db.collection('USERS').add({ 20 | data: { 21 | openId: wxContext.OPENID, 22 | appId: wxContext.APPID, 23 | unionId: wxContext.UNIONID, 24 | createTime: new Date() 25 | } 26 | }) 27 | return { 28 | code: 1, 29 | msg: '注册成功', 30 | data: null 31 | } 32 | } 33 | return { 34 | event, 35 | openId: wxContext.OPENID, 36 | appId: wxContext.APPID, 37 | unionId: wxContext.UNIONID 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true 5 | }, 6 | extends: [ 7 | 'airbnb-base', 8 | ], 9 | globals: { 10 | Atomics: 'readonly', 11 | SharedArrayBuffer: 'readonly', 12 | Component: 'readonly', 13 | getApp: 'writable', 14 | roundFun: 'writable', 15 | Page: 'writable', 16 | wx: 'writable', 17 | getCurrentPages: 'readonly', 18 | App: 'readonly' 19 | }, 20 | parserOptions: { 21 | ecmaVersion: 2018, 22 | sourceType: 'module', 23 | }, 24 | rules: { 25 | 'no-console': 0, 26 | 'semi': ["error", "never"], 27 | 'import/no-unresolved': 0, 28 | 'consistent-return': 0, 29 | 'no-underscore-dangle': 0, 30 | 'max-len': 0, 31 | 'no-nested-ternary': 0, 32 | 'no-param-reassign': 0, 33 | 'new-cap': 0, 34 | 'func-names': 0, 35 | 'camelcase': 0, 36 | 'import/no-extraneous-dependencies': 0, 37 | 'no-plusplus': 0, 38 | 'comma-dangle': ["error", "never"], 39 | eqeqeq: 0, 40 | 'linebreak-style': ["error", "windows"] 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /cloudfunctions/donate/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init() 5 | 6 | // 云函数入口函数 7 | exports.main = async (event) => { 8 | const wxContext = cloud.getWXContext() 9 | cloud.updateConfig({ 10 | env: wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 11 | 12 | }) 13 | // 初始化数据库 14 | const db = cloud.database({ 15 | env: wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 16 | }) 17 | // const _ = db.command; 18 | if (event.mode === 'get') { 19 | const res = await db.collection('DONATE').get() 20 | return { 21 | data: res.data, 22 | code: 1, 23 | msg: '获取成功' 24 | } 25 | } 26 | if (event.mode === 'add') { 27 | const { 28 | name, donateTime, url, word 29 | } = event 30 | const res = await db.collection('DONATE').add({ 31 | data: { 32 | donateTime, 33 | name, 34 | url, 35 | word 36 | } 37 | }) 38 | return { 39 | data: res.data, 40 | code: 1, 41 | msg: '新增成功' 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /miniprogram/pages/onboarding/onboarding.wxss: -------------------------------------------------------------------------------- 1 | .navigator { 2 | position: absolute; 3 | bottom: 0; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | height: 180rpx; 8 | } 9 | .navigator-right { 10 | background: #ff5b4c; 11 | right: 0; 12 | width: 45%; 13 | border-radius: 75rpx 0 0; 14 | color: #fff; 15 | border-left: 16rpx solid #ffe0dc; 16 | border-top: 16rpx solid #ffe0dc; 17 | font-size: 32rpx; 18 | } 19 | .navigator-left { 20 | width: 50%; 21 | } 22 | .dot { 23 | width: 8rpx; 24 | height: 8rpx; 25 | background-color: #ccc; 26 | margin-right: 21rpx; 27 | border-radius: 50%; 28 | } 29 | .dot-active { 30 | background-color: #1c1c1c; 31 | } 32 | .cover-out { 33 | height: 600rpx; 34 | display: flex; 35 | justify-content: center; 36 | align-items: center; 37 | } 38 | .cover { 39 | width: 800rpx; 40 | height: 600rpx; 41 | } 42 | .word { 43 | margin: 20rpx 40rpx; 44 | } 45 | .title { 46 | font-size: 70rpx; 47 | margin-bottom: 40rpx; 48 | } 49 | .description { 50 | color: #1c1c1c; 51 | font-size: 40rpx; 52 | } 53 | .bold { 54 | font-weight: bold; 55 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 GzhiYi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /miniprogram/pages/tab/tab.wxss: -------------------------------------------------------------------------------- 1 | tab-item-left { 2 | position: fixed; 3 | } 4 | .tab-item-top { 5 | position: fixed; 6 | bottom: 52rpx; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | border-radius: 50%; 11 | border: 10rpx solid rgba(204, 204, 204, 0.8); 12 | margin: 0 24rpx; 13 | height: fit-content; 14 | background-color: rgba(255, 255, 255, 0.8); 15 | } 16 | .icon { 17 | width: 70rpx; 18 | height: 70rpx; 19 | transition: .5s all; 20 | transform-origin: center; 21 | } 22 | .icon-middle { 23 | width: 90rpx; 24 | height: 90rpx; 25 | transition: .5s all; 26 | transform-origin: center; 27 | } 28 | .left { 29 | width: 70rpx; 30 | height: 70rpx; 31 | left: 155rpx; 32 | bottom: 62rpx; 33 | } 34 | .right { 35 | width: 70rpx; 36 | height: 70rpx; 37 | right: 155rpx; 38 | bottom: 62rpx; 39 | } 40 | .middle { 41 | width: 90rpx; 42 | height: 90rpx; 43 | left: 0; 44 | right: 0; 45 | margin: auto; 46 | bottom: 52rpx; 47 | } 48 | .active-bottom-tab { 49 | border: 10rpx solid rgba(255, 142, 142, 0.8); 50 | } 51 | .f2-chart { 52 | width: 100%; 53 | height: 500rpx; 54 | } -------------------------------------------------------------------------------- /miniprogram/pages/components/list/list.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | /* background-color: #f8fafb; */ 3 | } 4 | .list-page { 5 | /* background-color: #f8fafb; */ 6 | height: 100%; 7 | } 8 | .bill-item { 9 | display: flex; 10 | justify-content: space-between; 11 | align-items: center; 12 | margin: 20rpx 30rpx; 13 | padding: 20rpx 30rpx; 14 | background-color: #fff; 15 | border-radius: 14rpx; 16 | } 17 | .fake-bill-item { 18 | height: 100rpx; 19 | } 20 | .date { 21 | color: #ccc; 22 | font-size: 25rpx; 23 | text-align: right; 24 | min-width: 140rpx; 25 | } 26 | .type { 27 | background: #5e72e4; 28 | color: #fff; 29 | border-radius: 7rpx; 30 | font-size: 22rpx; 31 | padding: 5rpx 10rpx; 32 | width: fit-content; 33 | margin-top: 3rpx; 34 | } 35 | .money { 36 | font-size: 42rpx; 37 | text-align: right; 38 | } 39 | .description { 40 | font-size: 27rpx; 41 | margin: 13rpx 0 0; 42 | } 43 | .note-tips { 44 | height: 400rpx; 45 | display: flex; 46 | justify-content: center; 47 | align-items: center; 48 | font-size: 28rpx; 49 | color: grey; 50 | } 51 | .money-date { 52 | display: flex; 53 | align-items: center; 54 | } -------------------------------------------------------------------------------- /cloudfunctionTemplate/account.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "添加记录", 4 | "value": { 5 | "mode": "add", 6 | "money": "100", 7 | "categoryId": "others_sub", 8 | "noteDate": "2019-8-28", 9 | "description": "测试添加一条记录", 10 | "flow": "0" 11 | } 12 | }, 13 | { 14 | "name": "删除记录", 15 | "value": { 16 | "mode": "deleteById", 17 | "id": "5d262bd45d5c07a8050a02712b3053d9" 18 | } 19 | }, 20 | { 21 | "name": "修改记录模板", 22 | "value": { 23 | "mode": "updateById", 24 | "id": "5d262bd45d5c07a8050a02712b3053d9", 25 | "money": "10101", 26 | "categoryId": "_oh_update", 27 | "noteDate": "2020-2-14", 28 | "description": "不小心被修改了" 29 | } 30 | }, 31 | { 32 | "name": "获取一条指定记录", 33 | "value": { 34 | "mode": "getNoteById", 35 | "id": "3c4c6d855d5c0b07050a1a5a0b18e0fa" 36 | } 37 | }, 38 | { 39 | "name": "按菜单ID删除记录", 40 | "value": { 41 | "mode": "deleteByCategoryId", 42 | "categoryId": "" 43 | } 44 | }, 45 | { 46 | "name": "根据父ID获取列表", 47 | "value": { 48 | "mode": "getAccountByParentCID", 49 | "categoryId": "daily_necessities" 50 | } 51 | } 52 | ] -------------------------------------------------------------------------------- /cloudfunctionTemplate/category.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "新增一个分类", 4 | "value": { 5 | "mode": "add", 6 | "categoryName": "收入", 7 | "categoryIcon": "icon", 8 | "description": "收入汇总", 9 | "flow": "1", 10 | "type": "0", 11 | "parentId": "", 12 | "isSelectable": "false" 13 | } 14 | }, 15 | { 16 | "name": "删除一个分类", 17 | "value": { 18 | "mode": "deleteByIdAndFlow", 19 | "id": "efdeb2615d7b9e781a6129870c584626", 20 | "flow": "1" 21 | } 22 | }, 23 | { 24 | "name": "获取一个分类", 25 | "value": { 26 | "mode": "getCategoryById", 27 | "id": "efdeb2615d5d0f3d05a0c2372e18f53b" 28 | } 29 | }, 30 | { 31 | "name": "批量获取分类", 32 | "value": { 33 | "mode": "getCategoriesByIdBatch", 34 | "ids": [ 35 | "others", 36 | "clothes", 37 | "daily_necessities" 38 | ] 39 | } 40 | }, 41 | { 42 | "name": "添加系统分类", 43 | "value": { 44 | "mode": "add", 45 | "categoryName": "杂项", 46 | "categoryIcon": "icon", 47 | "description": "杂项", 48 | "flow": "1", 49 | "type": "0", 50 | "parentId": "income", 51 | "isSelectable": "true" 52 | } 53 | } 54 | ] -------------------------------------------------------------------------------- /cloudfunctions/search/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init() 5 | 6 | // 云函数入口函数 7 | exports.main = async (event) => { 8 | const wxContext = cloud.getWXContext() 9 | const env = wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 10 | cloud.updateConfig({ env }) 11 | // 初始化数据库 12 | const db = cloud.database({ env }) 13 | const { keyWord } = event 14 | const _ = db.command 15 | try { 16 | const result = await db.collection('DANDAN_NOTE') 17 | .where(_.or([{ 18 | description: db.RegExp({ 19 | regexp: `.*${keyWord}`, 20 | options: 'i' 21 | }) 22 | }, 23 | { 24 | money: db.RegExp({ 25 | regexp: keyWord, 26 | options: 'i' 27 | }) 28 | } 29 | ]).and({ 30 | openId: wxContext.OPENID, 31 | isDel: false 32 | })) 33 | .orderBy('noteDate', 'desc') 34 | .orderBy('createTime', 'desc') 35 | .get() 36 | return { 37 | code: 1, 38 | data: result.data, 39 | message: '操作成功' 40 | } 41 | } catch (error) { 42 | return { 43 | code: -1, 44 | data: error, 45 | message: '操作失败' 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /miniprogram/pages/search/search.wxml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 19 | 31 | 32 | 33 | 34 | 查询中... 35 | 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dandan-account", 3 | "version": "1.0.0", 4 | "description": "







无任何信息授权
持续更新
🎉欢迎使用🎉
🎉欢迎star && fork🎉



", 5 | "main": "app.js", 6 | "dependencies": { 7 | "dayjs": "^1.10.7", 8 | "wx-server-sdk": "^2.3.0" 9 | }, 10 | "devDependencies": { 11 | "eslint": "^6.7.2", 12 | "eslint-config-airbnb-base": "^14.0.0", 13 | "eslint-plugin-import": "^2.19.1", 14 | "husky": "^3.1.0", 15 | "lint-staged": "^9.5.0" 16 | }, 17 | "scripts": { 18 | "pre-commit": "lint-staged" 19 | }, 20 | "lint-staged": { 21 | "*.js": "eslint" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/GzhiYi/dandan-account.git" 26 | }, 27 | "keywords": [ 28 | "accounting" 29 | ], 30 | "author": "GzhiYi", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/GzhiYi/dandan-account/issues" 34 | }, 35 | "husky": { 36 | "hooks": { 37 | "pre-commit": "npm run pre-commit" 38 | } 39 | }, 40 | "homepage": "https://github.com/GzhiYi/dandan-account#readme" 41 | } 42 | -------------------------------------------------------------------------------- /miniprogram/pages/target-set/target-set.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | background-color: #FFE0CE !important; 3 | } 4 | .target-set .title { 5 | font-size: 50rpx; 6 | font-weight: bold; 7 | margin-bottom: 50rpx; 8 | } 9 | .base-padding { 10 | padding: 0 43rpx; 11 | } 12 | .form-label { 13 | margin-bottom: 20rpx; 14 | } 15 | .picker { 16 | color: #9D9D9F; 17 | border-bottom: 1px solid #9D9D9F; 18 | padding-bottom: 5px; 19 | } 20 | .form-input { 21 | margin-bottom: 20rpx; 22 | } 23 | .form-input input { 24 | border-bottom: 1rpx solid #9D9D9F; 25 | height: 60rpx; 26 | } 27 | .money { 28 | background-color: #fff; 29 | height: 80vh; 30 | border-radius: 60rpx; 31 | padding-top: 60rpx; 32 | } 33 | .create { 34 | background-color: #3A478A; 35 | color: #fff; 36 | padding: 25rpx 18rpx; 37 | border-radius: 19rpx; 38 | margin: 60rpx 0; 39 | text-align: center; 40 | font-size: 32rpx; 41 | } 42 | .circle { 43 | width: 500rpx; 44 | height: 500rpx; 45 | border-radius: 50%; 46 | } 47 | .circle-one { 48 | position: fixed; 49 | background-color: #F778AF; 50 | right: -0rpx; 51 | top: -264rpx; 52 | z-index: -1; 53 | } 54 | .circle-two { 55 | position: fixed; 56 | background-color: #3A478A; 57 | right: -300rpx; 58 | top: -100rpx; 59 | z-index: -2; 60 | } 61 | .required::before { 62 | content: '*'; 63 | color: #F778AF; 64 | margin-right: 4rpx; 65 | } -------------------------------------------------------------------------------- /miniprogram/reset.wxss: -------------------------------------------------------------------------------- 1 | .flex { 2 | display: flex; 3 | } 4 | 5 | .flex-all { 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | } 10 | 11 | .flex-v { 12 | display: flex; 13 | justify-content: center; 14 | } 15 | 16 | .flex-h { 17 | display: flex; 18 | align-items: center; 19 | } 20 | 21 | .relative { 22 | position: relative; 23 | } 24 | 25 | .right-0 { 26 | right: 0; 27 | } 28 | 29 | .left-0 { 30 | left: 0; 31 | } 32 | 33 | .top-0 { 34 | top: 0; 35 | } 36 | 37 | .bottom-0 { 38 | bottom: 0 39 | } 40 | 41 | .absolute { 42 | position: absolute; 43 | } 44 | 45 | .items-center { 46 | align-items: center; 47 | } 48 | 49 | .justify-center { 50 | justify-content: center; 51 | } 52 | 53 | .shadow { 54 | box-shadow: 0 10px 20px 0 rgba(33, 35, 41, .12); 55 | } 56 | .rounded-full { 57 | border-radius: 9999rpx; 58 | } 59 | .flex-col { 60 | flex-direction: column; 61 | } 62 | .mr-2 { 63 | margin-right: 8rpx; 64 | } 65 | .text-t26 { 66 | font-size: 26rpx; 67 | } 68 | .text-t24 { 69 | font-size: 24rpx; 70 | } 71 | .text-t22 { 72 | font-size: 22rpx; 73 | } 74 | .text-default { 75 | color: #303133; 76 | } 77 | .text-default-dim { 78 | color: #606266; 79 | } 80 | .text-default-dim2 { 81 | color: #909399; 82 | } 83 | .flex-wrap { 84 | flex-wrap: wrap; 85 | } 86 | .bg-white { 87 | background-color: #fff; 88 | } 89 | .text-center { 90 | text-align: center; 91 | } -------------------------------------------------------------------------------- /miniprogram/app.wxss: -------------------------------------------------------------------------------- 1 | @import "./iconfont.wxss"; 2 | @import "./reset.wxss"; 3 | 4 | page { 5 | background-color: #7571e015; 6 | } 7 | .container { 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | box-sizing: border-box; 12 | } 13 | 14 | button:focus{ 15 | outline: 0; 16 | } 17 | 18 | button::after{ 19 | border: none; 20 | } 21 | .primary { 22 | background-color: #1da1f2 !important; 23 | } 24 | .success { 25 | background-color: #4fd69c; 26 | } 27 | .danger { 28 | background-color: #f75676; 29 | } 30 | .danger-text { 31 | color: #f75676; 32 | } 33 | .warn { 34 | background-color: #ffdd57; 35 | } 36 | .sky { 37 | background-color: #54c7ec; 38 | } 39 | .purple { 40 | background-color: #5e72e4; 41 | } 42 | .dark { 43 | color: #343a40; 44 | } 45 | .d-header { 46 | font-size: 37rpx; 47 | text-align: center; 48 | padding: 30rpx; 49 | border-bottom: 2rpx solid #eee; 50 | } 51 | .dialog-tip { 52 | font-size: 27rpx; 53 | color: grey; 54 | margin: 4rpx 0; 55 | } 56 | .pop-indicator { 57 | background-color: #E9EBEE; 58 | height: 10rpx; 59 | width: 68rpx; 60 | margin: 3rpx auto; 61 | border-radius: 19rpx; 62 | } 63 | .divide { 64 | width: 43%; 65 | height: 3rpx; 66 | background-color: orange; 67 | margin: 20rpx auto; 68 | } 69 | .list-item-btn { 70 | padding: 24rpx; 71 | text-align: center; 72 | border-bottom: solid 1rpx rgba(0,0,0,.1); 73 | font-weight: bold; 74 | } 75 | -------------------------------------------------------------------------------- /miniprogram/pages/components/bill-list/bill-list.wxss: -------------------------------------------------------------------------------- 1 | .bill-list { 2 | margin-top: 22rpx; 3 | -webkit-overflow-scrolling: touch; 4 | } 5 | .empty-chart { 6 | height: 400rpx; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | font-size: 28rpx; 11 | color: grey; 12 | } 13 | .list-page { 14 | /* background-color: #f8fafb; */ 15 | height: 100%; 16 | } 17 | .bill-item { 18 | display: flex; 19 | justify-content: space-between; 20 | align-items: center; 21 | margin: 20rpx 30rpx; 22 | padding: 20rpx 30rpx; 23 | background-color: #fff; 24 | border-radius: 14rpx; 25 | } 26 | .fake-bill-item { 27 | height: 100rpx; 28 | } 29 | .date { 30 | color: #ccc; 31 | font-size: 25rpx; 32 | text-align: right; 33 | min-width: 140rpx; 34 | } 35 | .type { 36 | background: #5e72e4; 37 | color: #fff; 38 | border-radius: 7rpx; 39 | font-size: 22rpx; 40 | padding: 5rpx 10rpx; 41 | width: fit-content; 42 | margin-top: 3rpx; 43 | } 44 | .money { 45 | font-size: 42rpx; 46 | text-align: right; 47 | } 48 | .description { 49 | font-size: 27rpx; 50 | margin: 13rpx 0 0; 51 | } 52 | .note-tips { 53 | height: 400rpx; 54 | display: flex; 55 | justify-content: center; 56 | align-items: center; 57 | font-size: 28rpx; 58 | color: grey; 59 | } 60 | .money-date { 61 | display: flex; 62 | align-items: center; 63 | } 64 | .pop-indicator { 65 | background-color: #E9EBEE; 66 | height: 10rpx; 67 | width: 68rpx; 68 | margin: 3rpx auto; 69 | border-radius: 19rpx; 70 | } -------------------------------------------------------------------------------- /cloudfunctions/getCategory/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init() 5 | 6 | // 云函数入口函数 7 | exports.main = async (event) => { 8 | const wxContext = cloud.getWXContext() 9 | cloud.updateConfig({ 10 | env: wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 11 | 12 | }) 13 | // 初始化数据库 14 | const db = cloud.database({ 15 | env: wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 16 | 17 | }) 18 | const _ = db.command 19 | const { flow } = event 20 | try { 21 | // 先获取系统的分类, 或者某个用户创建的分类 22 | const query = { 23 | isDel: false, 24 | openId: _.eq(wxContext.OPENID).or(_.eq('SYSTEM')), 25 | flow: Number(flow) 26 | } 27 | if (!flow) delete query.flow 28 | const res = await db.collection('DANDAN_NOTE_CATEGORY') 29 | .where(query).get() 30 | const response = [] 31 | res.data.forEach((item) => { 32 | if (!item.parentId) { 33 | item.children = [] 34 | response.push(item) 35 | } 36 | }) 37 | if (response.length > 0) { 38 | res.data.forEach((item) => { 39 | response.forEach((one) => { 40 | if (one._id === item.parentId) { 41 | one.children.push(item) 42 | } 43 | }) 44 | }) 45 | } 46 | return { 47 | code: 1, 48 | data: response, 49 | message: '获取分类成功' 50 | } 51 | } catch (e) { 52 | return { 53 | code: -1, 54 | data: [], 55 | message: '获取失败' 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /miniprogram/pages/components/relate/relate.wxss: -------------------------------------------------------------------------------- 1 | .public-list { 2 | background-color: #ffffff; 3 | width:92%; 4 | margin:0 auto 20rpx; 5 | border-radius:20rpx; 6 | } 7 | .public-header { 8 | padding:30rpx 20rpx; 9 | font-size:39rpx; 10 | display: flex; 11 | align-items: center; 12 | } 13 | .public-icon { 14 | margin-right: 18rpx; 15 | width:32rpx; 16 | height:32rpx; 17 | } 18 | .public-item { 19 | padding:20rpx 20rpx; 20 | display:flex; 21 | justify-content: space-between; 22 | align-items:center; 23 | height:60rpx; 24 | } 25 | .public-item-bottom { 26 | border-bottom: none !important; 27 | } 28 | .public-item-right { 29 | display:flex; 30 | align-items:center; 31 | } 32 | .public-item-name { 33 | font-size: 34rpx; 34 | color: #313131; 35 | max-width: 400rpx; 36 | font-weight: bold; 37 | overflow: hidden; 38 | text-overflow: ellipsis; 39 | white-space: nowrap; 40 | } 41 | .mini-item { 42 | display: flex; 43 | padding: 20rpx 0; 44 | margin: 20rpxx 0; 45 | } 46 | .mini-item image { 47 | width: 100rpx; 48 | height: 100rpx; 49 | min-width: 100rpx; 50 | border-radius: 9999rpx; 51 | margin-right: 20rpx; 52 | } 53 | .mini-item .title { 54 | font-size: 38rpx; 55 | font-weight: bold; 56 | margin-bottom: 20rpx; 57 | } 58 | .mini-item .desc { 59 | font-size: 26rpx; 60 | color: #606266; 61 | } 62 | .mini-item .go { 63 | width: fit-content; 64 | padding: 10rpx 40rpx; 65 | border: 1rpx solid #2454FF; 66 | border-radius: 30rpx; 67 | background-color: #2454FF; 68 | color: #fff; 69 | font-size: 24rpx; 70 | margin: 20rpx 0; 71 | } -------------------------------------------------------------------------------- /miniprogram/pages/components/relate/relate.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 个人其余作品(欢迎支持) 4 | 5 | 6 | 7 | 8 | 9 | 一起算账 10 | 11 | 有群组的AA收款小程序,多人添加,一次结算出结果,再也不怕算错了。 12 | 13 | 14 | 15 | 使用 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 单单接龙 24 | 25 | 面向社交活动的一个接龙小程序,不要老是鸽子🕊好不好。 26 | 27 | 28 | 29 | 使用 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /miniprogram/.pnpm-debug.log: -------------------------------------------------------------------------------- 1 | { 2 | "0 debug pnpm:scope": { 3 | "selected": 1 4 | }, 5 | "1 error pnpm": { 6 | "code": "ERR_PNPM_REGISTRIES_MISMATCH", 7 | "err": { 8 | "name": "pnpm", 9 | "message": "This modules directory was created using the following registries configuration: {\"default\":\"https://registry.npmjs.org/\"}. The current configuration is {\"default\":\"https://registry.npm.taobao.org/\"}. To recreate the modules directory using the new settings, run \"pnpm install\".", 10 | "code": "ERR_PNPM_REGISTRIES_MISMATCH", 11 | "stack": "pnpm: This modules directory was created using the following registries configuration: {\"default\":\"https://registry.npmjs.org/\"}. The current configuration is {\"default\":\"https://registry.npm.taobao.org/\"}. To recreate the modules directory using the new settings, run \"pnpm install\".\n at validateModules (C:\\Users\\74528\\AppData\\Roaming\\npm\\node_modules\\pnpm\\dist\\pnpm.cjs:91599:15)\n at async getContext (C:\\Users\\74528\\AppData\\Roaming\\npm\\node_modules\\pnpm\\dist\\pnpm.cjs:91471:28)\n at async mutateModules (C:\\Users\\74528\\AppData\\Roaming\\npm\\node_modules\\pnpm\\dist\\pnpm.cjs:116264:19)\n at async handler (C:\\Users\\74528\\AppData\\Roaming\\npm\\node_modules\\pnpm\\dist\\pnpm.cjs:118510:35)\n at async C:\\Users\\74528\\AppData\\Roaming\\npm\\node_modules\\pnpm\\dist\\pnpm.cjs:183649:21\n at async run (C:\\Users\\74528\\AppData\\Roaming\\npm\\node_modules\\pnpm\\dist\\pnpm.cjs:183623:34)\n at async runPnpm (C:\\Users\\74528\\AppData\\Roaming\\npm\\node_modules\\pnpm\\dist\\pnpm.cjs:183835:5)\n at async C:\\Users\\74528\\AppData\\Roaming\\npm\\node_modules\\pnpm\\dist\\pnpm.cjs:183827:7" 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /miniprogram/pages/target/target.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | background-color: #FFE0CE; 3 | } 4 | .target-header { 5 | display: flex; 6 | justify-content: space-between; 7 | padding: 0 40rpx; 8 | align-items: center; 9 | } 10 | .target-name { 11 | font-size: 50rpx; 12 | font-weight: bold; 13 | margin-bottom: 20rpx; 14 | } 15 | .target-content { 16 | margin: 0 auto; 17 | justify-content: center; 18 | flex-wrap: wrap; 19 | } 20 | .target-item { 21 | background-color: #fff; 22 | width: 88%; 23 | border-radius: 30rpx; 24 | margin-bottom: 40rpx; 25 | padding: 36rpx 26rpx; 26 | box-sizing: border-box; 27 | height: fit-content; 28 | } 29 | .target-item-big { 30 | background-color: #fff; 31 | width: 88%; 32 | border-radius: 30rpx; 33 | margin-bottom: 40rpx; 34 | padding: 36rpx 26rpx; 35 | box-sizing: border-box; 36 | } 37 | .bg-white { 38 | background-color: #fff; 39 | } 40 | .bg-red { 41 | background-color: #CD394D; 42 | } 43 | .bg-pink { 44 | background-color: #F778AF; 45 | } 46 | .bg-purple { 47 | background-color: #3A478A; 48 | } 49 | .color-white { 50 | color: #fff; 51 | } 52 | .cover { 53 | width: 120rpx; 54 | margin: 0 auto; 55 | } 56 | .target-item-title { 57 | margin-bottom: 20rpx; 58 | } 59 | .flex-fall { 60 | flex-direction: column; 61 | } 62 | .flex-fall:nth-child(1) { 63 | align-items: flex-end; 64 | padding-right: 8px; 65 | box-sizing: border-box; 66 | } 67 | .flex-fall:nth-child(2) { 68 | align-items: flex-start; 69 | padding-left: 8px; 70 | box-sizing: border-box; 71 | } 72 | .flex-col { 73 | display: flex; 74 | flex-direction: column; 75 | } 76 | .f2-chart { 77 | width: 100%; 78 | height: 240rpx; 79 | } 80 | .f2-chart2 { 81 | width: 100%; 82 | height: 400rpx; 83 | } -------------------------------------------------------------------------------- /miniprogram/pages/components/nav/nav.js: -------------------------------------------------------------------------------- 1 | const { importStore } = getApp() 2 | const { create, store } = importStore 3 | create.Component(store, { 4 | externalClasses: ['my-class', 'my-icon-class'], 5 | options: { 6 | multipleSlots: true 7 | }, 8 | properties: { 9 | bgColor: { 10 | type: String, 11 | value: 'rgba(0,0,0,0)' 12 | }, 13 | showIcons: { 14 | type: Array 15 | } 16 | }, 17 | data: { 18 | showBackIcon: false, 19 | showIssue: false, 20 | showSearch: false, 21 | showBannerSetting: false, 22 | showSetting: false 23 | }, 24 | ready() { 25 | const { showIcons } = this.data 26 | this.setData({ 27 | statusBarHeight: store.data.sysInfo.statusBarHeight, 28 | showBackIcon: showIcons.includes('back'), 29 | showIssue: showIcons.includes('bug'), 30 | showBannerSetting: showIcons.includes('banner'), 31 | showSearch: showIcons.includes('search'), 32 | showSetting: showIcons.includes('setting') 33 | }) 34 | }, 35 | attached() { 36 | }, 37 | methods: { 38 | back() { 39 | wx.navigateBack({ 40 | delta: 1, 41 | fail() { 42 | wx.redirectTo({ 43 | url: '/pages/tab/tab' 44 | }) 45 | } 46 | }) 47 | }, 48 | goTo(event) { 49 | const { page } = event.currentTarget.dataset 50 | wx.navigateTo({ 51 | url: `/pages/${page}/${page}` 52 | }) 53 | }, 54 | showBanner() { 55 | this.triggerEvent('showBanner') 56 | }, 57 | goTotarget() { 58 | const { myTarget } = store.data 59 | let path = '/pages/target-set/target-set' 60 | if (myTarget && myTarget._id) { 61 | path = '/pages/target/target' 62 | } 63 | wx.navigateTo({ 64 | url: path 65 | }) 66 | } 67 | } 68 | }) 69 | -------------------------------------------------------------------------------- /miniprogram/pages/components/nav/nav.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 16 | 17 | 22 | 23 | 24 | 29 | 30 | 31 | 36 | 37 | 38 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /miniprogram/util.js: -------------------------------------------------------------------------------- 1 | // 解决JavaScript计算浮点的问题 2 | export function strip(num, precision = 12) { 3 | return +parseFloat(num.toPrecision(precision)) 4 | } 5 | // 函数节流 6 | export function debounce(func, wait, immediate) { 7 | let timeout 8 | let result 9 | const debounced = function () { 10 | const context = this 11 | // eslint-disable-next-line prefer-rest-params 12 | const args = arguments 13 | 14 | if (timeout) clearTimeout(timeout) 15 | if (immediate) { 16 | const callNow = !timeout 17 | timeout = setTimeout(() => { 18 | timeout = null 19 | }, wait) 20 | if (callNow) result = func.apply(context, args) 21 | } else { 22 | timeout = setTimeout(() => { 23 | func.apply(context, args) 24 | }, wait) 25 | } 26 | return result 27 | } 28 | 29 | debounced.cancel = function () { 30 | clearTimeout(timeout) 31 | timeout = null 32 | } 33 | return debounced 34 | } 35 | export function parseTime(time, cFormat) { 36 | if (arguments.length === 0) { 37 | return null 38 | } 39 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' 40 | let date 41 | if (typeof time === 'object') { 42 | date = time 43 | } else { 44 | // eslint-disable-next-line radix 45 | if ((`${time}`).length === 10) time = parseInt(time) * 1000 46 | date = new Date(time) 47 | } 48 | const formatObj = { 49 | y: date.getFullYear(), 50 | m: date.getMonth() + 1, 51 | d: date.getDate(), 52 | h: date.getHours(), 53 | i: date.getMinutes(), 54 | s: date.getSeconds(), 55 | a: date.getDay() 56 | } 57 | const timeStr = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 58 | let value = formatObj[key] 59 | if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1] 60 | if (result.length > 0 && value < 10) { 61 | value = `0${value}` 62 | } 63 | return value || 0 64 | }) 65 | return timeStr 66 | } 67 | -------------------------------------------------------------------------------- /miniprogram/pages/components/nav/nav.wxss: -------------------------------------------------------------------------------- 1 | .list-page { 2 | background-color: #f8fafb; 3 | } 4 | .content { 5 | position: relative; 6 | /* width: 100%; */ 7 | height: 88rpx; 8 | display:flex; 9 | align-items:center; 10 | padding:0 0 0 18rpx; 11 | } 12 | /* 新增图标 */ 13 | .nav-icon-view { 14 | border-radius:50%; 15 | width:70rpx; 16 | height:70rpx; 17 | display:flex; 18 | justify-content:center; 19 | align-items:center; 20 | margin-right: 22rpx; 21 | } 22 | .nav-icon-view-back { 23 | width:70rpx; 24 | height:70rpx; 25 | display:flex; 26 | justify-content:center; 27 | align-items:center; 28 | margin-right: 23rpx; 29 | } 30 | .has-font { 31 | width:74px; 32 | border-radius:50rpx; 33 | font-size:33rpx; 34 | padding:0 10rpx; 35 | } 36 | .nav-icon { 37 | width: 36rpx; 38 | height: 36rpx; 39 | background-size: 66%; 40 | background-repeat: no-repeat; 41 | } 42 | .loading-icon { 43 | animation: headRotate 2s linear infinite; 44 | } 45 | @keyframes headRotate{ 46 | 0% {transform: rotate(0deg);} 47 | 50% {transform: rotate(180deg);} 48 | 100% {transform: rotate(360deg);} 49 | } 50 | .issue { 51 | background-image:url("https://6461-dandan-zdm86-1259814516.tcb.qcloud.la/telemarketer.png?sign=52b81c866fee1a02585b92c6ca5e1441&t=1573302479"); 52 | } 53 | .search { 54 | background-image: url("https://6461-dandan-zdm86-1259814516.tcb.qcloud.la/search.png?sign=bf636b48f7322a460a4107910d4e9a3b&t=1573268446"); 55 | } 56 | .more-chart { 57 | background-image: url("https://6461-dandan-zdm86-1259814516.tcb.qcloud.la/stats.png?sign=0099917fdb61393a6fd2d8fb5f5baf56&t=1573302662"); 58 | } 59 | .setting { 60 | background-image: url("https://6461-dandan-zdm86-1259814516.tcb.qcloud.la/icon/gear.png?sign=ac534f78f7f4a7dbe014e090c92b4ada&t=1587830797"); 61 | } 62 | .target { 63 | background-image: url("https://6461-dandan-zdm86-1259814516.tcb.qcloud.la/icon/target.png?sign=c1a3041e9cb303858db6b7c7d50b3949&t=1587830639"); 64 | } -------------------------------------------------------------------------------- /miniprogram/pages/category/category.wxss: -------------------------------------------------------------------------------- 1 | .parent-name { 2 | font-weight: bold; 3 | font-size: 27rpx; 4 | color: #fff; 5 | padding: 10rpx; 6 | display: flex; 7 | width: 97%; 8 | height: 41rpx; 9 | justify-content: space-between; 10 | align-items: center; 11 | } 12 | .add-icon { 13 | font-size: 30rpx; 14 | float: right; 15 | margin-right: 10rpx; 16 | display: inline-block; 17 | width: 50rpx; 18 | text-align: right; 19 | 20 | } 21 | .category { 22 | padding: 0 0 130rpx; 23 | } 24 | .child-category { 25 | display: flex; 26 | flex-wrap: wrap; 27 | padding: 12rpx 0; 28 | background: #fff; 29 | border-radius: 0 0 6rpx 6rpx; 30 | } 31 | .child-category .child { 32 | display: flex; 33 | align-items: center; 34 | } 35 | .child .name { 36 | margin: 10rpx 16rpx; 37 | font-size: 26rpx; 38 | padding: 8rpx 20rpx; 39 | border-radius: 6rpx; 40 | background-color: #f7f8f9; 41 | color: rgba(0,0,0,.6); 42 | text-align: center; 43 | display: flex; 44 | align-items: center; 45 | } 46 | .child-selected { 47 | color: #fff !important; 48 | } 49 | .item { 50 | background-color: #5e72e4; 51 | margin: 24rpx 24rpx 30rpx 24rpx; 52 | border-radius: 8rpx; 53 | border: 1px solid rgba(255,255,255,.78); 54 | } 55 | .fill-cate { 56 | border-bottom: solid 1rpx rgba(0,0,0,.1); 57 | padding: 24rpx; 58 | text-align: center; 59 | margin: 30rpx 0; 60 | } 61 | .delete { 62 | font-size: 26rpx; 63 | padding: 6rpx; 64 | border-radius: 0 6rpx 6rpx 0; 65 | color: rgba(0,0,0,.6); 66 | text-align: center; 67 | display: flex; 68 | align-items: center; 69 | margin-left: -15rpx; 70 | background-color: #f7f8f9; 71 | } 72 | .delete-icon { 73 | border-left: 1rpx solid #ccd; 74 | padding: 0 8rpx; 75 | margin-left: 15rpx; 76 | margin-right: -12rpx; 77 | } 78 | .edit-mode { 79 | position: fixed; 80 | bottom: 60rpx; 81 | right: 60rpx; 82 | display: flex; 83 | border-radius: 50%; 84 | background-color: #fff; 85 | } 86 | .edit-mode-img { 87 | width: 90rpx; 88 | height: 90rpx; 89 | } -------------------------------------------------------------------------------- /cloudfunctions/exportFile/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /miniprogram/pages/group-bill-set/group-bill-set.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | background-color: #FFE0CE !important; 3 | } 4 | 5 | .target-set .title { 6 | font-size: 50rpx; 7 | font-weight: bold; 8 | margin-bottom: 50rpx; 9 | } 10 | 11 | .base-padding { 12 | padding: 0 43rpx; 13 | } 14 | 15 | .form-label { 16 | margin-bottom: 20rpx; 17 | } 18 | 19 | .picker { 20 | color: #9D9D9F; 21 | border-bottom: 1rpx solid #9D9D9F; 22 | padding-bottom: 5px; 23 | } 24 | 25 | .form-input { 26 | margin-bottom: 20rpx; 27 | } 28 | 29 | .form-input input { 30 | border-bottom: 1rpx solid #9D9D9F; 31 | height: 60rpx; 32 | } 33 | 34 | .money { 35 | background-color: #fff; 36 | height: 80vh; 37 | border-radius: 60rpx 60rpx 0 0; 38 | padding-top: 60rpx; 39 | } 40 | 41 | .create { 42 | background-color: #3A478A; 43 | color: #fff; 44 | padding: 25rpx 18rpx; 45 | border-radius: 19rpx; 46 | margin: 60rpx 0; 47 | text-align: center; 48 | font-size: 32rpx; 49 | } 50 | 51 | .circle { 52 | width: 500rpx; 53 | height: 500rpx; 54 | border-radius: 50%; 55 | } 56 | 57 | .circle-one { 58 | position: fixed; 59 | background-color: #F778AF; 60 | right: -0rpx; 61 | top: -264rpx; 62 | z-index: -1; 63 | } 64 | 65 | .circle-two { 66 | position: fixed; 67 | background-color: #3A478A; 68 | right: -300rpx; 69 | top: -100rpx; 70 | z-index: -2; 71 | } 72 | .required { 73 | display: flex; 74 | } 75 | .required::before { 76 | content: '*'; 77 | color: #F778AF; 78 | margin-right: 4rpx; 79 | } 80 | .fake-info { 81 | display: flex; 82 | flex-direction: column; 83 | width: 200rpx; 84 | align-items: center; 85 | margin: 0 auto; 86 | } 87 | .fake-info image { 88 | width: 150rpx; 89 | height: 150rpx; 90 | margin-bottom: 8rpx; 91 | } 92 | .fake-info input { 93 | font-size: 26qrpx; 94 | width: 220rpx; 95 | margin: 0 auto; 96 | text-align: center; 97 | border-bottom: 1rpx solid #9D9D9F; 98 | padding: 12rpx 0; 99 | } 100 | .fake-info .dices { 101 | width: 32rpx; 102 | height: 32rpx; 103 | } -------------------------------------------------------------------------------- /miniprogram/pages/components/bill-list/bill-list.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 16 | 17 | {{$.mapCategoryName[item.categoryId]}} 18 | {{item.description}} 19 | 20 | 21 | 22 | 23 | {{item.flow === 0 ? '-' : '+'}}{{fmt.fmtNum(item.money)}} 24 | 25 | {{item.noteDate}} 26 | 27 | 28 | 29 | 30 | 31 | {{['选择分类以查看账单', '加载账单中...', '所选时间没有账单数据(⊙o⊙)!'][loading]}} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {{editItem.category.categoryName}} 41 | 金额:{{editItem.flow === 0 ? '-' : '+'}}{{editItem.money}},记账日期:{{editItem.noteDate}} 42 | 备注:{{editItem.description}} 43 | 44 | 编辑 45 | 46 | {{showConfirmDelete ? '再次点击确认删除' : '删除'}} 47 | 48 | 取消 49 | 50 | -------------------------------------------------------------------------------- /miniprogram/pages/components/calendar/calendar.wxss: -------------------------------------------------------------------------------- 1 | .calendar { 2 | background-color: #fff; 3 | padding: 17rpx 20rpx 20rpx; 4 | margin: 0 auto; 5 | } 6 | .controller { 7 | display: flex; 8 | margin-right: 200rpx; 9 | } 10 | .control-item { 11 | font-size: 26rpx; 12 | padding: 0 4px; 13 | margin-right: 40rpx; 14 | border-radius: 17rpx; 15 | } 16 | .date-display { 17 | font-size: 28rpx; 18 | padding-left: 22rpx; 19 | color: #1da1f2; 20 | } 21 | .date { 22 | display: flex; 23 | justify-content: space-between; 24 | margin: 0 0 20rpx; 25 | min-width: 140rpx; 26 | } 27 | .week { 28 | display: flex; 29 | justify-content: center; 30 | } 31 | .days { 32 | display: flex; 33 | flex-wrap: wrap; 34 | justify-content: center; 35 | width: 100%; 36 | 37 | } 38 | .day { 39 | width: 40rpx; 40 | height: 40rpx; 41 | text-align: center; 42 | padding: 4px 4px; 43 | margin: 0rpx 22rpx 2rpx; 44 | font-size: 27rpx; 45 | line-height: 40rpx; 46 | } 47 | .pre-month, .next-month { 48 | opacity: .4; 49 | } 50 | .weeken { 51 | color: #f75676; 52 | } 53 | .icon { 54 | width: 32rpx; 55 | height: 32rpx; 56 | margin-top: 10rpx; 57 | } 58 | 59 | .today { 60 | border-bottom: 3rpx solid #0075ff; 61 | margin: 0rpx 22rpx -1rpx; 62 | } 63 | .selected-date, .range-pick { 64 | color: #fff; 65 | background-color: #0075ff; 66 | border-radius: 50%; 67 | border: none; 68 | } 69 | .inrange { 70 | background-color: #e6f2ff; 71 | } 72 | .show-daterange { 73 | height: 74rpx; 74 | font-size: 24rpx; 75 | line-height: 38rpx; 76 | margin: 0rpx 0 -10rpx 40rpx; 77 | align-items: center; 78 | } 79 | .show-daterange-in { 80 | display: flex; 81 | flex-direction: column; 82 | } 83 | .pick-tip-bg { 84 | background: #5e72e4; 85 | color: #fff; 86 | border-radius: 7rpx; 87 | font-size: 22rpx; 88 | padding: 5rpx 10rpx; 89 | width: fit-content; 90 | margin-top: 3rpx; 91 | } 92 | .item-income { 93 | color: #4fd69c; 94 | margin: 0 12rpx; 95 | } 96 | .item-pay { 97 | color: #f75676; 98 | margin: 0 12rpx; 99 | } 100 | .result-time { 101 | font-weight: bold; 102 | } -------------------------------------------------------------------------------- /miniprogram/pages/search/search.js: -------------------------------------------------------------------------------- 1 | import { strip } from '../../util' 2 | 3 | const { importStore } = getApp() 4 | const { create, store } = importStore 5 | let isNotifyReset = false 6 | create.Page(store, { 7 | use: ['sysInfo.screenHeight', 'sysInfo.statusBarHeight'], 8 | data: { 9 | billList: null, 10 | isSearching: false, 11 | word: '', 12 | isFocus: true, 13 | keyword: '', 14 | isSearched: false 15 | }, 16 | confirmTap() { 17 | const { keyword } = this.data 18 | const self = this 19 | if (!keyword || !keyword.trim()) return 20 | 21 | // 查询操作 22 | self.setData({ 23 | isSearching: true, 24 | billList: [] 25 | }) 26 | wx.cloud.callFunction({ 27 | name: 'search', 28 | data: { 29 | keyWord: keyword 30 | }, 31 | success(res) { 32 | if (res.result.code === 1) { 33 | const billList = res.result.data 34 | let income = 0 35 | let pay = 0 36 | billList.forEach((bill) => { 37 | if (Number(bill.flow) === 0) pay += bill.money 38 | if (Number(bill.flow) === 1) income += bill.money 39 | }) 40 | self.setData({ 41 | billList, 42 | isSearched: true, 43 | word: `关键字 ${keyword} 搜索结果:收入共:${strip(income)},支出共:${strip(pay)}` 44 | }) 45 | } 46 | }, 47 | fail() { 48 | getApp().showError('查询出错,要不稍后再试😢') 49 | }, 50 | complete() { 51 | self.setData({ 52 | isSearching: false 53 | }) 54 | } 55 | }) 56 | }, 57 | onInputChange(event) { 58 | const { value } = event.detail 59 | const { word } = this.data 60 | // 做判断,如果输入文字并且还没搜索出结果就提示重置 61 | if (value && word !== '点猪重置输入哦~' && !isNotifyReset) { 62 | this.setData({ 63 | word: '点猪重置输入哦~', 64 | isSearched: false 65 | }) 66 | isNotifyReset = true 67 | } 68 | this.setData({ 69 | keyword: value 70 | }) 71 | }, 72 | resetSearch() { 73 | this.setData({ 74 | keyword: '', 75 | word: '', 76 | isFocus: true 77 | }) 78 | }, 79 | onReFetchBillList() { 80 | store.data.shouldFetchList = true 81 | this.confirmTap() 82 | }, 83 | }) 84 | -------------------------------------------------------------------------------- /miniprogram/pages/search/search.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | background: linear-gradient(40deg, #f6c3d1, #e8bffe); 3 | } 4 | .banner { 5 | width: 100%; 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | } 10 | 11 | .banner-show { 12 | display: flex; 13 | align-items: center; 14 | margin-bottom: 20rpx; 15 | } 16 | 17 | .pig-talk { 18 | display: flex; 19 | align-items: center; 20 | padding: 14rpx 0; 21 | justify-content: center; 22 | margin-left: 27rpx; 23 | } 24 | 25 | .word-tang-left { 26 | width: 0; 27 | height: 0; 28 | border-bottom: 10px solid rgba(255, 142, 142, 0.8); 29 | border-left: 10px solid transparent; 30 | } 31 | 32 | .word-detail { 33 | max-width: 500rpx; 34 | font-size: 25rpx; 35 | background-color: rgba(255, 142, 142, 0.8); 36 | padding: 9px; 37 | border-radius: 24rpx; 38 | word-break: break-all; 39 | word-wrap: break-word; 40 | overflow: hidden; 41 | text-align: left; 42 | } 43 | .search { 44 | text-align: center; 45 | position: fixed; 46 | width: 100%; 47 | } 48 | .search-input { 49 | background-color: #6147ff; 50 | height: 80rpx; 51 | border-radius: 26rpx; 52 | width: 72%; 53 | margin: 0 auto; 54 | color: #fff; 55 | padding: 0 30rpx; 56 | font-weight: bold; 57 | box-shadow: 0px 10px 13px 4px #b0a5ff; 58 | } 59 | .bill-item { 60 | display: flex; 61 | justify-content: space-between; 62 | align-items: center; 63 | margin: 20rpx 30rpx; 64 | padding: 20rpx 30rpx; 65 | background-color: #fff; 66 | border-radius: 14rpx; 67 | } 68 | .date { 69 | color: #ccc; 70 | font-size: 25rpx; 71 | text-align: right; 72 | min-width: 140rpx; 73 | } 74 | .type { 75 | background: #5e72e4; 76 | color: #fff; 77 | border-radius: 7rpx; 78 | font-size: 22rpx; 79 | padding: 5rpx 10rpx; 80 | width: fit-content; 81 | margin-top: 3rpx; 82 | } 83 | .money { 84 | font-size: 42rpx; 85 | text-align: right; 86 | } 87 | .description { 88 | font-size: 27rpx; 89 | margin: 13rpx 0 0; 90 | } 91 | .place { 92 | width: 100%; 93 | height: 281rpx; 94 | } 95 | .empty-search { 96 | font-size: 28rpx; 97 | color: grey; 98 | text-align: center; 99 | margin-top: 70px; 100 | } 101 | -------------------------------------------------------------------------------- /project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "miniprogram/", 3 | "cloudfunctionRoot": "cloudfunctions/", 4 | "setting": { 5 | "urlCheck": false, 6 | "es6": true, 7 | "enhance": false, 8 | "postcss": true, 9 | "preloadBackgroundData": false, 10 | "minified": true, 11 | "newFeature": true, 12 | "coverView": true, 13 | "nodeModules": true, 14 | "autoAudits": false, 15 | "showShadowRootInWxmlPanel": true, 16 | "scopeDataCheck": false, 17 | "uglifyFileName": false, 18 | "checkInvalidKey": true, 19 | "checkSiteMap": true, 20 | "uploadWithSourceMap": true, 21 | "compileHotReLoad": true, 22 | "lazyloadPlaceholderEnable": false, 23 | "useMultiFrameRuntime": true, 24 | "useApiHook": true, 25 | "useApiHostProcess": true, 26 | "babelSetting": { 27 | "ignore": [], 28 | "disablePlugins": [], 29 | "outputPath": "" 30 | }, 31 | "enableEngineNative": false, 32 | "useIsolateContext": false, 33 | "userConfirmedBundleSwitch": false, 34 | "packNpmManually": false, 35 | "packNpmRelationList": [], 36 | "minifyWXSS": true, 37 | "disableUseStrict": false, 38 | "minifyWXML": true, 39 | "showES6CompileOption": false, 40 | "useCompilerPlugins": false 41 | }, 42 | "appid": "wxda00e9b7cd5f39b1", 43 | "projectname": "dandan-account", 44 | "libVersion": "2.19.5", 45 | "simulatorType": "wechat", 46 | "simulatorPluginLibVersion": {}, 47 | "cloudfunctionTemplateRoot": "cloudfunctionTemplate", 48 | "condition": { 49 | "plugin": { 50 | "list": [] 51 | }, 52 | "game": { 53 | "list": [] 54 | }, 55 | "gamePlugin": { 56 | "list": [] 57 | }, 58 | "miniprogram": { 59 | "list": [ 60 | { 61 | "id": -1, 62 | "name": "db guide", 63 | "pathName": "pages/databaseGuide/databaseGuide", 64 | "query": "" 65 | }, 66 | { 67 | "name": "pages/group/group", 68 | "pathName": "pages/group/group", 69 | "query": "groupId=98bb04175fefe0fb00fb02245ef3b0c9", 70 | "scene": null 71 | }, 72 | { 73 | "name": "没有参数的group", 74 | "pathName": "pages/group/group", 75 | "query": "", 76 | "scene": null 77 | } 78 | ] 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /miniprogram/pages/setting/setting.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | background-color: #F2F2F2; 3 | } 4 | .setting { 5 | margin-bottom: 50rpx; 6 | } 7 | .part { 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | margin-bottom: 30rpx; 12 | } 13 | 14 | .part .left { 15 | width: 20%; 16 | padding: 48rpx 20rpx; 17 | font-size: 30rpx; 18 | font-weight: 700; 19 | } 20 | 21 | .part .right { 22 | width: 54%; 23 | border-radius: 38rpx; 24 | padding: 48rpx; 25 | } 26 | .part-hot { 27 | border: 6rpx solid rgb(255, 68, 136); 28 | padding: 42rpx; 29 | } 30 | 31 | .dandan { 32 | color: #fff; 33 | } 34 | 35 | .dandan .title { 36 | font-size: 28rpx; 37 | font-weight: 600; 38 | margin-bottom: 10rpx; 39 | } 40 | .dandan .desc { 41 | font-size: 24rpx; 42 | } 43 | .dandan .pig { 44 | width: 100rpx; 45 | height: 100rpx; 46 | float: right; 47 | } 48 | .notify-status { 49 | font-size: 34rpx; 50 | margin-top: 20rpx; 51 | text-align: right; 52 | } 53 | .fill { 54 | width: 100%; 55 | height: 200rpx; 56 | } 57 | .invite-btn { 58 | background: none; 59 | padding: 0; 60 | width: 100rpx; 61 | display: flex; 62 | margin: 20rpx 0; 63 | height: 60rpx; 64 | line-height: 60rpx; 65 | text-align: center; 66 | float: right; 67 | color: #fff; 68 | } 69 | .sub-text { 70 | font-size: 22rpx; 71 | line-height: 80rpx; 72 | } 73 | .donate-img { 74 | height: 256rpx; 75 | width: 256rpx; 76 | margin: 0 auto; 77 | display: block; 78 | margin-bottom: 100rpx; 79 | } 80 | .donate-arrow { 81 | width: 32rpx; 82 | height: 32rpx; 83 | display: block; 84 | margin: 200rpx auto; 85 | } 86 | .lists { 87 | display: flex; 88 | flex-wrap: wrap; 89 | justify-content: space-around; 90 | padding: 0 15rpx; 91 | } 92 | .list-item { 93 | width: 120rpx; 94 | margin: 10rpx; 95 | height: 120rpx; 96 | border-radius: 99999rpx; 97 | display: block; 98 | box-shadow: 1px 6px 4px 0px #e8e8e8; 99 | border: 1rpx solid #ebeef5; 100 | } 101 | .me { 102 | font-size: 25rpx; 103 | color: #909399; 104 | width: fit-content; 105 | margin: 400rpx auto 100rpx; 106 | padding: 60rpx; 107 | line-height: 37rpx; 108 | border-radius: 20rpx; 109 | box-shadow: 1px 3px 5px 4px #efefefb8; 110 | } 111 | .me image { 112 | width: 50px; 113 | height: 50px; 114 | opacity: 0.7; 115 | margin-bottom: 30rpx; 116 | } -------------------------------------------------------------------------------- /miniprogram/pages/target-set/target-set.wxml: -------------------------------------------------------------------------------- 1 | 2 | var formatDate = function(date) { 3 | var dateArr = date.split('-') 4 | return dateArr[0] + '年' + dateArr[1] + '月' + dateArr[2] + '日' 5 | }; 6 | 7 | module.exports.formatDate = formatDate; 8 | 9 | 13 | 14 | 15 | 16 | 17 | 创建你的存钱目标 18 | 19 | 20 | 21 | 目标名 22 | 23 | 30 | 31 | 32 | 33 | 结束日 34 | 35 | 41 | 42 | {{endDate ? wxs.formatDate(endDate) : '选择目标截止时间'}} 43 | 44 | 45 | 46 | 47 | 48 | 49 | 初始金额 50 | 51 | 58 | 59 | 60 | 61 | 目标金额 62 | 63 | 70 | 71 | 72 | 当前版本暂不支持修改已创建的目标,先仔细定个目标~ 73 | 冲鸭 74 | 75 | 76 | -------------------------------------------------------------------------------- /miniprogram/pages/group-bill-set/group-bill-set.js: -------------------------------------------------------------------------------- 1 | import { parseTime } from '../../util' 2 | 3 | Page({ 4 | 5 | /** 6 | * 页面的初始数据 7 | */ 8 | data: { 9 | startDate: '', 10 | endDate: '', 11 | name: '', 12 | minEndDate: parseTime(new Date().getTime() + (86400000 * 2), '{y}-{m}-{d}'), 13 | randomAvatar: `https://api.multiavatar.com/${Math.ceil(Math.random() * 12230590464)}.svg`, 14 | nickName: '' 15 | }, 16 | changeAvatar() { 17 | this.setData({ 18 | randomAvatar: `https://api.multiavatar.com/${Math.ceil(Math.random() * 12230590464)}.svg` 19 | }) 20 | }, 21 | bindDateChange(event) { 22 | const { key } = event.currentTarget.dataset 23 | this.setData({ 24 | [`${key}`]: event.detail.value 25 | }) 26 | }, 27 | onInput(event) { 28 | this.setData({ 29 | [`${event.target.dataset.target}`]: event.detail.value 30 | }) 31 | }, 32 | checkParams() { 33 | const { name, startDate, nickName } = this.data 34 | let errMsg = '' 35 | if (!name) { 36 | errMsg = '输入组名' 37 | } else if (!startDate) { 38 | errMsg = '选择组账单开始时间' 39 | } else if (!nickName) { 40 | errMsg = '需要设置昵称噢' 41 | } 42 | if (errMsg) { 43 | wx.showToast({ 44 | title: errMsg, 45 | icon: 'none' 46 | }) 47 | return false 48 | } 49 | return true 50 | }, 51 | confirm() { 52 | // 验证下数据 53 | const { 54 | name, 55 | nickName, 56 | startDate, 57 | endDate, 58 | randomAvatar 59 | } = this.data 60 | if (this.checkParams()) { 61 | // 通过 62 | wx.cloud.callFunction({ 63 | name: 'groupbill', 64 | data: { 65 | mode: 'add', 66 | name, 67 | startDate, 68 | endDate, 69 | nickName, 70 | avatarUrl: randomAvatar 71 | }, 72 | success(res) { 73 | if (res.result.code === 1) { 74 | wx.showToast({ 75 | title: '账单组创建成功', 76 | icon: 'none' 77 | }) 78 | setTimeout(() => { 79 | wx.redirectTo({ 80 | url: '/pages/group/group' 81 | }) 82 | }, 1500) 83 | getApp().checkHasGroup() 84 | } 85 | }, 86 | fail() { 87 | wx.showToast({ 88 | title: '账单组创建失败,再试试?', 89 | icon: 'none' 90 | }) 91 | }, 92 | complete() { 93 | } 94 | }) 95 | } 96 | } 97 | }) 98 | -------------------------------------------------------------------------------- /cloudfunctions/accountAggregate/README.md: -------------------------------------------------------------------------------- 1 | ### 聚合记账数据 2 | --- 3 | 4 | ### 按开始时间与结束时间聚合金钱(函数名:accountAggregate, mode: aggregateAccountByDateRange) 5 | 6 | | key| 说明 | 是否必填 | 7 | | -------- | ----- | ---- | 8 | | startDate|开始时间, 需要注意的是, 假如是2019-9-8日, 传入的格式必须是 ** 2019-09-08 **|是| 9 | | endDate|结束时间, 需要注意的是, 假如是2019-9-8日, 传入的格式必须是 ** 2019-09-08 **|是| 10 | 11 | --- 12 | 返回格式: 13 | 14 | ``` 15 | 成功 16 | { 17 | code: 1, 18 | sumResult: [ 19 | { 20 | _id: 0, // _id代表flow 21 | allSum: 147, // 当前flow账单总金钱 22 | count: 147 // 当前flow账单条数 23 | }, 24 | { 25 | _id: 0, 26 | allSum: 248, 27 | count: 248 28 | } 29 | ] 30 | } 31 | 32 | 33 | 失败 34 | { 35 | code: 0 36 | } 37 | ``` 38 | 39 | 40 | --- 41 | 42 | ### 获取饼图数据, mode: getPieChartData) 43 | 44 | | key| 说明 | 是否必填 | 45 | | -------- | ----- | ---- | 46 | | startDate|开始时间, 需要注意的是, 假如是2019-9-8日, 传入的格式必须是 ** 2019-09-08 **|是| 47 | | endDate|结束时间, 需要注意的是, 假如是2019-9-8日, 传入的格式必须是 ** 2019-09-08 **|是| 48 | 49 | --- 50 | 返回格式: 51 | 52 | ``` 53 | 成功 54 | { 55 | code: 1, 56 | detailResult: { 57 | flowIn: { // 流入 58 | allSum: 100, // 流入总金额 59 | dataList: [ 60 | { 61 | categoryId: "others", 62 | categoryName: "杂项", 63 | allSum: 50, 64 | flow: 1 65 | }, 66 | { 67 | categoryId: "b_others", 68 | categoryName: "另一个杂项", 69 | allSum: 50, 70 | flow: 1 71 | }, 72 | ] 73 | }, 74 | flowOut: { // 流出 75 | allSum: 30, // 流出总金额 76 | dataList: [ 77 | { 78 | categoryId: "out1", 79 | categoryName: "流出1", 80 | allSum: 10, 81 | flow: 0 82 | }, 83 | { 84 | categoryId: "out2", 85 | categoryName: "流出1", 86 | allSum: 20, 87 | flow: 0 88 | }, 89 | ] 90 | } 91 | } 92 | } 93 | 94 | 95 | 失败 96 | { 97 | code: 0 98 | } 99 | ``` 100 | 101 | 102 | --- -------------------------------------------------------------------------------- /cloudfunctions/checkSubscribe/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init() 5 | 6 | // 云函数入口函数 7 | exports.main = async (event) => { 8 | const wxContext = cloud.getWXContext() 9 | const env = wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 10 | cloud.updateConfig({ 11 | env 12 | }) 13 | const db = cloud.database({ env }) 14 | try { 15 | // 先查询库中有没保留这个openId的记录 16 | const checkRes = await db.collection('SUBSCRIBE') 17 | .where({ 18 | openId: wxContext.OPENID 19 | }) 20 | .get() 21 | if (event.mode === 'post') { 22 | // 如果用户开启 23 | if (event.type === 'open') { 24 | try { 25 | if (checkRes.data.length === 0) { 26 | await db.collection('SUBSCRIBE').add({ 27 | data: { 28 | openId: wxContext.OPENID, 29 | canSubscribe: true, 30 | createTime: db.serverDate(), 31 | updateTime: db.serverDate() 32 | } 33 | }) 34 | } else { 35 | const docId = checkRes.data[0]._id 36 | await db.collection('SUBSCRIBE').doc(docId) 37 | .update({ 38 | data: { 39 | canSubscribe: true, 40 | updateTime: db.serverDate() 41 | } 42 | }) 43 | } 44 | return { 45 | code: 1, 46 | msg: '开启成功' 47 | } 48 | } catch (error) { 49 | return { 50 | code: 0, 51 | msg: '开启失败' 52 | } 53 | } 54 | } 55 | if (event.type === 'close') { 56 | try { 57 | // 如果关闭 58 | const docId = checkRes.data[0]._id 59 | await db.collection('SUBSCRIBE').doc(docId) 60 | .update({ 61 | data: { 62 | canSubscribe: false, 63 | updateTime: db.serverDate() 64 | } 65 | }) 66 | return { 67 | code: 1, 68 | msg: '关闭成功' 69 | } 70 | } catch (error) { 71 | return { 72 | code: 0, 73 | msg: '关闭失败' 74 | } 75 | } 76 | } 77 | } 78 | if (event.mode === 'get') { 79 | return { 80 | code: 1, 81 | data: checkRes.data.length === 1 ? checkRes.data[0].canSubscribe : false 82 | } 83 | } 84 | } catch (error) { 85 | return { 86 | code: 0, 87 | data: JSON.stringify(error) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /miniprogram/pages/tab/tab.wxml: -------------------------------------------------------------------------------- 1 | 7 | 14 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 36 | 49 | 62 | -------------------------------------------------------------------------------- /miniprogram/pages/target-set/target-set.js: -------------------------------------------------------------------------------- 1 | import { parseTime } from '../../util' 2 | 3 | Page({ 4 | 5 | /** 6 | * 页面的初始数据 7 | */ 8 | data: { 9 | endDate: '', 10 | money: null, 11 | startmoney: null, 12 | name: '', 13 | minEndDate: parseTime(new Date().getTime() + (86400000 * 2), '{y}-{m}-{d}') 14 | }, 15 | bindDateChange(event) { 16 | this.setData({ 17 | endDate: event.detail.value 18 | }) 19 | }, 20 | onInput(event) { 21 | this.setData({ 22 | [`${event.target.dataset.target}`]: event.detail.value 23 | }) 24 | }, 25 | checkParams() { 26 | const { 27 | name, startmoney, money, endDate 28 | } = this.data 29 | let errMsg = '' 30 | if (!name) { 31 | errMsg = '输入目标名' 32 | } else if (!endDate) { 33 | errMsg = '选择目标的结束日吧' 34 | } else if (!startmoney) { 35 | errMsg = '要输入初始金额哦' 36 | } else if (!money) { 37 | errMsg = '要输入目标金额哦' 38 | } else if (Number(money) <= Number(startmoney)) { 39 | errMsg = '初始金额不能大于目标金额' 40 | } 41 | if (errMsg) { 42 | wx.showToast({ 43 | title: errMsg, 44 | icon: 'none' 45 | }) 46 | return false 47 | } 48 | return true 49 | }, 50 | confirm() { 51 | // 验证下数据 52 | const { 53 | name, 54 | money, 55 | startmoney, 56 | endDate 57 | } = this.data 58 | if (this.checkParams()) { 59 | // 通过 60 | wx.cloud.callFunction({ 61 | name: 'target', 62 | data: { 63 | mode: 'add', 64 | startMoney: Number(String(startmoney).replace(/\b(0+)/gi, '')), 65 | targetMoney: Number(String(money).replace(/\b(0+)/gi, '')), 66 | name, 67 | endDate 68 | }, 69 | success(res) { 70 | if (res.result.code === 1) { 71 | wx.showToast({ 72 | title: '目标创建成功', 73 | icon: 'none' 74 | }) 75 | getApp().checkHasTarget() 76 | setTimeout(() => { 77 | wx.redirectTo({ 78 | url: '/pages/target/target' 79 | }) 80 | }, 1500) 81 | } 82 | }, 83 | fail() { 84 | wx.showToast({ 85 | title: '目标创建失败,再试试?', 86 | icon: 'none' 87 | }) 88 | }, 89 | complete() { 90 | } 91 | }) 92 | } 93 | }, 94 | /** 95 | * 用户点击右上角分享 96 | */ 97 | onShareAppMessage() { 98 | 99 | } 100 | }) 101 | -------------------------------------------------------------------------------- /miniprogram/pages/group-bill-set/group-bill-set.wxml: -------------------------------------------------------------------------------- 1 | 2 | var formatDate = function(date) { 3 | var dateArr = date.split('-') 4 | return dateArr[0] + '年' + dateArr[1] + '月' + dateArr[2] + '日' 5 | }; 6 | 7 | module.exports.formatDate = formatDate; 8 | 9 | 10 | 11 | 12 | 13 | 创建记账组 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 组名 26 | 27 | 28 | 29 | 30 | 31 | 组账单开始时间 32 | 33 | 34 | {{startDate ? wxs.formatDate(startDate) : '在开始时间之后的账单会进行共享'}} 35 | 36 | 37 | 38 | 39 | 组账单结束时间 40 | 41 | 42 | {{endDate ? wxs.formatDate(endDate) : '在结束时间之前的账单会进行共享'}} 43 | 44 | 45 | 46 | 47 | 48 | 提示:\n 49 | 1. 当前版本只提供单个组账单的创建。\n 50 | 2. 组成员可以随时退出。\n 51 | 3. 如果未填写组账单结束日期,则会将组开始时间之后的所有账单(标记未入组内账单除外)进行组内共享。\n 52 | 4. 单单不会主动获取微信授权的用户身份信息,但为了辨别邀请的用户是否为目标用户,需要设置一个用户名和头像(完全自定义)~\n 53 | 54 | 创建 55 | 56 | 57 | -------------------------------------------------------------------------------- /cloudfunctions/sendMessage/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const dayjs = require('dayjs') 4 | 5 | cloud.init() 6 | function notify(title, content) { 7 | // eslint-disable-next-line global-require 8 | const { bark } = require('./token') 9 | if (bark) { 10 | request(`https://api.day.app/${bark}/${encodeURI(title)}/${encodeURI(content)}`) 11 | } 12 | } 13 | // 云函数入口函数 14 | exports.main = async () => { 15 | const wxContext = cloud.getWXContext() 16 | const env = wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 17 | cloud.updateConfig({ 18 | env 19 | }) 20 | const db = cloud.database({ env }) 21 | const _ = db.command 22 | // 先查询库中有没保留这个openId的记录 23 | const checkRes = await db.collection('SUBSCRIBE').where({ 24 | canSubscribe: true 25 | }).get() 26 | const sendList = checkRes.data 27 | try { 28 | // 今天的开始和结束时间 29 | const todayStr = dayjs().format('YYYY-MM-DD') 30 | const startTime = `${todayStr} 00:00:00` 31 | const endTime = `${todayStr} 23:59:59` 32 | if (sendList.length > 0) { 33 | // 判断已订阅列表中的用户今日是否有记账 34 | const checkAccountTodayRes = await db.collection('DANDAN_NOTE') 35 | .where({ 36 | openId: _.in(sendList.map((user) => user.openId)), 37 | isDel: false, 38 | noteDate: _.gte(new Date(startTime)).and(_.lte(new Date(endTime))) 39 | }) 40 | .get() 41 | const hasNoteOpenIdList = Array.from(new Set(checkAccountTodayRes.data.map((item) => item.openId))) 42 | const canSendList = sendList.filter(u => !hasNoteOpenIdList.includes(u.openId)).map(item => item.openId) 43 | const sendTask = [] 44 | canSendList.forEach((item) => { 45 | const reqTask = cloud.openapi.subscribeMessage.send({ 46 | touser: item, 47 | page: 'pages/tab/tab', 48 | data: { 49 | time1: { 50 | value: dayjs().format('YYYY年MM月DD日') 51 | }, 52 | phrase2: { 53 | value: '记得记账哦' 54 | } 55 | }, 56 | templateId: '29PkwuWSDZ5qCe_bjIAYE8UPbw4A7HIXL_ZNmNCD__s' 57 | }) 58 | sendTask.push(reqTask) 59 | }) 60 | const limit = 30 61 | if (sendTask.length > limit) { 62 | const divide = sendTask.length / limit 63 | for(let i = 0; i < divide; i++) { 64 | await Promise.all(sendTask.slice(i * limit, (i + 1) * limit)) 65 | } 66 | notify( 67 | '模板发送提醒', 68 | ` 69 | 发送时间:${dayjs().format('YYYY-MM-DD HH:mm:ss')}, 70 | 分批次数:${Math.floor(divide)}, 71 | 发送人数: ${sendTask.length} 72 | `) 73 | } 74 | } 75 | } catch (err) { 76 | // eslint-disable-next-line no-console 77 | console.log('订阅信息发送失败!错误', err) 78 | return { 79 | code: 0, 80 | data: null, 81 | message: '订阅信息发送失败!' 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /cloudfunctions/word/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init() 5 | 6 | // 云函数入口函数 7 | exports.main = async (event) => { 8 | const wxContext = cloud.getWXContext() 9 | const env = wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 10 | cloud.updateConfig({ 11 | env 12 | }) 13 | // 初始化数据库 14 | const db = cloud.database({ 15 | env 16 | }) 17 | // 提示语的id,根据数据库中的id来定 18 | const wordId = { 19 | 'release-wifo3': '23fdfcbb-0f0c-4196-9d53-8c1ae616f04b', 20 | 'dandan-zdm86': '9a27f33c-a75f-4495-a04d-0d5f98f51231' 21 | } 22 | const { mode } = event 23 | if (mode === 'get') { 24 | try { 25 | const res = await db.collection('DANDAN_WORD').get() 26 | const payTypeAuthUsers = ['obBpt5XdwPJAfwnIWEq2FZdDIrBQ'] 27 | return { 28 | code: 1, 29 | data: res.data[0], 30 | showPayType: payTypeAuthUsers.includes(wxContext.OPENID), 31 | payTypeList: [ 32 | '', 33 | '支付宝', 34 | '微信', 35 | '信用卡', 36 | '掌上生活', 37 | '招商银行' 38 | ], 39 | message: '获取成功' 40 | } 41 | } catch (error) { 42 | return { 43 | code: -1, 44 | data: error, 45 | message: '获取失败' 46 | } 47 | } 48 | } 49 | if (mode === 'update') { 50 | try { 51 | const { word, expire } = event 52 | // 能够进行设置banner的openId列表 53 | const authUsers = [ 54 | 'obBpt5WNBt2DoPFnUQyX5BA0O7L8' 55 | ] 56 | if (!authUsers.includes(wxContext.OPENID)) { 57 | return { 58 | code: -1, 59 | data: null, 60 | message: '无访问权限' 61 | } 62 | } 63 | await db.collection('DANDAN_WORD').doc(wordId[env]) 64 | .update({ 65 | data: { 66 | word, 67 | show: true, 68 | expire 69 | } 70 | }) 71 | return { 72 | code: 1, 73 | data: null, 74 | message: '更新成功' 75 | } 76 | } catch (error) { 77 | return { 78 | code: -1, 79 | data: error, 80 | message: '更新失败' 81 | } 82 | } 83 | } 84 | if (mode === 'updateBannerUrl') { 85 | try { 86 | const { bannerurl, urlExpire } = event 87 | // 能够进行设置banner的openId列表 88 | const authUsers = [ 89 | 'obBpt5WNBt2DoPFnUQyX5BA0O7L8' 90 | ] 91 | if (!authUsers.includes(wxContext.OPENID)) { 92 | return { 93 | code: -1, 94 | data: null, 95 | message: '无访问权限' 96 | } 97 | } 98 | await db.collection('DANDAN_WORD').doc(wordId[env]) 99 | .update({ 100 | data: { 101 | bannerurl, 102 | show: true, 103 | urlExpire 104 | } 105 | }) 106 | return { 107 | code: 1, 108 | data: null, 109 | message: '更新成功' 110 | } 111 | } catch (error) { 112 | return { 113 | code: -1, 114 | data: error, 115 | message: '更新失败' 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /miniprogram/pages/group/group.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | background-color: #fff; 3 | } 4 | .logo { 5 | padding: 80rpx 0 20rpx; 6 | background-color: #7571e015; 7 | border-radius: 0 0 42rpx 42rpx; 8 | margin-bottom: 20rpx; 9 | } 10 | .nav-style { 11 | background-color: #7571e015; 12 | } 13 | .group-name { 14 | font-size: 50rpx; 15 | font-weight: bold; 16 | margin-bottom: 24rpx; 17 | } 18 | .content { 19 | padding: 0 24rpx; 20 | } 21 | .invite { 22 | color: #fff; 23 | border-radius: 9999rpx; 24 | background-color: #89D02C; 25 | padding: 40rpx; 26 | width: 64rpx; 27 | height: 64rpx; 28 | display: flex; 29 | justify-content: center; 30 | align-items: center; 31 | box-shadow: 0 10px 20px 0 rgba(33, 35, 41, .12); 32 | } 33 | .danger { 34 | color: #fff; 35 | border-radius: 9999rpx; 36 | background-color: #f75676; 37 | padding: 40rpx; 38 | width: 64rpx; 39 | height: 64rpx; 40 | display: flex; 41 | justify-content: center; 42 | align-items: center; 43 | box-shadow: 0 10px 20px 0 rgba(33, 35, 41, .12); 44 | } 45 | .go { 46 | color: #fff; 47 | border-radius: 16rpx; 48 | background-color: #89D02C; 49 | padding: 10rpx 40rpx; 50 | height: 64rpx; 51 | display: flex; 52 | justify-content: center; 53 | align-items: center; 54 | box-shadow: 0 10px 20px 0 rgba(33, 35, 41, .12); 55 | } 56 | .confirm-btn { 57 | color: #fff; 58 | border-radius: 16rpx; 59 | background-color: #89D02C; 60 | padding: 2rpx 8rpx; 61 | box-shadow: 0 10px 20px 0 rgba(33, 35, 41, .12); 62 | } 63 | .confirmed-btn { 64 | color: #828282; 65 | border-radius: 16rpx; 66 | background-color: #ebeef5; 67 | padding: 2rpx 8rpx; 68 | } 69 | .empty-list { 70 | color: #909399; 71 | font-size: 28rpx; 72 | padding: 80rpx 0; 73 | text-align: center; 74 | } 75 | .hide-share { 76 | position: absolute; 77 | width: 200rpx; 78 | height: 200rpx; 79 | opacity: 0; 80 | } 81 | .creator { 82 | height: 76rpx; 83 | width: 76rpx; 84 | } 85 | .info-item { 86 | display: flex; 87 | align-items: center; 88 | justify-content: space-between; 89 | padding: 0 26rpx; 90 | } 91 | .fake-info { 92 | display: flex; 93 | flex-direction: column; 94 | width: 200rpx; 95 | align-items: center; 96 | margin: 0 auto; 97 | } 98 | 99 | .fake-info .avatar { 100 | width: 150rpx; 101 | height: 150rpx; 102 | margin-bottom: 22rpx; 103 | } 104 | 105 | .fake-info .dices { 106 | width: 32rpx; 107 | height: 32rpx; 108 | } 109 | 110 | .fake-info input { 111 | font-size: 26qrpx; 112 | width: 220rpx; 113 | margin: 0 auto; 114 | text-align: center; 115 | border-bottom: 1rpx solid #ebeef5; 116 | margin-bottom: 8rpx; 117 | padding: 12rpx 0; 118 | } 119 | .tip { 120 | font-size: 26rpx; 121 | padding: 24rpx; 122 | text-align: center; 123 | width: fit-content; 124 | margin: 0 auto; 125 | border-radius: 19rpx; 126 | background-color: #f0f9eb; 127 | color: #67c23a; 128 | margin: 20rpx auto 102rpx; 129 | } 130 | .user-name { 131 | max-width: 106rpx; 132 | overflow: hidden; 133 | text-overflow: ellipsis; 134 | word-break: keep-all; 135 | white-space: nowrap; 136 | margin: 5rpx 0 12rpx; 137 | } 138 | .delete-btn { 139 | color: #f75676; 140 | font-size: 24rpx; 141 | } -------------------------------------------------------------------------------- /miniprogram/pages/onboarding/onboarding.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 很高兴认识你! 10 | 我会一直与你一起体验更好的记账工具。 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 重视隐私,不利用、不追踪。 21 | 单单记账永不获取用户隐私信息,给你最单纯的记账体验。 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 简单快速。 32 | 记一笔账只需要几秒,告别冗余操作。 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 直观图表。 43 | 无论查账还是统计图表,都十分清晰明了,让你有更清晰的理财计划。 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | 持续更新。 54 | 我们初衷是专注于功能和体验的记账工具,因此后续会持续更新更多功能,可一同期待! 55 | 56 | 57 | 58 | 59 | 61 | 62 | 63 | 一同开启… 64 | 开启你的记账生涯吧。 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {{step === 6 ? '开启' : '下一页'}} 77 | 78 | -------------------------------------------------------------------------------- /miniprogram/pages/components/chart/chart.wxss: -------------------------------------------------------------------------------- 1 | .month-selector { 2 | display: flex; 3 | flex-wrap: nowrap; 4 | font-size: 29rpx; 5 | color: #1324B7; 6 | align-items: center; 7 | justify-content: space-around; 8 | padding: 0 30rpx 40rpx; 9 | border-radius: 0 0 20% 20%; 10 | margin: 0 -20rpx 40rpx; 11 | overflow-x: hidden; 12 | } 13 | .selector-view { 14 | width: 100%; 15 | overflow-x: hidden; 16 | } 17 | .picker { 18 | width: 130rpx; 19 | height: 60rpx; 20 | line-height: 60rpx; 21 | } 22 | .hide-scroll { 23 | width: 80%; 24 | height: 69rpx; 25 | overflow: hidden; 26 | display: flex; 27 | align-items: center; 28 | } 29 | .months { 30 | display: flex; 31 | white-space: nowrap; 32 | align-items: flex-start; 33 | padding-bottom: 8rpx; 34 | width: 100%; 35 | height: 47px; 36 | } 37 | .month { 38 | margin: 20rpx; 39 | display: inline-block; 40 | height: 53rpx; 41 | line-height: 53rpx; 42 | color: #1324B7; 43 | } 44 | .active-month { 45 | border-bottom: 4rpx solid #C7DDF0; 46 | } 47 | .pie { 48 | width: 93%; 49 | min-height: 500rpx; 50 | background-color: #fff; 51 | margin: -69rpx auto 0; 52 | border-radius: 25rpx; 53 | box-shadow: 0 20px 50px rgba(94, 114, 228, 0.23); 54 | z-index: 2; 55 | position: relative; 56 | } 57 | .charts { 58 | margin: 0 auto; 59 | height:400rpx; 60 | background-color: #FFFFFF; 61 | } 62 | .summary { 63 | display: flex; 64 | width: fit-content; 65 | margin: 0 auto; 66 | position: relative; 67 | z-index: 1; 68 | } 69 | .summary-item { 70 | font-size: 27rpx; 71 | margin: 20rpx 20px 0; 72 | border-radius: 40rpx; 73 | padding: 20rpx; 74 | color: #000; 75 | background-color: #fff; 76 | border: 1rpx solid #eee; 77 | text-align: center; 78 | transition: .5s all; 79 | min-width: 60px; 80 | width: fit-content; 81 | } 82 | .pay { 83 | background: #f75676; 84 | color: #fff; 85 | box-shadow: 0 20px 50px rgba(247, 86, 118, 0.233); 86 | border: 1rpx solid rgba(247, 86, 118, 0.233); 87 | } 88 | .income { 89 | background: #4fd69c; 90 | color: #fff; 91 | box-shadow: 0 20px 50px rgba(79, 214, 155, 0.226); 92 | border: 1rpx solid rgba(79, 214, 155, 0.226); 93 | } 94 | .select-filter { 95 | display: flex; 96 | justify-content: space-between; 97 | padding: 0 30rpx 20rpx; 98 | font-size: 28rpx; 99 | } 100 | .empty-chart { 101 | height: 400rpx; 102 | display: flex; 103 | justify-content: center; 104 | align-items: center; 105 | font-size: 28rpx; 106 | color: grey; 107 | } 108 | .dialog-bills { 109 | max-height: 660rpx; 110 | } 111 | .bill-list { 112 | margin-top: 22rpx; 113 | -webkit-overflow-scrolling: touch; 114 | } 115 | .active-bar { 116 | width: 160rpx; 117 | height: 9rpx; 118 | position: absolute; 119 | background: rgba(255, 166, 0, 0.671); 120 | left: 0; 121 | right: 0; 122 | margin: auto; 123 | bottom: 10rpx; 124 | z-index: -1; 125 | border-radius: 6rpx; 126 | } 127 | .chart-sum { 128 | width: fit-content; 129 | font-size: 28rpx; 130 | } 131 | .chevron { 132 | width: 24rpx; 133 | height: 24rpx; 134 | } 135 | .select-parent { 136 | display: flex; 137 | align-items: center; 138 | } 139 | .hide-pie-tip { 140 | display: flex; 141 | justify-content: center; 142 | align-items: center; 143 | font-size: 28rpx; 144 | color: grey; 145 | } 146 | .category-list-style { 147 | padding: 2rpx 14rpx; 148 | border-radius: 9rpx; 149 | } 150 | .chart-sum { 151 | white-space: nowrap; 152 | } -------------------------------------------------------------------------------- /miniprogram/pages/components/calendar/calendar.wxml: -------------------------------------------------------------------------------- 1 | 2 | var handle = function(day, dateRange) { 3 | if (dateRange.indexOf(day.date) !== -1) return 'range-pick'; 4 | }; 5 | var formatDate = function(dateRange, today) { 6 | if (dateRange.length === 1) return '再选择一个日期'; 7 | if (dateRange.length === 0 || (dateRange[0] === today && dateRange[1] === today)) { 8 | return '今日'; 9 | } 10 | if (dateRange[0] === dateRange[1]) { 11 | return dateRange[0]; 12 | } 13 | return dateRange[0] + ' ~ ' + dateRange[1]; 14 | } 15 | var numParse = function(num) { 16 | num = Number(num) 17 | return +parseFloat(num.toPrecision(12)); 18 | } 19 | 20 | module.exports.handle = handle; 21 | module.exports.formatDate = formatDate 22 | module.exports.numParse = numParse 23 | 24 | 25 | 26 | 27 | 28 | 34 | {{pickDateDisplay}} 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 64 | {{item.dateNumber}} 65 | 66 | 67 | 68 | 69 | 70 | 71 | 本月 72 | 支出 {{fmt.fmtNum(currentMonthStatus[0])}} 73 | 收入 {{fmt.fmtNum(currentMonthStatus[1])}} 74 | 75 | 76 | {{rangeStyle.formatDate(dateRange, today)}} 77 | 支出 {{fmt.fmtNum($.pickDateListSumResult[0])}} 78 | 收入 {{fmt.fmtNum($.pickDateListSumResult[1])}} 79 | 80 | 81 | 选择结束日期 82 | 83 | 84 | -------------------------------------------------------------------------------- /miniprogram/pages/components/bill-list/bill-list.js: -------------------------------------------------------------------------------- 1 | const dayjs = require('dayjs') 2 | const { importStore } = getApp() 3 | const { create, store } = importStore 4 | create.Component(store, { 5 | options: { 6 | styleIsolation: 'shared' 7 | }, 8 | properties: { 9 | billList: { 10 | type: Array, 11 | value: [] 12 | }, 13 | loading: { 14 | type: Number 15 | }, 16 | showLoading: { 17 | type: Boolean, 18 | value: false 19 | }, 20 | fixHeight: { 21 | type: Number, 22 | value: 390 23 | } 24 | }, 25 | use: ['sysInfo.screenHeight', 'mapCategoryName'], 26 | /** 27 | * 组件的初始数据 28 | */ 29 | data: { 30 | showMenuDialog: false, 31 | showConfirmDelete: false, 32 | editItem: {}, 33 | fmtBillList: [] 34 | }, 35 | observers: { 36 | billList(list) { 37 | console.log('检查列表', list) 38 | if (list && list.length) { 39 | this.setData({ 40 | fmtBillList: list.map(item => { 41 | return { 42 | ...item, 43 | noteDate: dayjs(item.noteDate).format('YYYY-MM-DD') 44 | } 45 | }) 46 | }) 47 | } else { 48 | this.setData({ 49 | fmtBillList: [] 50 | }) 51 | } 52 | } 53 | }, 54 | 55 | /** 56 | * 组件的方法列表 57 | */ 58 | methods: { 59 | // 列表滚动到底部触发该事件 60 | onScrollBottom() { 61 | console.log('reach bottom') 62 | }, 63 | showMenu(event) { 64 | const { bill } = event.currentTarget.dataset 65 | console.log('bill', bill) 66 | this.setData({ 67 | editItem: bill, 68 | showMenuDialog: true 69 | }) 70 | store.data.showTabbar = false 71 | }, 72 | closeDialog() { 73 | this.setData({ 74 | showMenuDialog: false, 75 | editItem: {} 76 | }) 77 | store.data.showTabbar = true 78 | }, 79 | editBill() { 80 | const self = this 81 | const { editItem } = self.data 82 | self.setData({ 83 | showMenuDialog: false 84 | }) 85 | store.data.isEdit = false 86 | store.data.showTabbar = true 87 | store.data.editBill = editItem 88 | store.data.activeTab = 'index' 89 | store.data.isEdit = true 90 | const page = getCurrentPages()[ getCurrentPages().length -1] 91 | console.log('查看page', editItem) 92 | if (page.route === 'pages/tab/tab') { 93 | // this.triggerEvent('onEdit') 94 | } else { 95 | wx.navigateBack() 96 | } 97 | console.log('getCurrentPages()', getCurrentPages()) 98 | }, 99 | deleteBill() { 100 | const self = this 101 | const { editItem } = self.data 102 | if (!self.data.showConfirmDelete) { 103 | self.setData({ 104 | showConfirmDelete: !self.data.showConfirmDelete 105 | }) 106 | wx.vibrateShort() 107 | } else { 108 | self.closeDialog() 109 | wx.vibrateShort() 110 | wx.cloud.callFunction({ 111 | name: 'account', 112 | data: { 113 | mode: 'deleteById', 114 | id: editItem._id 115 | }, 116 | success(res) { 117 | if (res.result.code === 1) { 118 | wx.showToast({ 119 | title: '删除成功', 120 | icon: 'none' 121 | }) 122 | self.setData({ 123 | editItem: {}, 124 | showConfirmDelete: false 125 | }) 126 | self.triggerEvent('reFetchBillList', true) 127 | } else { 128 | wx.showToast({ 129 | title: '删除失败,请重试', 130 | icon: 'none' 131 | }) 132 | } 133 | } 134 | }) 135 | } 136 | } 137 | } 138 | }) 139 | -------------------------------------------------------------------------------- /miniprogram/pages/components/chart/chart.wxml: -------------------------------------------------------------------------------- 1 | 2 | var handle = function(num) { 3 | return +parseFloat(num.toPrecision(12)); 4 | }; 5 | 6 | module.exports.handle = handle; 7 | 8 | 9 | 10 | 11 | 12 | 19 | {{year}}年 ▾ 20 | 21 | 22 | 29 | {{item}}月 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 月支出 45 | {{fmt.fmtNum(numHandler.handle(pieChartData.flowOut.allSum || 0))}} 46 | 47 | 48 | 月收入 49 | {{fmt.fmtNum(numHandler.handle(pieChartData.flowIn.allSum || 0))}} 50 | 51 | 52 | 53 | 54 | 没有账单数据( ´・・)ノ(._.`) 55 | 56 | 57 | 63 | 64 | {{pickCategoryId ? '已选:' : '选择分类'}}{{$.mapCategoryName[pickCategoryId]}}{{pickCategoryId ? (" "+ total +" 笔") : ''}} 65 | 66 | 67 | 月余:{{(pieChartData.flowIn.allSum - pieChartData.flowOut.allSum) >= 0 ? '+' : ''}}{{fmt.fmtNum(numHandler.handle(pieChartData.flowIn.allSum - pieChartData.flowOut.allSum))}} 71 | 72 | 73 | 74 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 查看该分类下的账单 88 | 饼图比例小无法点击可在此选择查看 89 | 90 | 91 | 101 | {{item.categoryName}} 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /miniprogram/store/omix/path.js: -------------------------------------------------------------------------------- 1 | const OBJECTTYPE = '[object Object]' 2 | const ARRAYTYPE = '[object Array]' 3 | 4 | export function getUsing(data, paths) { 5 | if(!paths) return {} 6 | const obj = {} 7 | paths.forEach((path, index) => { 8 | const isPath = typeof path === 'string' 9 | if (!isPath) { 10 | const key = Object.keys(path)[0] 11 | const value = path[key] 12 | if (typeof value !== 'string') { 13 | 14 | const tempPath = value[0] 15 | if (typeof tempPath === 'string') { 16 | const tempVal = getTargetByPath(data, tempPath) 17 | obj[key] = value[1] ? value[1](tempVal) : tempVal 18 | } else { 19 | const args = [] 20 | tempPath.forEach(path => { 21 | args.push(getTargetByPath(data, path)) 22 | }) 23 | obj[key] = value[1].apply(null, args) 24 | } 25 | 26 | } 27 | } 28 | }) 29 | return obj 30 | } 31 | 32 | export function getTargetByPath(origin, path) { 33 | const arr = path 34 | .replace(/]/g, '') 35 | .replace(/\[/g, '.') 36 | .split('.') 37 | let current = origin 38 | for (let i = 0, len = arr.length; i < len; i++) { 39 | current = current[arr[i]] 40 | } 41 | return current 42 | } 43 | 44 | export function getPath(obj) { 45 | if (Object.prototype.toString.call(obj) === '[object Array]') { 46 | const result = {} 47 | obj.forEach(item => { 48 | if (typeof item === 'string') { 49 | result[item] = true 50 | } else { 51 | const tempPath = item[Object.keys(item)[0]] 52 | if (typeof tempPath === 'string') { 53 | result[tempPath] = true 54 | } else if (typeof tempPath[0] === 'string') { 55 | result[tempPath[0]] = true 56 | } else { 57 | tempPath[0].forEach(path => (result[path] = true)) 58 | } 59 | } 60 | }) 61 | return result 62 | } 63 | return getUpdatePath(obj) 64 | } 65 | 66 | export function getUpdatePath(data) { 67 | const result = {} 68 | dataToPath(data, result) 69 | return result 70 | } 71 | 72 | function dataToPath(data, result) { 73 | Object.keys(data).forEach(key => { 74 | result[key] = true 75 | const type = Object.prototype.toString.call(data[key]) 76 | if (type === OBJECTTYPE) { 77 | _objToPath(data[key], key, result) 78 | } else if (type === ARRAYTYPE) { 79 | _arrayToPath(data[key], key, result) 80 | } 81 | }) 82 | } 83 | 84 | function _objToPath(data, path, result) { 85 | Object.keys(data).forEach(key => { 86 | result[path + '.' + key] = true 87 | delete result[path] 88 | const type = Object.prototype.toString.call(data[key]) 89 | if (type === OBJECTTYPE) { 90 | _objToPath(data[key], path + '.' + key, result) 91 | } else if (type === ARRAYTYPE) { 92 | _arrayToPath(data[key], path + '.' + key, result) 93 | } 94 | }) 95 | } 96 | 97 | function _arrayToPath(data, path, result) { 98 | data.forEach((item, index) => { 99 | result[path + '[' + index + ']'] = true 100 | delete result[path] 101 | const type = Object.prototype.toString.call(item) 102 | if (type === OBJECTTYPE) { 103 | _objToPath(item, path + '[' + index + ']', result) 104 | } else if (type === ARRAYTYPE) { 105 | _arrayToPath(item, path + '[' + index + ']', result) 106 | } 107 | }) 108 | } 109 | 110 | export function needUpdate(diffResult, updatePath) { 111 | for (let keyA in diffResult) { 112 | if (updatePath[keyA]) { 113 | return true 114 | } 115 | for (let keyB in updatePath) { 116 | if (includePath(keyA, keyB)) { 117 | return true 118 | } 119 | } 120 | } 121 | return false 122 | } 123 | 124 | function includePath(pathA, pathB) { 125 | if (pathA.indexOf(pathB) === 0) { 126 | const next = pathA.substr(pathB.length, 1) 127 | if (next === '[' || next === '.') { 128 | return true 129 | } 130 | } 131 | return false 132 | } 133 | 134 | export function fixPath(path) { 135 | let mpPath = '' 136 | const arr = path.replace('#-', '').split('-') 137 | arr.forEach((item, index) => { 138 | if (index) { 139 | if (isNaN(Number(item))) { 140 | mpPath += '.' + item 141 | } else { 142 | mpPath += '[' + item + ']' 143 | } 144 | } else { 145 | mpPath += item 146 | } 147 | }) 148 | return mpPath 149 | } 150 | -------------------------------------------------------------------------------- /cloudfunctions/account/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const dayjs = require('dayjs') 4 | 5 | cloud.init() 6 | 7 | function strip(num, precision = 12) { 8 | num = Number(num).toFixed(2) 9 | return +parseFloat(Number(num).toPrecision(precision)) 10 | } 11 | // 云函数入口函数 12 | exports.main = async (event) => { 13 | const wxContext = cloud.getWXContext() 14 | const env = wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 15 | // 取参 16 | const { 17 | id, money, categoryId, noteDate, description, flow 18 | } = event 19 | cloud.updateConfig({ env }) 20 | // 初始化数据库 21 | const db = cloud.database({ env }) 22 | 23 | try { 24 | // 增加一条记录 25 | if (event.mode === 'add') { 26 | const res = await db.collection('DANDAN_NOTE').add({ 27 | data: { 28 | money: strip(money), 29 | categoryId, 30 | noteDate: new Date(noteDate), 31 | description, 32 | flow: Number(flow), // 金钱流向 33 | createTime: db.serverDate(), 34 | updateTime: db.serverDate(), 35 | openId: wxContext.OPENID, 36 | isDel: false 37 | } 38 | }) 39 | return { 40 | code: 1, 41 | data: res, 42 | message: '操作成功' 43 | } 44 | } 45 | let oldNote = null 46 | if (event.mode === 'deleteById' || event.mode === 'updateById') { 47 | const noteRes = await db.collection('DANDAN_NOTE').doc(id).get() 48 | // eslint-disable-next-line prefer-destructuring 49 | oldNote = noteRes.data[0] 50 | } 51 | const updateStat = async (nd) => { 52 | try { 53 | await cloud.callFunction({ 54 | name: 'stat', 55 | data: { 56 | openId: wxContext.OPENID, 57 | noteDate: dayjs(nd).format('YYYY-MM-DD') 58 | } 59 | }) 60 | } catch (error) { 61 | console.log(error) 62 | } 63 | } 64 | if (event.mode === 'deleteById') { 65 | const res = await db.collection('DANDAN_NOTE').doc(id).update({ 66 | data: { 67 | isDel: true 68 | } 69 | }) 70 | await updateStat(oldNote.noteDate) 71 | return { 72 | code: 1, 73 | data: res, 74 | message: '操作成功' 75 | } 76 | } 77 | 78 | if (event.mode === 'updateById') { 79 | const res = await db.collection('DANDAN_NOTE').doc(id).update({ 80 | data: { 81 | money: strip(money), 82 | categoryId, 83 | flow: Number(flow), // 金钱流向 84 | noteDate: new Date(noteDate), 85 | description, 86 | updateTime: db.serverDate() 87 | } 88 | }) 89 | // 更新旧的统计数据 90 | await updateStat(oldNote.noteDate) 91 | // 更新最新的统计数据 92 | await updateStat(noteDate) 93 | return { 94 | code: 1, 95 | data: res, 96 | message: '操作成功' 97 | } 98 | } 99 | // 通过id去获取某笔记录 100 | if (event.mode === 'getNoteById') { 101 | const res = await db.collection('DANDAN_NOTE') 102 | .where({ 103 | _id: id, 104 | isDel: false 105 | }).get() 106 | if (res.data.length > 0) { 107 | const tempCategory = await db.collection('DANDAN_NOTE_CATEGORY').doc(res.data[0].categoryId).field({ 108 | categoryIcon: true, 109 | categoryName: true, 110 | _id: true 111 | }).get() 112 | // 貌似没有记录的话, 就直接被catch掉了 113 | if (tempCategory.data != null) { 114 | res.data[0].category = tempCategory.data 115 | } 116 | } 117 | 118 | return { 119 | code: 1, 120 | data: res, 121 | message: '操作成功' 122 | } 123 | } 124 | 125 | // 删除分类之后会将分类下的记录分类更新为其他 126 | if (event.mode === 'deleteByCategoryId') { 127 | const afterCategoryId = flow == 1 ? 'income_others' : 'others_sub' 128 | 129 | const res = await db.collection('DANDAN_NOTE') 130 | .where({ 131 | categoryId, 132 | isDel: false 133 | }).update({ 134 | data: { 135 | categoryId: afterCategoryId 136 | } 137 | }) 138 | 139 | return { 140 | code: 1, 141 | data: res, 142 | message: '操作成功' 143 | } 144 | } 145 | } catch (e) { 146 | return { 147 | code: -1, 148 | data: e, 149 | message: '操作失败' 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /cloudfunctions/category/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init() 5 | 6 | // 云函数入口函数 7 | exports.main = async (event) => { 8 | const wxContext = cloud.getWXContext() 9 | const env = wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 10 | cloud.updateConfig({ env }) 11 | // 初始化数据库 12 | const db = cloud.database({ env }) 13 | const { 14 | id, 15 | categoryName, 16 | categoryIcon, 17 | description, 18 | flow, 19 | type, 20 | parentId, 21 | isSelectable, 22 | ids, 23 | OPENID 24 | } = event 25 | 26 | const _ = db.command 27 | 28 | try { 29 | if (event.mode === 'add') { 30 | const res = await db.collection('DANDAN_NOTE_CATEGORY') 31 | .add({ 32 | data: { 33 | categoryName, 34 | categoryIcon, 35 | description, 36 | flow: Number(flow), 37 | type: Number(type), 38 | parentId, 39 | isSelectable, 40 | createTime: db.serverDate(), 41 | openId: String(type) === '0' ? 'SYSTEM' : wxContext.OPENID, 42 | isDel: false 43 | } 44 | }) 45 | return { 46 | code: 1, 47 | data: res, 48 | message: '操作成功' 49 | } 50 | } 51 | 52 | if (event.mode === 'deleteByIdAndFlow') { 53 | const res = await db.collection('DANDAN_NOTE_CATEGORY').doc(id) 54 | .update({ 55 | data: { 56 | isDel: true 57 | } 58 | }) 59 | // 删除分类会将所有旧分类更新为其他 60 | if (res.stats.updated > 0) { 61 | const afterCategoryId = flow == 1 ? 'income_others' : 'others_sub' 62 | const updateRes = await db.collection('DANDAN_NOTE') 63 | .where({ 64 | categoryId: id, 65 | isDel: false 66 | }).update({ 67 | data: { 68 | categoryId: afterCategoryId 69 | } 70 | }) 71 | 72 | return { 73 | code: 1, 74 | data: updateRes, 75 | message: '操作成功' 76 | } 77 | } 78 | 79 | return { 80 | code: 1, 81 | data: res, 82 | message: '操作成功' 83 | } 84 | } 85 | 86 | if (event.mode === 'getCategoryById') { 87 | const res = await db.collection('DANDAN_NOTE_CATEGORY') 88 | .where({ 89 | _id: id, 90 | isDel: false 91 | }).get() 92 | return { 93 | code: 1, 94 | data: res, 95 | message: '操作成功' 96 | } 97 | } 98 | 99 | if (event.mode === 'getCategoriesByIdBatch') { 100 | const res = await db.collection('DANDAN_NOTE_CATEGORY') 101 | .where({ 102 | _id: _.in(ids), 103 | isDel: false 104 | }).get() 105 | 106 | return { 107 | code: 1, 108 | data: res, 109 | message: '操作成功' 110 | } 111 | } 112 | 113 | // Deprecated: 目录在同一个父分类下数量太多, 官方文档GET默认取一百条, 这是个挺坑的东西 114 | // 根据父分类ID获取子分类ID 115 | if (event.mode === 'getCategoriesByParentCID') { 116 | const res = await db.collection('DANDAN_NOTE_CATEGORY') 117 | .where({ 118 | parentId: id, 119 | isDel: false 120 | }).get() 121 | return { 122 | code: 1, 123 | data: res, 124 | message: '操作成功' 125 | } 126 | } 127 | 128 | // 根据父分类ID获取系统分类 129 | if (event.mode === 'getCategoriesByParentCIDAndSystem') { 130 | const res = await db.collection('DANDAN_NOTE_CATEGORY') 131 | .where({ 132 | type: 0, 133 | parentId: id, 134 | isDel: false 135 | }).limit(300).get() 136 | return { 137 | code: 1, 138 | data: res, 139 | message: '操作成功' 140 | } 141 | } 142 | 143 | // 根据父分类ID获取某个用户定义的分类 144 | if (event.mode === 'getCategoriesByParentCIDAndOpenId') { 145 | const res = await db.collection('DANDAN_NOTE_CATEGORY') 146 | .where({ 147 | openId: (OPENID || wxContext.OPENID), 148 | type: 1, 149 | parentId: id, 150 | isDel: false 151 | }).limit(300).get() 152 | return { 153 | code: 1, 154 | data: res, 155 | message: '操作成功' 156 | } 157 | } 158 | return { 159 | code: -1, 160 | data: [], 161 | message: '没有方法匹配' 162 | } 163 | } catch (e) { 164 | return { 165 | code: -1, 166 | data: '', 167 | message: '操作失败' 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /cloudfunctions/target/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init() 5 | 6 | function getPureDate(time) { 7 | // eslint-disable-next-line no-extend-native 8 | Date.prototype.addHours = function (h) { 9 | this.setHours(this.getHours() + h) 10 | return this 11 | } 12 | const date = new Date(time).addHours(8) 13 | return date.toLocaleDateString() 14 | } 15 | // 云函数入口函数 16 | exports.main = async (event) => { 17 | const wxContext = cloud.getWXContext() 18 | const env = wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 19 | // 初始化数据库 20 | const db = cloud.database({ env }) 21 | const _ = db.command 22 | const { 23 | id, 24 | startMoney, 25 | targetMoney, 26 | name, 27 | endDate 28 | } = event 29 | cloud.updateConfig({ env }) 30 | 31 | try { 32 | // 增加一条记录 33 | if (event.mode === 'add') { 34 | const res = await db.collection('TARGET').add({ 35 | data: { 36 | startMoney: roundFun(startMoney, 2), 37 | targetMoney: roundFun(targetMoney, 2), 38 | name, 39 | endDate: new Date(endDate), 40 | createTime: db.serverDate(), 41 | updateTime: db.serverDate(), 42 | openId: wxContext.OPENID, 43 | isDel: false 44 | } 45 | }) 46 | return { 47 | code: 1, 48 | data: res, 49 | message: '操作成功' 50 | } 51 | } 52 | 53 | if (event.mode === 'deleteById') { 54 | const res = await db.collection('TARGET').doc(id).update({ 55 | data: { 56 | isDel: true 57 | } 58 | }) 59 | return { 60 | code: 1, 61 | data: res, 62 | message: '操作成功' 63 | } 64 | } 65 | // 检查是否已有未删除的目标 66 | if (event.mode === 'check') { 67 | const res = await db.collection('TARGET').where({ 68 | openId: wxContext.OPENID, 69 | isDel: false 70 | }).get() 71 | return { 72 | code: 1, 73 | data: res.data, 74 | message: '操作成功' 75 | } 76 | } 77 | 78 | // 获取目标的数据 79 | if (event.mode === 'targetInfo') { 80 | const MAX_LIMIT = 100 81 | const targetBaseInfo = await db.collection('TARGET').where({ 82 | openId: wxContext.OPENID, 83 | isDel: false 84 | }).get() 85 | if (targetBaseInfo.data.length) { 86 | const targetData = targetBaseInfo.data[0] 87 | // 获取开始时间到结束时间的所有账单数 88 | const sameParam = { 89 | openId: wxContext.OPENID, 90 | isDel: false, 91 | noteDate: _.gte(new Date(`${getPureDate(targetData.createTime)} 00:00:00`)).and(_.lte(new Date(`${getPureDate(targetData.endDate)} 23:59:59`))) 92 | } 93 | const countResult = await db.collection('DANDAN_NOTE') 94 | .where(sameParam) 95 | .count() 96 | const { 97 | total 98 | } = countResult 99 | const batchTimes = Math.ceil(total / 100) 100 | const tasks = [] 101 | for (let i = 0; i < batchTimes; i++) { 102 | const promise = db.collection('DANDAN_NOTE') 103 | .where(sameParam) 104 | .skip(i * MAX_LIMIT).limit(MAX_LIMIT) 105 | .get() 106 | tasks.push(promise) 107 | } 108 | const billList = await Promise.all(tasks) 109 | const returnBillList = [] 110 | billList.forEach((bill) => { 111 | bill.data.forEach((inBill) => { 112 | returnBillList.push(inBill) 113 | }) 114 | }) 115 | return { 116 | code: 1, 117 | data: { 118 | targetData, 119 | billList: returnBillList 120 | }, 121 | message: '获取成功' 122 | } 123 | } 124 | return { 125 | code: -1, 126 | data: '', 127 | message: '未设置目标' 128 | } 129 | } 130 | if (event.mode === 'delete') { 131 | console.log('删除id', event.id) 132 | const res = await db.collection('TARGET').doc(event.id) 133 | .update({ 134 | data: { 135 | isDel: true 136 | } 137 | }) 138 | return { 139 | code: 1, 140 | data: res, 141 | message: '操作成功' 142 | } 143 | } 144 | } catch (e) { 145 | return { 146 | code: -1, 147 | data: '', 148 | message: '操作失败' 149 | } 150 | } 151 | } 152 | 153 | // eslint-disable-next-line no-restricted-properties 154 | roundFun = (value, n) => Math.round(value * Math.pow(10, n)) / Math.pow(10, n) 155 | // eslint-disable-next-line no-undef 156 | -------------------------------------------------------------------------------- /miniprogram/pages/components/index/index.wxss: -------------------------------------------------------------------------------- 1 | .banner { 2 | width: 100%; 3 | height: 410rpx; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | } 8 | .banner-bg { 9 | width: 100%; 10 | height: 410rpx; 11 | position: fixed; 12 | z-index: -1; 13 | } 14 | .banner-special { 15 | width: 100%; 16 | height: 410rpx; 17 | background: linear-gradient(40deg, #f6c3d1, #e8bffe); 18 | position: fixed; 19 | z-index: -1; 20 | } 21 | .banner-img-bg1 { 22 | background-image: url("https://wx4.sinaimg.cn/mw690/82f26360gy1gaa4n3pz01g20ia09px6p.gif"); 23 | } 24 | .banner-img-bg2 { 25 | background-image: url("https://wx3.sinaimg.cn/mw690/82f26360gy1gabawajibxg20ia09p7wi.gif"); 26 | } 27 | .banner-img-bg3 { 28 | background-image: url("https://wx4.sinaimg.cn/mw690/82f26360gy1gabaqvv0yog20ia09pqmw.gif"); 29 | } 30 | .banner-img-bg4 { 31 | background-image: url("https://wx1.sinaimg.cn/mw690/82f26360gy1gabb5vsfchg20ia09pwyo.gif"); 32 | } 33 | .banner-show { 34 | display: flex; 35 | align-items: center; 36 | } 37 | .tab { 38 | display: flex; 39 | justify-content: space-around; 40 | padding: 15rpx 0 0; 41 | color: #6c757d; 42 | border-radius: 14rpx 14rpx 0 0; 43 | margin-top: -40rpx; 44 | background: #fff; 45 | margin: -40rpx 20rpx 0; 46 | } 47 | .tab-item { 48 | width: 50%; 49 | text-align: center; 50 | padding: 0 0 15rpx; 51 | border-bottom: 4rpx solid #f5f5f5; 52 | } 53 | .active-tab { 54 | color: #ffdd57; 55 | border-bottom: 4rpx solid #ffdd57; 56 | } 57 | .section { 58 | display: flex; 59 | align-items: center; 60 | justify-content: space-between; 61 | padding: 16rpx 20rpx; 62 | border-bottom: solid 1rpx rgba(0,0,0,.1); 63 | margin: 0 20rpx; 64 | background-color: #fff; 65 | } 66 | .section input { 67 | /* padding: 20rpx 0; */ 68 | height: 80rpx; 69 | text-align: right; 70 | width: 100%; 71 | position: relative; 72 | z-index: 9; 73 | } 74 | .section .label { 75 | word-break: keep-all; 76 | white-space: nowrap; 77 | } 78 | .input-field { 79 | display: flex; 80 | align-items: center; 81 | position: relative; 82 | flex-direction: column; 83 | justify-content: flex-end; 84 | width: 100%; 85 | } 86 | .default-text { 87 | position: absolute; 88 | right: 0; 89 | line-height: 80rpx; 90 | } 91 | .cate { 92 | margin: 10rpx 16rpx; 93 | font-size: 26rpx; 94 | padding: 8rpx 20rpx; 95 | border-radius: 6rpx; 96 | background-color: #f7f8f9; 97 | color: rgba(0,0,0,.6); 98 | text-align: center; 99 | } 100 | .active { 101 | color: #fff; 102 | } 103 | .date-item { 104 | margin: 10rpx; 105 | padding: 5rpx 10rpx; 106 | background-color: #ccc; 107 | text-align: center; 108 | border-radius: 10rpx; 109 | } 110 | .btn-area { 111 | text-align: center; 112 | } 113 | .warn { 114 | border: 1px solid transparent; 115 | border-radius: 9rpx; 116 | font-size: 28rpx; 117 | padding: 20rpx 40rpx; 118 | display: inline-block; 119 | position: relative; 120 | font-weight: 600; 121 | letter-spacing: 5rpx; 122 | text-decoration: none; 123 | line-height: normal; 124 | width: 90%; 125 | margin: 40rpx 0 0rpx; 126 | } 127 | .input-sum, .default-text { 128 | font-size: 46rpx; 129 | } 130 | .select-field { 131 | display: flex; 132 | justify-content: flex-end; 133 | width: 100%; 134 | } 135 | .select-date { 136 | width: 100%; 137 | text-align: right; 138 | margin-right: 17rpx; 139 | font-size: 24rpx; 140 | } 141 | .edit-tip { 142 | background-color: #ffdd57; 143 | width: 60%; 144 | border-radius: 6rpx; 145 | color: rgba(0,0,0,.6); 146 | font-size: 28rpx; 147 | text-align: center; 148 | margin: 10rpx auto 10rpx; 149 | padding: 3rpx 0; 150 | } 151 | .pig-talk { 152 | display: flex; 153 | align-items: center; 154 | padding: 14rpx 0; 155 | justify-content: center; 156 | margin-left: 27rpx; 157 | } 158 | .word-tang-left { 159 | width: 0; 160 | height: 0; 161 | border-bottom: 10px solid rgba(255, 142, 142, 0.8); 162 | border-left: 10px solid transparent; 163 | } 164 | .word-detail { 165 | max-width: 500rpx; 166 | font-size: 25rpx; 167 | background-color: rgba(255, 142, 142, 0.8); 168 | padding: 9px; 169 | border-radius: 24rpx; 170 | word-break: break-all; 171 | word-wrap: break-word; 172 | overflow: hidden; 173 | text-align: left; 174 | } 175 | .active-paytype { 176 | color: #ff6ec4; 177 | } 178 | .target-toast { 179 | border: 1rpx solid #fde998; 180 | background-color: #faf6e7; 181 | padding: 16rpx; 182 | border-radius: 24rpx; 183 | width: fit-content; 184 | font-size: 26rpx; 185 | margin: 0 auto; 186 | transition: 0.5s ease-in-out; 187 | } -------------------------------------------------------------------------------- /cloudfunctions/exportFile/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | const excel = require('excel-export') 4 | 5 | cloud.init() 6 | const MAX_LIMIT = 100 7 | 8 | function parseTime(time, cFormat) { 9 | if (arguments.length === 0) { 10 | return null 11 | } 12 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' 13 | let date 14 | if (typeof time === 'object') { 15 | date = time 16 | } else { 17 | // eslint-disable-next-line radix 18 | if ((`${time}`).length === 10) time = parseInt(time) * 1000 19 | date = new Date(time) 20 | } 21 | const formatObj = { 22 | y: date.getFullYear(), 23 | m: date.getMonth() + 1, 24 | d: date.getDate(), 25 | h: date.getHours(), 26 | i: date.getMinutes(), 27 | s: date.getSeconds(), 28 | a: date.getDay() 29 | } 30 | const timeStr = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 31 | let value = formatObj[key] 32 | if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1] 33 | if (result.length > 0 && value < 10) { 34 | value = `0${value}` 35 | } 36 | return value || 0 37 | }) 38 | return timeStr 39 | } 40 | // 云函数入口函数 41 | exports.main = async () => { 42 | const wxContext = cloud.getWXContext() 43 | const env = wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 44 | cloud.updateConfig({ 45 | env 46 | }) 47 | const db = cloud.database({ 48 | env 49 | }) 50 | const cateMap = {} 51 | const _ = db.command 52 | try { 53 | // 查询该用户已有分类 54 | const getCategoryRes = await db.collection('DANDAN_NOTE_CATEGORY') 55 | .where({ 56 | isDel: false, 57 | openId: _.eq(wxContext.OPENID).or(_.eq('SYSTEM')) 58 | }) 59 | .get() 60 | if (getCategoryRes.data) { 61 | const categoryList = getCategoryRes.data 62 | categoryList.forEach((cate) => { 63 | cateMap[cate._id] = cate 64 | }) 65 | } 66 | } catch (error) { 67 | // eslint-disable-next-line no-console 68 | console.error('获取分类失败啦', error) 69 | } 70 | // 查询用户的账单 71 | try { 72 | const countResult = await db.collection('DANDAN_NOTE') 73 | .where({ 74 | openId: wxContext.OPENID, 75 | isDel: false 76 | }) 77 | .count() 78 | const { total } = countResult 79 | const batchTimes = Math.ceil(total / 100) 80 | const tasks = [] 81 | 82 | for (let i = 0; i < batchTimes; i++) { 83 | const promise = db.collection('DANDAN_NOTE') 84 | .where({ 85 | openId: wxContext.OPENID, 86 | isDel: false 87 | }) 88 | .skip(i * MAX_LIMIT).limit(MAX_LIMIT) 89 | .get() 90 | tasks.push(promise) 91 | } 92 | const final = await Promise.all(tasks) 93 | const rowData = [] 94 | for (let i = 0; i < final.length; i++) { 95 | const tempInLoop = final[i].data 96 | for (let j = 0; j < tempInLoop.length; j++) { 97 | rowData.push([ 98 | parseTime(tempInLoop[j].createTime, '{y}/{m}/{d} {h}:{m}:{s}'), 99 | parseTime(tempInLoop[j].noteDate, '{y}/{m}/{d}'), 100 | cateMap[tempInLoop[j].categoryId] ? cateMap[tempInLoop[j].categoryId].categoryName : '杂项', 101 | tempInLoop[j].flow === 0 ? -tempInLoop[j].money : tempInLoop[j].money, 102 | tempInLoop[j].description 103 | ]) 104 | } 105 | } 106 | try { 107 | // 做数据导出操作 108 | const conf = {} 109 | conf.stylesXmlFile = 'styles.xml' 110 | conf.name = 'mysheet' 111 | // 设置表格列格式等 112 | conf.cols = [{ 113 | caption: '创建时间', 114 | type: 'string' 115 | }, { 116 | caption: '消费日期', 117 | type: 'string' 118 | }, { 119 | caption: '分类', 120 | type: 'string' 121 | }, { 122 | caption: '金额', 123 | type: 'number' 124 | }, { 125 | caption: '备注', 126 | type: 'string' 127 | }] 128 | // 设置表格内容 129 | conf.rows = rowData 130 | const result = excel.execute(conf) 131 | Buffer.from(result.toString(), 'binary') 132 | const uplodaRes = await cloud.uploadFile({ 133 | cloudPath: `download/sheet/单单记账-账单(${wxContext.OPENID.slice(3, 20)})-${new Date().getTime()}.xlsx`, // excel文件名称及路径,即云存储中的路径 134 | fileContent: Buffer.from(result.toString(), 'binary') 135 | }) 136 | // eslint-disable-next-line no-console 137 | console.log('uplodaRes', uplodaRes) 138 | return { 139 | code: 1, 140 | data: uplodaRes 141 | } 142 | } catch (error) { 143 | // eslint-disable-next-line no-console 144 | console.error('导出出错', error) 145 | } 146 | } catch (error) { 147 | // eslint-disable-next-line no-console 148 | console.error('查询出错', error) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /cloudfunctions/getAccountList/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | // 云函数入口文件 3 | const cloud = require('wx-server-sdk') 4 | const dayjs = require('dayjs') 5 | 6 | cloud.init() 7 | 8 | // 云函数入口函数 9 | exports.main = async (event) => { 10 | /** 11 | * 获取账单列表 12 | * @param {String} categoryId 分类id 13 | * @param {String} startDate 开始日期 14 | * @param {String} endDate 结束日期 15 | * @param {Number} page 页码,必填 16 | * @param {Number} limit 每页数量,必填 17 | * @param {String} mode 请求接口 18 | */ 19 | const wxContext = cloud.getWXContext() 20 | const env = wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 21 | cloud.updateConfig({ env }) 22 | // 初始化数据库 23 | const db = cloud.database({ env }) 24 | const _ = db.command 25 | // page: 当前页数 26 | // limit: 当前页面加载的个数 27 | const { 28 | page, 29 | limit, 30 | startDate, 31 | endDate, 32 | categoryId 33 | } = event 34 | if (!page || !limit) { 35 | return { 36 | code: 0, 37 | msg: 'page or limit is empty' 38 | } 39 | } 40 | try { 41 | // 分页偏移量公式: (page - 1) * limit 42 | // 计算偏移量 43 | const offset = (page - 1) * limit 44 | const basicWhere = { 45 | isDel: false, 46 | openId: _.eq(wxContext.OPENID), 47 | noteDate: _.gte(new Date(startDate)).and(_.lte(new Date(endDate))) 48 | } 49 | // 如果有分类id, 则按照分类id划定分类范围 50 | let matchCategoryList = [] // 每一笔NOTE都是取用子类的,所以只需要找出子类就好 51 | if (categoryId) { 52 | // 查询开始和结束时间内,分类为categoryId以及该categoryId下子分类的所有账单 53 | // 1. 查询账单应当包含的分类id,用数组表示 54 | const matchCategoryRes = await db.collection('DANDAN_NOTE_CATEGORY').where({ 55 | isDel: false, 56 | openId: _.in([wxContext.OPENID, 'SYSTEM']), 57 | parentId: categoryId 58 | }).get() 59 | matchCategoryList = matchCategoryRes.data 60 | basicWhere.categoryId = _.in(matchCategoryList.map((item) => item._id)) 61 | } 62 | // 计算总笔数 63 | const totalCountRes = await db.collection('DANDAN_NOTE').where(basicWhere).count() 64 | // 2. 查询账单 65 | const noteRes = await db.collection('DANDAN_NOTE') 66 | .where(basicWhere) 67 | .skip(offset) 68 | .limit(limit) 69 | .orderBy('noteDate', 'desc') 70 | .orderBy('createTime', 'desc') 71 | .get() 72 | const noteList = noteRes.data 73 | noteList.forEach((note) => { 74 | note.noteDate = dayjs(note.noteDate).format('YYYY-MM-DD') 75 | }) 76 | console.log('查看返回账单', noteList, totalCountRes) 77 | return { 78 | code: 1, 79 | data: { 80 | page: noteList, 81 | count: totalCountRes.total, 82 | rangeResult: [], 83 | monthResult: [] 84 | }, 85 | message: '获取记录成功' 86 | } 87 | } catch (e) { 88 | return { 89 | code: -1, 90 | data: { 91 | page: [], 92 | count: 0 93 | }, 94 | message: `获取记录失败,e:${e}` 95 | } 96 | } 97 | } 98 | 99 | // 补全账单内容 100 | // function completeInfo(note, category) { 101 | // // 转换日期格式 102 | // // eslint-disable-next-line no-use-before-define 103 | // note.noteDate = parseTime(note.noteDate, '{y}-{m}-{d}') 104 | 105 | // // 貌似没有记录的话, 就直接被catch掉了 106 | // if (category !== undefined) { 107 | // note.category = category 108 | // } 109 | // } 110 | 111 | // function parseTime(time, cFormat) { 112 | // if (arguments.length === 0) { 113 | // return null 114 | // } 115 | // const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' 116 | // let date 117 | // if (typeof time === 'object') { 118 | // date = time 119 | // } else { 120 | // // eslint-disable-next-line radix 121 | // if ((`${time}`).length === 10) time = parseInt(time) * 1000 122 | // date = new Date(time) 123 | // } 124 | // const formatObj = { 125 | // y: date.getFullYear(), 126 | // m: date.getMonth() + 1, 127 | // d: date.getDate(), 128 | // h: date.getHours(), 129 | // i: date.getMinutes(), 130 | // s: date.getSeconds(), 131 | // a: date.getDay() 132 | // } 133 | // const timeStr = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 134 | // let value = formatObj[key] 135 | // if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1] 136 | // if (result.length > 0 && value < 10) { 137 | // value = `0${value}` 138 | // } 139 | // return value || 0 140 | // }) 141 | // return timeStr 142 | // } 143 | 144 | // function doHandleMonth() { 145 | // const myDate = new Date() 146 | // const tMonth = myDate.getMonth() 147 | 148 | // let m = tMonth + 1 149 | // if (m.toString().length === 1) { 150 | // m = `0${m}` 151 | // } 152 | // return m 153 | // } 154 | 155 | // function doHandleYear() { 156 | // const myDate = new Date() 157 | // const tYear = myDate.getFullYear() 158 | 159 | // return tYear 160 | // } 161 | -------------------------------------------------------------------------------- /miniprogram/app.js: -------------------------------------------------------------------------------- 1 | import createStore from './store/omix/create' 2 | import store from './store/index' 3 | 4 | const Flow = { 5 | pay: 0, 6 | income: 1 7 | } 8 | App({ 9 | importStore: { 10 | create: createStore, 11 | store 12 | }, 13 | onLaunch() { 14 | if (!wx.cloud) { 15 | // eslint-disable-next-line no-console 16 | console.error('请使用 2.2.3 或以上的基础库以使用云能力') 17 | } else { 18 | wx.cloud.init({ 19 | traceUser: true, 20 | // env: 'release-wifo3' // 测试环境 21 | env: 'dandan-zdm86' // 正式环境 22 | }) 23 | } 24 | // 获取手机信息以配置顶栏 25 | wx.getSystemInfo({ 26 | success: (res) => { 27 | store.data.sysInfo = res 28 | } 29 | }) 30 | // 分类应当全局优先获取 31 | this.getCategory() 32 | 33 | // 获取用户是否有设置目标 34 | this.checkHasTarget() 35 | // 获取用户是否有设置目标 36 | // this.checkHasGroup() 37 | 38 | // 如果开启过小程序,则跳到onBoarding页面 39 | const isOnboarding = wx.getStorageSync('isOnboarding') 40 | if (!isOnboarding) { 41 | wx.redirectTo({ 42 | url: '/pages/onboarding/onboarding' 43 | }) 44 | } 45 | }, 46 | // 在app.js处进行分类的获取,以便所有页面方便使用 47 | getCategory() { 48 | // 在获取分类数据之前,优先读取本地缓存的数据 49 | const storeCategory = wx.getStorageSync('category') 50 | const storeDefaultCategory = wx.getStorageSync('defaultCategory') 51 | if (storeCategory) { 52 | store.data.categoryList = storeCategory 53 | } 54 | if (storeDefaultCategory) { 55 | store.data.defaultCategoryList = storeDefaultCategory 56 | } 57 | return new Promise((resolve, reject) => { 58 | const categoryList = {} 59 | const defaultCategoryList = [] 60 | const plainCategoryList = [] 61 | const mapCategoryName = {} 62 | wx.cloud.callFunction({ 63 | name: 'getCategory', 64 | data: {}, 65 | success(res) { 66 | if (res.result.code === 1) { 67 | const list = res.result.data 68 | console.log('categoryList', list) 69 | list.forEach((item) => { 70 | if (item._id) mapCategoryName[item._id] = item.categoryName 71 | if (item.children && item.children.length) { 72 | item.children.forEach((inItem) => { 73 | if (inItem._id) mapCategoryName[inItem._id] = inItem.categoryName 74 | }) 75 | } 76 | }) 77 | store.data.mapCategoryName = mapCategoryName 78 | // 分离出支出和收入的分类列表 79 | categoryList.pay = list.filter((item) => item.flow === Flow.pay) 80 | categoryList.income = list.filter((item) => item.flow === Flow.income) 81 | // 筛选出默认下的分类为:早餐午餐和晚餐 82 | const defaultCategoryIds = ['food_and_drink_breakfast', 'food_and_drink_lunch', 'food_and_drink_dinner'] 83 | 84 | store.data.categoryList = categoryList 85 | list.forEach((parent) => { 86 | parent.children.forEach((child) => { 87 | if (defaultCategoryIds.includes(child._id)) { 88 | defaultCategoryList.push(child) 89 | } 90 | plainCategoryList.push(child) 91 | }) 92 | }) 93 | store.data.plainCategoryList = plainCategoryList 94 | // 将分类缓存在本地,优先读取,后续更新 95 | wx.setStorage({ 96 | key: 'category', 97 | data: categoryList 98 | }) 99 | wx.setStorage({ 100 | key: 'defaultCategory', 101 | data: defaultCategoryList 102 | }) 103 | store.data.defaultCategoryList = defaultCategoryList 104 | resolve(res) 105 | } 106 | }, 107 | fail(error) { 108 | reject(error) 109 | } 110 | }) 111 | }) 112 | }, 113 | // 检查是否已经设置了目标 114 | checkHasTarget() { 115 | wx.cloud.callFunction({ 116 | name: 'target', 117 | data: { 118 | mode: 'check' 119 | }, 120 | success(res) { 121 | if (res.result.code === 1) { 122 | // eslint-disable-next-line prefer-destructuring 123 | store.data.myTarget = res.result.data[0] 124 | } 125 | } 126 | }) 127 | }, 128 | // 检查是否已经设置了目标 129 | checkHasGroup() { 130 | wx.cloud.callFunction({ 131 | name: 'groupbill', 132 | data: { 133 | mode: 'check' 134 | }, 135 | success(res) { 136 | if (res.result.code === 1) { 137 | // eslint-disable-next-line prefer-destructuring 138 | store.data.myGroup = Array.isArray(res.result.data) && res.result.data.length ? res.result.data[0] : {} 139 | } 140 | } 141 | }) 142 | }, 143 | showError(title = '请求失败,请稍后再试😢') { 144 | wx.showToast({ 145 | title, 146 | icon: 'none' 147 | }) 148 | }, 149 | enterEditMode(ctx) { 150 | const index = ctx.selectComponent('#index') 151 | index.dectiveEdit() 152 | } 153 | }) 154 | -------------------------------------------------------------------------------- /miniprogram/pages/components/list/list.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-destructuring */ 2 | import dayjs from 'dayjs' 3 | 4 | const { importStore } = getApp() 5 | const { create, store } = importStore 6 | let dateRange = null 7 | create.Component(store, { 8 | options: { 9 | styleIsolation: 'shared' 10 | }, 11 | use: ['sysInfo.screenHeight', 'sysInfo.statusBarHeight', 'currentMonthData', 'mapCategoryName'], 12 | properties: { 13 | tab: String 14 | }, 15 | data: { 16 | billList: null, 17 | showMenuDialog: false, 18 | editItem: {}, 19 | showConfirmDelete: false, 20 | today: '', 21 | billResult: null 22 | }, 23 | ready() { 24 | const now = dayjs().format('YYYY-MM-DD') 25 | this.getBillList(now, now, 'index') 26 | this.setData({ 27 | today: now 28 | }) 29 | }, 30 | methods: { 31 | getBillList(startDate, endDate, fetchFrom, page = 1) { 32 | const self = this 33 | if (fetchFrom !== 'index') { 34 | wx.showLoading({ 35 | title: '加载中...' 36 | }) 37 | } 38 | const data = { 39 | page, 40 | limit: 100, 41 | startDate, 42 | endDate 43 | } 44 | if (dateRange) { 45 | data.startDate = dateRange[0] 46 | data.endDate = dateRange[1] 47 | } 48 | wx.cloud.callFunction({ 49 | name: 'getAccountList', 50 | data, 51 | success(res) { 52 | if (res.result && res.result.code === 1) { 53 | const response = res.result.data 54 | self.setData({ 55 | billResult: response.page 56 | }) 57 | const flowOutList = response.page.filter((item) => item.flow == 0) 58 | const flowInList = response.page.filter((item) => item.flow == 1) 59 | let outMonty = 0 60 | let inMoney = 0 61 | flowOutList.forEach((item) => { 62 | outMonty += item.money 63 | }) 64 | flowInList.forEach((item) => { 65 | inMoney += item.money 66 | }) 67 | store.data.pickDateListSumResult = [outMonty, inMoney] 68 | } else { 69 | wx.showToast({ 70 | title: '获取账单失败,稍后再试', 71 | icon: 'none' 72 | }) 73 | self.setData({ 74 | billResult: null 75 | }) 76 | } 77 | }, 78 | complete() { 79 | wx.hideLoading() 80 | } 81 | }) 82 | }, 83 | switchTab() { 84 | this.triggerEvent('switchTab', 'index') 85 | }, 86 | showMenu(event) { 87 | const self = this 88 | const { bill } = event.currentTarget.dataset 89 | self.setData({ 90 | editItem: bill, 91 | showMenuDialog: true 92 | }) 93 | self.triggerEvent('hideTab', true) 94 | }, 95 | closeDialog() { 96 | this.setData({ 97 | showMenuDialog: false, 98 | showConfirmDelete: false 99 | }) 100 | this.triggerEvent('hideTab', false) 101 | }, 102 | editBill() { 103 | const self = this 104 | const { editItem } = self.data 105 | self.setData({ 106 | showMenuDialog: false 107 | }) 108 | this.triggerEvent('hideTab', false) 109 | self.triggerEvent('editBill', editItem) 110 | }, 111 | deleteBill() { 112 | const self = this 113 | const { editItem } = self.data 114 | if (!self.data.showConfirmDelete) { 115 | self.setData({ 116 | showConfirmDelete: !self.data.showConfirmDelete 117 | }) 118 | wx.vibrateShort() 119 | } else { 120 | self.closeDialog() 121 | wx.vibrateShort() 122 | wx.cloud.callFunction({ 123 | name: 'account', 124 | data: { 125 | mode: 'deleteById', 126 | id: editItem._id 127 | }, 128 | success(res) { 129 | if (res.result.code === 1) { 130 | wx.showToast({ 131 | title: '删除成功', 132 | icon: 'none' 133 | }) 134 | self.setData({ 135 | editItem: {} 136 | }) 137 | self.triggerEvent('reFetchBillList') 138 | } else { 139 | wx.showToast({ 140 | title: '删除失败,请重试', 141 | icon: 'none' 142 | }) 143 | } 144 | } 145 | }) 146 | } 147 | }, 148 | onRangePick(event) { 149 | dateRange = event.detail 150 | this.getBillList(event.detail[0], event.detail[1], 'list') 151 | }, 152 | onControl(event) { 153 | const self = this 154 | const { mode } = event.detail 155 | if (mode === 'reset') { 156 | dateRange = null 157 | self.getBillList(this.data.today, this.data.today, 'list') 158 | } 159 | }, 160 | reFetchBillList() { 161 | this.triggerEvent('reFetchBillList') 162 | } 163 | } 164 | }) 165 | -------------------------------------------------------------------------------- /miniprogram/pages/morechart/morechart.js: -------------------------------------------------------------------------------- 1 | const dayjs = require('dayjs') 2 | 3 | const baseConfig = (chart, type = 'month') => { 4 | chart.scale('date', { 5 | type: 'timeCat', 6 | tickCount: 10 7 | }) 8 | chart.scale('value', { 9 | tickCount: 5 10 | }) 11 | chart.tooltip({ 12 | showCrosshairs: true, 13 | showTitle: type == 'month', 14 | offsetY: 20 15 | }) 16 | chart.legend({ 17 | position: 'bottom' 18 | }) 19 | chart.line().position('date*value').shape('smooth').color('type', (val) => { 20 | if (val === '收入') { 21 | return '#4fd69c' 22 | } if (val === '支出') { 23 | return '#f75676' 24 | } if (val === '净收入') { 25 | return '#ffdd57' 26 | } 27 | }) 28 | } 29 | Page({ 30 | data: { 31 | cWidth: 0, 32 | cHeight: 0, 33 | date: dayjs().format('YYYY-MM'), 34 | year: dayjs().format('YYYY'), 35 | month: dayjs().format('MM'), 36 | monthChartShow: false, 37 | initMonthChart: null, 38 | yearChartShow: false, 39 | initYearChart: null 40 | }, 41 | onLoad() { 42 | this.fillChart() 43 | }, 44 | fillChart() { 45 | this.getMonthData() 46 | this.getYearData() 47 | }, 48 | getMonthData() { 49 | const self = this 50 | const { 51 | date 52 | } = this.data 53 | wx.showLoading() 54 | wx.cloud.callFunction({ 55 | name: 'getAccountChart', 56 | data: { 57 | mode: 'getAccountChartByMonth', 58 | date 59 | }, 60 | success(res) { 61 | const { categories, series } = res.result.data 62 | if (res.result.code === 1 && categories) { 63 | const data = [] 64 | categories.forEach((inDate, index) => { 65 | series.forEach((line) => { 66 | data.push({ 67 | date: inDate, 68 | type: line.name, 69 | value: line.data[index] 70 | }) 71 | }) 72 | }) 73 | self.renderMonthChart(data) 74 | } 75 | }, 76 | complete() { 77 | wx.hideLoading() 78 | } 79 | }) 80 | }, 81 | renderMonthChart(data) { 82 | this.setData({ 83 | initMonthChart(F2, config) { 84 | config.self = this 85 | const chart = new F2.Chart(config) 86 | chart.source(data) 87 | baseConfig(chart) 88 | chart.axis('date', { 89 | label: (text) => ({ 90 | text: `${text.slice(8)}日` 91 | }) 92 | }) 93 | chart.render() 94 | // 注意:需要把chart return 出来 95 | return chart 96 | } 97 | }, () => { 98 | this.setData({ 99 | monthChartShow: true 100 | }) 101 | }) 102 | }, 103 | getYearData() { 104 | const self = this 105 | const { 106 | date 107 | } = this.data 108 | wx.showLoading() 109 | wx.cloud.callFunction({ 110 | name: 'getAccountChart', 111 | data: { 112 | mode: 'getAccountChartByYear', 113 | date: date.split('-')[0] 114 | }, 115 | success(res) { 116 | const { categories, series } = res.result.data 117 | if (res.result.code === 1 && categories) { 118 | const data = [] 119 | categories.forEach((inDate, index) => { 120 | series.forEach((line) => { 121 | data.push({ 122 | date: inDate, 123 | type: line.name, 124 | value: line.data[index] 125 | }) 126 | }) 127 | }) 128 | self.renderYearChart(data) 129 | } 130 | } 131 | }) 132 | }, 133 | renderYearChart(data) { 134 | this.setData({ 135 | initYearChart(F2, config) { 136 | config.self = this 137 | const chart = new F2.Chart(config) 138 | chart.source(data) 139 | baseConfig(chart, 'year') 140 | chart.axis('date', { 141 | label: (text) => ({ 142 | text: `${text.slice(5, 7)}月` 143 | }) 144 | }) 145 | chart.render() 146 | // 注意:需要把chart return 出来 147 | return chart 148 | } 149 | }, () => { 150 | this.setData({ 151 | yearChartShow: true 152 | }) 153 | }) 154 | }, 155 | bindDateChange(event) { 156 | const oldMonth = this.data.month 157 | const oldYear = this.data.year 158 | const newMonth = dayjs(event.detail.value).format('MM') 159 | const newYear = dayjs(event.detail.value).format('YYYY') 160 | this.setData({ 161 | date: event.detail.value, 162 | month: newMonth, 163 | year: newYear 164 | }) 165 | 166 | if (oldMonth !== newMonth) { 167 | this.setData({ 168 | monthChartShow: false 169 | }) 170 | this.getMonthData() 171 | } 172 | if (oldYear !== newYear) { 173 | this.setData({ 174 | monthChartShow: false, 175 | yearChartShow: false 176 | }) 177 | this.getMonthData() 178 | this.getYearData() 179 | } 180 | } 181 | }) 182 | -------------------------------------------------------------------------------- /cloudfunctions/stat/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 每天统计每个用户的记账基本数据,包含字段: 3 | 1. 日期 4 | 2. 支出 5 | 3. 收入 6 | 4. 净收入 7 | 5. 记账次数 8 | 6. 用户openId 9 | */ 10 | const cloud = require('wx-server-sdk') 11 | const dayjs = require('dayjs') 12 | const request = require('request') 13 | 14 | cloud.init() 15 | const MAX_LIMIT = 100 16 | function strip(num, precision = 12) { 17 | return +parseFloat(num.toPrecision(precision)) 18 | } 19 | function notify(title, content) { 20 | // eslint-disable-next-line global-require 21 | const { bark } = require('./token') 22 | if (bark) { 23 | request(`https://api.day.app/${bark}/${encodeURI(title)}/${encodeURI(content)}`) 24 | } 25 | } 26 | // 云函数入口函数 27 | exports.main = async (event) => { 28 | const wxContext = cloud.getWXContext() 29 | const env = wxContext.ENV === 'local' ? 'dandan-zdm86' : wxContext.ENV 30 | cloud.updateConfig({ env }) 31 | // 初始化数据库 32 | const db = cloud.database({ env }) 33 | const _ = db.command 34 | // 有openId则表示只更新某个人的统计记录 35 | let { noteDate } = event 36 | const { openId } = event 37 | // 定时任务不具有参数 38 | if (!noteDate) { 39 | noteDate = dayjs().subtract(1, 'day').format('YYYY-MM-DD') 40 | } 41 | // 传入日期的开始和结束时间 42 | // 由于是定时任务,所以需要减少一天 43 | const statDate = openId ? dayjs(noteDate).format('YYYY-MM-DD') : noteDate 44 | const startTime = `${statDate} 00:00:00` 45 | const endTime = `${statDate} 23:59:59` 46 | const isToday = dayjs().format('YYYY-MM-DD') === statDate 47 | // 如果是更新某个人的统计数据,并且日期等于今天,则不更新 48 | if (openId && isToday) { 49 | return { 50 | code: 0, 51 | msg: '今天等定时任务统计', 52 | data: null 53 | } 54 | } 55 | // 由于性能有限,现在默认只统计自然日的数据,后面再把其他数据跑回来 56 | const queryAll = async (collectName, queryParams) => { 57 | const resultNum = await db.collection(collectName).where(queryParams).count() 58 | const { total } = resultNum 59 | const batchTimes = Math.ceil(total / MAX_LIMIT) 60 | const tasks = [] 61 | for (let i = 0; i < batchTimes; i++) { 62 | const promise = db.collection(collectName).where({ 63 | ...queryParams 64 | }).skip(i * MAX_LIMIT).limit(MAX_LIMIT) 65 | .get() 66 | tasks.push(promise) 67 | } 68 | return (await Promise.all(tasks)).reduce((acc, cur) => ({ 69 | data: acc.data.concat(cur.data), 70 | errMsg: acc.errMsg 71 | })) 72 | } 73 | const calNote = (noteList, openIdList) => { 74 | let index = -1 75 | const addData = [] 76 | while (++index < openIdList.length) { 77 | const oneOpenId = openIdList[index] 78 | const noteListByOpenId = noteList.filter((note) => note.openId === oneOpenId) 79 | let pay = 0 80 | let income = 0 81 | let netAsset = 0 82 | let payCount = 0 83 | let incomeCount = 0 84 | noteListByOpenId.forEach((note) => { 85 | // 支出 86 | if (note.flow === 0) { 87 | pay += note.money 88 | payCount += 1 89 | } else { 90 | income += note.money 91 | incomeCount += 1 92 | } 93 | }) 94 | netAsset = income - pay 95 | addData.push({ 96 | openId: oneOpenId, 97 | noteDate: new Date(noteDate), 98 | pay: strip(pay), 99 | income: strip(income), 100 | netAsset: strip(netAsset), 101 | payCount, 102 | incomeCount, 103 | createTime: new Date(), // 写入时间 104 | updateTime: new Date(), // 更新时间 105 | type: 'day' 106 | }) 107 | } 108 | return addData 109 | } 110 | const params = { 111 | noteDate: _.gte(new Date(startTime)).and(_.lte(new Date(endTime))), 112 | isDel: false 113 | } 114 | if (openId) { 115 | params.openId = openId 116 | } 117 | const noteListRes = await queryAll('DANDAN_NOTE', params) 118 | const noteList = noteListRes.data 119 | // get openId from noteList 120 | const openIdList = Array.from(new Set(noteList.map((note) => note.openId))) 121 | const addData = calNote(noteList, openIdList) 122 | // 有openId,则只更新该用户该天的统计数据 123 | if (openId) { 124 | const oldRes = await db.collection('STAT').where({ 125 | openId, 126 | noteDate: _.eq(statDate) 127 | }).get() 128 | // 更新该条记录 129 | const updateData = addData[0] 130 | delete updateData.createTime 131 | try { 132 | if (oldRes.data.length) { 133 | await db.collection('STAT').doc(oldRes.data[0]._id).update({ 134 | data: updateData 135 | }) 136 | } 137 | } catch (error) { 138 | notify('更新统计数据失败', error.toString.slice(0, 100)) 139 | } 140 | } else { 141 | // 插入今日统计数据 142 | try { 143 | await db.collection('STAT').add({ 144 | data: addData 145 | }) 146 | notify('写入统计数据成功', `写入${statDate}统计数据共${addData.length}条,完成时间:${dayjs().format('YYYY-MM-DD HH:mm:ss')}`) 147 | } catch (error) { 148 | notify('写入统计数据失败', error.toString.slice(0, 100)) 149 | } 150 | } 151 | return { 152 | code: 1, 153 | msg: '写入STAT成功', 154 | data: null 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /miniprogram/pages/setting/setting.js: -------------------------------------------------------------------------------- 1 | import { debounce } from '../../util' 2 | 3 | const { importStore } = getApp() 4 | const { create, store } = importStore 5 | create.Page(store, { 6 | data: { 7 | canSubscribe: false, 8 | status: null, 9 | isChangeing: false, 10 | showAuthDialog: false, 11 | isExporting: false, 12 | canExport: false, 13 | donateList: [] 14 | }, 15 | onLoad() { 16 | if (wx.requestSubscribeMessage) { 17 | this.setData({ 18 | canSubscribe: true 19 | }) 20 | } 21 | this.getUserSucscribeStatus() 22 | this.getDonateData() 23 | }, 24 | onShow() { 25 | // getApp().checkHasGroup() 26 | }, 27 | changeNotify: debounce(function () { 28 | const self = this 29 | const { 30 | status 31 | } = this.data 32 | if (this.data.canSubscribe) { 33 | self.setData({ 34 | isChangeing: true 35 | }) 36 | if (!status) { 37 | wx.requestSubscribeMessage({ 38 | tmplIds: ['29PkwuWSDZ5qCe_bjIAYE8UPbw4A7HIXL_ZNmNCD__s'], 39 | success(res) { 40 | if (res.errMsg === 'requestSubscribeMessage:ok') { 41 | // 如果订阅成功,则修改状态 42 | self.changeStatus('open') 43 | } 44 | }, 45 | fail() { 46 | self.setData({ 47 | showAuthDialog: true 48 | }) 49 | self.changeStatus('close') 50 | } 51 | }) 52 | } else { 53 | self.changeStatus('close') 54 | } 55 | } else { 56 | wx.showToast({ 57 | title: '你的微信版本过低不能订阅哦~', 58 | icon: 'none' 59 | }) 60 | } 61 | }, 600, true), 62 | changeStatus(type) { 63 | const self = this 64 | wx.cloud.callFunction({ 65 | name: 'checkSubscribe', 66 | data: { 67 | mode: 'post', 68 | type 69 | }, 70 | success(res) { 71 | if (res.result.code === 1) { 72 | setTimeout(() => { 73 | wx.showToast({ 74 | title: type === 'open' ? '开启订阅成功' : '关闭订阅成功', 75 | icon: 'none' 76 | }) 77 | }, 1000) 78 | } 79 | }, 80 | complete() { 81 | self.getUserSucscribeStatus() 82 | self.setData({ 83 | isChangeing: false 84 | }) 85 | } 86 | }) 87 | }, 88 | getUserSucscribeStatus() { 89 | const self = this 90 | wx.cloud.callFunction({ 91 | name: 'checkSubscribe', 92 | data: { 93 | mode: 'get' 94 | }, 95 | success(res) { 96 | if (res.result.code === 1) { 97 | self.setData({ 98 | status: res.result.data 99 | }) 100 | } 101 | } 102 | }) 103 | }, 104 | openSetting() { 105 | const self = this 106 | wx.openSetting({ 107 | success() { 108 | self.setData(({ 109 | showAuthDialog: false 110 | })) 111 | } 112 | }) 113 | }, 114 | closeDialog() { 115 | this.setData({ 116 | showAuthDialog: false 117 | }) 118 | }, 119 | copyLink() { 120 | wx.setClipboardData({ 121 | data: 'https://github.com/GzhiYi/dandan-account', 122 | success() { } 123 | }) 124 | }, 125 | copyWechat() { 126 | wx.setClipboardData({ 127 | data: 'Yi745285458', 128 | success() { } 129 | }) 130 | }, 131 | onExportFile: debounce(function () { 132 | const self = this 133 | self.setData({ 134 | isExporting: true 135 | }) 136 | wx.cloud.callFunction({ 137 | name: 'exportFile', 138 | data: {}, 139 | success(res) { 140 | if (res.result.code === 1) { 141 | wx.cloud.getTempFileURL({ 142 | fileList: [res.result.data.fileID], 143 | success: (tempRes) => { 144 | // eslint-disable-next-line no-console 145 | console.log(tempRes.fileList) 146 | wx.setClipboardData({ 147 | data: tempRes.fileList[0].tempFileURL, 148 | success() { } 149 | }) 150 | } 151 | }) 152 | } 153 | }, 154 | complete() { 155 | self.setData({ 156 | isExporting: false 157 | }) 158 | } 159 | }) 160 | }, 1000, true), 161 | showPreview() { 162 | wx.previewImage({ 163 | current: 'https://6461-dandan-zdm86-1259814516.tcb.qcloud.la/WechatIMG11.jpeg?sign=bdaed572942b8bc2e7b3a61f7183d743&t=1576081688', // 当前显示图片的http链接 164 | urls: ['https://6461-dandan-zdm86-1259814516.tcb.qcloud.la/donate/IMG_2451.JPG?sign=6c60168b3e63c375cd2619a5599c9a97&t=1623579505'] // 需要预览的图片http链接列表 165 | }) 166 | }, 167 | getDonateData() { 168 | const self = this 169 | wx.cloud.callFunction({ 170 | name: 'donate', 171 | data: { 172 | mode: 'get' 173 | }, 174 | success(res) { 175 | self.setData({ 176 | donateList: res.result.data 177 | }) 178 | } 179 | }) 180 | }, 181 | showWord(event) { 182 | const { word } = event.currentTarget.dataset.item 183 | if (word) { 184 | wx.showToast({ 185 | title: word, 186 | icon: 'none' 187 | }) 188 | } 189 | }, 190 | goToGroupBill() { 191 | const { myGroup } = store.data 192 | if (myGroup._id) { 193 | wx.navigateTo({ 194 | url: '/pages/group/group' 195 | }) 196 | } else { 197 | wx.navigateTo({ 198 | url: '/pages/group-bill-set/group-bill-set' 199 | }) 200 | } 201 | } 202 | }) 203 | -------------------------------------------------------------------------------- /miniprogram/pages/group/group.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | {{groupInfo.isMyGroup ? '邀请一起记账' : '组成员'}} 23 | 24 | 25 | 26 | 27 | {{item.nickName}} 28 | 确认 29 | 已确认 30 | 创建者 31 | 32 | 33 | 34 | 同意邀请的用户会出现在这噢!所有能打开链接的用户均能同意加入组内,建议私发。接受邀请的用户需要你确认加入才算最终完成。 35 | 36 | 37 | 38 | 39 | 邀请 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 退出 48 | 49 | 50 | 51 | 52 | 设置头像和昵称,与Ta一起记账吧~ 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 为了让对方验证你的身份,填写Ta熟悉的名称吧~ 64 | 65 | 66 | 加入中... 67 | 立即加入 68 | 69 | 70 | 71 | 你已在该组啦 72 | 73 | 如果还没看到关联的账单,则等候Ta通过申请即可。 74 | 75 | 76 | 77 | 78 | 79 | 80 | 确认加入提示 81 | 82 | 同意之后,账单将会进行共享,请注意确认该用户是否为你邀请的用户。 83 | 84 | 85 | 确认 86 | 87 | 取消 88 | 89 | 90 | 91 | 92 | 93 | 离开组提示 94 | 95 | 确认离开该组吗?离开组后将可以自行创建组。 96 | 97 | 98 | 确认 99 | 100 | 取消 101 | 102 | 103 | 104 | 105 | 106 | 组删除提示 107 | 108 | 确认删除该组吗?删除组后将可以自行创建组。 109 | 110 | 111 | 确认 112 | 113 | 取消 114 | 115 | -------------------------------------------------------------------------------- /cloudfunctions/accountAggregate/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init() 5 | 6 | // 云函数入口函数 7 | exports.main = async (event) => { 8 | const wxContext = cloud.getWXContext() 9 | 10 | cloud.updateConfig({ 11 | env: wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 12 | }) 13 | // 初始化数据库 14 | const db = cloud.database({ 15 | env: wxContext.ENV === 'local' ? 'release-wifo3' : wxContext.ENV 16 | }) 17 | 18 | const _ = db.command 19 | 20 | const $ = db.command.aggregate 21 | const { 22 | mode, startDate, endDate, OPENID 23 | } = event 24 | 25 | try { 26 | // 要显示的字段 27 | const basicProject = { 28 | _id: 0, 29 | money: 1, 30 | isDel: 1, 31 | openId: 1, 32 | flow: 1, 33 | categoryId: 1, 34 | isTarget: $.and([ 35 | $.gte([$.dateToString({ 36 | date: '$noteDate', 37 | format: '%Y-%m-%d', 38 | timezone: 'Asia/Shanghai' 39 | }), startDate]), 40 | $.lte([$.dateToString({ 41 | date: '$noteDate', 42 | format: '%Y-%m-%d', 43 | timezone: 'Asia/Shanghai' 44 | }), endDate]) 45 | ]) 46 | } 47 | 48 | // 先查询是否有组 49 | const basicOpenId = [OPENID || wxContext.OPENID] 50 | 51 | // 按时间聚合, 聚合出支出和收入的数据 52 | if (mode === 'aggregateAccountByDateRange') { 53 | const basicMatch = { 54 | isDel: false, 55 | openId: _.in(basicOpenId), 56 | isTarget: true 57 | } 58 | 59 | const sumResult = await db.collection('DANDAN_NOTE') 60 | .aggregate() 61 | .project(basicProject) 62 | .match(basicMatch) 63 | .group({ 64 | _id: '$flow', 65 | allSum: $.sum('$money'), 66 | count: $.sum(1) 67 | }) 68 | .end() 69 | return { 70 | code: 1, 71 | sumResult: sumResult.list.sort((a, b) => a._id - b._id) 72 | } 73 | } 74 | 75 | // 获取饼图数据 76 | if (mode === 'getPieChartData') { 77 | const basicMatch = { 78 | isDel: false, 79 | openId: _.in(basicOpenId), 80 | isTarget: true 81 | } 82 | 83 | const detailResult = await db.collection('DANDAN_NOTE') 84 | .aggregate() 85 | .project(basicProject) 86 | .match(basicMatch) 87 | .group({ 88 | _id: { 89 | categoryId: '$categoryId', 90 | flow: '$flow' 91 | }, 92 | allSum: $.sum('$money'), 93 | count: $.sum(1) 94 | }) 95 | .replaceRoot({ 96 | newRoot: $.mergeObjects(['$_id', '$$ROOT']) 97 | }) 98 | .project({ 99 | _id: 0 100 | }) 101 | // 查子目录的信息, 以此获取父目录的ID 102 | .lookup({ 103 | from: 'DANDAN_NOTE_CATEGORY', 104 | localField: 'categoryId', 105 | foreignField: '_id', 106 | as: 'categoryInfo' 107 | }) 108 | .replaceRoot({ 109 | newRoot: $.mergeObjects([$.arrayElemAt(['$categoryInfo', 0]), '$$ROOT']) 110 | }) 111 | .project({ 112 | allSum: 1, 113 | count: 1, 114 | flow: 1, 115 | _id: 0, 116 | fatherCategoryId: '$parentId' 117 | }) 118 | // 已经得到parentId, 可以再次进行聚合 119 | .group({ 120 | _id: { 121 | categoryId: '$fatherCategoryId', 122 | flow: '$flow' 123 | }, 124 | allSum: $.sum('$allSum'), 125 | count: $.sum('$count') 126 | }) 127 | .replaceRoot({ 128 | newRoot: $.mergeObjects(['$_id', '$$ROOT']) 129 | }) 130 | .project({ 131 | _id: 0 132 | }) 133 | // 用父目录ID查询父目录信息 134 | .lookup({ 135 | from: 'DANDAN_NOTE_CATEGORY', 136 | localField: 'categoryId', 137 | foreignField: '_id', 138 | as: 'fatherCategoryInfo' 139 | }) 140 | .replaceRoot({ 141 | newRoot: $.mergeObjects([$.arrayElemAt(['$fatherCategoryInfo', 0]), '$$ROOT']) 142 | }) 143 | .project({ 144 | _id: 0, 145 | allSum: 1, 146 | count: 1, 147 | flow: 1, 148 | categoryId: 1, 149 | categoryName: 1 150 | }) 151 | .end() 152 | 153 | const returnObj = {} 154 | 155 | const flowOutList = [] 156 | const flowInList = [] 157 | let sumAllIn = 0 158 | let sumAllOut = 0 159 | // 遍历获取每个流的总金额 160 | // eslint-disable-next-line no-restricted-syntax 161 | for (const item of detailResult.list) { 162 | if (item.flow === 1) { 163 | // eslint-disable-next-line no-use-before-define 164 | sumAllIn = keepTwoDecimal(sumAllIn + item.allSum) 165 | flowInList.push(item) 166 | } else { 167 | // eslint-disable-next-line no-use-before-define 168 | sumAllOut = keepTwoDecimal(sumAllOut + item.allSum) 169 | flowOutList.push(item) 170 | } 171 | } 172 | returnObj.flowIn = { 173 | allSum: sumAllIn, 174 | dataList: flowInList 175 | } 176 | returnObj.flowOut = { 177 | allSum: sumAllOut, 178 | dataList: flowOutList 179 | } 180 | 181 | return { 182 | code: 1, 183 | detailResult: returnObj 184 | } 185 | } 186 | } catch (e) { 187 | return { 188 | code: 0 189 | } 190 | } 191 | } 192 | 193 | function keepTwoDecimal(num) { 194 | let result = parseFloat(num) 195 | // eslint-disable-next-line no-restricted-globals 196 | if (isNaN(result)) { 197 | return false 198 | } 199 | result = Math.round(num * 100) / 100 200 | return result 201 | } 202 | -------------------------------------------------------------------------------- /miniprogram/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.3 2 | 3 | specifiers: 4 | '@antv/wx-f2': 2.0.1 5 | dayjs: ^1.10.7 6 | 7 | dependencies: 8 | '@antv/wx-f2': registry.npmmirror.com/@antv/wx-f2/2.0.1 9 | dayjs: registry.nlark.com/dayjs/1.10.7 10 | 11 | packages: 12 | 13 | registry.nlark.com/dayjs/1.10.7: 14 | resolution: {integrity: sha1-LPX5Gt0oEWdIRAhmoKHSbzps5Gg=, registry: https://registry.npm.taobao.org/, tarball: https://registry.nlark.com/dayjs/download/dayjs-1.10.7.tgz} 15 | name: dayjs 16 | version: 1.10.7 17 | dev: false 18 | 19 | registry.nlark.com/regenerator-runtime/0.13.9: 20 | resolution: {integrity: sha1-iSV0Kpj/2QgUmI11Zq0wyjsmO1I=, registry: https://registry.npm.taobao.org/, tarball: https://registry.nlark.com/regenerator-runtime/download/regenerator-runtime-0.13.9.tgz?cache=0&sync_timestamp=1631499871421&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fregenerator-runtime%2Fdownload%2Fregenerator-runtime-0.13.9.tgz} 21 | name: regenerator-runtime 22 | version: 0.13.9 23 | dev: false 24 | 25 | registry.npmmirror.com/@antv/adjust/0.1.1: 26 | resolution: {integrity: sha1-4mOrDhoZQaZIhC/Ahs9lp+O3Xpg=, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@antv/adjust/download/@antv/adjust-0.1.1.tgz} 27 | name: '@antv/adjust' 28 | version: 0.1.1 29 | dependencies: 30 | '@antv/util': registry.npmmirror.com/@antv/util/1.3.1 31 | dev: false 32 | 33 | registry.npmmirror.com/@antv/attr/0.1.2: 34 | resolution: {integrity: sha1-LusSL8qvhRoth0mrx8YFGdP3fjc=, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@antv/attr/download/@antv/attr-0.1.2.tgz} 35 | name: '@antv/attr' 36 | version: 0.1.2 37 | dependencies: 38 | '@antv/util': registry.npmmirror.com/@antv/util/1.3.1 39 | dev: false 40 | 41 | registry.npmmirror.com/@antv/f2/3.5.0: 42 | resolution: {integrity: sha1-gdoIGUrUp+UxRIqq15zmX1m820M=, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@antv/f2/download/@antv/f2-3.5.0.tgz} 43 | name: '@antv/f2' 44 | version: 3.5.0 45 | dependencies: 46 | '@antv/adjust': registry.npmmirror.com/@antv/adjust/0.1.1 47 | '@antv/attr': registry.npmmirror.com/@antv/attr/0.1.2 48 | '@antv/scale': registry.npmmirror.com/@antv/scale/0.1.5 49 | '@antv/util': registry.npmmirror.com/@antv/util/1.2.5 50 | '@babel/runtime': registry.npmmirror.com/@babel/runtime/7.16.5 51 | hammerjs: registry.npmmirror.com/hammerjs/2.0.8 52 | dev: false 53 | 54 | registry.npmmirror.com/@antv/gl-matrix/2.7.1: 55 | resolution: {integrity: sha1-rLjjf3qz3wE0WrpDcteUK+QuuhQ=, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@antv/gl-matrix/download/@antv/gl-matrix-2.7.1.tgz} 56 | name: '@antv/gl-matrix' 57 | version: 2.7.1 58 | dev: false 59 | 60 | registry.npmmirror.com/@antv/scale/0.1.5: 61 | resolution: {integrity: sha1-JDJm6LkEfPZLL9/ED5g0zwhGSW4=, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@antv/scale/download/@antv/scale-0.1.5.tgz} 62 | name: '@antv/scale' 63 | version: 0.1.5 64 | dependencies: 65 | '@antv/util': registry.npmmirror.com/@antv/util/1.3.1 66 | fecha: registry.npmmirror.com/fecha/2.3.3 67 | dev: false 68 | 69 | registry.npmmirror.com/@antv/util/1.2.5: 70 | resolution: {integrity: sha1-iJbFBV7Cnko0S12ql7+CkF99QM0=, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@antv/util/download/@antv/util-1.2.5.tgz} 71 | name: '@antv/util' 72 | version: 1.2.5 73 | dependencies: 74 | '@antv/gl-matrix': registry.npmmirror.com/@antv/gl-matrix/2.7.1 75 | dev: false 76 | 77 | registry.npmmirror.com/@antv/util/1.3.1: 78 | resolution: {integrity: sha1-MKNLIB/5Em7A1YxyyBZqnD5kTM0=, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@antv/util/download/@antv/util-1.3.1.tgz} 79 | name: '@antv/util' 80 | version: 1.3.1 81 | dependencies: 82 | '@antv/gl-matrix': registry.npmmirror.com/@antv/gl-matrix/2.7.1 83 | dev: false 84 | 85 | registry.npmmirror.com/@antv/wx-f2/2.0.1: 86 | resolution: {integrity: sha1-s4mOI4rDwdug8dUemsZ6J10ro3s=, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@antv/wx-f2/download/@antv/wx-f2-2.0.1.tgz} 87 | name: '@antv/wx-f2' 88 | version: 2.0.1 89 | dependencies: 90 | '@antv/f2': registry.npmmirror.com/@antv/f2/3.5.0 91 | dev: false 92 | 93 | registry.npmmirror.com/@babel/runtime/7.16.5: 94 | resolution: {integrity: sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/runtime/download/@babel/runtime-7.16.5.tgz} 95 | name: '@babel/runtime' 96 | version: 7.16.5 97 | engines: {node: '>=6.9.0'} 98 | dependencies: 99 | regenerator-runtime: registry.nlark.com/regenerator-runtime/0.13.9 100 | dev: false 101 | 102 | registry.npmmirror.com/fecha/2.3.3: 103 | resolution: {integrity: sha1-lI50FX3xoy/RsSw6PDzctuydls0=, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fecha/download/fecha-2.3.3.tgz} 104 | name: fecha 105 | version: 2.3.3 106 | dev: false 107 | 108 | registry.npmmirror.com/hammerjs/2.0.8: 109 | resolution: {integrity: sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/hammerjs/download/hammerjs-2.0.8.tgz} 110 | name: hammerjs 111 | version: 2.0.8 112 | engines: {node: '>=0.8.0'} 113 | dev: false 114 | -------------------------------------------------------------------------------- /miniprogram/pages/setting/setting.wxml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 自言自语 8 | 9 | 很高兴能与你一起 10 | 继续坚持记账 11 | 12 | 13 | 14 | 26 | 27 | 对你啰嗦 28 | 29 | 需要推送提醒你记账吗? 30 | 31 | 如果你今天没记账,我会提示一下你。【注意】要是开启了可别嫌弃我啰嗦噢。为了更好推送,需要勾选【总是保持允许,不再询问】。如果不保持选择,会在记账成功后弹出申请,挺烦的,建议开启。 32 | {{status ? '已开启,点击关闭' : '已关闭,点击开启'}} 33 | ... 34 | 35 | 36 | 37 | 38 | 数据安全 39 | 40 | 每日备份 41 | 42 | 所有账单数据每天进行备份,保护数据的安全。只要小程序还在,数据就不会丢。 43 | 44 | 45 | 46 | 47 | 导出账单 48 | 49 | 目前只支持导出所有账单数据 50 | 51 | 点击导出后稍等会将文件链接进行复制。可以粘贴到浏览器打开下载。文件有效期一天。 52 | 点击导出 53 | 正在导出... 54 | 55 | 56 | 57 | 58 | 源码 59 | 60 | 代码开源 61 | 62 | 小程序在开源社区Github开源。对数据处理透明化。点我复制仓库链接,欢迎Star鼓励或贡献代码。 63 | 64 | 65 | 66 | 67 | 一杯奶茶? 68 | 69 | 开源不易,我会尽最大的努力保持单单干净无广告侵扰。 70 | 71 | 希望各位喜欢单单的朋友可以多多分享给好友知道❤️❤️,我也没太多心思去推广啦。 72 | 73 | 74 | 80 | 点击肚子饿的他保存图片赏一杯奶茶 81 | ԅ(¯﹃¯ԅ)流口水 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 捐赠墙 91 | 你的捐赠将会提升单单的使用体验! 92 | 捐赠后希望可以到客服联系开发者补充上墙的信息~ 93 | 94 | 99 | 103 | {{item.name}} 104 | 105 | 106 | 107 | 108 | 109 | 如果需要合作或加入单单用户群等 110 | 点击复制添加作者微信:Yi745285458 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 订阅信息开启失败 121 | 由于你的设备拒绝了订阅权限,我们无法主动替你开启。请打开设置进行手动开启~ 122 | 123 | 打开设置 124 | 取消 125 | 126 | --------------------------------------------------------------------------------