大家好!如果你也曾被繁琐的记账流程所困扰——每次消费都要打开 App、选择分类、手动输入金额,月底看着凌乱的账单却得不到任何有效洞察——那么,这篇文章正是你的“解药”。

我花了大量时间探索和迭代,最终在 Obsidian 中搭建了一套真正“全自动化”的个人消费追踪系统。它将彻底改变你的记账体验。

现在,我将毫无保留地分享给你。只需跟着指南操作,你就能拥有一个“一键录入、自动合并、智能排序”的录入模板,以及一个“多维度、可交互、带预警”的全功能财务仪表盘。

为什么是 Obsidian?因为它将数据所有权 100% 交还给你,同时通过强大的插件生态,让你的本地笔记库化身为无所不能的个人数据中心。准备好了吗?让我们开启高效、智能的记账新纪元!

最终效果预览

想象一下你理想中的记账流程:

  1. 一键快速录入:无论何时何地消费,只需按下快捷键,输入金额(甚至支持 30+15 这样的快捷计算),选择分类,即可完成记录。整个过程不超过10秒。
  2. 同日消费自动合并:今天已经记过一笔外卖?晚上又去超市购物?没问题,再次录入后,脚本会自动找到今天的记录,将两笔消费智能合并到同一行,总金额自动累加。你的日志永远保持干净整洁。
  3. 年/月标题自动创建:当你记录下新一年的第一笔消费时,系统会自动为你插入一个醒目的年份标题(如 ## 2026);记录新月份的第一笔消费时,则会自动插入月份标题(如 ### 26-01)。你的消费日志将自动按时间层级完美归档。
  4. 全功能财务仪表盘:在一个专属页面,你可以清晰地看到:
    • 月度/年度可切换视图:轻松回顾上个月或去年的消费状况。
    • 动态图表:消费分类饼图、月度消费趋势折线图,一目了然。
    • 预算超支警告:当月消费超过预设,系统会自动发出醒目提醒。
    • 深度数据洞察:自动计算月均消费、消费最高月份、支出冠军类别等关键指标。

这套系统不仅为你省时,更能将枯燥的数据转化为帮你优化消费、实现理财目标的强大动力。

以下是仪表盘效果图(示例)

dashboard-demo.png dashboard-demo.png
(这是一个根据描述生成的效果图示例,你的实际仪表盘会实时反映你的数据)

所需工具

  • Obsidian(免费下载自官网)。
  • 插件(在Obsidian设置 > 社区插件中安装并启用):
    • Templater:自动化脚本的核心引擎,负责处理所有的数据录入和文件修改逻辑。
    • Dataview:强大的数据查询与可视化工具,负责驱动我们的财务仪表盘。

请确保安装并启用上述插件后,重启 Obsidian。

Part 1: 构建数据核心 (The Core Data System)

我们的系统由三个部分组成:一个智能录入模板,一个纯文本日志文件,以及一个数据仪表盘。首先,我们来创建前两个。

1.1 智能录入模板 (消费记录.md)

在你的 Templater 模板文件夹中(例如 “Templates”),创建一个名为 消费记录.md 的文件,并粘贴以下完整代码。这是整个系统的“写入大脑”。

⚙️ 点击查看/折叠“智能消费录入脚本”代码
1
2
3
4
5
6
7
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
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
<%*
// --- 智能消费录入脚本 (V3.4 - 支持年/月标题自动创建) ---
// --- ⚙️ 配置区 ---
const filePath = "记录/消费/消费-log.md"; // ⚠️【极其重要】请修改为你自己的消费日志文件路径!
const dateFormat = "YY-MM-DD";
const categories = [" #餐饮 ", " #交通 ", " #购物 ", " #娱乐 ", " #学习 ", " #生活杂费 ", " #医疗健康 "];
// --- 结束配置 ---

// --- Helper Function: 安全计算器 ---
function safeCalculate(expression) {
try {
const sanitized = expression.trim();
if (!/^[0-9\.\+\-\s]+$/.test(sanitized)) return NaN;
const result = new Function('return ' + sanitized)();
return result;
} catch (error) {
return NaN;
}
}

// --- 1. 模式选择与数据输入 ---
let targetDateString;
let amountInput;
const initialInput = await tp.system.prompt("请输入消费金额 (支持加减, 或输入 '@' 补录)", "", true);
if (!initialInput) return;

if (initialInput.trim() === '@') {
const recentDays = [
{ text: `今天 (${tp.date.now(dateFormat)})`, value: tp.date.now(dateFormat) },
{ text: `昨天 (${tp.date.now(dateFormat, -1)})`, value: tp.date.now(dateFormat, -1) },
];
for (let i = 2; i <= 7; i++) {
recentDays.push({ text: `${i} 天前 (${tp.date.now(dateFormat, -i)})`, value: tp.date.now(dateFormat, -i) });
}
recentDays.push({ text: "手动指定日期...", value: "manual" });

const selectedDateOption = await tp.system.suggester(recentDays.map(d => d.text), recentDays.map(d => d.value), false, "请选择消费日期");
if (!selectedDateOption) return;

if (selectedDateOption === 'manual') {
const manualDate = await tp.system.prompt(`请输入日期 (格式: ${dateFormat})`, tp.date.now(dateFormat));
if (!manualDate || !/^\d{2}-\d{2}-\d{2}$/.test(manualDate.trim())) {
new Notice("❌ 日期无效或格式错误", 4000);
return;
}
targetDateString = manualDate.trim();
} else {
targetDateString = selectedDateOption;
}

amountInput = await tp.system.prompt("请输入消费金额 (支持加减)", "", true);
if (!amountInput) return;

} else {
targetDateString = tp.date.now(dateFormat);
amountInput = initialInput;
}

// --- 2. 通用数据处理 ---
const newAmount = safeCalculate(amountInput);

if (isNaN(newAmount) || newAmount <= 0) {
new Notice("❌ 金额无效。请输入正数或简单的加减法 (如 30+10)。", 4000);
return;
}
const categoryInput = (await tp.system.suggester(categories, categories, false, "请选择消费类别"))?.replace('#', '').trim();
if (!categoryInput) return;

const remarkInput = (await tp.system.prompt("请输入具体备注 (可选)", "", false))?.trim() || "";

// --- 3. 核心文件处理模块 (V3.4 智能分区与年/月标题) ---
const file = tp.file.find_tfile(filePath);
if (!file) {
new Notice(`❌ 错误:找不到文件 "${filePath}"`, 5000);
return;
}

const yearPart = targetDateString.substring(0, 2);
const yearHeader = `## 20${yearPart}`;
const monthHeader = `### ${targetDateString.substring(0, 5)}`;

const content = await app.vault.read(file);
const yearHeaderExists = content.includes(yearHeader);
const monthHeaderExists = content.includes(monthHeader);

const originalLines = content.split('\n');
const lineRegex = /^- \[(\d{2}-\d{2}-\d{2})\]-\[(\d+(?:\.\d+)?)\]-\[(.*)\]$/;

const nonTransactionLines = [];
let transactionLines = [];

for (const line of originalLines) {
if (line.match(lineRegex) || line.startsWith('### ') || line.startsWith('## ')) {
transactionLines.push(line);
} else {
nonTransactionLines.push(line);
}
}

let recordUpdated = false;
const newRemarkSegment = remarkInput
? `${categoryInput}:${remarkInput}@${newAmount.toFixed(2)}`
: `${categoryInput}@${newAmount.toFixed(2)}`;

for (let i = 0; i < transactionLines.length; i++) {
const match = transactionLines[i].match(lineRegex);
if (match && match === targetDateString) {
const currentAmount = parseFloat(match);
const totalAmount = currentAmount + newAmount;
const currentRemark = match.trim();
const updatedRemark = currentRemark ? `${currentRemark}${newRemarkSegment}` : newRemarkSegment;

transactionLines[i] = `- [${targetDateString}]-[${totalAmount.toFixed(2)}]-[${updatedRemark}]`;
recordUpdated = true;
new Notice(`✅ [${targetDateString}] 消费已合并!`, 4000);
break;
}
}

if (!recordUpdated) {
const newEntry = `- [${targetDateString}]-[${newAmount.toFixed(2)}]-[${newRemarkSegment}]`;

if (!yearHeaderExists) {
transactionLines.push(yearHeader);
new Notice(`🎉 已为您创建新的年份标题: ${yearHeader}`, 4000);
}

if (!monthHeaderExists) {
transactionLines.push(monthHeader);
new Notice(`✒️ 已为您创建新的月份标题: ${monthHeader}`, 4000);
}

transactionLines.push(newEntry);
new Notice(`📝 已为 [${targetDateString}] 记录第一笔消费`, 4000);
}

transactionLines.sort((a, b) => {
const isAYearHeader = a.startsWith('## ');
const isAMonthHeader = a.startsWith('### ');
const isBYearHeader = b.startsWith('## ');
const isBMonthHeader = b.startsWith('### ');

let keyA, keyB;

if (isAYearHeader) keyA = a.substring(5, 7);
else if (isAMonthHeader) keyA = a.substring(4);
else keyA = a.match(lineRegex)?. || '';

if (isBYearHeader) keyB = b.substring(5, 7);
else if (isBMonthHeader) keyB = b.substring(4);
else keyB = b.match(lineRegex)?. || '';

return keyA.localeCompare(keyB);
});

// --- 4. 文件写入 ---
const finalLines = [...nonTransactionLines.filter(line => line.trim() !== ''), ...transactionLines];
const finalContent = finalLines.join('\n').replace(/\n{3,}/g, '\n\n');

await app.vault.modify(file, finalContent);
%>

1.2 消费日志文件 (消费-log.md)

这是你的“数据库”。在你希望存放日志的位置(例如 记录/消费/ 文件夹下),创建一个名为 消费-log.md 的文件。

在文件最顶部,你可以粘贴以下代码块,它能为你快速计算日志中的总消费额。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
```dataviewjs
// 此脚本用于快速计算本文件内的总消费
const fileContent = await dv.io.load(dv.current().file.path);
const lines = fileContent.split('\n');
const regex = /-\s*\[.*?\]-\[(\d+\.?\d*)\]-.*/;
let totalAmount = 0;
for (const line of lines) {
const match = line.match(regex);
if (match && match) {
const amountString = match;
totalAmount += parseFloat(amountString);
}
}
dv.paragraph(`💰 **当前日志总消费:¥${totalAmount.toFixed(2)}**`);

[!IMPORTANT] 关键一致性原则
请确保智能录入脚本财务仪表盘(见Part 3)顶部的 filePath 路径,与你这个日志文件的真实路径和文件名完全一致!这是整个系统能协同工作的基石。


Part 2: 配置“一键录入”命令

让魔法发生得更简单。

  1. 设置 Templater 模板文件夹

    • 打开 Obsidian 设置 > 社区插件 > Templater。
    • 在“Template folder location”中指定你的模板文件夹路径(如 Templates)。
  2. 绑定快捷键

    • 在 Templater 设置中,找到“Template Hotkeys”部分。
    • 点击“Add new for template”,选择我们刚刚创建的 消费记录.md 模板。
    • 为它分配一个你顺手的快捷键(例如 ⌥+C,C for Cost),实现真正的一键触发。

现在,试试按下你的快捷键,体验丝滑的录入流程吧!

Part 3: 打造你的专属“财务仪表盘”

最后一步,让沉睡的数据“开口说话”。在你希望展示仪表盘的任何地方(例如你的主页 Homepage.md),创建一个新的笔记(例如 消费仪表盘.md),并粘贴以下完整代码

📊 点击查看/折叠“财务仪表盘”代码
1
2
3
4
5
6
7
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
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
```dataviewjs
// --- 消费仪表盘 (V4.0) ---
// --- ⚙️ 配置区 ---
const FILE_PATH = "记录/消费/消费-log.md";
const budgets = { "餐饮": 1500, "购物": 1000, "娱乐": 500, "交通": 800 };
// --- 结束配置 ---
// 全局状态管理
let state = {
monthOffset: 0,
yearOffset: 0
};
// 全局数据缓存
let allTransactions = null;
/**
* 主渲染函数
*/
async function renderDashboard(container) {
container.innerHTML = '';
if (allTransactions === null) {
allTransactions = await parseTransactionData();
if (allTransactions === null) {
container.createEl("p", { text: "❌ **错误:** 无法加载或解析消费日志文件。" });
return;
}
}
renderMonthlyView(container, allTransactions, state.monthOffset);
container.createEl('hr');
renderAnnualView(container, allTransactions, state.yearOffset);
}
/**
* 渲染月度视图
*/
function renderMonthlyView(container, data, offset) {
const view = container.createEl('div', { cls: 'monthly-view' });
const targetMonth = dv.luxon.DateTime.now().plus({ months: offset });
const nav = view.createEl('div', { attr: { style: 'display: flex; align-items: center; justify-content: center; gap: 15px; margin-bottom: 20px;' }});

// --- 按钮 ---
const prevButton = nav.createEl('button', { text: '<< 上个月' });
prevButton.onclick = () => { state.monthOffset--; renderDashboard(container); };

nav.createEl('h3', { text: targetMonth.toFormat("yyyy年MM月"), attr: { style: 'margin: 0;' }});

const nextButton = nav.createEl('button', { text: '下个月 >>' });
nextButton.disabled = offset >= 0;
nextButton.onclick = () => { state.monthOffset++; renderDashboard(container); };

const homeButton = nav.createEl('button', { text: '回到本月' });
homeButton.disabled = offset === 0;
homeButton.onclick = () => { state.monthOffset = 0; renderDashboard(container); };

// --- 月度数据聚合与渲染 ---
const transactions = data.filter(t => t.date.hasSame(targetMonth, 'month'));
if (transactions.length === 0) {
view.createEl("p", { text: `✅ 在 **${targetMonth.toFormat("yyyy年MM月")}** 没有找到任何消费记录。` });
return;
}

const total = transactions.reduce((sum, t) => sum + t.amount, 0);
const categoryTotals = {};
transactions.forEach(t => {
categoryTotals[t.category] = (categoryTotals[t.category] || 0) + t.amount;
});
view.createEl('h2', { text: "月度消费概览" });
if (offset === 0) {
let warnings = [];
for (const cat in budgets) {
const spent = categoryTotals[cat] || 0;
if (spent > budgets[cat]) warnings.push(`- **${cat}** 已超支 <span style="color:red;">¥${(spent - budgets[cat]).toFixed(2)}</span>`);
}
if (warnings.length > 0) dv.markdown(view, `> [!WARNING] 预算超支提醒\n> ${warnings.join('\n> ')}`);
}
let summary = `本月总支出: **¥${total.toFixed(2)}**`;
if (offset === 0) {
const weekTotal = transactions.filter(t => t.date.hasSame(dv.luxon.DateTime.now(), 'week')).reduce((s, t) => s + t.amount, 0);
summary += ` | 本周总支出: **¥${weekTotal.toFixed(2)}**`;
}
dv.paragraph(summary);
const chartContainer = view.createEl('div', { attr: { style: 'width: 300px; max-height: 300px; margin: 20px auto; text-align: center;' } });
renderPieChart(chartContainer, categoryTotals, `${targetMonth.toFormat("yyyy年MM月")}消费分类`);

view.createEl('h4', { text: "详细分类统计" });
dv.table(["消费类别", "总金额 (¥)", "占比"], Object.entries(categoryTotals).sort((a, b) => b[1] - a[1]).map(([cat, amount]) => [cat, `¥${amount.toFixed(2)}`, `${((amount / total) * 100).toFixed(1)}%`]));
}
/**
* 渲染年度视图
*/
function renderAnnualView(container, data, offset) {
const view = container.createEl('div', { cls: 'annual-view' });
const targetYear = dv.luxon.DateTime.now().plus({ years: offset });
const nav = view.createEl('div', { attr: { style: 'display: flex; align-items: center; justify-content: center; gap: 15px; margin-top: 20px; margin-bottom: 20px;' }});

const prevButton = nav.createEl('button', { text: '<< 上一年' });
prevButton.onclick = () => { state.yearOffset--; renderDashboard(container); };

nav.createEl('h3', { text: `${targetYear.toFormat("yyyy")}年回顾`, attr: { style: 'margin: 0;' }});

const nextButton = nav.createEl('button', { text: '下一年 >>' });
nextButton.disabled = offset >= 0;
nextButton.onclick = () => { state.yearOffset++; renderDashboard(container); };

const homeButton = nav.createEl('button', { text: '回到今年' });
homeButton.disabled = offset === 0;
homeButton.onclick = () => { state.yearOffset = 0; renderDashboard(container); };

const transactions = data.filter(t => t.date.hasSame(targetYear, 'year'));
if (transactions.length === 0) {
view.createEl("p", { text: `✅ 在 **${targetYear.toFormat("yyyy")}年** 没有找到任何消费记录。` });
return;
}
const monthlyTotals = Array(12).fill(0);
const categoryTotals = {};
transactions.forEach(t => {
monthlyTotals[t.date.month - 1] += t.amount;
categoryTotals[t.category] = (categoryTotals[t.category] || 0) + t.amount;
});
const total = monthlyTotals.reduce((sum, m) => sum + m, 0);
const activeMonths = targetYear.year === dv.luxon.DateTime.now().year ? dv.luxon.DateTime.now().month : 12;
const avg = total / activeMonths;
const maxMonthValue = Math.max(...monthlyTotals);
const maxMonthIndex = monthlyTotals.indexOf(maxMonthValue);
const topCategory = Object.entries(categoryTotals).sort((a, b) => b[1] - a[1])[0];
view.createEl('h2', { text: "年度财务摘要" });
const summaryEl = view.createEl('div', { attr: { style: 'display: flex; justify-content: space-around; text-align: center; margin-bottom: 20px;' }});
summaryEl.createEl('div').innerHTML = `<strong>年度总支出</strong><br>¥${total.toFixed(2)}`;
summaryEl.createEl('div').innerHTML = `<strong>月均消费</strong><br>¥${avg.toFixed(2)}`;
summaryEl.createEl('div').innerHTML = `<strong>消费最高月份</strong><br>${maxMonthIndex + 1}月 (¥${maxMonthValue.toFixed(2)})`;
summaryEl.createEl('div').innerHTML = `<strong>支出冠军类别</strong><br>${topCategory[0]} (¥${topCategory[1].toFixed(2)})`;

const chartContainer = view.createEl('div');
renderLineChart(chartContainer, monthlyTotals, `${targetYear.year}年月度消费趋势`);
}
/**
* 数据解析函数
*/
async function parseTransactionData() {
const file = app.vault.getAbstractFileByPath(FILE_PATH);
if (!file) return null;
const content = await app.vault.read(file);
const lines = content.trim().split('\n').filter(line => line.trim());
if (lines.length === 0) return [];
const transactions = [];
const regex = /^-\s*\[(\d{2}-\d{2}-\d{2})\]-\[(\d+\.?\d*)\]-\[(.*?)\]$/;
for (const line of lines) {
const match = line.match(regex);
if (!match) continue;
const date = dv.luxon.DateTime.fromFormat(match[1], "yy-MM-dd", { zone: 'local' });
if (!date.isValid) continue;
const totalAmount = parseFloat(match[2]);
if (isNaN(totalAmount) || totalAmount <= 0) continue;
const remarksStr = match[3].trim();
const remarks = remarksStr.split('→').map(r => r.trim()).filter(r => r);
if (remarks.length === 0) continue;

const averageAmount = totalAmount / remarks.length;
let hasSubAmounts = false, subSum = 0;
const tempSubs = [];
for (const r of remarks) {
const parts = r.split('@');
if (parts.length > 1) {
const sub = parseFloat(parts.pop().trim());
if (!isNaN(sub)) { tempSubs.push(sub); subSum += sub; hasSubAmounts = true; }
else { tempSubs.push(null); }
} else { tempSubs.push(null); }
}

const useSubAmounts = hasSubAmounts && tempSubs.every(s => s !== null) && Math.abs(subSum - totalAmount) < 0.01;

for (let i = 0; i < remarks.length; i++) {
const r = remarks[i];
const parts = r.split('@');
const catNote = parts.length > 1 ? parts.slice(0, -1).join('@') : r;
const itemAmount = useSubAmounts ? tempSubs[i] : averageAmount;
const [category, ...noteParts] = catNote.split(':').map(p => p.trim());
transactions.push({
date,
amount: itemAmount,
category: category.replace(/[`#]/g, '') || '未分类',
note: noteParts.join(':').trim()
});
}
}
return transactions;
}
/**
* 图表渲染函数(仅修复饼图)
*/
function renderPieChart(container, data, title) {
const chartData = {
type: 'pie',
data: {
labels: Object.keys(data),
datasets: [{
data: Object.values(data).map(v => parseFloat(v.toFixed(2))),
backgroundColor: ["#FF6384", "#36A2EB", "#FFCE56", "#4BC0C0", "#9966FF", "#FF9F40"]
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
title: { display: true, text: title }
}
}
};
if (typeof Chart !== 'undefined') {
try {
window.renderChart(chartData, container);
console.log(`饼图渲染成功: ${title}`);
} catch (e) {
container.createEl('p', { text: `❌ 饼图渲染失败: ${e.message}` });
console.error('饼图渲染错误:', e);
}
} else {
container.createEl('p', { text: '📈 Chart.js 未加载,饼图无法显示。' });
}
}
function renderLineChart(container, data, title) {
const chartData = { type: 'line', data: { labels: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'], datasets: [{ label: '月度消费', data: data.map(v => v.toFixed(2)), fill: false, borderColor: 'rgb(75, 192, 192)', tension: 0.1 }] }, options: { plugins: { title: { display: true, text: title } } } };
if (typeof Chart !== 'undefined') window.renderChart(chartData, container);
}
/**
* 启动逻辑
*/
function initialize() {
if (typeof Chart === 'undefined') {
dv.paragraph("⚠️ Chart.js 未加载,尝试动态加载...");
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/chart.js';
document.head.appendChild(script);
script.onload = () => {
console.log('Chart.js 加载成功');
renderDashboard(this.container);
};
script.onerror = () => {
dv.paragraph("❌ 无法加载 Chart.js 库,请检查网络连接。");
renderDashboard(this.container); // 继续渲染(无图表)
};
} else {
console.log('Chart.js 已加载');
renderDashboard(this.container);
}
}
if (typeof dv.luxon === 'undefined') {
dv.paragraph("❌ **错误:** Luxon 未加载,Dataview 环境异常。");
} else {
initialize.call(this);
}

提示:仪表盘代码会自动从网络加载 Chart.js 库来绘制图表。如果图表不显示,请检查网络连接和 Obsidian 的网络权限。

结语

恭喜你!你已经拥有了一套强大、私密且完全由你掌控的个人消费自动化系统。从现在开始,告别繁琐,拥抱高效。这套系统不仅是一个工具,更是你实践个人理财、优化生活方式的得力助手。

我将自己摸索和调试的经验浓缩在这篇教程里,希望能让你少走弯路,直接享受成果。如果在配置或使用中遇到任何问题,欢迎在评论区留言交流。

如果你觉得这篇文章对你有帮助,不妨分享给同样热爱 Obsidian 和效率生活的朋友们。

✨​温馨提示​✨

以上所有代码均为纯粹的本地化脚本,所有的数据读取、处理和计算都在你自己的设备上完成。
​最关键的一点:它不会将你的任何数据上传到任何服务器!​​你的财务数据,永远只属于你。

如果你遇到了程序错误,或者灵光一现有了超棒的想法,随时欢迎告诉我!

📧 邮件:Socrates.02zx@Gmail.com

感谢阅读,祝你记账愉快!:)


本站由 Setix 使用 Stellar 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
本站总访问量