前言
用 Notion 记了半年的账,现在觉得有以下缺点:新建账单步骤太多,应用账单模板不够方便,账单列表瀑布流加载非常不顺畅,搜索关键字速度很慢,按月汇总计算需要每个月手动激活,查看汇总需要切换好几个页面。
总之就是越来越不好用了,不如我自己写一个。
写好了,项目地址: https://github.com/c53hzn/april-bill
参考资料
文章介绍了一个记账APP的开发过程,前端用Vue,后端用Firebase。作者使用了 Vue CLI
作为构建工具,但是我决定使用 Vite
,因为之前的使用体验告诉我, Vite
是比 Vue CLI
和 Nuxt
更快的构建工具。
这是关于Firebase用户登录的文档,文章介绍了firebase的用户注册、登入、登出功能。由于我要做一个私人APP,不需要陌生人来注册,那就只要登入登出即可。
大家可能会想,那如果要在刷新页面或切换页面后保持登录状态,是不是要用到 Vuex store
呢?但是我只有一个页面,所有功能都在同一版面,那也用不到 store
了。
功能设计
APP的入口页面应当是一个SPA的静态网页,可以使用 GitHub pages
部署,然后在登入后动态加载 Firebase
的数据。
需要实现的功能大概是这样:
用户验证
- 登入/登出
- 保持登入状态(用localStorage储存uid,不主动登出就永远保持登入)
加载账单
- 显示默认时间区间的账单(默认时间过去一个月,会根据月份横跨不同的天数)
- 设置每页显示条数
- 筛选特定时间区间的账单(加载一次call一次API)
- 显示交易汇总(加载一次计算一次)
- 分页显示(翻页不call API)
- 在本页账单内搜索关键字(搜索不call API)
查看汇总
- 查看账户流水汇总
- 查看分类交易汇总
账户和分类设置
- 编辑账户名和类型(更新数据库,刷新选项)
- 编辑分类名和类型(更新数据库,刷新选项)
账单操作
- 新增空白账单(更新数据库,重新加载账单)
- 根据收入或支出性质来加载分类和账户的选项
- 查看现有账单
- 更新现有账单(更新数据库,重新加载账单)
- 另存现有账单(更新数据库,重新加载账单)
- 删除现有账单(更新数据库,重新加载账单)
- 新建/删除账单模板(更新数据库,重新加载模板)
- 应用账单模板
系统界面UI设计
登录页面
登入后页面
编辑账户选项
编辑分类选项
新增/修改/查看账单
新增/修改/应用账单模板
查看账户交易汇总
查看分类交易汇总
数据库和服务器
本项目使用Firebase的Firestore数据库服务,目前用量为 spark
套餐,暂不收费。
数据结构
账户(ACCOUNT)
{
id: "ACC001",
TYPE: "信用卡",
NAME: "信用卡A",
initialBalance: 0,
balance: 0
}
分类(CATEGORY)
{
id: "CAT001",
TYPE: "支出",
NAME: "生活缴费"
}
账单(BILL)
{
id: "BILL202307240101",
DATE: "2023-07-24",
NATURE: "支出",
CATEGORY: "饮食",
NAME: "晚饭",
ACC_IN: "",
ACC_OUT: "信用卡A",
AMOUNT: 45.5,
REMARK: "",
NOTES: ""
}
账单模板(BILL_template)
{
id: "template001",
DATE: "",
NATURE: "支出",
CATEGORY: "饮食",
NAME: "晚饭",
ACC_IN: "",
ACC_OUT: "信用卡A",
AMOUNT: 45.5,
REMARK: "",
NOTES: ""
}
在firebase项目里新建一个用户,拿到 uid
,然后在firestore里建立一个账本集合 BOOK
,再用 uid
作为 BOOK
之下的文档的 id
,然后在 uid
这个文档之下新建上面四个集合,分别是 ACCOUNT
CATEGORY
BILL
BILL_template
。
假设我们要调用 id 为 BILL202307240101
的账单文档,调用的路径为
var path = `BOOK/${uid}/BILL/BILL202307240101`;
然后在登录之后拿到这个 uid
就可以操作自己的私人账单了。
开发模式转生产模式
在Firebase上创建项目的时候,一开始系统会让你选是要开发模式还是生产模式,如果选了开发模式,那么接下来30天的时间里,任何拿到你的客户端credentials的人都可以操作你的数据库。
这是靠项目设置里面的“安全规则”来实现的,如果需要从开发模式转到生产模式,其实数据上不需要做任何操作,只需要把项目设置里的安全规则改一改就行了。
用户验证及权限设置
在firebase的 Authentication
之下新建一个邮箱密码的登录账号,拿到 uid
。
开发的时候设计一下用 uid
验证登录,抄下这个 uid ,一会我们用得到。
在firebase的 firestore database
里面设置 rule
原本默认有一条规则,设置的时间是项目创建后一个月的日期,这条规则的意思是“允许创建后一个月之内的读写操作”,这就算是“开发模式”了,默认你只开发一个月吗?😱
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.time < timestamp.date(${year}, ${month}, ${day});
}
}
}
我们把它改成这样,意思是“允许uid是这个的用户进行读写”,取消了时间限制就,新增了一个用户限制,就保证了只有我自己输入的这个uid才能操作。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid == ${uid};
}
}
}
为什么要限制仅允许一个 uid ?
因为我是开发给我自己用的,我希望即使别人拿到我的firebase credentials也用不了它。
导出Notion账单并转移到APP
之前在 Notion
上记了半年的账,导出来csv一看,其实也才几百条,不知道为什么 Notion
加载得越来越慢,不过这次转移数据之后,这些都不重要了。
在Excel或者WPS里面打开csv文件,将表头改成 id
DATE
NAME
NATURE
CATEGORY
ACC_OUT
ACC_IN
AMOUNT
REMARK
NOTES
,然后用公式自动生成 BILL202307240101
格式的 id
,保存csv。
此时要注意,我的APP需要的日期格式是 YYYY-MM-DD
,但是Excel或者WPS有个坏习惯,就是在识别到日期的时候自动转成 YYYY/M/D
的格式,所以我们需要手动调整一下,改对了之后再保存。
然后在网上搜一个 csv to json
的工具,上传 csv
文件,拿到我们需要的 json
数据,此时不要保存成 json
。为了方便使用,我们把它存成这样的 bills.js
文件。
var bills = [{
id: "BILL202307240101",
DATE: "2023-07-24",
NATURE: "支出",
CATEGORY: "饮食",
NAME: "晚饭",
ACC_IN: "",
ACC_OUT: "信用卡A",
AMOUNT: 45.5,
REMARK: "",
NOTES: ""
}];
然后在同文件夹下新建一个 app.html
文件,内容如下
<head>
<meta charset="UTF-8">
</head>
<body>
asdf
</body>
<script src="./bills.js"></script>
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.0.0/firebase-app.js";
import { getFirestore,doc,setDoc,collection } from "https://www.gstatic.com/firebasejs/10.0.0/firebase-firestore.js";
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: "***",
authDomain: "***.firebaseapp.com",
projectId: "***",
storageBucket: "***.appspot.com",
messagingSenderId: "***",
appId: "***"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
for (let i = 0; i < bills.length; i++) {
addBill(bills[i]);
}
async function addBill(bill) {
await setDoc(doc(db, `BOOK/${uid}/BILL`, bill.id), bill);
}
</script>
这是一个极简的导入账单的网页,直接在本机使用即可。
网页一打开就会开始上传账单,网页和浏览器控制台不显示结果,但是你可以到浏览器的开发者工具的“网络”标签页查看,等到上传任务不再增多,就说明上传完了。
这里有一个很重要的地方,那就是 <head></head>
里面一定要写明此html文档的文字编码
<meta charset="UTF-8">
如果不写明是 UTF-8
,那么上传内容有中文的时候会变成乱码,而写了 UTF-8
就能上传正常的内容了。
总结
以上就是本项目的要点,其他的都在代码里了。
如果有什么问题,欢迎大家和我交流。
全文完~