// 向测试环境补充标签与自定义规则演示数据。 import axios from 'axios' import { login } from './browser.js' import { API_URL } from './config.js' /** 需要补充的标签:名称与颜色 */ const TAGS: Array<{ name: string; color: string }> = [ { name: '咖啡', color: '#8B5A2B' }, { name: '外卖', color: '#F97316' }, ] /** * 创建标签并返回其主键。 * * @param token 访问令牌 * @param name 标签名称 * @param color 标签颜色 * @returns 标签主键 */ async function createTag(token: string, name: string, color: string): Promise { const headers = { Authorization: `Bearer ${token}` } const res = await axios.post(`${API_URL}/tag`, { name, color }, { headers }) console.log(`标签 ${name} -> ${res.data.code}`) // 创建接口不返回主键,统一从列表中按名称查找 const list = await axios.get(`${API_URL}/tag/list`, { headers }) const found = (list.data.data as Array<{ id: number; name: string }>).find((t) => t.name === name) if (!found) throw new Error(`未找到标签: ${name}`) return found.id } /** * 创建自定义归类规则。 * * @param token 访问令牌 * @param payload 规则请求体 */ async function createRule(token: string, payload: Record): Promise { const res = await axios.post(`${API_URL}/rule`, payload, { headers: { Authorization: `Bearer ${token}` } }) console.log(`规则 ${payload.name} -> ${res.data.code}`) } /** * 按名称查询分类主键。 * * @param token 访问令牌 * @param name 分类名称 * @returns 分类主键 */ async function getCategoryId(token: string, name: string): Promise { const headers = { Authorization: `Bearer ${token}` } const res = await axios.get(`${API_URL}/category/list`, { headers }) const found = (res.data.data as Array<{ id: number; name: string }>).find((c) => c.name === name) if (!found) throw new Error(`未找到分类: ${name}`) return found.id } /** * 按名称查询标签主键,不存在时创建。 * * @param token 访问令牌 * @param name 标签名称 * @param color 标签颜色 * @returns 标签主键 */ async function getTagId(token: string, name: string, color: string): Promise { const headers = { Authorization: `Bearer ${token}` } const res = await axios.get(`${API_URL}/tag/list`, { headers }) const found = (res.data.data as Array<{ id: number; name: string }>).find((t) => t.name === name) if (found) return found.id return createTag(token, name, color) } /** 交易记录(用于给若干记录打标签,供「AI 生成规则」截图使用) */ interface TransactionRow { id: number categoryId: number | null type: string amount: number channel: string | null remark: string | null transactionTime: string includeInStats: boolean } /** * 给若干记录打上标签,便于「AI 生成规则」按已打标签记录归纳。 * * @param token 访问令牌 * @param coffeeId 咖啡标签主键 * @param takeoutId 外卖标签主键 * @param bigAmountId 大额消费标签主键 */ async function tagSomeTransactions( token: string, coffeeId: number, takeoutId: number, bigAmountId: number, ): Promise { const headers = { Authorization: `Bearer ${token}` } const res = await axios.get(`${API_URL}/transaction/page`, { headers, params: { page: 1, size: 50 } }) const rows = (res.data.data?.records ?? []) as TransactionRow[] let tagged = 0 for (const row of rows) { if (tagged >= 6) break const remark = row.remark ?? '' let tagIds: number[] = [] if (remark.includes('星巴克') || remark.includes('瑞幸') || remark.includes('咖啡')) { tagIds = [coffeeId] } else if (remark.includes('美团') || remark.includes('外卖') || remark.includes('饿了么')) { tagIds = [takeoutId] } else if (Number(row.amount) >= 500) { tagIds = [bigAmountId] } if (tagIds.length === 0) continue await axios.put(`${API_URL}/transaction/${row.id}`, { categoryId: row.categoryId, type: row.type, amount: row.amount, channel: row.channel, remark: row.remark, transactionTime: row.transactionTime, includeInStats: row.includeInStats, tagIds, }, { headers }) tagged++ } console.log(`已为 ${tagged} 条记录打标签`) } /** 清空现有规则,避免重复创建 */ async function clearRules(token: string): Promise { const headers = { Authorization: `Bearer ${token}` } const res = await axios.get(`${API_URL}/rule/list`, { headers }) for (const rule of res.data.data as Array<{ id: number }>) { await axios.delete(`${API_URL}/rule/${rule.id}`, { headers }) } } async function main(): Promise { const token = await login() await clearRules(token) const coffeeId = await getTagId(token, '咖啡', '#8B5A2B') const takeoutId = await getTagId(token, '外卖', '#F97316') const bigAmountId = await getTagId(token, '大额消费', '#F97316') const diningId = await getCategoryId(token, '餐饮') const transportId = await getCategoryId(token, '交通') console.log('标签:', { 咖啡: coffeeId, 外卖: takeoutId, 大额消费: bigAmountId }) const rules: Array> = [ { name: '星巴克归入餐饮', enabled: true, type: 'EXPENSE', keyword: '星巴克', categoryId: diningId, tagIds: [coffeeId] }, { name: '滴滴出行归入交通', enabled: true, type: 'EXPENSE', keyword: '滴滴', categoryId: transportId }, { name: '大额支出标记', enabled: true, type: 'EXPENSE', minAmount: 500, tagIds: [bigAmountId] }, ] for (const rule of rules) { await createRule(token, rule) } // 给若干记录打标签,供「AI 生成规则」截图 await tagSomeTransactions(token, coffeeId, takeoutId, bigAmountId) } main().catch((err) => { console.error('FAIL', err) process.exit(1) })