feat: 添加通知设置功能,支持用户开启/关闭通知捕获,优化设置页功能显示,升级kotlin构建版本
This commit is contained in:
@@ -18,7 +18,7 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// <EFBFBD><EFBFBD>ʽͳһ Java / Kotlin <EFBFBD>ı<EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><EFBFBD><EFBFBD>汾<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Capacitor <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD>£<EFBFBD>Java 21<EFBFBD><EFBFBD>
|
// 显式统一 Java / Kotlin 的编译目标版本,保持与 Capacitor 生成配置一致(Java 21)
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_21
|
sourceCompatibility JavaVersion.VERSION_21
|
||||||
targetCompatibility JavaVersion.VERSION_21
|
targetCompatibility JavaVersion.VERSION_21
|
||||||
@@ -53,3 +53,4 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply from: 'capacitor.build.gradle'
|
apply from: 'capacitor.build.gradle'
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package com.echo.app.notification
|
package com.echo.app.notification
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.provider.Settings
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import com.getcapacitor.JSArray
|
import com.getcapacitor.JSArray
|
||||||
import com.getcapacitor.JSObject
|
import com.getcapacitor.JSObject
|
||||||
@@ -12,7 +13,7 @@ import com.getcapacitor.PluginCall
|
|||||||
import com.getcapacitor.PluginMethod
|
import com.getcapacitor.PluginMethod
|
||||||
import com.getcapacitor.annotation.CapacitorPlugin
|
import com.getcapacitor.annotation.CapacitorPlugin
|
||||||
|
|
||||||
// Capacitor <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD>˱<EFBFBD>¶֪ͨȨ<EFBFBD><EFBFBD><EFBFBD>顢<EFBFBD><EFBFBD><EFBFBD>ж<EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD>Լ<EFBFBD>֪ͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD>
|
// Capacitor 插件:向前端暴露通知权限、通知队列以及通知事件
|
||||||
@CapacitorPlugin(name = "NotificationBridge")
|
@CapacitorPlugin(name = "NotificationBridge")
|
||||||
class NotificationBridgePlugin : Plugin() {
|
class NotificationBridgePlugin : Plugin() {
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ class NotificationBridgePlugin : Plugin() {
|
|||||||
override fun load() {
|
override fun load() {
|
||||||
super.load()
|
super.load()
|
||||||
|
|
||||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> NotificationBridgeService <EFBFBD><EFBFBD>Ӧ<EFBFBD><EFBFBD><EFBFBD>ڹ㲥<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ת<EFBFBD><EFBFBD>Ϊ JS <EFBFBD>¼<EFBFBD>
|
// 监听 NotificationBridgeService 发送的应用内广播,并转发为 JS 事件
|
||||||
val filter = IntentFilter("com.echo.app.NOTIFICATION_POSTED")
|
val filter = IntentFilter("com.echo.app.NOTIFICATION_POSTED")
|
||||||
notificationPostedReceiver = object : BroadcastReceiver() {
|
notificationPostedReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
@@ -52,10 +53,19 @@ class NotificationBridgePlugin : Plugin() {
|
|||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun requestPermission(call: PluginCall) {
|
fun requestPermission(call: PluginCall) {
|
||||||
// <EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>״̬<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ת<EFBFBD><EFBFBD>ϵͳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
// 当前仅返回权限状态,实际的引导文案和跳转由前端控制
|
||||||
hasPermission(call)
|
hasPermission(call)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun openNotificationSettings(call: PluginCall) {
|
||||||
|
// 跳转到系统的“通知使用权”设置页面,引导用户手动开启监听权限
|
||||||
|
val intent = Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
context.startActivity(intent)
|
||||||
|
call.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun getPendingNotifications(call: PluginCall) {
|
fun getPendingNotifications(call: PluginCall) {
|
||||||
val queue = NotificationStorage.list(context)
|
val queue = NotificationStorage.list(context)
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<!-- <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӧ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>⣬<EFBFBD><EFBFBD><EFBFBD>ں<EFBFBD> Capacitor Ĭ<><C4AC><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD> -->
|
<!-- 应用主主题:为 Capacitor WebView 启用沉浸式状态栏和导航栏 -->
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
<item name="android:windowFullscreen">false</item>
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||||
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- <EFBFBD><EFBFBD><EFBFBD><EFBFBD> Activity ר<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>⣨Ŀǰ<EFBFBD><EFBFBD> AppTheme һ<>£<EFBFBD><C2A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɽ<EFBFBD><C9BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҳ<EFBFBD><D2B3> -->
|
<!-- 启动 Activity 专用主题(当前与 AppTheme 一致,后续可单独定制) -->
|
||||||
<style name="AppTheme.NoActionBarLaunch" parent="AppTheme" />
|
<style name="AppTheme.NoActionBarLaunch" parent="AppTheme" />
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.8.22'
|
ext.kotlin_version = '2.0.0'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.0.0'
|
classpath 'com.android.tools.build:gradle:8.4.2'
|
||||||
classpath 'com.google.gms:google-services:4.3.15'
|
classpath 'com.google.gms:google-services:4.3.15'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
@@ -26,3 +26,5 @@ allprojects {
|
|||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
10
src/App.vue
10
src/App.vue
@@ -1,4 +1,4 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from 'vue-router'
|
||||||
import BottomDock from './components/BottomDock.vue'
|
import BottomDock from './components/BottomDock.vue'
|
||||||
@@ -12,20 +12,18 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 整体应用容器:居中展示移动端画布 -->
|
<!-- 整体应用容器:居中展示移动端画布,配合沉浸式状态栏使用安全区域内边距 -->
|
||||||
<div class="min-h-screen bg-warmOffwhite text-stone-800 flex items-center justify-center">
|
<div class="min-h-screen bg-warmOffwhite text-stone-800 flex items-center justify-center">
|
||||||
<div
|
<div
|
||||||
class="h-screen max-h-[844px] max-w-md w-full mx-auto bg-warmOffwhite shadow-2xl relative overflow-hidden flex flex-col"
|
class="h-screen max-h-[844px] max-w-md w-full mx-auto bg-warmOffwhite shadow-2xl relative overflow-hidden flex flex-col"
|
||||||
|
style="padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom);"
|
||||||
>
|
>
|
||||||
<!-- 顶部状态栏占位 -->
|
|
||||||
<div class="h-8 w-full shrink-0" />
|
|
||||||
|
|
||||||
<!-- 主内容区域:由 vue-router 控制具体页面 -->
|
<!-- 主内容区域:由 vue-router 控制具体页面 -->
|
||||||
<main class="flex-1 overflow-y-auto hide-scrollbar px-5 pt-2 pb-28 relative">
|
<main class="flex-1 overflow-y-auto hide-scrollbar px-5 pt-2 pb-28 relative">
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- 底部 Dock 导航栏 -->
|
<!-- 底部 Dock 导航 -->
|
||||||
<BottomDock />
|
<BottomDock />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
transformNotificationToTransaction,
|
transformNotificationToTransaction,
|
||||||
} from '../services/notificationRuleService'
|
} from '../services/notificationRuleService'
|
||||||
import NotificationBridge, { isNativeNotificationBridgeAvailable } from '../lib/notificationBridge'
|
import NotificationBridge, { isNativeNotificationBridgeAvailable } from '../lib/notificationBridge'
|
||||||
|
import { useSettingsStore } from '../stores/settings'
|
||||||
|
|
||||||
const notifications = ref([])
|
const notifications = ref([])
|
||||||
const processingId = ref('')
|
const processingId = ref('')
|
||||||
@@ -35,6 +36,7 @@ const ensureRulesReady = async () => {
|
|||||||
|
|
||||||
export const useTransactionEntry = () => {
|
export const useTransactionEntry = () => {
|
||||||
const transactionStore = useTransactionStore()
|
const transactionStore = useTransactionStore()
|
||||||
|
const settingsStore = useSettingsStore()
|
||||||
|
|
||||||
// 原生端:在首次同步前做一次权限校验与请求
|
// 原生端:在首次同步前做一次权限校验与请求
|
||||||
const ensureNativePermission = async () => {
|
const ensureNativePermission = async () => {
|
||||||
@@ -83,6 +85,11 @@ export const useTransactionEntry = () => {
|
|||||||
if (syncing.value) return
|
if (syncing.value) return
|
||||||
syncing.value = true
|
syncing.value = true
|
||||||
try {
|
try {
|
||||||
|
// 若用户关闭了通知自动捕获,则不从原生/模拟队列拉取新通知,只清空待确认列表
|
||||||
|
if (!settingsStore.notificationCaptureEnabled) {
|
||||||
|
notifications.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
if (nativeBridgeReady) {
|
if (nativeBridgeReady) {
|
||||||
await ensureNativePermission()
|
await ensureNativePermission()
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/stores/settings.js
Normal file
31
src/stores/settings.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export const useSettingsStore = defineStore(
|
||||||
|
'settings',
|
||||||
|
() => {
|
||||||
|
const notificationCaptureEnabled = ref(true)
|
||||||
|
const aiAutoCategoryEnabled = ref(false)
|
||||||
|
|
||||||
|
const setNotificationCaptureEnabled = (value) => {
|
||||||
|
notificationCaptureEnabled.value = !!value
|
||||||
|
}
|
||||||
|
|
||||||
|
const setAiAutoCategoryEnabled = (value) => {
|
||||||
|
aiAutoCategoryEnabled.value = !!value
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
notificationCaptureEnabled,
|
||||||
|
aiAutoCategoryEnabled,
|
||||||
|
setNotificationCaptureEnabled,
|
||||||
|
setAiAutoCategoryEnabled,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
persist: {
|
||||||
|
paths: ['notificationCaptureEnabled', 'aiAutoCategoryEnabled'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@@ -1,66 +1,184 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
// 设置页当前只展示静态 UI,后续会接入真实设置状态和持久化逻辑
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import NotificationBridge, { isNativeNotificationBridgeAvailable } from '../lib/notificationBridge'
|
||||||
|
import { useSettingsStore } from '../stores/settings'
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore()
|
||||||
|
|
||||||
|
const notificationCaptureEnabled = computed({
|
||||||
|
get: () => settingsStore.notificationCaptureEnabled,
|
||||||
|
set: (value) => settingsStore.setNotificationCaptureEnabled(value),
|
||||||
|
})
|
||||||
|
|
||||||
|
const aiAutoCategoryEnabled = computed({
|
||||||
|
get: () => settingsStore.aiAutoCategoryEnabled,
|
||||||
|
set: (value) => settingsStore.setAiAutoCategoryEnabled(value),
|
||||||
|
})
|
||||||
|
|
||||||
|
const nativeBridgeReady = isNativeNotificationBridgeAvailable()
|
||||||
|
const notificationPermissionGranted = ref(true)
|
||||||
|
const checkingPermission = ref(false)
|
||||||
|
|
||||||
|
const checkNotificationPermission = async () => {
|
||||||
|
if (!nativeBridgeReady) return
|
||||||
|
checkingPermission.value = true
|
||||||
|
try {
|
||||||
|
const result = await NotificationBridge.hasPermission()
|
||||||
|
notificationPermissionGranted.value = !!result?.granted
|
||||||
|
} catch {
|
||||||
|
notificationPermissionGranted.value = false
|
||||||
|
} finally {
|
||||||
|
checkingPermission.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleNotificationCapture = async () => {
|
||||||
|
const next = !notificationCaptureEnabled.value
|
||||||
|
notificationCaptureEnabled.value = next
|
||||||
|
if (next && nativeBridgeReady) {
|
||||||
|
await checkNotificationPermission()
|
||||||
|
if (!notificationPermissionGranted.value) {
|
||||||
|
try {
|
||||||
|
await NotificationBridge.openNotificationSettings()
|
||||||
|
} catch {
|
||||||
|
// 插件调用失败时忽略,由下方文案提示用户
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleAiAutoCategory = () => {
|
||||||
|
aiAutoCategoryEnabled.value = !aiAutoCategoryEnabled.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleExportData = () => {
|
||||||
|
window.alert('导出功能即将上线:届时可以一键导出 CSV / Excel。当前版本建议先通过截图或复制方式备份关键信息。')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClearCache = () => {
|
||||||
|
const ok = window.confirm('确认清除缓存吗?这不会删除正式账本数据,但会重置筛选偏好等本地设置。')
|
||||||
|
if (!ok) return
|
||||||
|
try {
|
||||||
|
// 暂时只清理本地设置缓存,避免误删账本
|
||||||
|
localStorage.removeItem('settings')
|
||||||
|
window.alert('已清除设置缓存,部分偏好将在下次启动时重置。')
|
||||||
|
} catch {
|
||||||
|
window.alert('清除缓存失败,可稍后重试。')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
void checkNotificationPermission()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="space-y-6 animate-fade-in pb-10">
|
<div class="space-y-6 animate-fade-in pb-10">
|
||||||
<h2 class="text-2xl font-extrabold text-stone-800">设置</h2>
|
<h2 class="text-2xl font-extrabold text-stone-800">设置</h2>
|
||||||
|
|
||||||
<!-- Profile Section -->
|
<!-- 个人信息 / 简短说明 -->
|
||||||
<div class="bg-white rounded-3xl p-5 flex items-center gap-4 shadow-sm border border-stone-100">
|
<div class="bg-white rounded-3xl p-5 flex items-center gap-4 shadow-sm border border-stone-100">
|
||||||
<img
|
<img
|
||||||
src="https://api.dicebear.com/7.x/avataaars/svg?seed=Felix"
|
src="https://api.dicebear.com/7.x/avataaars/svg?seed=Echo"
|
||||||
alt="Avatar"
|
alt="Avatar"
|
||||||
class="w-16 h-16 rounded-full bg-stone-100"
|
class="w-16 h-16 rounded-full bg-stone-100"
|
||||||
/>
|
/>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h3 class="font-bold text-lg text-stone-800">Alex Chen</h3>
|
<h3 class="font-bold text-lg text-stone-800">Echo 用户</h3>
|
||||||
<p class="text-xs text-stone-400">Pro 会员 (2025.12 到期)</p>
|
<p class="text-xs text-stone-400">本地优先 · 数据只存这台设备</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
class="w-8 h-8 rounded-full bg-stone-50 flex items-center justify-center text-stone-400 active:bg-stone-100 transition"
|
|
||||||
>
|
|
||||||
<i class="ph-bold ph-pencil-simple" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Settings Group 1: Automation & AI -->
|
<!-- 自动化 & AI -->
|
||||||
<section>
|
<section>
|
||||||
<h3 class="text-xs font-bold text-stone-400 uppercase tracking-widest mb-3 ml-2">
|
<h3 class="text-xs font-bold text-stone-400 uppercase tracking-widest mb-3 ml-2">
|
||||||
自动化 & AI
|
自动化 & AI
|
||||||
</h3>
|
</h3>
|
||||||
<div class="bg-white rounded-3xl overflow-hidden shadow-sm border border-stone-100">
|
<div class="bg-white rounded-3xl overflow-hidden shadow-sm border border-stone-100">
|
||||||
<div class="flex items-center justify-between p-4 border-b border-stone-50">
|
<!-- 自动捕获支付通知 -->
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex flex-col gap-1 border-b border-stone-50">
|
||||||
<div
|
<div class="flex items-center justify-between p-4">
|
||||||
class="w-8 h-8 rounded-full bg-blue-50 text-blue-500 flex items-center justify-center"
|
<div class="flex items-center gap-3">
|
||||||
>
|
<div
|
||||||
<i class="ph-fill ph-bell-ringing" />
|
class="w-8 h-8 rounded-full bg-blue-50 text-blue-500 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<i class="ph-fill ph-bell-ringing" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="font-bold text-stone-700 text-sm">自动捕获支付通知</p>
|
||||||
|
<p class="text-[11px] text-stone-400 mt-0.5">
|
||||||
|
监听支付宝 / 微信 / 银行 App 通知,在本机自动生成记账草稿。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="font-bold text-stone-700 text-sm">自动捕获支付通知</span>
|
<button
|
||||||
|
class="w-11 h-6 rounded-full px-0.5 flex items-center transition-all duration-200"
|
||||||
|
:class="
|
||||||
|
notificationCaptureEnabled
|
||||||
|
? 'bg-orange-400 justify-end'
|
||||||
|
: 'bg-stone-200 justify-start'
|
||||||
|
"
|
||||||
|
@click="toggleNotificationCapture"
|
||||||
|
>
|
||||||
|
<div class="w-4 h-4 bg-white rounded-full shadow-sm" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- 开关目前为静态 UI,后续接入真实状态 -->
|
|
||||||
<div class="w-11 h-6 bg-orange-400 rounded-full relative cursor-pointer">
|
<div v-if="notificationCaptureEnabled" class="px-4 pb-4">
|
||||||
<div class="absolute right-1 top-1 w-4 h-4 bg-white rounded-full shadow-sm" />
|
<div
|
||||||
|
v-if="!notificationPermissionGranted"
|
||||||
|
class="bg-orange-50 border border-orange-200 rounded-2xl px-3 py-2 flex items-start gap-2"
|
||||||
|
>
|
||||||
|
<i class="ph-bold ph-warning text-orange-500 mt-0.5" />
|
||||||
|
<div class="text-[11px] text-orange-700 leading-relaxed">
|
||||||
|
<p>系统尚未授予 Echo「通知使用权」,否则无法自动捕获通知。</p>
|
||||||
|
<button
|
||||||
|
class="mt-1 text-[11px] font-bold text-orange-600 underline"
|
||||||
|
@click="NotificationBridge.openNotificationSettings()"
|
||||||
|
>
|
||||||
|
去系统设置开启
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p v-else class="text-[11px] text-emerald-600 flex items-center gap-1">
|
||||||
|
<i class="ph-bold ph-check-circle" />
|
||||||
|
通知权限已开启,Echo 会在收到新通知后自动刷新首页。
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between p-4">
|
|
||||||
<div class="flex items-center gap-3">
|
<!-- AI 自动分类开关(预留) -->
|
||||||
<div
|
<div class="flex flex-col gap-1 p-4">
|
||||||
class="w-8 h-8 rounded-full bg-purple-50 text-purple-500 flex items-center justify-center"
|
<div class="flex items-center justify-between">
|
||||||
>
|
<div class="flex items-center gap-3">
|
||||||
<i class="ph-fill ph-magic-wand" />
|
<div
|
||||||
|
class="w-8 h-8 rounded-full bg-purple-50 text-purple-500 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<i class="ph-fill ph-magic-wand" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<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>
|
</div>
|
||||||
<span class="font-bold text-stone-700 text-sm">AI 自动分类</span>
|
<button
|
||||||
</div>
|
class="w-11 h-6 rounded-full px-0.5 flex items-center transition-all duration-200"
|
||||||
<div class="w-11 h-6 bg-orange-400 rounded-full relative cursor-pointer">
|
:class="aiAutoCategoryEnabled ? 'bg-orange-400 justify-end' : 'bg-stone-200 justify-start'"
|
||||||
<div class="absolute right-1 top-1 w-4 h-4 bg-white rounded-full shadow-sm" />
|
@click="toggleAiAutoCategory"
|
||||||
|
>
|
||||||
|
<div class="w-4 h-4 bg-white rounded-full shadow-sm" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<p v-if="aiAutoCategoryEnabled" class="px-4 pb-1 text-[11px] text-purple-600">
|
||||||
|
当前版本仅记录偏好,后续接入云端 AI 后会自动生效。
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Settings Group 2: General -->
|
<!-- 通用设置 -->
|
||||||
<section>
|
<section>
|
||||||
<h3 class="text-xs font-bold text-stone-400 uppercase tracking-widest mb-3 ml-2">
|
<h3 class="text-xs font-bold text-stone-400 uppercase tracking-widest mb-3 ml-2">
|
||||||
通用
|
通用
|
||||||
@@ -68,6 +186,7 @@
|
|||||||
<div class="bg-white rounded-3xl overflow-hidden shadow-sm border border-stone-100">
|
<div class="bg-white rounded-3xl overflow-hidden shadow-sm border border-stone-100">
|
||||||
<button
|
<button
|
||||||
class="flex w-full items-center justify-between p-4 border-b border-stone-50 active:bg-stone-50 transition text-left"
|
class="flex w-full items-center justify-between p-4 border-b border-stone-50 active:bg-stone-50 transition text-left"
|
||||||
|
@click="handleExportData"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div
|
<div
|
||||||
@@ -75,27 +194,35 @@
|
|||||||
>
|
>
|
||||||
<i class="ph-fill ph-export" />
|
<i class="ph-fill ph-export" />
|
||||||
</div>
|
</div>
|
||||||
<span class="font-bold text-stone-700 text-sm">导出账单数据 (Excel)</span>
|
<div>
|
||||||
|
<p class="font-bold text-stone-700 text-sm">导出账单数据(预留)</p>
|
||||||
|
<p class="text-[11px] text-stone-400 mt-0.5">未来支持导出为 CSV / Excel 文件。</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<i class="ph-bold ph-caret-right text-stone-300" />
|
<i class="ph-bold ph-caret-right text-stone-300" />
|
||||||
</button>
|
</button>
|
||||||
<div class="flex items-center justify-between p-4 active:bg-stone-50 transition">
|
<button
|
||||||
|
class="flex w-full items-center justify-between p-4 active:bg-stone-50 transition text-left"
|
||||||
|
@click="handleClearCache"
|
||||||
|
>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div
|
<div
|
||||||
class="w-8 h-8 rounded-full bg-red-50 text-red-500 flex items-center justify-center"
|
class="w-8 h-8 rounded-full bg-red-50 text-red-500 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<i class="ph-fill ph-trash" />
|
<i class="ph-fill ph-trash" />
|
||||||
</div>
|
</div>
|
||||||
<span class="font-bold text-stone-700 text-sm">清除缓存</span>
|
<div>
|
||||||
|
<p class="font-bold text-stone-700 text-sm">清除缓存(设置)</p>
|
||||||
|
<p class="text-[11px] text-stone-400 mt-0.5">仅清理本地偏好,不影响正式账本数据。</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs text-stone-400">128 MB</span>
|
<span class="text-xs text-stone-400">≈ 数 KB</span>
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="text-center pt-4">
|
<div class="text-center pt-4">
|
||||||
<p class="text-xs text-stone-300">Version 1.0.2 Beta</p>
|
<p class="text-xs text-stone-300">Echo · Local-first Beta</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user