Files
ledgerly/frontend/src/router/index.ts
T
rosercode e0fa1abec5 feat: 表格分页支持统一配置并记忆各表格每页条数
- 后端新增聚合用户偏好 paginationPrefs(每页条数候选列表与默认每页条数),提供 GET/PUT /api/setting/pagination-prefs,默认候选 [10,20,50,100]、默认每页 20,非法值自动过滤并回退
- 后端新增 /category/page、/tag/page、/channel/page、/email-bill/configs/page、/email-bill/sender-rules/page 分页接口,原全量接口保持不变以免影响下拉、筛选、AI 与导出
- 前端新增共享 composables/usePageSizeOptions.ts 与 useTablePageSize.ts,所有表格统一取用候选列表并记住各自上次选择的每页条数,优先级为表格记忆 > 全局默认 > 20
- 各表格上次选择以聚合偏好 finance.pagination.tablePageSizes 存于本机 localStorage,收支记录页旧的 filterPrefs.pageSize 自动迁移
- 设置页外观设置新增「表格每页条数选项」分组,可增删候选值并配置默认每页条数,保存后全局即时生效
- 路由守卫在已登录时预加载分页偏好,保证表格页首次渲染即使用配置值
- 前端分类、标签、渠道、邮箱配置、发件人规则表格接入后端分页
- 补充中文、英文、日文三语文案
2026-09-23 11:14:54 +08:00

180 lines
6.3 KiB
TypeScript

// 路由配置文件:定义应用的所有路由及导航守卫
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '../store/user'
import { useDatabaseStore } from '../store/database'
import { useRegistrationStore } from '../store/registration'
import { migrateKey, readString } from '../utils/storage'
import { LEGACY_STORAGE_KEYS, STORAGE_KEYS } from '../utils/storageKeys'
import { loadPageSizeOptions } from '../composables/usePageSizeOptions'
// 创建路由实例,使用 HTML5 History 模式
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/database-setup',
name: 'DatabaseSetup',
component: () => import('../views/DatabaseSettings.vue'), // 首次数据库配置引导页(无需登录)
},
{
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue'), // 登录页
},
{
path: '/register',
name: 'Register',
component: () => import('../views/Register.vue'), // 注册页
},
{
path: '/bill-import',
name: 'BillImport',
component: () => import('../views/BillImport.vue'), // 账单导入(独立页,无侧边栏)
},
{
path: '/',
component: () => import('../views/Layout.vue'), // 主布局(带侧边栏)
redirect: '/dashboard', // 默认跳转到统计面板
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('../views/Dashboard.vue'), // 统计面板
},
{
path: 'transactions',
name: 'Transactions',
component: () => import('../views/TransactionList.vue'), // 收支记录
},
{
path: 'categories',
name: 'Categories',
component: () => import('../views/CategoryManage.vue'), // 分类管理
},
{
path: 'tags',
name: 'Tags',
component: () => import('../views/TagManage.vue'), // 标签管理
},
{
path: 'rules',
name: 'Rules',
component: () => import('../views/RuleManage.vue'), // 规则管理(自定义分类与标签规则)
},
{
path: 'channels',
name: 'Channels',
component: () => import('../views/ChannelManage.vue'), // 交易渠道管理(独立页,不加入侧边栏)
},
{
path: 'email-bills',
// 跳转到上次访问的标签页,无记录时默认邮箱配置
redirect: () => {
const tabs = ['configs', 'senderRules', 'messages']
// 读取前迁移旧键,保证老用户标签页偏好不丢失
migrateKey(LEGACY_STORAGE_KEYS.emailBillTab, STORAGE_KEYS.emailBillTab)
const saved = readString(STORAGE_KEYS.emailBillTab) || null
return `/email-bills/${saved && tabs.includes(saved) ? saved : 'configs'}`
},
},
{
path: 'email-bills/:tab(configs|senderRules|messages)',
name: 'EmailBills',
component: () => import('../views/EmailBillManage.vue'), // 邮箱账单(邮箱配置/发件人规则/待处理账单)
},
{
path: 'settings',
name: 'Settings',
component: () => import('../views/Settings.vue'), // 系统设置(数据库 + AI 模型)
},
{
path: 'ai-usage',
name: 'AiUsage',
component: () => import('../views/AiUsage.vue'), // 模型使用情况
},
{
path: 'logs',
// 跳转到上次访问的日志标签页,无记录时默认访问日志
redirect: () => {
const tabs = ['http', 'login', 'app']
// 读取前迁移旧键,保证老用户标签页偏好不丢失
migrateKey(LEGACY_STORAGE_KEYS.logsTab, STORAGE_KEYS.logsTab)
const saved = readString(STORAGE_KEYS.logsTab) || null
return `/logs/${saved && tabs.includes(saved) ? saved : 'http'}`
},
},
{
path: 'logs/:tab(http|login|app)',
name: 'Logs',
component: () => import('../views/Logs.vue'), // 系统日志(访问日志/登录日志/运行日志)
},
],
},
],
})
// 全局前置守卫:优先校验数据库配置,再校验登录状态
router.beforeEach(async (to, _from, next) => {
const databaseStore = useDatabaseStore()
// 首次进入时从后端加载数据库配置状态
if (!databaseStore.loaded) {
await databaseStore.load()
}
// 数据库未配置:强制引导到配置页,避免用户卡在无法登录的状态
if (!databaseStore.configured) {
if (to.path === '/database-setup') {
next()
} else {
next('/database-setup')
}
return
}
// 已配置数据库:配置引导页不再需要,跳转到登录或首页
if (to.path === '/database-setup') {
const userStore = useUserStore()
next(userStore.token ? '/dashboard' : '/login')
return
}
// 已登录用户访问登录/注册页时,自动跳转到回跳地址或首页
const userStore = useUserStore()
if ((to.path === '/login' || to.path === '/register') && userStore.token) {
// 仅允许站内路径回跳,避免开放重定向
const redirect = Array.isArray(to.query.redirect) ? to.query.redirect[0] : to.query.redirect
const target =
typeof redirect === 'string' && redirect.startsWith('/') && !redirect.startsWith('//')
? redirect
: '/dashboard'
next(target)
return
}
// 注册功能关闭且已有用户时,不允许访问注册页
if (to.path === '/register') {
const registrationStore = useRegistrationStore()
if (!registrationStore.loaded) {
await registrationStore.load()
}
if (!registrationStore.status?.allowed) {
next('/login')
return
}
}
// 如果目标页面不是登录/注册页,且没有 token,则重定向到登录页并记录来源,登录后可回跳
if (to.path !== '/login' && to.path !== '/register' && !userStore.token) {
next({ path: '/login', query: { redirect: to.fullPath } })
return
}
// 已登录:预加载表格分页偏好,保证表格页首次渲染即拿到默认每页条数
if (userStore.token) {
await loadPageSizeOptions()
}
next()
})
export default router