feat: AI 图片记账、自定义交易渠道与淘宝/京东账单导入

- AI 图片记账:多图上传由视觉模型识别,SSE 实时推送进度,提取 rawData,按「类型+金额+时间」检测重复,支持缩略图与来源图片查看
- 自定义交易渠道:新增 t_channel 与渠道管理页,支持新建/改名/删除,并接入统计、导出与 AI 查询/记账/建议提示词
- AI 建议:单条建议新增交易渠道推荐(推荐已有渠道 / AI 新建渠道)
- 账单导入:新增淘宝(xlsx)、京东(CSV)解析器
- 交易:新增批量创建接口,导入时按去重键兜底跳过重复
- 运行目录统一经 AppHome 解析,图片与数据文件落盘规范化
- 前端:AI 记账方式偏好记忆、AI 按钮与输入框对齐、来源图片弹窗支持加载原图
This commit is contained in:
2026-09-21 16:21:58 +08:00
parent 96130d269e
commit 2ade60da42
73 changed files with 5024 additions and 354 deletions
+11 -2
View File
@@ -7,7 +7,7 @@ import {
useMessage, NCard, NForm, NFormItem, NInput, NSwitch, NButton, NSpace, NAlert, NText, NTag, NIcon,
} from 'naive-ui'
import {
SettingsOutline, KeyOutline, LinkOutline, CubeOutline, SaveOutline, FlashOutline, StatsChartOutline,
SettingsOutline, KeyOutline, LinkOutline, CubeOutline, SaveOutline, FlashOutline, StatsChartOutline, CameraOutline,
} from '@vicons/ionicons5'
import { getAiConfig, saveAiConfig, testAiConfig } from '../api/ai'
@@ -20,7 +20,7 @@ const testing = ref(false)
const hasApiKey = ref(false)
// 表单数据
const form = ref({ enabled: false, apiKey: '', baseUrl: 'https://api.deepseek.com', model: 'deepseek-chat' })
const form = ref({ enabled: false, apiKey: '', baseUrl: 'https://api.deepseek.com', model: 'deepseek-chat', visionModel: 'deepseek-flash' })
/** 加载当前用户的 AI 配置 */
async function loadConfig() {
@@ -32,6 +32,7 @@ async function loadConfig() {
apiKey: '',
baseUrl: data.baseUrl || 'https://api.deepseek.com',
model: data.model || 'deepseek-chat',
visionModel: data.visionModel || 'deepseek-flash',
}
hasApiKey.value = !!data.hasApiKey
} catch (e: any) {
@@ -46,6 +47,7 @@ function buildPayload() {
apiKey: form.value.apiKey || undefined,
baseUrl: form.value.baseUrl,
model: form.value.model,
visionModel: form.value.visionModel,
}
}
@@ -145,6 +147,13 @@ onMounted(loadConfig)
</template>
</NInput>
</NFormItem>
<NFormItem :label="t('ai.visionModel')">
<NInput v-model:value="form.visionModel" placeholder="deepseek-flash">
<template #prefix>
<NIcon :component="CameraOutline" />
</template>
</NInput>
</NFormItem>
<NFormItem>
<NSpace justify="end" style="width: 100%;">
<NButton type="primary" :loading="saving" @click="handleSave">