feat: 邮箱账单自动拉取

- 新增邮箱配置、发件人规则、待处理账单三张表(SQLite/MySQL 双脚本)及 CRUD 接口
- 按邮件发件人匹配账单类型,未命中忽略;发件人规则可配置解压密码与默认分类/标签
- 支持 IMAP IDLE 实时监听、定时兜底与手动立即检查;拉取范围可配置(仅新邮件/最近 N 天/最近 N 封)
- 附件保存至本地 data/email-bill,解析预览后手动导入,复用账单解析与规则引擎
- 立即检查通过 SSE 实时推送执行进度
- 前端新增「邮箱账单」页(邮箱配置/发件人规则/待处理账单三 Tab),支持子路由与 Tab 偏好记忆,配置弹窗提供常用 IMAP 服务器候选项
- 新用户注册自动初始化默认发件人规则(支付宝/微信/美团/招商银行/交通银行)
- 抽取共用账单预览与结果组件,供上传导入与邮箱账单复用

同时包含:
- 统计新增口径筛选(仅计入/全部/仅不计入),支付方式改为后端动态获取
- 统一实体 createdAt/updatedAt 位置至 id 之后,调整部分日志级别
This commit is contained in:
2026-09-19 17:50:55 +08:00
parent dedf30c74d
commit 9bc858eefc
65 changed files with 5940 additions and 529 deletions
+191
View File
@@ -0,0 +1,191 @@
// 邮箱账单相关 API:IMAP 配置、发件人规则与待处理账单的解析导入
import request from './request'
import type { BillType, ParsedBillRow, BillImportItem } from './transaction'
/** 邮箱配置 */
export interface EmailBillConfig {
id: number
name: string
host: string
port: number
ssl: boolean
username: string
hasPassword: boolean
folder: string
enabled: boolean
status: string
lastError: string | null
lastConnectedAt: string | null
lastUid: number | null
expenseCategoryId: number | null
incomeCategoryId: number | null
tagIds: number[]
fetchScope: string
fetchLimit: number | null
createdAt: string
updatedAt: string
}
/** 发件人识别规则 */
export interface EmailBillSenderRule {
id: number
name: string
enabled: boolean
sortOrder: number
senderPatterns: string[]
billType: BillType
archivePassword: string | null
expenseCategoryId: number | null
incomeCategoryId: number | null
tagIds: number[]
createdAt: string
updatedAt: string
}
/** 待处理账单消息 */
export interface EmailBillMessage {
id: number
configId: number
uid: number
subject: string | null
fromAddress: string | null
receivedAt: string | null
attachmentName: string | null
billType: BillType | null
status: string
error: string | null
createdAt: string
}
/** 邮箱配置保存请求 */
export interface EmailBillConfigPayload {
name: string
host: string
port: number
ssl: boolean
username: string
password?: string
folder: string
enabled: boolean
expenseCategoryId?: number | null
incomeCategoryId?: number | null
tagIds?: number[]
fetchScope?: string
fetchLimit?: number | null
}
/** 发件人规则保存请求 */
export interface EmailBillSenderRulePayload {
name: string
enabled: boolean
sortOrder: number
senderPatterns: string[]
billType: BillType
archivePassword?: string | null
expenseCategoryId?: number | null
incomeCategoryId?: number | null
tagIds?: number[]
}
/** 解析结果 */
export interface EmailBillParseResult {
billType: string
total: number
rows: ParsedBillRow[]
}
/** 查询邮箱配置列表 */
export function listEmailBillConfigs() {
return request.get('/email-bill/configs')
}
/** 新增邮箱配置 */
export function createEmailBillConfig(data: EmailBillConfigPayload) {
return request.post('/email-bill/configs', data)
}
/** 更新邮箱配置,授权码留空表示不修改 */
export function updateEmailBillConfig(id: number, data: EmailBillConfigPayload) {
return request.put(`/email-bill/configs/${id}`, data)
}
/** 删除邮箱配置 */
export function deleteEmailBillConfig(id: number) {
return request.delete(`/email-bill/configs/${id}`)
}
/** 启用或停用邮箱配置 */
export function toggleEmailBillConfig(id: number, enabled: boolean) {
return request.put(`/email-bill/configs/${id}/enabled`, { enabled })
}
/** 测试邮箱连接 */
export function testEmailBillConfig(id: number) {
return request.post(`/email-bill/configs/${id}/test`, {}, { timeout: 60000 })
}
/** 立即检查邮箱并增量拉取 */
export function checkEmailBillConfig(id: number) {
return request.post(`/email-bill/configs/${id}/check`, {}, { timeout: 120000 })
}
/** 查询发件人规则列表 */
export function listEmailBillSenderRules() {
return request.get('/email-bill/sender-rules')
}
/** 新增发件人规则 */
export function createEmailBillSenderRule(data: EmailBillSenderRulePayload) {
return request.post('/email-bill/sender-rules', data)
}
/** 更新发件人规则 */
export function updateEmailBillSenderRule(id: number, data: EmailBillSenderRulePayload) {
return request.put(`/email-bill/sender-rules/${id}`, data)
}
/** 删除发件人规则 */
export function deleteEmailBillSenderRule(id: number) {
return request.delete(`/email-bill/sender-rules/${id}`)
}
/** 启用或停用发件人规则 */
export function toggleEmailBillSenderRule(id: number, enabled: boolean) {
return request.put(`/email-bill/sender-rules/${id}/enabled`, { enabled })
}
/** 分页查询账单消息 */
export function pageEmailBillMessages(params: {
status?: string
configId?: number
page?: number
size?: number
}) {
return request.get('/email-bill/messages', { params })
}
/** 解析账单附件(不落库),可覆盖账单类型与解压密码 */
export function parseEmailBillMessage(id: number, data: { billType?: string; password?: string }) {
return request.post(`/email-bill/messages/${id}/parse`, data, { timeout: 120000 })
}
/** 确认导入勾选的账单行 */
export function importEmailBillMessage(id: number, data: {
billType: BillType
expenseCategoryId?: number | null
incomeCategoryId?: number | null
tagIds?: number[]
items: BillImportItem[]
}) {
return request.post(`/email-bill/messages/${id}/import`, data, { timeout: 120000 })
}
/** 忽略账单消息 */
export function ignoreEmailBillMessage(id: number) {
return request.put(`/email-bill/messages/${id}/ignore`)
}
/** 删除账单消息 */
export function deleteEmailBillMessage(id: number) {
return request.delete(`/email-bill/messages/${id}`)
}
+2
View File
@@ -27,6 +27,8 @@ export interface StatisticsFilterParams {
minAmount?: number
maxAmount?: number
remark?: string
/** 统计口径:INCLUDED-仅计入(默认)、ALL-全部、EXCLUDED-仅不计入 */
statsScope?: 'INCLUDED' | 'ALL' | 'EXCLUDED'
}
/** 获取指定时间范围的统计概览(收支汇总、日均、最大单笔、平均每笔) */