fix: 修复文件编码问题,确保导入语句格式正确
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
package com.echo.app.notification
|
||||
package com.echo.app.notification
|
||||
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
16
public/default-avatar.svg
Normal file
16
public/default-avatar.svg
Normal 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>
|
||||
@@ -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 = [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const notificationRules = [
|
||||
const notificationRules = [
|
||||
{
|
||||
id: 'alipay-expense',
|
||||
label: '支付宝消费',
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import notificationRulesSource from '../config/notificationRules.js'
|
||||
import notificationRulesSource from '../config/notificationRules.js'
|
||||
|
||||
const fallbackRules = notificationRulesSource.map((rule) => ({
|
||||
...rule,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
// 分析页目前为静态占位,实现基础对话 UI,后续接入真实 AI 逻辑
|
||||
<script setup>
|
||||
// 分析页当前仍是占位页,先展示未来 AI 财务顾问的交互方向。
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -12,13 +12,11 @@
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
|
||||
<!-- Chat Area -->
|
||||
<div class="flex-1 overflow-y-auto space-y-5 pr-1 pb-2">
|
||||
<!-- Bot Message -->
|
||||
<div class="flex gap-3">
|
||||
<div
|
||||
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
|
||||
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="flex items-center gap-2 mb-2">
|
||||
<i class="ph-fill ph-warning text-orange-400" />
|
||||
<span class="font-bold text-stone-700 text-xs">高热量警告</span>
|
||||
<i class="ph-fill ph-chart-line text-orange-400" />
|
||||
<span class="font-bold text-stone-700 text-xs">本月摘要</span>
|
||||
</div>
|
||||
<p class="text-xs">
|
||||
周二和周四的晚餐摄入热量超标 30%,主要是因为“炸鸡”和“奶茶”。
|
||||
餐饮与通勤是主要支出来源,夜间消费频率明显高于白天,预算消耗速度偏快。
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-3">建议今晚尝试清淡饮食,比如蔬菜沙拉或三文鱼。</p>
|
||||
<p class="mt-3">下一步会支持自动分类、标签建议、异常支出提醒和自然语言问答。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Message -->
|
||||
<div class="flex gap-3 flex-row-reverse">
|
||||
<div
|
||||
class="bg-stone-700 text-white p-4 rounded-2xl rounded-tr-none shadow-lg text-sm max-w-[85%]"
|
||||
>
|
||||
如果是自己做饭,有什么推荐的食谱吗?要简单的。
|
||||
帮我总结这周花钱最多的三类项目。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bot Message 2 -->
|
||||
<div class="flex gap-3">
|
||||
<div
|
||||
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
|
||||
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 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">
|
||||
<li>热量:约 450 kcal</li>
|
||||
<li>耗时:10 分钟</li>
|
||||
<li>成本:约 ¥18</li>
|
||||
<p>示例输出会像这样:</p>
|
||||
<ul class="list-disc list-inside text-xs space-y-1 marker:text-orange-400 mt-2">
|
||||
<li>餐饮:¥428,集中在晚餐和外卖。</li>
|
||||
<li>通勤:¥156,工作日为主。</li>
|
||||
<li>一般支出:¥98,主要是零碎网购和订阅扣费。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Input Area -->
|
||||
<div class="mt-2 relative shrink-0">
|
||||
<input
|
||||
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"
|
||||
/>
|
||||
<button
|
||||
@@ -87,4 +81,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
<p class="text-xs text-stone-400 font-bold tracking-wider">
|
||||
{{ currentDayLabel }}
|
||||
</p>
|
||||
<h1 class="text-2xl font-extrabold text-stone-800">浠婃棩姒傝</h1>
|
||||
<h1 class="text-2xl font-extrabold text-stone-800">今日概览</h1>
|
||||
</div>
|
||||
<div
|
||||
class="w-10 h-10 rounded-full bg-orange-50 p-0.5 cursor-pointer border border-orange-100"
|
||||
@click="goSettings"
|
||||
>
|
||||
<img
|
||||
:src="
|
||||
settingsStore.profileAvatar ||
|
||||
'https://api.dicebear.com/7.x/avataaars/svg?seed=Echo'
|
||||
"
|
||||
class="rounded-full"
|
||||
:src="settingsStore.profileAvatar || defaultAvatar"
|
||||
class="rounded-full w-full h-full object-cover"
|
||||
alt="User"
|
||||
/>
|
||||
</div>
|
||||
@@ -170,7 +171,7 @@ const openTransactionDetail = (tx) => {
|
||||
<div class="relative z-10 flex flex-col h-full justify-between">
|
||||
<div>
|
||||
<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
|
||||
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>
|
||||
<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 class="font-bold text-lg">{{ formatCurrency(totalIncome) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<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 class="font-bold text-lg">{{ formatCurrency(Math.abs(totalExpense)) }}</p>
|
||||
</div>
|
||||
@@ -201,7 +202,7 @@ const openTransactionDetail = (tx) => {
|
||||
|
||||
<div class="bg-white rounded-2xl p-4 shadow-sm border border-stone-100">
|
||||
<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">
|
||||
<span class="text-xs font-bold text-stone-800">
|
||||
{{ Math.round(budgetUsage * 100) }}%
|
||||
@@ -210,7 +211,8 @@ const openTransactionDetail = (tx) => {
|
||||
class="text-[10px] text-stone-400 underline-offset-2 hover:text-stone-600"
|
||||
@click="goSettings"
|
||||
>
|
||||
鍘昏缃? </button>
|
||||
去设置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full bg-stone-100 rounded-full h-2.5 overflow-hidden">
|
||||
@@ -220,37 +222,27 @@ const openTransactionDetail = (tx) => {
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-between mt-2 text-[10px] text-stone-400">
|
||||
<span>宸茬敤 {{ formatCurrency(Math.abs(periodExpense)) }}</span>
|
||||
<span>鍓╀綑 {{ formatCurrency(remainingBudget) }}</span>
|
||||
<span>已用 {{ formatCurrency(Math.abs(periodExpense)) }}</span>
|
||||
<span>剩余 {{ formatCurrency(remainingBudget) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between mt-1 text-[10px] text-stone-400">
|
||||
<span>
|
||||
周期:
|
||||
{{
|
||||
budgetResetCycle === 'weekly'
|
||||
? '每周重置'
|
||||
: budgetResetCycle === 'none'
|
||||
? '不重置'
|
||||
: budgetResetCycle === 'custom'
|
||||
? '自定义'
|
||||
: '每月重置'
|
||||
}}
|
||||
</span>
|
||||
<span>周期:{{ cycleLabel }}</span>
|
||||
<button
|
||||
class="text-[10px] text-stone-400 underline-offset-2 hover:text-stone-600"
|
||||
@click="goSettings"
|
||||
>
|
||||
鍘昏缃? </button>
|
||||
去设置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -262,7 +254,7 @@ const openTransactionDetail = (tx) => {
|
||||
class="text-xs font-bold text-gradient-warm"
|
||||
@click="goList"
|
||||
>
|
||||
鏌ョ湅鍏ㄩ儴
|
||||
查看全部
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="recentTransactions.length" class="space-y-4">
|
||||
@@ -300,22 +292,19 @@ const openTransactionDetail = (tx) => {
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-sm text-stone-400 text-center py-4">
|
||||
杩樻病鏈夎褰曪紝蹇幓娣诲姞绗竴绗旀秷璐瑰惂锝?
|
||||
还没有记录,去添加第一笔流水吧。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="notifications.length"
|
||||
class="space-y-3"
|
||||
>
|
||||
<div v-if="notifications.length" class="space-y-3">
|
||||
<div
|
||||
v-for="notif in notifications"
|
||||
:key="notif.id"
|
||||
@@ -340,60 +329,38 @@ const openTransactionDetail = (tx) => {
|
||||
:disabled="processingId === notif.id"
|
||||
@click="confirmNotification(notif.id)"
|
||||
>
|
||||
鍏ヨ处
|
||||
入账
|
||||
</button>
|
||||
<button
|
||||
class="text-[11px] text-stone-400"
|
||||
@click="dismissNotification(notif.id)"
|
||||
>
|
||||
蹇界暐
|
||||
忽略
|
||||
</button>
|
||||
</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>
|
||||
</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
|
||||
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"
|
||||
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"
|
||||
>
|
||||
<div
|
||||
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>
|
||||
<i class="ph-fill ph-robot text-xl" />
|
||||
</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"
|
||||
>
|
||||
<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 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>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
<img
|
||||
:src="
|
||||
settingsStore.profileAvatar ||
|
||||
'https://api.dicebear.com/7.x/avataaars/svg?seed=Echo'
|
||||
'/default-avatar.svg'
|
||||
"
|
||||
alt="Avatar"
|
||||
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" />
|
||||
</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">
|
||||
开启后,未来会优先使用云端 AI 对商户进行分类与标签分析。
|
||||
开启后,未来会自动补全分类、标签,并生成可复用的商户画像。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -405,7 +398,7 @@ onMounted(() => {
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="aiAutoCategoryEnabled" class="px-4 pb-1 text-[11px] text-purple-600">
|
||||
当前版本仅记录偏好,后续接入云端 AI 后会自动生效。
|
||||
当前版本仅记录偏好,后续接入 AI 能力后会自动生效。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -460,3 +453,4 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user