├── 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 |
--------------------------------------------------------------------------------
/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 | 
14 |
15 |
16 |
17 | 
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 |
7 |
8 |
13 |
14 |
15 | {{word}}
16 |
17 |
18 |
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 |
--------------------------------------------------------------------------------
/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 |
8 |
13 |
14 |
15 |
22 |
23 |
24 |
31 |
32 |
33 |
34 |
35 |
36 |
43 |
48 |
49 |
56 |
61 |
62 |
69 |
75 | ...
76 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------
/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 |
4 |
5 |
6 | {{groupInfo.name}}
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{fakeUserInfo.nickName}}
14 | 共享的账单日期开始于:{{groupInfo.startDate}}
15 |
16 |
17 | 删除
18 |
19 |
20 |
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 |
90 |
103 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------