fix: 修复文件编码问题,确保导入语句格式正确

This commit is contained in:
2026-03-12 10:37:07 +08:00
parent bf15918136
commit 8cbe01dd9c
10 changed files with 111 additions and 142 deletions

View File

@@ -1,4 +1,4 @@
package com.echo.app.notification package com.echo.app.notification
import kotlin.math.abs import kotlin.math.abs

16
public/default-avatar.svg Normal file
View File

@@ -0,0 +1,16 @@
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg" x1="24" y1="20" x2="136" y2="140" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFB36B"/>
<stop offset="1" stop-color="#F06449"/>
</linearGradient>
<linearGradient id="ring" x1="38" y1="36" x2="120" y2="124" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFF7ED" stop-opacity="0.95"/>
<stop offset="1" stop-color="#FFE7D4" stop-opacity="0.72"/>
</linearGradient>
</defs>
<rect x="8" y="8" width="144" height="144" rx="48" fill="url(#bg)"/>
<circle cx="80" cy="80" r="48" fill="url(#ring)"/>
<path d="M61 52H101V64H75V75H97V87H75V108H61V52Z" fill="#8A3412"/>
<path d="M103 52H117V108H103V52Z" fill="#8A3412" fill-opacity="0.25"/>
</svg>

View File

@@ -1,4 +1,4 @@
import assert from 'node:assert/strict' import assert from 'node:assert/strict'
import { transformNotificationToTransaction } from '../src/services/notificationRuleService.js' import { transformNotificationToTransaction } from '../src/services/notificationRuleService.js'
const cases = [ const cases = [

View File

@@ -1,4 +1,4 @@
const notificationRules = [ const notificationRules = [
{ {
id: 'alipay-expense', id: 'alipay-expense',
label: '支付宝消费', label: '支付宝消费',

View File

@@ -1,4 +1,4 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import './style.css' import './style.css'
import '@phosphor-icons/web/regular' import '@phosphor-icons/web/regular'
import '@phosphor-icons/web/bold' import '@phosphor-icons/web/bold'
@@ -10,7 +10,7 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { defineCustomElements as jeepSqliteCE } from 'jeep-sqlite/loader' import { defineCustomElements as jeepSqliteCE } from 'jeep-sqlite/loader'
import { Capacitor } from '@capacitor/core' import { Capacitor } from '@capacitor/core'
// 注册 jeep-sqlite 自定义元素并保证 Web 端具备 Capacitor 环境 // 注册 jeep-sqlite 自定义元素,并为 Web 环境挂载 Capacitor 对象。
window.Capacitor = Capacitor window.Capacitor = Capacitor
jeepSqliteCE(window) jeepSqliteCE(window)

View File

@@ -1,4 +1,4 @@
import notificationRulesSource from '../config/notificationRules.js' import notificationRulesSource from '../config/notificationRules.js'
const fallbackRules = notificationRulesSource.map((rule) => ({ const fallbackRules = notificationRulesSource.map((rule) => ({
...rule, ...rule,

View File

@@ -1,5 +1,5 @@
<script setup> <script setup>
// 分析页目前为静态占位,实现基础对话 UI后续接入真实 AI 逻辑 // 分析页当前仍是占位页,先展示未来 AI 财务顾问的交互方向。
</script> </script>
<template> <template>
@@ -12,13 +12,11 @@
</div> </div>
<div> <div>
<h2 class="text-xl font-extrabold text-stone-800">AI 财务顾问</h2> <h2 class="text-xl font-extrabold text-stone-800">AI 财务顾问</h2>
<p class="text-xs text-stone-400">Powered by Gemini</p> <p class="text-xs text-stone-400">即将接入对话分析与自动分类</p>
</div> </div>
</div> </div>
<!-- Chat Area -->
<div class="flex-1 overflow-y-auto space-y-5 pr-1 pb-2"> <div class="flex-1 overflow-y-auto space-y-5 pr-1 pb-2">
<!-- Bot Message -->
<div class="flex gap-3"> <div class="flex gap-3">
<div <div
class="w-8 h-8 rounded-full bg-stone-100 flex items-center justify-center text-stone-400 mt-1 shrink-0" class="w-8 h-8 rounded-full bg-stone-100 flex items-center justify-center text-stone-400 mt-1 shrink-0"
@@ -28,30 +26,28 @@
<div <div
class="bg-white p-4 rounded-2xl rounded-tl-none shadow-sm border border-stone-100 text-sm text-stone-600 leading-relaxed max-w-[85%]" class="bg-white p-4 rounded-2xl rounded-tl-none shadow-sm border border-stone-100 text-sm text-stone-600 leading-relaxed max-w-[85%]"
> >
<p>你好 Alex👋 我分析了你本周的饮食记录</p> <p>我已经整理了你最近 30 天的消费记录</p>
<div class="mt-3 bg-stone-50 rounded-xl p-3 border border-stone-100"> <div class="mt-3 bg-stone-50 rounded-xl p-3 border border-stone-100">
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="ph-fill ph-warning text-orange-400" /> <i class="ph-fill ph-chart-line text-orange-400" />
<span class="font-bold text-stone-700 text-xs">高热量警告</span> <span class="font-bold text-stone-700 text-xs">本月摘要</span>
</div> </div>
<p class="text-xs"> <p class="text-xs">
周二和周四的晚餐摄入热量超标 30%主要是因为炸鸡奶茶 餐饮与通勤是主要支出来源夜间消费频率明显高于白天预算消耗速度偏快
</p> </p>
</div> </div>
<p class="mt-3">建议今晚尝试清淡饮食比如蔬菜沙拉或三文鱼</p> <p class="mt-3">下一步会支持自动分类标签建议异常支出提醒和自然语言问答</p>
</div> </div>
</div> </div>
<!-- User Message -->
<div class="flex gap-3 flex-row-reverse"> <div class="flex gap-3 flex-row-reverse">
<div <div
class="bg-stone-700 text-white p-4 rounded-2xl rounded-tr-none shadow-lg text-sm max-w-[85%]" class="bg-stone-700 text-white p-4 rounded-2xl rounded-tr-none shadow-lg text-sm max-w-[85%]"
> >
如果是自己做饭有什么推荐的食谱吗要简单的 帮我总结这周花钱最多的三类项目
</div> </div>
</div> </div>
<!-- Bot Message 2 -->
<div class="flex gap-3"> <div class="flex gap-3">
<div <div
class="w-8 h-8 rounded-full bg-stone-100 flex items-center justify-center text-stone-400 mt-1 shrink-0" class="w-8 h-8 rounded-full bg-stone-100 flex items-center justify-center text-stone-400 mt-1 shrink-0"
@@ -61,22 +57,20 @@
<div <div
class="bg-white p-4 rounded-2xl rounded-tl-none shadow-sm border border-stone-100 text-sm text-stone-600 leading-relaxed max-w-[85%]" class="bg-white p-4 rounded-2xl rounded-tl-none shadow-sm border border-stone-100 text-sm text-stone-600 leading-relaxed max-w-[85%]"
> >
<p>基于你冰箱里的食材上次记录推荐</p> <p>示例输出会像这样</p>
<p class="font-bold text-stone-800 mt-2 mb-1">🥑 牛油果虾仁拌饭</p> <ul class="list-disc list-inside text-xs space-y-1 marker:text-orange-400 mt-2">
<ul class="list-disc list-inside text-xs space-y-1 marker:text-orange-400"> <li>餐饮¥428集中在晚餐和外卖</li>
<li>热量 450 kcal</li> <li>通勤¥156工作日为主</li>
<li>耗时10 分钟</li> <li>一般支出¥98主要是零碎网购和订阅扣费</li>
<li>成本 ¥18</li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
<!-- Input Area -->
<div class="mt-2 relative shrink-0"> <div class="mt-2 relative shrink-0">
<input <input
type="text" type="text"
placeholder="输入消息..." placeholder="输入想分析的问题..."
class="w-full pl-5 pr-12 py-3.5 rounded-full bg-white border border-stone-100 focus:outline-none focus:border-orange-300 focus:ring-4 focus:ring-orange-50 transition text-sm shadow-sm text-stone-700 font-medium placeholder-stone-300" class="w-full pl-5 pr-12 py-3.5 rounded-full bg-white border border-stone-100 focus:outline-none focus:border-orange-300 focus:ring-4 focus:ring-orange-50 transition text-sm shadow-sm text-stone-700 font-medium placeholder-stone-300"
/> />
<button <button
@@ -87,4 +81,3 @@
</div> </div>
</div> </div>
</template> </template>

View File

@@ -19,13 +19,15 @@ const { totalIncome, totalExpense, todaysIncome, todaysExpense, latestTransactio
const { notifications, confirmNotification, dismissNotification, processingId, syncNotifications } = const { notifications, confirmNotification, dismissNotification, processingId, syncNotifications } =
useTransactionEntry() useTransactionEntry()
const defaultAvatar = '/default-avatar.svg'
let appStateListener = null let appStateListener = null
onMounted(async () => { onMounted(async () => {
await bootstrapApp() await bootstrapApp()
await syncNotifications() await syncNotifications()
try { try {
// App 浠庡悗鍙板洖鍒板墠鍙版椂锛岃嚜鍔ㄥ埛鏂版湰鍦拌处鏈拰閫氱煡闃熷垪 // App 回到前台时,刷新本地账本和待处理通知。
appStateListener = await App.addListener('appStateChange', async ({ isActive }) => { appStateListener = await App.addListener('appStateChange', async ({ isActive }) => {
if (isActive) { if (isActive) {
await transactionStore.hydrateTransactions() await transactionStore.hydrateTransactions()
@@ -33,7 +35,7 @@ onMounted(async () => {
} }
}) })
} catch (error) { } 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 monthlyBudget = computed(() => settingsStore.monthlyBudget || 0)
const budgetResetCycle = computed(() => settingsStore.budgetResetCycle || 'monthly') const budgetResetCycle = computed(() => settingsStore.budgetResetCycle || 'monthly')
const currentDayLabel = computed( const currentDayLabel = computed(() =>
() => new Intl.DateTimeFormat('zh-CN', {
new Intl.DateTimeFormat('zh-CN', { month: 'long',
month: 'long', day: 'numeric',
day: 'numeric', weekday: 'long',
weekday: 'long', }).format(new Date()),
}).format(new Date()),
) )
const periodStart = computed(() => { const periodStart = computed(() => {
const cycle = budgetResetCycle.value const cycle = budgetResetCycle.value
const now = new Date() const now = new Date()
if (cycle === 'weekly') { if (cycle === 'weekly') {
const day = now.getDay() || 7 // 鍛ㄤ竴=1锛屽懆鏃?7 const day = now.getDay() || 7
const diff = day - 1 return new Date(now.getFullYear(), now.getMonth(), now.getDate() - (day - 1))
return new Date(now.getFullYear(), now.getMonth(), now.getDate() - diff)
} }
if (cycle === 'monthly') { if (cycle === 'monthly') {
const day = settingsStore.budgetMonthlyResetDay || 1 const day = settingsStore.budgetMonthlyResetDay || 1
const safeDay = Math.min(Math.max(Number(day) || 1, 1), 28) const safeDay = Math.min(Math.max(Number(day) || 1, 1), 28)
const candidate = new Date(now.getFullYear(), now.getMonth(), safeDay) const candidate = new Date(now.getFullYear(), now.getMonth(), safeDay)
if (now >= candidate) { if (now >= candidate) return candidate
return candidate
}
return new Date(now.getFullYear(), now.getMonth() - 1, safeDay) return new Date(now.getFullYear(), now.getMonth() - 1, safeDay)
} }
if (cycle === 'custom') { if (cycle === 'custom') {
if (!settingsStore.budgetCustomStartDate) return null if (!settingsStore.budgetCustomStartDate) return null
return new Date(settingsStore.budgetCustomStartDate) return new Date(settingsStore.budgetCustomStartDate)
} }
// none锛氫笉闄愬埗鍛ㄦ湡
return null return null
}) })
const periodExpense = computed(() => { const periodExpense = computed(() => {
const cycle = budgetResetCycle.value const cycle = budgetResetCycle.value
const start = periodStart.value const start = periodStart.value
if (cycle === 'none' || !start) { if (cycle === 'none' || !start) return totalExpense.value || 0
return totalExpense.value || 0
}
return (sortedTransactions.value || []).reduce((sum, tx) => { return (sortedTransactions.value || []).reduce((sum, tx) => {
if (tx.amount >= 0) return sum if (tx.amount >= 0) return sum
const date = new Date(tx.date) return new Date(tx.date) >= start ? sum + tx.amount : sum
return date >= start ? sum + tx.amount : sum
}, 0) }, 0)
}) })
@@ -108,17 +107,22 @@ const remainingBudget = computed(() =>
const todayIncomeValue = computed(() => todaysIncome.value || 0) const todayIncomeValue = computed(() => todaysIncome.value || 0)
const todayExpenseValue = computed(() => Math.abs(todaysExpense.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 cycleLabel = computed(() => {
const formatAmount = (value) => if (budgetResetCycle.value === 'weekly') return '每周重置'
`${value >= 0 ? '+' : '-'}${Math.abs(value || 0).toFixed(2)}` 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) => const formatTime = (value) =>
new Intl.DateTimeFormat('zh-CN', { hour: '2-digit', minute: '2-digit' }).format( new Intl.DateTimeFormat('zh-CN', { hour: '2-digit', minute: '2-digit' }).format(
new Date(value), new Date(value),
) )
const recentTransactions = computed(() => latestTransactions.value.slice(0, 4))
const goSettings = () => { const goSettings = () => {
router.push({ name: 'settings' }) router.push({ name: 'settings' })
} }
@@ -144,18 +148,15 @@ const openTransactionDetail = (tx) => {
<p class="text-xs text-stone-400 font-bold tracking-wider"> <p class="text-xs text-stone-400 font-bold tracking-wider">
{{ currentDayLabel }} {{ currentDayLabel }}
</p> </p>
<h1 class="text-2xl font-extrabold text-stone-800">浠婃棩姒傝</h1> <h1 class="text-2xl font-extrabold text-stone-800">今日概览</h1>
</div> </div>
<div <div
class="w-10 h-10 rounded-full bg-orange-50 p-0.5 cursor-pointer border border-orange-100" class="w-10 h-10 rounded-full bg-orange-50 p-0.5 cursor-pointer border border-orange-100"
@click="goSettings" @click="goSettings"
> >
<img <img
:src=" :src="settingsStore.profileAvatar || defaultAvatar"
settingsStore.profileAvatar || class="rounded-full w-full h-full object-cover"
'https://api.dicebear.com/7.x/avataaars/svg?seed=Echo'
"
class="rounded-full"
alt="User" alt="User"
/> />
</div> </div>
@@ -170,7 +171,7 @@ const openTransactionDetail = (tx) => {
<div class="relative z-10 flex flex-col h-full justify-between"> <div class="relative z-10 flex flex-col h-full justify-between">
<div> <div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<p class="text-white/80 text-xs font-bold tracking-widest uppercase">湀缁撲綑</p> <p class="text-white/80 text-xs font-bold tracking-widest uppercase">当前结余</p>
<button <button
class="w-8 h-8 rounded-full bg-white/20 flex items-center justify-center backdrop-blur-md hover:bg-white/30 transition" class="w-8 h-8 rounded-full bg-white/20 flex items-center justify-center backdrop-blur-md hover:bg-white/30 transition"
> >
@@ -185,13 +186,13 @@ const openTransactionDetail = (tx) => {
<div class="flex gap-8"> <div class="flex gap-8">
<div> <div>
<p class="text-white/70 text-xs mb-0.5 flex items-center gap-1"> <p class="text-white/70 text-xs mb-0.5 flex items-center gap-1">
<i class="ph-bold ph-arrow-down-left" /> 鏀跺叆 <i class="ph-bold ph-arrow-down-left" /> 收入
</p> </p>
<p class="font-bold text-lg">{{ formatCurrency(totalIncome) }}</p> <p class="font-bold text-lg">{{ formatCurrency(totalIncome) }}</p>
</div> </div>
<div> <div>
<p class="text-white/70 text-xs mb-0.5 flex items-center gap-1"> <p class="text-white/70 text-xs mb-0.5 flex items-center gap-1">
<i class="ph-bold ph-arrow-up-right" /> <i class="ph-bold ph-arrow-up-right" /> 支出
</p> </p>
<p class="font-bold text-lg">{{ formatCurrency(Math.abs(totalExpense)) }}</p> <p class="font-bold text-lg">{{ formatCurrency(Math.abs(totalExpense)) }}</p>
</div> </div>
@@ -201,7 +202,7 @@ const openTransactionDetail = (tx) => {
<div class="bg-white rounded-2xl p-4 shadow-sm border border-stone-100"> <div class="bg-white rounded-2xl p-4 shadow-sm border border-stone-100">
<div class="flex justify-between items-center mb-2"> <div class="flex justify-between items-center mb-2">
<span class="text-xs font-bold text-stone-500">湡棰勭畻</span> <span class="text-xs font-bold text-stone-500">本期预算</span>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-xs font-bold text-stone-800"> <span class="text-xs font-bold text-stone-800">
{{ Math.round(budgetUsage * 100) }}% {{ Math.round(budgetUsage * 100) }}%
@@ -210,7 +211,8 @@ const openTransactionDetail = (tx) => {
class="text-[10px] text-stone-400 underline-offset-2 hover:text-stone-600" class="text-[10px] text-stone-400 underline-offset-2 hover:text-stone-600"
@click="goSettings" @click="goSettings"
> >
鍘昏? </button> 去设置
</button>
</div> </div>
</div> </div>
<div class="w-full bg-stone-100 rounded-full h-2.5 overflow-hidden"> <div class="w-full bg-stone-100 rounded-full h-2.5 overflow-hidden">
@@ -220,37 +222,27 @@ const openTransactionDetail = (tx) => {
/> />
</div> </div>
<div class="flex justify-between mt-2 text-[10px] text-stone-400"> <div class="flex justify-between mt-2 text-[10px] text-stone-400">
<span>宸茬敤 {{ formatCurrency(Math.abs(periodExpense)) }}</span> <span>已用 {{ formatCurrency(Math.abs(periodExpense)) }}</span>
<span> {{ formatCurrency(remainingBudget) }}</span> <span>剩余 {{ formatCurrency(remainingBudget) }}</span>
</div> </div>
<div class="flex justify-between mt-1 text-[10px] text-stone-400"> <div class="flex justify-between mt-1 text-[10px] text-stone-400">
<span> <span>周期{{ cycleLabel }}</span>
周期
{{
budgetResetCycle === 'weekly'
? '每周重置'
: budgetResetCycle === 'none'
? '不重置'
: budgetResetCycle === 'custom'
? '自定义'
: '每月重置'
}}
</span>
<button <button
class="text-[10px] text-stone-400 underline-offset-2 hover:text-stone-600" class="text-[10px] text-stone-400 underline-offset-2 hover:text-stone-600"
@click="goSettings" @click="goSettings"
> >
鍘昏? </button> 去设置
</button>
</div> </div>
</div> </div>
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3">
<div class="bg-white rounded-2xl p-4 border border-stone-100 shadow-sm"> <div class="bg-white rounded-2xl p-4 border border-stone-100 shadow-sm">
<p class="text-xs text-stone-400 mb-2">浠婃棩鏀跺叆</p> <p class="text-xs text-stone-400 mb-2">今日收入</p>
<p class="text-xl font-bold text-emerald-600">{{ formatCurrency(todayIncomeValue) }}</p> <p class="text-xl font-bold text-emerald-600">{{ formatCurrency(todayIncomeValue) }}</p>
</div> </div>
<div class="bg-white rounded-2xl p-4 border border-stone-100 shadow-sm"> <div class="bg-white rounded-2xl p-4 border border-stone-100 shadow-sm">
<p class="text-xs text-stone-400 mb-2">浠婃棩鏀</p> <p class="text-xs text-stone-400 mb-2">今日支出</p>
<p class="text-xl font-bold text-rose-600">- {{ formatCurrency(todayExpenseValue) }}</p> <p class="text-xl font-bold text-rose-600">- {{ formatCurrency(todayExpenseValue) }}</p>
</div> </div>
</div> </div>
@@ -262,7 +254,7 @@ const openTransactionDetail = (tx) => {
class="text-xs font-bold text-gradient-warm" class="text-xs font-bold text-gradient-warm"
@click="goList" @click="goList"
> >
鏌ョ湅鍏ㄩ儴 查看全部
</button> </button>
</div> </div>
<div v-if="recentTransactions.length" class="space-y-4"> <div v-if="recentTransactions.length" class="space-y-4">
@@ -300,22 +292,19 @@ const openTransactionDetail = (tx) => {
</div> </div>
</div> </div>
<div v-else class="text-sm text-stone-400 text-center py-4"> <div v-else class="text-sm text-stone-400 text-center py-4">
杩樻病鏈夎褰曪紝蹇幓娣诲姞绗竴绗旀秷璐瑰惂锝? 还没有记录去添加第一笔流水吧
</div> </div>
</div> </div>
<div class="space-y-3"> <div class="space-y-3">
<div class="flex justify-between items-end"> <div class="flex justify-between items-end">
<h3 class="font-bold text-lg text-stone-700">寰呯璁ら氱煡</h3> <h3 class="font-bold text-lg text-stone-700">待确认通知</h3>
<span class="text-[10px] bg-orange-100 text-orange-600 px-2 py-1 rounded-full font-bold"> <span class="text-[10px] bg-orange-100 text-orange-600 px-2 py-1 rounded-full font-bold">
姩鎹曡幏 自动捕获
</span> </span>
</div> </div>
<div <div v-if="notifications.length" class="space-y-3">
v-if="notifications.length"
class="space-y-3"
>
<div <div
v-for="notif in notifications" v-for="notif in notifications"
:key="notif.id" :key="notif.id"
@@ -340,60 +329,38 @@ const openTransactionDetail = (tx) => {
:disabled="processingId === notif.id" :disabled="processingId === notif.id"
@click="confirmNotification(notif.id)" @click="confirmNotification(notif.id)"
> >
鍏ヨ处 入账
</button> </button>
<button <button
class="text-[11px] text-stone-400" class="text-[11px] text-stone-400"
@click="dismissNotification(notif.id)" @click="dismissNotification(notif.id)"
> >
蹇界暐 忽略
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div v-else class="text-sm text-stone-400 text-center py-4 border border-dashed border-stone-100 rounded-2xl"> <div v-else class="text-sm text-stone-400 text-center py-4 border border-dashed border-stone-100 rounded-2xl">
鏆傛棤鏂伴氱煡锛屼韩鍙楁參鐢熸椿 鈽曪笍 暂无新通知先安心生活
</div> </div>
</div> </div>
<div class="grid grid-cols-2 gap-4"> <div
class="bg-white p-5 rounded-3xl shadow-sm border border-stone-100 flex flex-col justify-between h-32 relative overflow-hidden group"
@click="goAnalysis"
>
<div <div
class="bg-white p-5 rounded-3xl shadow-sm border border-stone-100 flex flex-col justify-between h-32 relative overflow-hidden group" class="absolute right-[-10px] top-[-10px] w-20 h-20 bg-purple-50 rounded-full blur-xl group-hover:bg-purple-100 transition"
@click="goAnalysis" />
<div
class="w-10 h-10 rounded-full bg-purple-100 text-purple-600 flex items-center justify-center z-10"
> >
<div <i class="ph-fill ph-robot text-xl" />
class="absolute right-[-10px] top-[-10px] w-20 h-20 bg-purple-50 rounded-full blur-xl group-hover:bg-purple-100 transition"
/>
<div
class="w-10 h-10 rounded-full bg-purple-100 text-purple-600 flex items-center justify-center z-10"
>
<i class="ph-fill ph-robot text-xl" />
</div>
<div class="z-10">
<h4 class="font-bold text-stone-700">AI 椤鹃棶</h4>
<p class="text-xs text-stone-400 mt-1">鍒嗘瀽鎴戠殑娑堣垂涔犳儻</p>
</div>
</div> </div>
<div class="z-10">
<div <h4 class="font-bold text-stone-700">AI 财务顾问</h4>
class="bg-white p-5 rounded-3xl shadow-sm border border-stone-100 flex flex-col justify-between h-32 relative overflow-hidden group" <p class="text-xs text-stone-400 mt-1">查看消费总结自动分类建议和可执行的预算提醒</p>
>
<div
class="absolute right-[-10px] top-[-10px] w-20 h-20 bg-emerald-50 rounded-full blur-xl group-hover:bg-emerald-100 transition"
/>
<div
class="w-10 h-10 rounded-full bg-emerald-100 text-emerald-600 flex items-center justify-center z-10"
>
<i class="ph-fill ph-carrot text-xl" />
</div>
<div class="z-10">
<h4 class="font-bold text-stone-700">噺绠</h4>
<p class="text-xs text-stone-400 mt-1">浠婃棩鍓 750 kcal</p>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -94,14 +94,7 @@ const hasAnyCategoryBudget = computed(() =>
Object.values(categoryBudgets.value || {}).some((v) => (v || 0) > 0), Object.values(categoryBudgets.value || {}).some((v) => (v || 0) > 0),
) )
const budgetCategories = [ const budgetCategories = BUDGET_CATEGORY_OPTIONS
{ value: 'Food', label: '餐饮' },
{ value: 'Transport', label: '通勤' },
{ value: 'Health', label: '健康' },
{ value: 'Groceries', label: '买菜' },
{ value: 'Entertainment', label: '娱乐' },
{ value: 'Uncategorized', label: '其他' },
]
const handleAvatarClick = () => { const handleAvatarClick = () => {
if (fileInputRef.value) { if (fileInputRef.value) {
fileInputRef.value.click() fileInputRef.value.click()
@@ -155,7 +148,7 @@ onMounted(() => {
<img <img
:src=" :src="
settingsStore.profileAvatar || settingsStore.profileAvatar ||
'https://api.dicebear.com/7.x/avataaars/svg?seed=Echo' '/default-avatar.svg'
" "
alt="Avatar" alt="Avatar"
class="w-16 h-16 rounded-full bg-stone-100 object-cover cursor-pointer" class="w-16 h-16 rounded-full bg-stone-100 object-cover cursor-pointer"
@@ -390,9 +383,9 @@ onMounted(() => {
<i class="ph-fill ph-magic-wand" /> <i class="ph-fill ph-magic-wand" />
</div> </div>
<div> <div>
<p class="font-bold text-stone-700 text-sm">AI 自动分类预留</p> <p class="font-bold text-stone-700 text-sm">AI 自动分类与标签预留</p>
<p class="text-[11px] text-stone-400 mt-0.5"> <p class="text-[11px] text-stone-400 mt-0.5">
开启后未来会优先使用云端 AI 对商户进行分类与标签分析 开启后未来会自动补全分类标签并生成可复用的商户画像
</p> </p>
</div> </div>
</div> </div>
@@ -405,7 +398,7 @@ onMounted(() => {
</button> </button>
</div> </div>
<p v-if="aiAutoCategoryEnabled" class="px-4 pb-1 text-[11px] text-purple-600"> <p v-if="aiAutoCategoryEnabled" class="px-4 pb-1 text-[11px] text-purple-600">
当前版本仅记录偏好后续接入云端 AI 后会自动生效 当前版本仅记录偏好后续接入 AI 能力后会自动生效
</p> </p>
</div> </div>
</div> </div>
@@ -460,3 +453,4 @@ onMounted(() => {
</div> </div>
</template> </template>

View File

@@ -1,11 +1,10 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
// 从 package.json 注入版本号,便于前端展示 // 从 package.json 注入版本号,供前端设置页展示
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const appVersion = (process && process.env && process.env.npm_package_version) || '0.0.0' const appVersion = (process && process.env && process.env.npm_package_version) || '0.0.0'
// https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue()],
define: { define: {