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 @@ - - 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 }}

-

浠婃棩姒傝

+

今日概览

User
@@ -170,7 +171,7 @@ const openTransactionDetail = (tx) => {
-

鏈湀缁撲綑

+

当前结余

+ 去设置 +
@@ -220,37 +222,27 @@ const openTransactionDetail = (tx) => { />
- 宸茬敤 {{ formatCurrency(Math.abs(periodExpense)) }} - 鍓╀綑 {{ formatCurrency(remainingBudget) }} + 已用 {{ formatCurrency(Math.abs(periodExpense)) }} + 剩余 {{ formatCurrency(remainingBudget) }}
- - 周期: - {{ - budgetResetCycle === 'weekly' - ? '每周重置' - : budgetResetCycle === 'none' - ? '不重置' - : budgetResetCycle === 'custom' - ? '自定义' - : '每月重置' - }} - + 周期:{{ cycleLabel }} + 去设置 +
-

浠婃棩鏀跺叆

+

今日收入

{{ formatCurrency(todayIncomeValue) }}

-

浠婃棩鏀嚭

+

今日支出

- {{ formatCurrency(todayExpenseValue) }}

@@ -262,7 +254,7 @@ const openTransactionDetail = (tx) => { class="text-xs font-bold text-gradient-warm" @click="goList" > - 鏌ョ湅鍏ㄩ儴 + 查看全部
@@ -300,22 +292,19 @@ 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: {