From 338bac20ce41f2ab5a058d7488184f0906b8f3aa Mon Sep 17 00:00:00 2001 From: SunCheng Date: Mon, 2 Feb 2026 16:59:24 +0800 Subject: [PATCH] todo --- Dockerfile | 5 +- Web/VERSION_SWITCH_SUMMARY.md | 130 +++++ Web/VERSION_SWITCH_TEST.md | 143 +++++ Web/src/App.vue | 27 +- Web/src/router/index.js | 35 ++ Web/src/stores/version.js | 19 + Web/src/views/CalendarV2.vue | 488 ++++++++++++++---- Web/src/views/SettingView.vue | 70 ++- .../TransactionCategoryController.cs | 8 +- 9 files changed, 819 insertions(+), 106 deletions(-) create mode 100644 Web/VERSION_SWITCH_SUMMARY.md create mode 100644 Web/VERSION_SWITCH_TEST.md create mode 100644 Web/src/stores/version.js diff --git a/Dockerfile b/Dockerfile index 45a9cec..81e04c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,9 +44,8 @@ COPY Service/ ./Service/ COPY WebApi/ ./WebApi/ # 构建并发布 -# 使用 -m:1 限制 CPU/内存并行度,减少容器构建崩溃风险 -RUN dotnet publish WebApi/WebApi.csproj -c Release -o /app/publish --no-restore -m:1 - +# 使用 /m:1 限制 CPU/内存并行度,减少容器构建崩溃风险 +RUN dotnet publish WebApi/WebApi.csproj -c Release -o /app/publish --no-restore /m:1 # 将前端构建产物复制到后端的 wwwroot 目录 COPY --from=frontend-build /app/frontend/dist /app/publish/wwwroot diff --git a/Web/VERSION_SWITCH_SUMMARY.md b/Web/VERSION_SWITCH_SUMMARY.md new file mode 100644 index 0000000..9a83021 --- /dev/null +++ b/Web/VERSION_SWITCH_SUMMARY.md @@ -0,0 +1,130 @@ +# 版本切换功能实现总结 + +## 实现概述 + +在设置的开发者选项中添加了版本切换功能,用户可以在 V1 和 V2 版本之间切换。 + +## 修改的文件 + +### 1. Web/src/stores/version.js (新增) +- 创建 Pinia store 管理版本状态 +- 使用 localStorage 持久化版本选择 +- 提供 `setVersion()` 和 `isV2()` 方法 + +### 2. Web/src/views/SettingView.vue (修改) +- 在开发者选项中添加"切换版本"选项 +- 显示当前版本(V1/V2) +- 实现版本切换对话框 +- 实现版本切换后的路由跳转逻辑 + +### 3. Web/src/router/index.js (修改) +- 引入 version store +- 在路由守卫中添加版本路由重定向逻辑 +- V2 模式下自动跳转到 V2 路由(如果存在) +- V1 模式下自动跳转到 V1 路由(如果在 V2 路由) + +## 核心功能 + +1. **版本选择界面** + - 设置页面显示当前版本 + - 点击弹出对话框,选择 V1 或 V2 + - 切换成功后显示提示信息 + +2. **智能路由跳转** + - 选择 V2 后,如果当前路由有 V2 版本,自动跳转 + - 选择 V1 后,如果当前在 V2 路由,自动跳转到 V1 + - 没有对应版本时,保持当前路由不变 + +3. **路由守卫保护** + - 每次路由跳转时检查版本设置 + - 自动重定向到正确版本的路由 + - 保留 query 和 params 参数 + +4. **状态持久化** + - 版本选择保存在 localStorage + - 刷新页面后版本设置保持不变 + +## V2 路由命名规范 + +V2 路由必须遵循命名规范:`原路由名-v2` + +示例: +- V1: `calendar` → V2: `calendar-v2` +- V1: `budget` → V2: `budget-v2` + +## 当前支持的 V2 路由 + +- `calendar` → `calendar-v2` (CalendarV2.vue) + +## 测试验证 + +- ✅ ESLint 检查通过(无错误) +- ✅ 构建成功(pnpm build) +- ✅ 所有修改文件符合项目代码规范 + +## 使用示例 + +### 用户操作流程 + +1. 进入"设置"页面 +2. 滚动到"开发者"分组 +3. 点击"切换版本"(当前版本显示在右侧) +4. 选择"V1"或"V2" +5. 系统自动跳转到对应版本的路由 + +### 开发者添加新 V2 路由 + +```javascript +// router/index.js +{ + path: '/xxx-v2', + name: 'xxx-v2', + component: () => import('../views/XxxViewV2.vue'), + meta: { requiresAuth: true } +} +``` + +添加后即可自动支持版本切换。 + +## 技术细节 + +### 版本检测逻辑 + +```javascript +// 在路由守卫中 +if (versionStore.isV2()) { + // 尝试跳转到 V2 路由 + const v2RouteName = `${routeName}-v2` + if (存在 v2Route) { + 跳转到 v2Route + } else { + 保持当前路由 + } +} +``` + +### 版本状态管理 + +```javascript +// stores/version.js +const currentVersion = ref(localStorage.getItem('app-version') || 'v1') + +const setVersion = (version) => { + currentVersion.value = version + localStorage.setItem('app-version', version) +} +``` + +## 注意事项 + +1. V2 路由必须按照 `xxx-v2` 命名规范 +2. 如果页面没有 V2 版本,切换后会保持在 V1 版本 +3. 路由守卫会自动处理所有版本相关的路由跳转 +4. 版本状态持久化在 localStorage 中 + +## 后续改进建议 + +1. 可以在 UI 上添加更明显的版本标识 +2. 可以在无 V2 路由时给出提示 +3. 可以添加版本切换的动画效果 +4. 可以为不同版本设置不同的主题样式 diff --git a/Web/VERSION_SWITCH_TEST.md b/Web/VERSION_SWITCH_TEST.md new file mode 100644 index 0000000..773cc33 --- /dev/null +++ b/Web/VERSION_SWITCH_TEST.md @@ -0,0 +1,143 @@ +# 版本切换功能测试文档 + +## 功能说明 + +在设置的开发者选项中添加了版本切换功能,用户可以在 V1 和 V2 版本之间切换。当选择 V2 时,如果有对应的 V2 路由则自动跳转,否则保持当前路由。 + +## 实现文件 + +1. **Store**: `Web/src/stores/version.js` - 版本状态管理 +2. **View**: `Web/src/views/SettingView.vue` - 设置页面添加版本切换入口 +3. **Router**: `Web/src/router/index.js` - 路由守卫实现版本路由重定向 + +## 功能特性 + +- ✅ 版本状态持久化存储(localStorage) +- ✅ 设置页面显示当前版本(V1/V2) +- ✅ 点击弹出对话框选择版本 +- ✅ 自动检测并跳转到对应版本路由 +- ✅ 如果没有对应版本路由,保持当前路由 +- ✅ 路由守卫自动处理版本路由 + +## 测试步骤 + +### 1. 基础功能测试 + +1. 启动应用并登录 +2. 进入"设置"页面 +3. 找到"开发者"分组下的"切换版本"选项 +4. 当前版本应显示为 "V1"(首次使用) + +### 2. 切换到 V2 测试 + +1. 点击"切换版本" +2. 弹出对话框,显示"选择版本"标题 +3. 对话框有两个按钮:"V1"(取消按钮)和"V2"(确认按钮) +4. 点击"V2"按钮 +5. 应显示提示"已切换到 V2" +6. "切换版本"选项的值应更新为 "V2" + +### 3. V2 路由跳转测试 + +#### 测试有 V2 路由的情况(日历页面) + +1. 确保当前版本为 V2 +2. 点击导航栏的"日历"(路由名:`calendar`) +3. 应自动跳转到 `calendar-v2`(CalendarV2.vue) +4. 地址栏 URL 应为 `/calendar-v2` + +#### 测试没有 V2 路由的情况 + +1. 确保当前版本为 V2 +2. 点击导航栏的"账单分析"(路由名:`bill-analysis`) +3. 应保持在 `bill-analysis` 路由(没有 v2 版本) +4. 地址栏 URL 应为 `/bill-analysis` + +### 4. 切换回 V1 测试 + +1. 当前版本为 V2,在 `calendar-v2` 页面 +2. 进入"设置"页面,点击"切换版本" +3. 点击"V1"按钮 +4. 应显示提示"已切换到 V1" +5. 如果当前在 V2 路由(如 `calendar-v2`),应自动跳转到 V1 路由(`calendar`) +6. 地址栏 URL 应为 `/calendar` + +### 5. 持久化测试 + +1. 切换到 V2 版本 +2. 刷新页面 +3. 重新登录后,进入"设置"页面 +4. "切换版本"选项应仍显示 "V2" +5. 访问有 V2 路由的页面,应自动跳转到 V2 版本 + +### 6. 路由守卫测试 + +#### 直接访问 V2 路由(V1 模式下) + +1. 确保当前版本为 V1 +2. 在地址栏直接输入 `/calendar-v2` +3. 应自动重定向到 `/calendar` + +#### 直接访问 V1 路由(V2 模式下) + +1. 确保当前版本为 V2 +2. 在地址栏直接输入 `/calendar` +3. 应自动重定向到 `/calendar-v2` + +## 当前支持 V2 的路由 + +- `calendar` → `calendar-v2` (CalendarV2.vue) + +## 代码验证 + +### 版本 Store 检查 + +```javascript +// 打开浏览器控制台 +const versionStore = useVersionStore() +console.log(versionStore.currentVersion) // 应输出 'v1' 或 'v2' +console.log(versionStore.isV2()) // 应输出 true 或 false +``` + +### LocalStorage 检查 + +```javascript +// 打开浏览器控制台 +console.log(localStorage.getItem('app-version')) // 应输出 'v1' 或 'v2' +``` + +## 预期结果 + +- ✅ 所有路由跳转正常 +- ✅ 版本切换提示正常显示 +- ✅ 版本状态持久化正常 +- ✅ 路由守卫正常工作 +- ✅ 没有控制台错误 +- ✅ UI 响应流畅 + +## 潜在问题 + +1. 如果用户在 V2 路由页面直接切换到 V1,可能会出现短暂的页面重载 +2. 某些页面可能没有 V2 版本,切换后会保持在 V1 版本 + +## 后续扩展 + +如需添加更多 V2 路由,只需: + +1. 创建新的 Vue 组件(如 `XXXViewV2.vue`) +2. 在 `router/index.js` 中添加路由,命名格式为 `原路由名-v2` +3. 路由守卫会自动处理版本切换逻辑 + +## 示例:添加新的 V2 路由 + +```javascript +// router/index.js +{ + path: '/budget-v2', + name: 'budget-v2', + component: () => import('../views/BudgetViewV2.vue'), + meta: { requiresAuth: true } +} +``` + +添加后,当用户选择 V2 版本并访问 `/budget` 时,会自动跳转到 `/budget-v2`。 diff --git a/Web/src/App.vue b/Web/src/App.vue index 19200cd..4298590 100644 --- a/Web/src/App.vue +++ b/Web/src/App.vue @@ -4,7 +4,17 @@ class="app-provider" >
- + + + + + { const vh = window.innerHeight document.documentElement.style.setProperty('--vh', `${vh}px`) @@ -122,6 +141,7 @@ const showTabbar = computed(() => { return ( route.path === '/' || route.path === '/calendar' || + route.path === '/calendar-v2' || route.path === '/message' || route.path === '/setting' || route.path === '/balance' || @@ -136,6 +156,8 @@ const theme = ref('light') const updateTheme = () => { const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches theme.value = isDark ? 'dark' : 'light' + // 在文档根元素上设置 data-theme 属性,使 CSS 变量生效 + document.documentElement.setAttribute('data-theme', theme.value) } // 监听系统主题变化 @@ -165,6 +187,7 @@ const setActive = (path) => { active.value = (() => { switch (path) { case '/calendar': + case '/calendar-v2': return 'ccalendar' case '/balance': case '/message': @@ -180,7 +203,7 @@ const setActive = (path) => { } const isShowAddBill = computed(() => { - return route.path === '/' || route.path === '/balance' || route.path === '/message' || route.path === '/calendar' + return route.path === '/' || route.path === '/balance' || route.path === '/message' || route.path === '/calendar' || route.path === '/calendar-v2' }) onUnmounted(() => { diff --git a/Web/src/router/index.js b/Web/src/router/index.js index 69452ea..09998ec 100644 --- a/Web/src/router/index.js +++ b/Web/src/router/index.js @@ -1,5 +1,6 @@ import { createRouter, createWebHistory } from 'vue-router' import { useAuthStore } from '@/stores/auth' +import { useVersionStore } from '@/stores/version' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -34,6 +35,12 @@ const router = createRouter({ component: () => import('../views/CalendarView.vue'), meta: { requiresAuth: true } }, + { + path: '/calendar-v2', + name: 'calendar-v2', + component: () => import('../views/CalendarV2.vue'), + meta: { requiresAuth: true } + }, { path: '/smart-classification', name: 'smart-classification', @@ -113,6 +120,7 @@ const router = createRouter({ // 路由守卫 router.beforeEach((to, from, next) => { const authStore = useAuthStore() + const versionStore = useVersionStore() const requiresAuth = to.meta.requiresAuth !== false // 默认需要认证 if (requiresAuth && !authStore.isAuthenticated) { @@ -122,6 +130,33 @@ router.beforeEach((to, from, next) => { // 已登录用户访问登录页,跳转到首页 next({ name: 'transactions' }) } else { + // 版本路由处理 + if (versionStore.isV2()) { + // 如果当前选择 V2,尝试跳转到 V2 路由 + const routeName = to.name?.toString() + if (routeName && !routeName.endsWith('-v2')) { + const v2RouteName = `${routeName}-v2` + const v2Route = router.getRoutes().find(route => route.name === v2RouteName) + + if (v2Route) { + next({ name: v2RouteName, query: to.query, params: to.params }) + return + } + } + } else { + // 如果当前选择 V1,且访问的是 V2 路由,跳转到 V1 + const routeName = to.name?.toString() + if (routeName && routeName.endsWith('-v2')) { + const v1RouteName = routeName.replace(/-v2$/, '') + const v1Route = router.getRoutes().find(route => route.name === v1RouteName) + + if (v1Route) { + next({ name: v1RouteName, query: to.query, params: to.params }) + return + } + } + } + next() } }) diff --git a/Web/src/stores/version.js b/Web/src/stores/version.js new file mode 100644 index 0000000..7023f39 --- /dev/null +++ b/Web/src/stores/version.js @@ -0,0 +1,19 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export const useVersionStore = defineStore('version', () => { + const currentVersion = ref(localStorage.getItem('app-version') || 'v1') + + const setVersion = (version) => { + currentVersion.value = version + localStorage.setItem('app-version', version) + } + + const isV2 = () => currentVersion.value === 'v2' + + return { + currentVersion, + setVersion, + isV2 + } +}) diff --git a/Web/src/views/CalendarV2.vue b/Web/src/views/CalendarV2.vue index d79365f..265b2bc 100644 --- a/Web/src/views/CalendarV2.vue +++ b/Web/src/views/CalendarV2.vue @@ -1,18 +1,30 @@