feat: 用户级偏好配置与外观设置,枚举统一抽取与账单类型后端下发
- 新增用户偏好表 t_user_preference 与进程内缓存,账单来源统计默认值由全局改为按用户存储(/api/setting) - 新增外观设置:不计入统计记录支持删除线与灰色标签两种展示样式 - 抽取 ChannelType / TransactionType / StatisticsScope 枚举,替换各处硬编码的渠道、类型与统计口径 - BillType 内聚各来源扩展名,新增 /transaction/import/types 接口,前端改为按后端下发渲染 - 仪表盘趋势图支持点击柱子跳转收支记录并带入筛选条件 - 布局内容区按路由记忆滚动位置;已登录访问登录/注册页自动跳转
This commit is contained in:
@@ -8,6 +8,7 @@ import type { DataTableColumns } from 'naive-ui'
|
||||
import { getTransactionPage, createTransaction, updateTransaction, deleteTransaction, exportTransaction, toggleTransactionStats, toggleRemarkHidden, batchUpdateCategory, batchTags, getChannels, batchDeleteTransactions } from '../api/transaction'
|
||||
import { getAiConfig, suggestByAi, suggestOneByAi, applyAiSuggestions, queryByAi, parseRecordByAi, parseCategoryByAi, parseTagByAi } from '../api/ai'
|
||||
import type { AiSuggestion, AiQueryResult, AiRecordResult } from '../api/ai'
|
||||
import { getExcludedDisplayStyle } from '../api/setting'
|
||||
import { getCategoryList, createCategory } from '../api/category'
|
||||
import { getTagList, createTag } from '../api/tag'
|
||||
import { getChannelLabel } from '../utils/channel'
|
||||
@@ -92,6 +93,8 @@ const pageExcludedSummary = computed(() => {
|
||||
return { income, expense }
|
||||
})
|
||||
const loading = ref(false) // 列表加载状态
|
||||
// 「不计入统计」记录的展示样式:strikethrough-整行删除线(默认)、muted-灰色标签
|
||||
const excludedDisplayStyle = ref<'strikethrough' | 'muted'>('strikethrough')
|
||||
const submitting = ref(false) // 表单提交状态
|
||||
const showModal = ref(false) // 新增/编辑弹窗显示状态
|
||||
const showImportModal = ref(false) // 账单导入弹窗显示状态
|
||||
@@ -424,6 +427,8 @@ async function setRemarkHidden(row: TransactionItem, hidden: boolean) {
|
||||
|
||||
/** 创建表格列配置 */
|
||||
function createColumns(): DataTableColumns<TransactionItem> {
|
||||
// 读取展示样式,使列配置随样式偏好变化而重新计算
|
||||
const muted = excludedDisplayStyle.value === 'muted'
|
||||
return [
|
||||
// 多选列:用于批量操作(如批量删除)
|
||||
{ type: 'selection' },
|
||||
@@ -445,6 +450,10 @@ function createColumns(): DataTableColumns<TransactionItem> {
|
||||
{
|
||||
title: t('transaction.column.type'), key: 'type', width: 90,
|
||||
render(row) {
|
||||
// 灰色标签样式下,不计入统计的记录类型显示为「不计入」
|
||||
if (muted && !row.includeInStats) {
|
||||
return h(NTag, { type: 'default', size: 'small' }, { default: () => t('transaction.notIncluded') })
|
||||
}
|
||||
return h(NTag, { type: row.type === 'INCOME' ? 'success' : 'error', size: 'small' }, { default: () => row.type === 'INCOME' ? t('common.types.income') : t('common.types.expense') })
|
||||
},
|
||||
},
|
||||
@@ -458,6 +467,10 @@ function createColumns(): DataTableColumns<TransactionItem> {
|
||||
{
|
||||
title: t('transaction.column.amount'), key: 'amount', width: 120,
|
||||
render(row) {
|
||||
// 灰色标签样式下,不计入统计的记录金额置灰且不显示正负号
|
||||
if (muted && !row.includeInStats) {
|
||||
return h('span', { style: { color: '#999', fontWeight: 'bold' } }, row.amount.toFixed(2))
|
||||
}
|
||||
return h('span', { style: { color: row.type === 'INCOME' ? '#18a058' : '#d03050', fontWeight: 'bold' } },
|
||||
`${row.type === 'INCOME' ? '+' : '-'}${row.amount.toFixed(2)}`)
|
||||
},
|
||||
@@ -535,6 +548,18 @@ async function loadChannels() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载「不计入统计」记录展示样式偏好,失败时保持默认删除线 */
|
||||
async function loadExcludedDisplayStyle() {
|
||||
try {
|
||||
const res: any = await getExcludedDisplayStyle()
|
||||
if (res.data === 'muted' || res.data === 'strikethrough') {
|
||||
excludedDisplayStyle.value = res.data
|
||||
}
|
||||
} catch (e) {
|
||||
// 静默处理,保持默认样式
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载分类列表 */
|
||||
async function fetchCategories() {
|
||||
try {
|
||||
@@ -1487,6 +1512,7 @@ onMounted(() => {
|
||||
remarkHistory.load()
|
||||
aiQueryHistory.load()
|
||||
loadAiConfig()
|
||||
loadExcludedDisplayStyle()
|
||||
fetchCategories()
|
||||
fetchTags()
|
||||
loadChannels()
|
||||
@@ -1688,7 +1714,7 @@ onMounted(() => {
|
||||
:loading="loading"
|
||||
:bordered="false"
|
||||
:row-key="(row: TransactionItem) => row.id"
|
||||
:row-class-name="(row: TransactionItem) => row.includeInStats ? '' : 'excluded-row'"
|
||||
:row-class-name="(row: TransactionItem) => (!row.includeInStats && excludedDisplayStyle === 'strikethrough') ? 'excluded-row' : ''"
|
||||
v-model:checked-row-keys="checkedTransactionIds"
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user