From 8cbe01dd9c9821e1ef42bd1866c00c1ea13181a1 Mon Sep 17 00:00:00 2001
From: Jafeng <2998840497@qq.com>
Date: Thu, 12 Mar 2026 10:37:07 +0800
Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=96=87=E4=BB=B6?=
=?UTF-8?q?=E7=BC=96=E7=A0=81=E9=97=AE=E9=A2=98=EF=BC=8C=E7=A1=AE=E4=BF=9D?=
=?UTF-8?q?=E5=AF=BC=E5=85=A5=E8=AF=AD=E5=8F=A5=E6=A0=BC=E5=BC=8F=E6=AD=A3?=
=?UTF-8?q?=E7=A1=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../notification/NotificationRuleEngine.kt | 2 +-
public/default-avatar.svg | 16 ++
scripts/rule-smoke.mjs | 2 +-
src/config/notificationRules.js | 2 +-
src/main.js | 4 +-
src/services/notificationRuleService.js | 2 +-
src/views/AnalysisView.vue | 37 ++--
src/views/HomeView.vue | 165 +++++++-----------
src/views/SettingsView.vue | 18 +-
vite.config.js | 5 +-
10 files changed, 111 insertions(+), 142 deletions(-)
create mode 100644 public/default-avatar.svg
diff --git a/android/app/src/main/java/com/echo/app/notification/NotificationRuleEngine.kt b/android/app/src/main/java/com/echo/app/notification/NotificationRuleEngine.kt
index 0de075f..f9113c2 100644
--- a/android/app/src/main/java/com/echo/app/notification/NotificationRuleEngine.kt
+++ b/android/app/src/main/java/com/echo/app/notification/NotificationRuleEngine.kt
@@ -1,4 +1,4 @@
-package com.echo.app.notification
+package com.echo.app.notification
import kotlin.math.abs
diff --git a/public/default-avatar.svg b/public/default-avatar.svg
new file mode 100644
index 0000000..db51cab
--- /dev/null
+++ b/public/default-avatar.svg
@@ -0,0 +1,16 @@
+
diff --git a/scripts/rule-smoke.mjs b/scripts/rule-smoke.mjs
index db6cfb1..79375e0 100644
--- a/scripts/rule-smoke.mjs
+++ b/scripts/rule-smoke.mjs
@@ -1,4 +1,4 @@
-import assert from 'node:assert/strict'
+import assert from 'node:assert/strict'
import { transformNotificationToTransaction } from '../src/services/notificationRuleService.js'
const cases = [
diff --git a/src/config/notificationRules.js b/src/config/notificationRules.js
index 527239d..9112168 100644
--- a/src/config/notificationRules.js
+++ b/src/config/notificationRules.js
@@ -1,4 +1,4 @@
-const notificationRules = [
+const notificationRules = [
{
id: 'alipay-expense',
label: '支付宝消费',
diff --git a/src/main.js b/src/main.js
index 404fd14..48af6b8 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,4 +1,4 @@
-import { createApp } from 'vue'
+import { createApp } from 'vue'
import './style.css'
import '@phosphor-icons/web/regular'
import '@phosphor-icons/web/bold'
@@ -10,7 +10,7 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { defineCustomElements as jeepSqliteCE } from 'jeep-sqlite/loader'
import { Capacitor } from '@capacitor/core'
-// 注册 jeep-sqlite 自定义元素并保证 Web 端具备 Capacitor 环境
+// 注册 jeep-sqlite 自定义元素,并为 Web 环境挂载 Capacitor 对象。
window.Capacitor = Capacitor
jeepSqliteCE(window)
diff --git a/src/services/notificationRuleService.js b/src/services/notificationRuleService.js
index 6158033..ae8957c 100644
--- a/src/services/notificationRuleService.js
+++ b/src/services/notificationRuleService.js
@@ -1,4 +1,4 @@
-import notificationRulesSource from '../config/notificationRules.js'
+import notificationRulesSource from '../config/notificationRules.js'
const fallbackRules = notificationRulesSource.map((rule) => ({
...rule,
diff --git a/src/views/AnalysisView.vue b/src/views/AnalysisView.vue
index 70da7a7..e949df6 100644
--- a/src/views/AnalysisView.vue
+++ b/src/views/AnalysisView.vue
@@ -1,5 +1,5 @@
-
@@ -12,13 +12,11 @@
-
Powered by Gemini
+
即将接入对话分析与自动分类
-
-
-
你好 Alex!👋 我分析了你本周的饮食记录。
+
我已经整理了你最近 30 天的消费记录。
-
- 高热量警告
+
+ 本月摘要
- 周二和周四的晚餐摄入热量超标 30%,主要是因为“炸鸡”和“奶茶”。
+ 餐饮与通勤是主要支出来源,夜间消费频率明显高于白天,预算消耗速度偏快。
-
建议今晚尝试清淡饮食,比如蔬菜沙拉或三文鱼。
+
下一步会支持自动分类、标签建议、异常支出提醒和自然语言问答。
-
- 如果是自己做饭,有什么推荐的食谱吗?要简单的。
+ 帮我总结这周花钱最多的三类项目。
-
-
基于你冰箱里的食材(上次记录),推荐:
-
🥑 牛油果虾仁拌饭
-
- - 热量:约 450 kcal
- - 耗时:10 分钟
- - 成本:约 ¥18
+ 示例输出会像这样:
+
+ - 餐饮:¥428,集中在晚餐和外卖。
+ - 通勤:¥156,工作日为主。
+ - 一般支出:¥98,主要是零碎网购和订阅扣费。
-
-
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index b843a69..6d84dee 100644
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -19,13 +19,15 @@ const { totalIncome, totalExpense, todaysIncome, todaysExpense, latestTransactio
const { notifications, confirmNotification, dismissNotification, processingId, syncNotifications } =
useTransactionEntry()
+const defaultAvatar = '/default-avatar.svg'
let appStateListener = null
onMounted(async () => {
await bootstrapApp()
await syncNotifications()
+
try {
- // App 浠庡悗鍙板洖鍒板墠鍙版椂锛岃嚜鍔ㄥ埛鏂版湰鍦拌处鏈拰閫氱煡闃熷垪
+ // App 回到前台时,刷新本地账本和待处理通知。
appStateListener = await App.addListener('appStateChange', async ({ isActive }) => {
if (isActive) {
await transactionStore.hydrateTransactions()
@@ -33,7 +35,7 @@ onMounted(async () => {
}
})
} catch (error) {
- // Web 鐜鎴栨彃浠朵笉鍙敤鏃跺拷鐣? console.warn('[notifications] appStateChange listener failed', error)
+ console.warn('[notifications] appStateChange listener failed', error)
}
})
@@ -47,50 +49,47 @@ onBeforeUnmount(() => {
const monthlyBudget = computed(() => settingsStore.monthlyBudget || 0)
const budgetResetCycle = computed(() => settingsStore.budgetResetCycle || 'monthly')
-const currentDayLabel = computed(
- () =>
- new Intl.DateTimeFormat('zh-CN', {
- month: 'long',
- day: 'numeric',
- weekday: 'long',
- }).format(new Date()),
+const currentDayLabel = computed(() =>
+ new Intl.DateTimeFormat('zh-CN', {
+ month: 'long',
+ day: 'numeric',
+ weekday: 'long',
+ }).format(new Date()),
)
const periodStart = computed(() => {
const cycle = budgetResetCycle.value
const now = new Date()
+
if (cycle === 'weekly') {
- const day = now.getDay() || 7 // 鍛ㄤ竴=1锛屽懆鏃?7
- const diff = day - 1
- return new Date(now.getFullYear(), now.getMonth(), now.getDate() - diff)
+ const day = now.getDay() || 7
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate() - (day - 1))
}
+
if (cycle === 'monthly') {
const day = settingsStore.budgetMonthlyResetDay || 1
const safeDay = Math.min(Math.max(Number(day) || 1, 1), 28)
const candidate = new Date(now.getFullYear(), now.getMonth(), safeDay)
- if (now >= candidate) {
- return candidate
- }
+ if (now >= candidate) return candidate
return new Date(now.getFullYear(), now.getMonth() - 1, safeDay)
}
+
if (cycle === 'custom') {
if (!settingsStore.budgetCustomStartDate) return null
return new Date(settingsStore.budgetCustomStartDate)
}
- // none锛氫笉闄愬埗鍛ㄦ湡
+
return null
})
const periodExpense = computed(() => {
const cycle = budgetResetCycle.value
const start = periodStart.value
- if (cycle === 'none' || !start) {
- return totalExpense.value || 0
- }
+ if (cycle === 'none' || !start) return totalExpense.value || 0
+
return (sortedTransactions.value || []).reduce((sum, tx) => {
if (tx.amount >= 0) return sum
- const date = new Date(tx.date)
- return date >= start ? sum + tx.amount : sum
+ return new Date(tx.date) >= start ? sum + tx.amount : sum
}, 0)
})
@@ -108,17 +107,22 @@ const remainingBudget = computed(() =>
const todayIncomeValue = computed(() => todaysIncome.value || 0)
const todayExpenseValue = computed(() => Math.abs(todaysExpense.value || 0))
+const recentTransactions = computed(() => latestTransactions.value.slice(0, 4))
-const formatCurrency = (value) => `楼 ${Math.abs(value || 0).toFixed(2)}`
-const formatAmount = (value) =>
- `${value >= 0 ? '+' : '-'} 楼${Math.abs(value || 0).toFixed(2)}`
+const cycleLabel = computed(() => {
+ if (budgetResetCycle.value === 'weekly') return '每周重置'
+ if (budgetResetCycle.value === 'none') return '不重置'
+ if (budgetResetCycle.value === 'custom') return '自定义'
+ return '每月重置'
+})
+
+const formatCurrency = (value) => `¥${Math.abs(value || 0).toFixed(2)}`
+const formatAmount = (value) => `${value >= 0 ? '+' : '-'} ¥${Math.abs(value || 0).toFixed(2)}`
const formatTime = (value) =>
new Intl.DateTimeFormat('zh-CN', { hour: '2-digit', minute: '2-digit' }).format(
new Date(value),
)
-const recentTransactions = computed(() => latestTransactions.value.slice(0, 4))
-
const goSettings = () => {
router.push({ name: 'settings' })
}
@@ -144,18 +148,15 @@ const openTransactionDetail = (tx) => {
{{ currentDayLabel }}
-
+
@@ -170,7 +171,7 @@ const openTransactionDetail = (tx) => {
-
鏈湀缁撲綑
+
当前结余
- 杩樻病鏈夎褰曪紝蹇幓娣诲姞绗竴绗旀秷璐瑰惂锝?
+ 还没有记录,去添加第一笔流水吧。
-
寰呯‘璁ら€氱煡
+ 待确认通知
- 鑷姩鎹曡幏
+ 自动捕获
-
+
{
:disabled="processingId === notif.id"
@click="confirmNotification(notif.id)"
>
- 鍏ヨ处
+ 入账
- 蹇界暐
+ 忽略
- 鏆傛棤鏂伴€氱煡锛屼韩鍙楁參鐢熸椿 鈽曪笍
+ 暂无新通知,先安心生活。
-
+
+
-
-
-
-
-
-
AI 椤鹃棶
-
鍒嗘瀽鎴戠殑娑堣垂涔犳儻
-
+
-
-
-
-
-
-
-
-
鐑噺绠$悊
-
浠婃棩鍓╀綑 750 kcal
-
+
+
AI 财务顾问
+
查看消费总结、自动分类建议和可执行的预算提醒。
-
-
-
diff --git a/src/views/SettingsView.vue b/src/views/SettingsView.vue
index e66c147..075d989 100644
--- a/src/views/SettingsView.vue
+++ b/src/views/SettingsView.vue
@@ -94,14 +94,7 @@ const hasAnyCategoryBudget = computed(() =>
Object.values(categoryBudgets.value || {}).some((v) => (v || 0) > 0),
)
-const budgetCategories = [
- { value: 'Food', label: '餐饮' },
- { value: 'Transport', label: '通勤' },
- { value: 'Health', label: '健康' },
- { value: 'Groceries', label: '买菜' },
- { value: 'Entertainment', label: '娱乐' },
- { value: 'Uncategorized', label: '其他' },
-]
+const budgetCategories = BUDGET_CATEGORY_OPTIONS
const handleAvatarClick = () => {
if (fileInputRef.value) {
fileInputRef.value.click()
@@ -155,7 +148,7 @@ onMounted(() => {
![Avatar]()
{
-
AI 自动分类(预留)
+
AI 自动分类与标签(预留)
- 开启后,未来会优先使用云端 AI 对商户进行分类与标签分析。
+ 开启后,未来会自动补全分类、标签,并生成可复用的商户画像。
@@ -405,7 +398,7 @@ onMounted(() => {
- 当前版本仅记录偏好,后续接入云端 AI 后会自动生效。
+ 当前版本仅记录偏好,后续接入 AI 能力后会自动生效。
@@ -460,3 +453,4 @@ onMounted(() => {
+
diff --git a/vite.config.js b/vite.config.js
index 6698196..7c581bf 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,11 +1,10 @@
-import { defineConfig } from 'vite'
+import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
-// 从 package.json 注入版本号,便于前端展示
+// 从 package.json 注入版本号,供前端设置页展示。
// eslint-disable-next-line no-undef
const appVersion = (process && process.env && process.env.npm_package_version) || '0.0.0'
-// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
define: {