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
+47 -1
View File
@@ -8,6 +8,7 @@ export interface AiConfig {
hasApiKey: boolean
baseUrl: string
model: string
visionModel: string
}
/** AI 建议新建的标签 */
@@ -27,6 +28,8 @@ export interface AiSuggestion {
newCategoryIcon: string | null
tagIds: number[]
tagNames: string[]
channel: string | null
channelName: string | null
newTags: AiNewTag[]
reason: string
}
@@ -37,7 +40,7 @@ export function getAiConfig() {
}
/** 保存 AI 配置,apiKey 留空表示不修改原值 */
export function saveAiConfig(data: { enabled: boolean; apiKey?: string; baseUrl?: string; model?: string }) {
export function saveAiConfig(data: { enabled: boolean; apiKey?: string; baseUrl?: string; model?: string; visionModel?: string }) {
return request.put('/ai/config', data)
}
@@ -77,6 +80,17 @@ export function parseTagByAi(text: string) {
return request.post('/ai/tag', { text }, { timeout: 60000 })
}
/** AI 生成的交易渠道建议 */
export interface AiChannelResult {
name: string
reason: string
}
/** 根据自然语言描述生成交易渠道(调用大模型,超时放宽) */
export function parseChannelByAi(text: string) {
return request.post('/ai/channel', { text }, { timeout: 60000 })
}
/** 根据表单当前内容生成单条分类与标签建议(调用大模型,超时放宽) */
export function suggestOneByAi(data: { type: string; amount?: number | null; channel?: string; remark?: string }) {
return request.post('/ai/suggest-one', data, { timeout: 60000 })
@@ -213,6 +227,14 @@ export interface AiRecordResult {
tagIds: number[]
tagNames: string[]
unmatched: string[]
/** 图片记账时由模型提取的原始字段 JSON 文本,文本记账为 null */
rawData: string | null
/** 来源图片序号(图片记账,从 1 开始),文本记账为 null */
imageIndex: number | null
/** 来源图片ID(图片记账),文本记账为 null */
imageId: number | null
/** 是否疑似重复:与已有记录或本批其它记录的类型、金额、时间一致 */
duplicate: boolean | null
}
/** 将自然语言记账内容解析为交易记录 */
@@ -220,6 +242,30 @@ export function parseRecordByAi(text: string) {
return request.post('/ai/record', { text }, { timeout: 120000 })
}
/** 图片记账解析结果 */
export interface AiRecordImageResult {
/** 保存的图片ID列表,按上传顺序排列 */
imageIds: number[]
summary: string
records: AiRecordResult[]
unmatched: string[]
}
/** 上传一至多张账单/支付截图,由 AI 一次性识别并生成多条交易记录(调用视觉模型,超时放宽) */
export function parseRecordByImage(files: File[], hint?: string) {
const formData = new FormData()
for (const file of files) {
formData.append('files', file)
}
if (hint) formData.append('hint', hint)
return request.post('/ai/record/image', formData, { timeout: 180000 })
}
/** 获取 AI 记账图片(blob,经鉴权头请求,避免 token 出现在 URL;thumbnail=true 返回压缩缩略图,默认原图) */
export function getAiImageBlob(id: number, thumbnail = false) {
return request.get(`/ai/image/${id}${thumbnail ? '?thumbnail=true' : ''}`, { responseType: 'blob', timeout: 30000 })
}
/** AI 使用累计汇总 */
export interface AiUsageSummary {
totalCalls: number
+30
View File
@@ -0,0 +1,30 @@
// 用户自定义交易渠道相关 API 接口:增删改查
import request from './request'
/** 自定义渠道数据结构 */
export interface ChannelItem {
id: number
code: string
name: string
transactionCount: number
}
/** 查询当前用户的自定义渠道列表(含使用账单数) */
export function getChannelList() {
return request.get('/channel')
}
/** 新建自定义渠道 */
export function createChannel(name: string) {
return request.post('/channel', { name })
}
/** 重命名自定义渠道 */
export function renameChannel(id: number, name: string) {
return request.put(`/channel/${id}`, { name })
}
/** 删除自定义渠道(存在账单使用时后端会拒绝) */
export function deleteChannel(id: number) {
return request.delete(`/channel/${id}`)
}
+25 -2
View File
@@ -1,9 +1,13 @@
// 收支记录相关 API 接口:交易记录的增删改查(分页)
import request from './request'
import { setCustomChannels } from '../utils/channel'
/** 获取系统支持的交易渠道编码列表(含账单导入来源与人工交易渠道) */
/** 获取交易渠道列表(内置 + 当前用户自定义),并注册自定义渠道名称供展示解析 */
export function getChannels() {
return request.get('/transaction/channels')
return request.get('/transaction/channels').then((res: any) => {
setCustomChannels(res.data || [])
return res
})
}
/** 分页获取交易记录列表,支持按类型、分类、交易渠道、时间范围、计入统计、金额区间多选筛选 */
@@ -43,6 +47,25 @@ export function createTransaction(data: {
return request.post('/transaction', data)
}
/** 批量创建交易记录,可关联 AI 记账图片 */
export function batchCreateTransactions(data: {
imageId?: number | null
records: {
categoryId?: number | null
type: string
amount: number
channel?: string
remark?: string
transactionTime: string
includeInStats?: boolean
tagIds?: number[]
rawData?: string | null
imageId?: number | null
}[]
}) {
return request.post('/transaction/batch', data, { timeout: 120000 })
}
/** 更新指定交易记录(categoryId 为空表示不设置分类) */
export function updateTransaction(id: number, data: {
categoryId: number | null