Explorar o código

refactor: 重构项目目录结构为强隔离双端 Monorepo 模式

- 将原有移动端代码整体迁移至 src/app/mobile 目录下

- 创建 src/app/pc 目录作为 PC 端开发预留占位

- 新增 app-mobile.routes.ts 和 app-pc.routes.ts,实现异构路由前缀分发

- 修正移动端路由配置与页面内链的跳转路径(补充 /mobile 前缀)

- 将 parse.ts 移动至 core 层级,修复组件引用路径

- 修复深色模式开关切换页面时的动画跳动问题

- 编写并新增架构规范文档 ARCHITECTURE_GUIDELINES.md
ice-static hai 4 meses
pai
achega
9faaa81c52
Modificáronse 100 ficheiros con 196 adicións e 256 borrados
  1. 6 212
      backend/pnpm-lock.yaml
  2. 119 0
      doc/前端目录规范.md
  3. 11 22
      frontend/src/app/app.routes.ts
  4. 1 1
      frontend/src/app/app.ts
  5. 29 0
      frontend/src/app/mobile/app-mobile.routes.ts
  6. 1 1
      frontend/src/app/mobile/core/guards/auth.guard.ts
  7. 0 0
      frontend/src/app/mobile/core/models/course.model.ts
  8. 0 0
      frontend/src/app/mobile/core/models/practice.model.ts
  9. 0 0
      frontend/src/app/mobile/core/models/qa.model.ts
  10. 0 0
      frontend/src/app/mobile/core/models/quiz.model.ts
  11. 0 0
      frontend/src/app/mobile/core/models/score.model.ts
  12. 0 0
      frontend/src/app/mobile/core/models/statistics.model.ts
  13. 0 0
      frontend/src/app/mobile/core/models/user.model.ts
  14. 1 1
      frontend/src/app/mobile/core/parse.ts
  15. 0 0
      frontend/src/app/mobile/core/services/ai-chat.service.ts
  16. 1 1
      frontend/src/app/mobile/core/services/auth.service.ts
  17. 0 0
      frontend/src/app/mobile/core/services/data.service.ts
  18. 1 1
      frontend/src/app/mobile/core/services/qa-storage.service.ts
  19. 0 0
      frontend/src/app/mobile/core/services/session.service.ts
  20. 0 0
      frontend/src/app/mobile/core/services/theme.service.ts
  21. 0 0
      frontend/src/app/mobile/core/services/toast.service.ts
  22. 0 0
      frontend/src/app/mobile/features/home/components/quick-entries/quick-entries.html
  23. 0 0
      frontend/src/app/mobile/features/home/components/quick-entries/quick-entries.scss
  24. 0 0
      frontend/src/app/mobile/features/home/components/quick-entries/quick-entries.ts
  25. 0 0
      frontend/src/app/mobile/features/home/components/recent-learning/recent-learning.html
  26. 0 0
      frontend/src/app/mobile/features/home/components/recent-learning/recent-learning.scss
  27. 0 0
      frontend/src/app/mobile/features/home/components/recent-learning/recent-learning.ts
  28. 0 0
      frontend/src/app/mobile/features/home/components/today-tasks/today-tasks.html
  29. 0 0
      frontend/src/app/mobile/features/home/components/today-tasks/today-tasks.scss
  30. 0 0
      frontend/src/app/mobile/features/home/components/today-tasks/today-tasks.ts
  31. 0 0
      frontend/src/app/mobile/features/home/components/welcome-card/welcome-card.html
  32. 0 0
      frontend/src/app/mobile/features/home/components/welcome-card/welcome-card.scss
  33. 0 0
      frontend/src/app/mobile/features/home/components/welcome-card/welcome-card.ts
  34. 0 0
      frontend/src/app/mobile/features/home/home/home.html
  35. 0 0
      frontend/src/app/mobile/features/home/home/home.scss
  36. 0 0
      frontend/src/app/mobile/features/home/home/home.ts
  37. 0 0
      frontend/src/app/mobile/features/learning/learning/learning.html
  38. 0 0
      frontend/src/app/mobile/features/learning/learning/learning.scss
  39. 2 2
      frontend/src/app/mobile/features/learning/learning/learning.ts
  40. 0 0
      frontend/src/app/mobile/features/practice/components/category-filter/category-filter.html
  41. 0 0
      frontend/src/app/mobile/features/practice/components/category-filter/category-filter.scss
  42. 0 0
      frontend/src/app/mobile/features/practice/components/category-filter/category-filter.ts
  43. 0 0
      frontend/src/app/mobile/features/practice/components/chat-interface/chat-interface.html
  44. 0 0
      frontend/src/app/mobile/features/practice/components/chat-interface/chat-interface.scss
  45. 0 0
      frontend/src/app/mobile/features/practice/components/chat-interface/chat-interface.ts
  46. 0 0
      frontend/src/app/mobile/features/practice/components/practice-stats/practice-stats.html
  47. 0 0
      frontend/src/app/mobile/features/practice/components/practice-stats/practice-stats.scss
  48. 0 0
      frontend/src/app/mobile/features/practice/components/practice-stats/practice-stats.ts
  49. 0 0
      frontend/src/app/mobile/features/practice/components/practice-summary/practice-summary.html
  50. 0 0
      frontend/src/app/mobile/features/practice/components/practice-summary/practice-summary.scss
  51. 0 0
      frontend/src/app/mobile/features/practice/components/practice-summary/practice-summary.ts
  52. 0 0
      frontend/src/app/mobile/features/practice/components/scene-grid/scene-grid.html
  53. 0 0
      frontend/src/app/mobile/features/practice/components/scene-grid/scene-grid.scss
  54. 0 0
      frontend/src/app/mobile/features/practice/components/scene-grid/scene-grid.ts
  55. 0 0
      frontend/src/app/mobile/features/practice/practice/practice.html
  56. 0 0
      frontend/src/app/mobile/features/practice/practice/practice.scss
  57. 0 0
      frontend/src/app/mobile/features/practice/practice/practice.ts
  58. 2 2
      frontend/src/app/mobile/features/profile/profile/profile.html
  59. 0 0
      frontend/src/app/mobile/features/profile/profile/profile.scss
  60. 11 2
      frontend/src/app/mobile/features/profile/profile/profile.ts
  61. 0 0
      frontend/src/app/mobile/features/qa/qa/qa.html
  62. 0 0
      frontend/src/app/mobile/features/qa/qa/qa.scss
  63. 0 0
      frontend/src/app/mobile/features/qa/qa/qa.ts
  64. 0 0
      frontend/src/app/mobile/features/score/components/capability-radar/capability-radar.html
  65. 0 0
      frontend/src/app/mobile/features/score/components/capability-radar/capability-radar.scss
  66. 0 0
      frontend/src/app/mobile/features/score/components/capability-radar/capability-radar.ts
  67. 0 0
      frontend/src/app/mobile/features/score/components/learning-history/learning-history.html
  68. 0 0
      frontend/src/app/mobile/features/score/components/learning-history/learning-history.scss
  69. 0 0
      frontend/src/app/mobile/features/score/components/learning-history/learning-history.ts
  70. 0 0
      frontend/src/app/mobile/features/score/components/learning-stats/learning-stats.html
  71. 0 0
      frontend/src/app/mobile/features/score/components/learning-stats/learning-stats.scss
  72. 0 0
      frontend/src/app/mobile/features/score/components/learning-stats/learning-stats.ts
  73. 0 0
      frontend/src/app/mobile/features/score/score/score.html
  74. 0 0
      frontend/src/app/mobile/features/score/score/score.scss
  75. 0 0
      frontend/src/app/mobile/features/score/score/score.ts
  76. 0 0
      frontend/src/app/mobile/layouts/mobile-layout/mobile-layout.html
  77. 0 0
      frontend/src/app/mobile/layouts/mobile-layout/mobile-layout.scss
  78. 0 0
      frontend/src/app/mobile/layouts/mobile-layout/mobile-layout.ts
  79. 2 2
      frontend/src/app/mobile/pages/course-detail/course-detail.html
  80. 0 0
      frontend/src/app/mobile/pages/course-detail/course-detail.scss
  81. 0 0
      frontend/src/app/mobile/pages/course-detail/course-detail.ts
  82. 0 0
      frontend/src/app/mobile/pages/login/login.html
  83. 0 0
      frontend/src/app/mobile/pages/login/login.scss
  84. 0 0
      frontend/src/app/mobile/pages/login/login.ts
  85. 2 2
      frontend/src/app/mobile/pages/quiz-result/quiz-result.html
  86. 0 0
      frontend/src/app/mobile/pages/quiz-result/quiz-result.scss
  87. 0 0
      frontend/src/app/mobile/pages/quiz-result/quiz-result.ts
  88. 0 0
      frontend/src/app/mobile/pages/quiz/quiz.html
  89. 0 0
      frontend/src/app/mobile/pages/quiz/quiz.scss
  90. 2 2
      frontend/src/app/mobile/pages/quiz/quiz.ts
  91. 0 0
      frontend/src/app/mobile/pages/register/register.html
  92. 0 0
      frontend/src/app/mobile/pages/register/register.scss
  93. 1 1
      frontend/src/app/mobile/pages/register/register.ts
  94. 2 2
      frontend/src/app/mobile/pages/score-report/score-report.html
  95. 0 0
      frontend/src/app/mobile/pages/score-report/score-report.scss
  96. 0 0
      frontend/src/app/mobile/pages/score-report/score-report.ts
  97. 2 2
      frontend/src/app/mobile/pages/video-player/video-player.html
  98. 0 0
      frontend/src/app/mobile/pages/video-player/video-player.scss
  99. 0 0
      frontend/src/app/mobile/pages/video-player/video-player.ts
  100. 0 0
      frontend/src/app/mobile/shared/components/bottom-nav/bottom-nav.html

+ 6 - 212
backend/pnpm-lock.yaml

@@ -24,9 +24,6 @@ importers:
       '@types/express':
       '@types/express':
         specifier: ^5.0.0
         specifier: ^5.0.0
         version: 5.0.6
         version: 5.0.6
-      nodemon:
-        specifier: ^3.1.0
-        version: 3.1.14
       tsx:
       tsx:
         specifier: ^4.19.0
         specifier: ^4.19.0
         version: 4.21.0
         version: 4.21.0
@@ -229,30 +226,10 @@ packages:
     resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
     resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
     engines: {node: '>= 0.6'}
     engines: {node: '>= 0.6'}
 
 
-  anymatch@3.1.3:
-    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
-    engines: {node: '>= 8'}
-
-  balanced-match@4.0.4:
-    resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
-    engines: {node: 18 || 20 || >=22}
-
-  binary-extensions@2.3.0:
-    resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
-    engines: {node: '>=8'}
-
   body-parser@2.2.2:
   body-parser@2.2.2:
     resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
     resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
     engines: {node: '>=18'}
     engines: {node: '>=18'}
 
 
-  brace-expansion@5.0.5:
-    resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==}
-    engines: {node: 18 || 20 || >=22}
-
-  braces@3.0.3:
-    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
-    engines: {node: '>=8'}
-
   bytes@3.1.2:
   bytes@3.1.2:
     resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
     resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
     engines: {node: '>= 0.8'}
     engines: {node: '>= 0.8'}
@@ -265,10 +242,6 @@ packages:
     resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
     resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
     engines: {node: '>= 0.4'}
     engines: {node: '>= 0.4'}
 
 
-  chokidar@3.6.0:
-    resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
-    engines: {node: '>= 8.10.0'}
-
   content-disposition@1.1.0:
   content-disposition@1.1.0:
     resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==}
     resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==}
     engines: {node: '>=18'}
     engines: {node: '>=18'}
@@ -345,10 +318,6 @@ packages:
     resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
     resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
     engines: {node: '>= 18'}
     engines: {node: '>= 18'}
 
 
-  fill-range@7.1.1:
-    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
-    engines: {node: '>=8'}
-
   finalhandler@2.1.1:
   finalhandler@2.1.1:
     resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
     resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
     engines: {node: '>= 18.0.0'}
     engines: {node: '>= 18.0.0'}
@@ -380,18 +349,10 @@ packages:
   get-tsconfig@4.14.0:
   get-tsconfig@4.14.0:
     resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==}
     resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==}
 
 
-  glob-parent@5.1.2:
-    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
-    engines: {node: '>= 6'}
-
   gopd@1.2.0:
   gopd@1.2.0:
     resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
     resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
     engines: {node: '>= 0.4'}
     engines: {node: '>= 0.4'}
 
 
-  has-flag@3.0.0:
-    resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
-    engines: {node: '>=4'}
-
   has-symbols@1.1.0:
   has-symbols@1.1.0:
     resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
     resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
     engines: {node: '>= 0.4'}
     engines: {node: '>= 0.4'}
@@ -408,9 +369,6 @@ packages:
     resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
     resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
     engines: {node: '>=0.10.0'}
     engines: {node: '>=0.10.0'}
 
 
-  ignore-by-default@1.0.1:
-    resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
-
   inherits@2.0.4:
   inherits@2.0.4:
     resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
     resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
 
 
@@ -418,22 +376,6 @@ packages:
     resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
     resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
     engines: {node: '>= 0.10'}
     engines: {node: '>= 0.10'}
 
 
-  is-binary-path@2.1.0:
-    resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
-    engines: {node: '>=8'}
-
-  is-extglob@2.1.1:
-    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
-    engines: {node: '>=0.10.0'}
-
-  is-glob@4.0.3:
-    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
-    engines: {node: '>=0.10.0'}
-
-  is-number@7.0.0:
-    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
-    engines: {node: '>=0.12.0'}
-
   is-promise@4.0.0:
   is-promise@4.0.0:
     resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
     resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
 
 
@@ -457,10 +399,6 @@ packages:
     resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
     resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
     engines: {node: '>=18'}
     engines: {node: '>=18'}
 
 
-  minimatch@10.2.5:
-    resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
-    engines: {node: 18 || 20 || >=22}
-
   ms@2.1.3:
   ms@2.1.3:
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
 
 
@@ -468,15 +406,6 @@ packages:
     resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
     resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
     engines: {node: '>= 0.6'}
     engines: {node: '>= 0.6'}
 
 
-  nodemon@3.1.14:
-    resolution: {integrity: sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==}
-    engines: {node: '>=10'}
-    hasBin: true
-
-  normalize-path@3.0.0:
-    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
-    engines: {node: '>=0.10.0'}
-
   object-assign@4.1.1:
   object-assign@4.1.1:
     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
     engines: {node: '>=0.10.0'}
     engines: {node: '>=0.10.0'}
@@ -499,17 +428,10 @@ packages:
   path-to-regexp@8.4.2:
   path-to-regexp@8.4.2:
     resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
     resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
 
 
-  picomatch@2.3.2:
-    resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
-    engines: {node: '>=8.6'}
-
   proxy-addr@2.0.7:
   proxy-addr@2.0.7:
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     engines: {node: '>= 0.10'}
     engines: {node: '>= 0.10'}
 
 
-  pstree.remy@1.1.8:
-    resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
-
   qs@6.15.1:
   qs@6.15.1:
     resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==}
     resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==}
     engines: {node: '>=0.6'}
     engines: {node: '>=0.6'}
@@ -522,10 +444,6 @@ packages:
     resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
     resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
     engines: {node: '>= 0.10'}
     engines: {node: '>= 0.10'}
 
 
-  readdirp@3.6.0:
-    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
-    engines: {node: '>=8.10.0'}
-
   resolve-pkg-maps@1.0.0:
   resolve-pkg-maps@1.0.0:
     resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
     resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
 
 
@@ -536,11 +454,6 @@ packages:
   safer-buffer@2.1.2:
   safer-buffer@2.1.2:
     resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
     resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
 
 
-  semver@7.7.4:
-    resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
-    engines: {node: '>=10'}
-    hasBin: true
-
   send@1.2.1:
   send@1.2.1:
     resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
     resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
     engines: {node: '>= 18'}
     engines: {node: '>= 18'}
@@ -568,30 +481,14 @@ packages:
     resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
     resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
     engines: {node: '>= 0.4'}
     engines: {node: '>= 0.4'}
 
 
-  simple-update-notifier@2.0.0:
-    resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
-    engines: {node: '>=10'}
-
   statuses@2.0.2:
   statuses@2.0.2:
     resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
     resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
     engines: {node: '>= 0.8'}
     engines: {node: '>= 0.8'}
 
 
-  supports-color@5.5.0:
-    resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
-    engines: {node: '>=4'}
-
-  to-regex-range@5.0.1:
-    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
-    engines: {node: '>=8.0'}
-
   toidentifier@1.0.1:
   toidentifier@1.0.1:
     resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
     resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
     engines: {node: '>=0.6'}
     engines: {node: '>=0.6'}
 
 
-  touch@3.1.1:
-    resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==}
-    hasBin: true
-
   tsx@4.21.0:
   tsx@4.21.0:
     resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
     resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
     engines: {node: '>=18.0.0'}
     engines: {node: '>=18.0.0'}
@@ -606,9 +503,6 @@ packages:
     engines: {node: '>=14.17'}
     engines: {node: '>=14.17'}
     hasBin: true
     hasBin: true
 
 
-  undefsafe@2.0.5:
-    resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
-
   undici-types@7.19.2:
   undici-types@7.19.2:
     resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
     resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
 
 
@@ -753,20 +647,11 @@ snapshots:
       mime-types: 3.0.2
       mime-types: 3.0.2
       negotiator: 1.0.0
       negotiator: 1.0.0
 
 
-  anymatch@3.1.3:
-    dependencies:
-      normalize-path: 3.0.0
-      picomatch: 2.3.2
-
-  balanced-match@4.0.4: {}
-
-  binary-extensions@2.3.0: {}
-
   body-parser@2.2.2:
   body-parser@2.2.2:
     dependencies:
     dependencies:
       bytes: 3.1.2
       bytes: 3.1.2
       content-type: 1.0.5
       content-type: 1.0.5
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3
       http-errors: 2.0.1
       http-errors: 2.0.1
       iconv-lite: 0.7.2
       iconv-lite: 0.7.2
       on-finished: 2.4.1
       on-finished: 2.4.1
@@ -776,14 +661,6 @@ snapshots:
     transitivePeerDependencies:
     transitivePeerDependencies:
       - supports-color
       - supports-color
 
 
-  brace-expansion@5.0.5:
-    dependencies:
-      balanced-match: 4.0.4
-
-  braces@3.0.3:
-    dependencies:
-      fill-range: 7.1.1
-
   bytes@3.1.2: {}
   bytes@3.1.2: {}
 
 
   call-bind-apply-helpers@1.0.2:
   call-bind-apply-helpers@1.0.2:
@@ -796,18 +673,6 @@ snapshots:
       call-bind-apply-helpers: 1.0.2
       call-bind-apply-helpers: 1.0.2
       get-intrinsic: 1.3.0
       get-intrinsic: 1.3.0
 
 
-  chokidar@3.6.0:
-    dependencies:
-      anymatch: 3.1.3
-      braces: 3.0.3
-      glob-parent: 5.1.2
-      is-binary-path: 2.1.0
-      is-glob: 4.0.3
-      normalize-path: 3.0.0
-      readdirp: 3.6.0
-    optionalDependencies:
-      fsevents: 2.3.3
-
   content-disposition@1.1.0: {}
   content-disposition@1.1.0: {}
 
 
   content-type@1.0.5: {}
   content-type@1.0.5: {}
@@ -821,11 +686,9 @@ snapshots:
       object-assign: 4.1.1
       object-assign: 4.1.1
       vary: 1.1.2
       vary: 1.1.2
 
 
-  debug@4.4.3(supports-color@5.5.0):
+  debug@4.4.3:
     dependencies:
     dependencies:
       ms: 2.1.3
       ms: 2.1.3
-    optionalDependencies:
-      supports-color: 5.5.0
 
 
   depd@2.0.0: {}
   depd@2.0.0: {}
 
 
@@ -890,7 +753,7 @@ snapshots:
       content-type: 1.0.5
       content-type: 1.0.5
       cookie: 0.7.2
       cookie: 0.7.2
       cookie-signature: 1.2.2
       cookie-signature: 1.2.2
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3
       depd: 2.0.0
       depd: 2.0.0
       encodeurl: 2.0.0
       encodeurl: 2.0.0
       escape-html: 1.0.3
       escape-html: 1.0.3
@@ -915,13 +778,9 @@ snapshots:
     transitivePeerDependencies:
     transitivePeerDependencies:
       - supports-color
       - supports-color
 
 
-  fill-range@7.1.1:
-    dependencies:
-      to-regex-range: 5.0.1
-
   finalhandler@2.1.1:
   finalhandler@2.1.1:
     dependencies:
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3
       encodeurl: 2.0.0
       encodeurl: 2.0.0
       escape-html: 1.0.3
       escape-html: 1.0.3
       on-finished: 2.4.1
       on-finished: 2.4.1
@@ -961,14 +820,8 @@ snapshots:
     dependencies:
     dependencies:
       resolve-pkg-maps: 1.0.0
       resolve-pkg-maps: 1.0.0
 
 
-  glob-parent@5.1.2:
-    dependencies:
-      is-glob: 4.0.3
-
   gopd@1.2.0: {}
   gopd@1.2.0: {}
 
 
-  has-flag@3.0.0: {}
-
   has-symbols@1.1.0: {}
   has-symbols@1.1.0: {}
 
 
   hasown@2.0.3:
   hasown@2.0.3:
@@ -987,24 +840,10 @@ snapshots:
     dependencies:
     dependencies:
       safer-buffer: 2.1.2
       safer-buffer: 2.1.2
 
 
-  ignore-by-default@1.0.1: {}
-
   inherits@2.0.4: {}
   inherits@2.0.4: {}
 
 
   ipaddr.js@1.9.1: {}
   ipaddr.js@1.9.1: {}
 
 
-  is-binary-path@2.1.0:
-    dependencies:
-      binary-extensions: 2.3.0
-
-  is-extglob@2.1.1: {}
-
-  is-glob@4.0.3:
-    dependencies:
-      is-extglob: 2.1.1
-
-  is-number@7.0.0: {}
-
   is-promise@4.0.0: {}
   is-promise@4.0.0: {}
 
 
   math-intrinsics@1.1.0: {}
   math-intrinsics@1.1.0: {}
@@ -1019,29 +858,10 @@ snapshots:
     dependencies:
     dependencies:
       mime-db: 1.54.0
       mime-db: 1.54.0
 
 
-  minimatch@10.2.5:
-    dependencies:
-      brace-expansion: 5.0.5
-
   ms@2.1.3: {}
   ms@2.1.3: {}
 
 
   negotiator@1.0.0: {}
   negotiator@1.0.0: {}
 
 
-  nodemon@3.1.14:
-    dependencies:
-      chokidar: 3.6.0
-      debug: 4.4.3(supports-color@5.5.0)
-      ignore-by-default: 1.0.1
-      minimatch: 10.2.5
-      pstree.remy: 1.1.8
-      semver: 7.7.4
-      simple-update-notifier: 2.0.0
-      supports-color: 5.5.0
-      touch: 3.1.1
-      undefsafe: 2.0.5
-
-  normalize-path@3.0.0: {}
-
   object-assign@4.1.1: {}
   object-assign@4.1.1: {}
 
 
   object-inspect@1.13.4: {}
   object-inspect@1.13.4: {}
@@ -1058,15 +878,11 @@ snapshots:
 
 
   path-to-regexp@8.4.2: {}
   path-to-regexp@8.4.2: {}
 
 
-  picomatch@2.3.2: {}
-
   proxy-addr@2.0.7:
   proxy-addr@2.0.7:
     dependencies:
     dependencies:
       forwarded: 0.2.0
       forwarded: 0.2.0
       ipaddr.js: 1.9.1
       ipaddr.js: 1.9.1
 
 
-  pstree.remy@1.1.8: {}
-
   qs@6.15.1:
   qs@6.15.1:
     dependencies:
     dependencies:
       side-channel: 1.1.0
       side-channel: 1.1.0
@@ -1080,15 +896,11 @@ snapshots:
       iconv-lite: 0.7.2
       iconv-lite: 0.7.2
       unpipe: 1.0.0
       unpipe: 1.0.0
 
 
-  readdirp@3.6.0:
-    dependencies:
-      picomatch: 2.3.2
-
   resolve-pkg-maps@1.0.0: {}
   resolve-pkg-maps@1.0.0: {}
 
 
   router@2.2.0:
   router@2.2.0:
     dependencies:
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3
       depd: 2.0.0
       depd: 2.0.0
       is-promise: 4.0.0
       is-promise: 4.0.0
       parseurl: 1.3.3
       parseurl: 1.3.3
@@ -1098,11 +910,9 @@ snapshots:
 
 
   safer-buffer@2.1.2: {}
   safer-buffer@2.1.2: {}
 
 
-  semver@7.7.4: {}
-
   send@1.2.1:
   send@1.2.1:
     dependencies:
     dependencies:
-      debug: 4.4.3(supports-color@5.5.0)
+      debug: 4.4.3
       encodeurl: 2.0.0
       encodeurl: 2.0.0
       escape-html: 1.0.3
       escape-html: 1.0.3
       etag: 1.8.1
       etag: 1.8.1
@@ -1155,24 +965,10 @@ snapshots:
       side-channel-map: 1.0.1
       side-channel-map: 1.0.1
       side-channel-weakmap: 1.0.2
       side-channel-weakmap: 1.0.2
 
 
-  simple-update-notifier@2.0.0:
-    dependencies:
-      semver: 7.7.4
-
   statuses@2.0.2: {}
   statuses@2.0.2: {}
 
 
-  supports-color@5.5.0:
-    dependencies:
-      has-flag: 3.0.0
-
-  to-regex-range@5.0.1:
-    dependencies:
-      is-number: 7.0.0
-
   toidentifier@1.0.1: {}
   toidentifier@1.0.1: {}
 
 
-  touch@3.1.1: {}
-
   tsx@4.21.0:
   tsx@4.21.0:
     dependencies:
     dependencies:
       esbuild: 0.27.7
       esbuild: 0.27.7
@@ -1188,8 +984,6 @@ snapshots:
 
 
   typescript@5.9.3: {}
   typescript@5.9.3: {}
 
 
-  undefsafe@2.0.5: {}
-
   undici-types@7.19.2: {}
   undici-types@7.19.2: {}
 
 
   unpipe@1.0.0: {}
   unpipe@1.0.0: {}

+ 119 - 0
doc/前端目录规范.md

@@ -0,0 +1,119 @@
+# 📐 前端项目架构与开发规范文档 (强隔离双端 Monorepo)
+
+## 一、 架构设计理念
+
+本项目采用 **“强隔离双端 Monorepo”** 架构。
+
+**核心思想:**
+虽然 PC 端和移动端代码在同一个 Angular 项目仓库(Monorepo)中,但它们在逻辑和物理层面上是**完全隔离的两个独立应用**。
+
+**为什么这么做?**
+为了换取未来双端业务独立演进的绝对自由。PC 端和移动端在业务流程、UI 展现、甚至数据结构和鉴权方式上往往差异巨大。通过物理隔离,我们彻底杜绝了“改 PC 崩移动”的风险,并且如果未来需要将 PC 端拆分为独立的 Git 仓库,只需剪切文件夹即可,零耦合成本。
+
+---
+
+## 二、 目录结构概览
+
+`src/app` 根目录被严格划分为 `mobile` 和 `pc` 两个平行的自治世界。
+
+```text
+src/app/
+├── mobile/                     # [📱 移动端绝对隔离区]
+│   ├── app-mobile.routes.ts    # 移动端主路由 (挂载于 /mobile)
+│   ├── core/                   # 移动端专属基建
+│   │   ├── models/             # 移动端数据模型
+│   │   ├── services/           # 移动端 API 与状态服务
+│   │   ├── guards/             # 移动端路由守卫 (如 AuthGuard)
+│   │   └── interceptors/       # 移动端 HTTP 拦截器
+│   ├── shared/                 # 移动端专属共享 UI 与工具
+│   │   ├── components/         # 如 mobile-bottom-nav
+│   │   ├── directives/         
+│   │   └── utils/              
+│   ├── layouts/                # 移动端骨架布局
+│   ├── features/               # 移动端主线业务模块 (如 home, practice)
+│   └── pages/                  # 移动端独立页面 (如 login, user-center)
+│
+├── pc/                         # [💻 PC端绝对隔离区]
+│   ├── app-pc.routes.ts        # PC端主路由 (挂载于 /pc)
+│   ├── core/                   # PC端专属基建 (同移动端结构)
+│   ├── shared/                 # PC端专属共享 UI 与工具 (同移动端结构)
+│   ├── layouts/                # PC端骨架布局
+│   ├── features/               # PC端主线业务模块
+│   └── pages/                  # PC端独立页面
+│
+├── app.routes.ts               # [全局路由分发器] (仅负责 /mobile 和 /pc 的分发)
+├── app.component.ts            # [全局根组件] (仅包含 <router-outlet>)
+└── app.config.ts               # [全局应用配置] (注入全局 providers)
+```
+
+---
+
+## 三、 核心开发纪律(必须遵守)
+
+### 🚫 1. 绝对禁止跨区导入 (防越界法则)
+*   `mobile/` 目录下的任何文件,**绝对禁止**通过相对路径(如 `../../pc/...`)导入 `pc/` 目录下的任何内容(包括 Model、Service、Component、Utils)。
+*   `pc/` 目录同理,绝对禁止访问 `mobile/`。
+*   **重复是被允许的**:如果双端有一个完全一样的工具函数或数据模型,**请忍受重复**,分别在各自的目录下创建一份。解耦优先于复用。
+
+### 🚫 2. 禁止在根目录创建业务代码
+*   除了 `app.routes.ts`、`app.component.ts` 和 `app.config.ts`,`src/app` 根目录下不应存在任何业务代码、全局 Service 或 Model。所有的代码必须归属于 `mobile/` 或 `pc/`。
+
+---
+
+## 四、 路由与守卫规范
+
+### 1. 异构路由前缀
+*   移动端所有页面 URL 必须以 `/mobile/` 开头。
+*   PC 端所有页面 URL 必须以 `/pc/` 开头。
+*   根路径 `/` 应配置为设备嗅探器,根据 `navigator.userAgent` 自动重定向到对应的平台入口。
+
+### 2. 路由守卫彻底分离
+由于双端登录逻辑可能不同(如 PC 扫码/账密,移动端微信授权):
+*   移动端守卫:存放在 `mobile/core/guards/`,仅在 `app-mobile.routes.ts` 中使用。
+*   PC 端守卫:存放在 `pc/core/guards/`,仅在 `app-pc.routes.ts` 中使用。
+
+---
+
+## 五、 命名规范
+
+由于我们采用了物理隔离,在各自的目录上下文中,命名不需要带冗长的平台前缀(目录结构已经说明了一切)。
+
+| 层级/类型 | 命名约定 | 示例路径及文件名 |
+| :--- | :--- | :--- |
+| **数据模型** | `[实体].model.ts` | `pc/core/models/user.model.ts` |
+| **服务** | `[功能].service.ts` | `mobile/core/services/auth.service.ts` |
+| **路由文件** | `app-[平台].routes.ts` | `mobile/app-mobile.routes.ts` |
+| **组件类名** | 正常命名即可 | 移动端的首页和PC端的首页类名都可以是 `export class HomeComponent` |
+
+---
+
+## 六、 依赖注入 (DI) 注意事项
+
+为了确保双端服务的彻底隔离,即使它们在同一个 Angular 运行上下文中:
+
+1. **避免全局单例污染**:尽量减少使用 `@Injectable({ providedIn: 'root' })`。
+2. **路由级提供者**:推荐在各自的平台路由配置中提供核心服务,使其作用域限制在当前平台路由树内。
+
+```typescript
+// mobile/app-mobile.routes.ts 示例
+export const mobileRoutes: Routes = [
+  {
+    path: '',
+    providers: [
+      AuthService, // 这个 AuthService 来源于 mobile/core/services
+      DataService
+    ],
+    children: [ /* 具体的移动端路由 */ ]
+  }
+];
+```
+
+如果必须使用 `providedIn: 'root'`,请确保双端的 Service 类名或 Token 具有明确区分(即使文件名看起来一样),以避免在根注入器中发生覆盖。
+
+---
+
+## 七、 接手与维护指南
+
+1. **接到新需求时**:首先明确这个需求是针对 PC 端还是移动端,或者是双端需求。
+2. **修改双端通用功能时**:如果后端修改了通用接口的返回结构,你需要**同时修改** `mobile/core/models/` 和 `pc/core/models/` 下对应的模型定义。
+3. **新增模块时**:严格遵循本规范的目录层级(是属于 `features` 还是 `pages`),不要将组件随意堆砌在根目录下。

+ 11 - 22
frontend/src/app/app.routes.ts

@@ -1,29 +1,18 @@
 import { Routes } from '@angular/router';
 import { Routes } from '@angular/router';
-import { AuthGuard } from './core/guards/auth.guard';
 
 
 export const routes: Routes = [
 export const routes: Routes = [
-  { path: '', redirectTo: 'home', pathMatch: 'full' },
-  { path: 'login', canActivate: [AuthGuard], loadComponent: () => import('./pages/login/login').then(m => m.Login) },
-  { path: 'register', canActivate: [AuthGuard], loadComponent: () => import('./pages/register/register').then(m => m.Register) },
+  {
+    path: 'mobile',
+    loadChildren: () => import('./mobile/app-mobile.routes').then(m => m.mobileRoutes)
+  },
+  {
+    path: 'pc',
+    loadChildren: () => import('./pc/app-pc.routes').then(m => m.pcRoutes)
+  },
   {
   {
     path: '',
     path: '',
-    loadComponent: () => import('./layouts/mobile-layout/mobile-layout')
-      .then(m => m.MobileLayout),
-    canActivate: [AuthGuard],
-    children: [
-      { path: '', redirectTo: 'home', pathMatch: 'full' },
-      { path: 'home', loadComponent: () => import('./features/home/home/home').then(m => m.Home) },
-      { path: 'learning', loadComponent: () => import('./features/learning/learning/learning').then(m => m.Learning) },
-      { path: 'practice', loadComponent: () => import('./features/practice/practice/practice').then(m => m.Practice) },
-      { path: 'qa', loadComponent: () => import('./features/qa/qa/qa').then(m => m.Qa) },
-      { path: 'score', loadComponent: () => import('./features/score/score/score').then(m => m.Score) },
-      { path: 'profile', loadComponent: () => import('./features/profile/profile/profile').then(m => m.Profile) },
-    ]
+    pathMatch: 'full',
+    redirectTo: 'mobile' // 简单重定向到移动端,后续可添加设备嗅探
   },
   },
-  { path: 'course-detail/:id', canActivate: [AuthGuard], loadComponent: () => import('./pages/course-detail/course-detail').then(m => m.CourseDetail) },
-  { path: 'video/:id', canActivate: [AuthGuard], loadComponent: () => import('./pages/video-player/video-player').then(m => m.VideoPlayer) },
-  { path: 'quiz/:id', canActivate: [AuthGuard], loadComponent: () => import('./pages/quiz/quiz').then(m => m.Quiz) },
-  { path: 'quiz-result', canActivate: [AuthGuard], loadComponent: () => import('./pages/quiz-result/quiz-result').then(m => m.QuizResult) },
-  { path: 'score-report', canActivate: [AuthGuard], loadComponent: () => import('./pages/score-report/score-report').then(m => m.ScoreReport) },
-  { path: '**', redirectTo: 'home' },
+  { path: '**', redirectTo: 'mobile' }
 ];
 ];

+ 1 - 1
frontend/src/app/app.ts

@@ -1,6 +1,6 @@
 import { Component } from '@angular/core';
 import { Component } from '@angular/core';
 import { RouterOutlet } from '@angular/router';
 import { RouterOutlet } from '@angular/router';
-import { Toast } from './shared/components/toast/toast';
+import { Toast } from './mobile/shared/components/toast/toast';
 
 
 @Component({
 @Component({
   selector: 'app-root',
   selector: 'app-root',

+ 29 - 0
frontend/src/app/mobile/app-mobile.routes.ts

@@ -0,0 +1,29 @@
+import { Routes } from '@angular/router';
+import { AuthGuard } from './core/guards/auth.guard';
+
+export const mobileRoutes: Routes = [
+  { path: '', redirectTo: 'home', pathMatch: 'full' },
+  { path: 'login', canActivate: [AuthGuard], loadComponent: () => import('./pages/login/login').then(m => m.Login) },
+  { path: 'register', canActivate: [AuthGuard], loadComponent: () => import('./pages/register/register').then(m => m.Register) },
+  {
+    path: '',
+    loadComponent: () => import('./layouts/mobile-layout/mobile-layout')
+      .then(m => m.MobileLayout),
+    canActivate: [AuthGuard],
+    children: [
+      { path: '', redirectTo: 'home', pathMatch: 'full' },
+      { path: 'home', loadComponent: () => import('./features/home/home/home').then(m => m.Home) },
+      { path: 'learning', loadComponent: () => import('./features/learning/learning/learning').then(m => m.Learning) },
+      { path: 'practice', loadComponent: () => import('./features/practice/practice/practice').then(m => m.Practice) },
+      { path: 'qa', loadComponent: () => import('./features/qa/qa/qa').then(m => m.Qa) },
+      { path: 'score', loadComponent: () => import('./features/score/score/score').then(m => m.Score) },
+      { path: 'profile', loadComponent: () => import('./features/profile/profile/profile').then(m => m.Profile) },
+    ]
+  },
+  { path: 'course-detail/:id', canActivate: [AuthGuard], loadComponent: () => import('./pages/course-detail/course-detail').then(m => m.CourseDetail) },
+  { path: 'video/:id', canActivate: [AuthGuard], loadComponent: () => import('./pages/video-player/video-player').then(m => m.VideoPlayer) },
+  { path: 'quiz/:id', canActivate: [AuthGuard], loadComponent: () => import('./pages/quiz/quiz').then(m => m.Quiz) },
+  { path: 'quiz-result', canActivate: [AuthGuard], loadComponent: () => import('./pages/quiz-result/quiz-result').then(m => m.QuizResult) },
+  { path: 'score-report', canActivate: [AuthGuard], loadComponent: () => import('./pages/score-report/score-report').then(m => m.ScoreReport) },
+  { path: '**', redirectTo: 'home' },
+];

+ 1 - 1
frontend/src/app/core/guards/auth.guard.ts → frontend/src/app/mobile/core/guards/auth.guard.ts

@@ -1,6 +1,6 @@
 import { Injectable, inject } from '@angular/core';
 import { Injectable, inject } from '@angular/core';
 import { CanActivate, Router, UrlTree, ActivatedRouteSnapshot } from '@angular/router';
 import { CanActivate, Router, UrlTree, ActivatedRouteSnapshot } from '@angular/router';
-import { MSQParse } from '../../parse';
+import { MSQParse } from '../parse';
 import { SessionService } from '../services/session.service';
 import { SessionService } from '../services/session.service';
 
 
 @Injectable({ providedIn: 'root' })
 @Injectable({ providedIn: 'root' })

+ 0 - 0
frontend/src/app/core/models/course.model.ts → frontend/src/app/mobile/core/models/course.model.ts


+ 0 - 0
frontend/src/app/core/models/practice.model.ts → frontend/src/app/mobile/core/models/practice.model.ts


+ 0 - 0
frontend/src/app/core/models/qa.model.ts → frontend/src/app/mobile/core/models/qa.model.ts


+ 0 - 0
frontend/src/app/core/models/quiz.model.ts → frontend/src/app/mobile/core/models/quiz.model.ts


+ 0 - 0
frontend/src/app/core/models/score.model.ts → frontend/src/app/mobile/core/models/score.model.ts


+ 0 - 0
frontend/src/app/core/models/statistics.model.ts → frontend/src/app/mobile/core/models/statistics.model.ts


+ 0 - 0
frontend/src/app/core/models/user.model.ts → frontend/src/app/mobile/core/models/user.model.ts


+ 1 - 1
frontend/src/app/parse.ts → frontend/src/app/mobile/core/parse.ts

@@ -1,5 +1,5 @@
 import { FmodeParse } from 'fmode-ng/core';
 import { FmodeParse } from 'fmode-ng/core';
-import { environment } from '../environments/environment';
+import { environment } from '../../../environments/environment';
 
 
 export const MSQParse = FmodeParse.initialize({
 export const MSQParse = FmodeParse.initialize({
   appId: environment.parse.appId,
   appId: environment.parse.appId,

+ 0 - 0
frontend/src/app/core/services/ai-chat.service.ts → frontend/src/app/mobile/core/services/ai-chat.service.ts


+ 1 - 1
frontend/src/app/core/services/auth.service.ts → frontend/src/app/mobile/core/services/auth.service.ts

@@ -1,5 +1,5 @@
 import { Injectable, inject } from '@angular/core';
 import { Injectable, inject } from '@angular/core';
-import { MSQParse } from '../../parse';
+import { MSQParse } from '../parse';
 import { SessionService } from './session.service';
 import { SessionService } from './session.service';
 
 
 export interface LoginResult {
 export interface LoginResult {

+ 0 - 0
frontend/src/app/core/services/data.service.ts → frontend/src/app/mobile/core/services/data.service.ts


+ 1 - 1
frontend/src/app/core/services/qa-storage.service.ts → frontend/src/app/mobile/core/services/qa-storage.service.ts

@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core';
 import { Injectable } from '@angular/core';
-import { MSQParse } from '../../parse';
+import { MSQParse } from '../parse';
 import { QAMessage, ConversationItem } from '../models/qa.model';
 import { QAMessage, ConversationItem } from '../models/qa.model';
 
 
 const CLASS_NAME = 'QAConversation';
 const CLASS_NAME = 'QAConversation';

+ 0 - 0
frontend/src/app/core/services/session.service.ts → frontend/src/app/mobile/core/services/session.service.ts


+ 0 - 0
frontend/src/app/core/services/theme.service.ts → frontend/src/app/mobile/core/services/theme.service.ts


+ 0 - 0
frontend/src/app/core/services/toast.service.ts → frontend/src/app/mobile/core/services/toast.service.ts


+ 0 - 0
frontend/src/app/features/home/components/quick-entries/quick-entries.html → frontend/src/app/mobile/features/home/components/quick-entries/quick-entries.html


+ 0 - 0
frontend/src/app/features/home/components/quick-entries/quick-entries.scss → frontend/src/app/mobile/features/home/components/quick-entries/quick-entries.scss


+ 0 - 0
frontend/src/app/features/home/components/quick-entries/quick-entries.ts → frontend/src/app/mobile/features/home/components/quick-entries/quick-entries.ts


+ 0 - 0
frontend/src/app/features/home/components/recent-learning/recent-learning.html → frontend/src/app/mobile/features/home/components/recent-learning/recent-learning.html


+ 0 - 0
frontend/src/app/features/home/components/recent-learning/recent-learning.scss → frontend/src/app/mobile/features/home/components/recent-learning/recent-learning.scss


+ 0 - 0
frontend/src/app/features/home/components/recent-learning/recent-learning.ts → frontend/src/app/mobile/features/home/components/recent-learning/recent-learning.ts


+ 0 - 0
frontend/src/app/features/home/components/today-tasks/today-tasks.html → frontend/src/app/mobile/features/home/components/today-tasks/today-tasks.html


+ 0 - 0
frontend/src/app/features/home/components/today-tasks/today-tasks.scss → frontend/src/app/mobile/features/home/components/today-tasks/today-tasks.scss


+ 0 - 0
frontend/src/app/features/home/components/today-tasks/today-tasks.ts → frontend/src/app/mobile/features/home/components/today-tasks/today-tasks.ts


+ 0 - 0
frontend/src/app/features/home/components/welcome-card/welcome-card.html → frontend/src/app/mobile/features/home/components/welcome-card/welcome-card.html


+ 0 - 0
frontend/src/app/features/home/components/welcome-card/welcome-card.scss → frontend/src/app/mobile/features/home/components/welcome-card/welcome-card.scss


+ 0 - 0
frontend/src/app/features/home/components/welcome-card/welcome-card.ts → frontend/src/app/mobile/features/home/components/welcome-card/welcome-card.ts


+ 0 - 0
frontend/src/app/features/home/home/home.html → frontend/src/app/mobile/features/home/home/home.html


+ 0 - 0
frontend/src/app/features/home/home/home.scss → frontend/src/app/mobile/features/home/home/home.scss


+ 0 - 0
frontend/src/app/features/home/home/home.ts → frontend/src/app/mobile/features/home/home/home.ts


+ 0 - 0
frontend/src/app/features/learning/learning/learning.html → frontend/src/app/mobile/features/learning/learning/learning.html


+ 0 - 0
frontend/src/app/features/learning/learning/learning.scss → frontend/src/app/mobile/features/learning/learning/learning.scss


+ 2 - 2
frontend/src/app/features/learning/learning/learning.ts → frontend/src/app/mobile/features/learning/learning/learning.ts

@@ -50,10 +50,10 @@ export class Learning {
 
 
   startLearning(): void {
   startLearning(): void {
     this.showCourseDetail.set(false);
     this.showCourseDetail.set(false);
-    this.router.navigate(['/video', '1']);
+    this.router.navigate(['/mobile/video', '1']);
   }
   }
 
 
   goToPractice(): void {
   goToPractice(): void {
-    this.router.navigate(['/practice']);
+    this.router.navigate(['/mobile/practice']);
   }
   }
 }
 }

+ 0 - 0
frontend/src/app/features/practice/components/category-filter/category-filter.html → frontend/src/app/mobile/features/practice/components/category-filter/category-filter.html


+ 0 - 0
frontend/src/app/features/practice/components/category-filter/category-filter.scss → frontend/src/app/mobile/features/practice/components/category-filter/category-filter.scss


+ 0 - 0
frontend/src/app/features/practice/components/category-filter/category-filter.ts → frontend/src/app/mobile/features/practice/components/category-filter/category-filter.ts


+ 0 - 0
frontend/src/app/features/practice/components/chat-interface/chat-interface.html → frontend/src/app/mobile/features/practice/components/chat-interface/chat-interface.html


+ 0 - 0
frontend/src/app/features/practice/components/chat-interface/chat-interface.scss → frontend/src/app/mobile/features/practice/components/chat-interface/chat-interface.scss


+ 0 - 0
frontend/src/app/features/practice/components/chat-interface/chat-interface.ts → frontend/src/app/mobile/features/practice/components/chat-interface/chat-interface.ts


+ 0 - 0
frontend/src/app/features/practice/components/practice-stats/practice-stats.html → frontend/src/app/mobile/features/practice/components/practice-stats/practice-stats.html


+ 0 - 0
frontend/src/app/features/practice/components/practice-stats/practice-stats.scss → frontend/src/app/mobile/features/practice/components/practice-stats/practice-stats.scss


+ 0 - 0
frontend/src/app/features/practice/components/practice-stats/practice-stats.ts → frontend/src/app/mobile/features/practice/components/practice-stats/practice-stats.ts


+ 0 - 0
frontend/src/app/features/practice/components/practice-summary/practice-summary.html → frontend/src/app/mobile/features/practice/components/practice-summary/practice-summary.html


+ 0 - 0
frontend/src/app/features/practice/components/practice-summary/practice-summary.scss → frontend/src/app/mobile/features/practice/components/practice-summary/practice-summary.scss


+ 0 - 0
frontend/src/app/features/practice/components/practice-summary/practice-summary.ts → frontend/src/app/mobile/features/practice/components/practice-summary/practice-summary.ts


+ 0 - 0
frontend/src/app/features/practice/components/scene-grid/scene-grid.html → frontend/src/app/mobile/features/practice/components/scene-grid/scene-grid.html


+ 0 - 0
frontend/src/app/features/practice/components/scene-grid/scene-grid.scss → frontend/src/app/mobile/features/practice/components/scene-grid/scene-grid.scss


+ 0 - 0
frontend/src/app/features/practice/components/scene-grid/scene-grid.ts → frontend/src/app/mobile/features/practice/components/scene-grid/scene-grid.ts


+ 0 - 0
frontend/src/app/features/practice/practice/practice.html → frontend/src/app/mobile/features/practice/practice/practice.html


+ 0 - 0
frontend/src/app/features/practice/practice/practice.scss → frontend/src/app/mobile/features/practice/practice/practice.scss


+ 0 - 0
frontend/src/app/features/practice/practice/practice.ts → frontend/src/app/mobile/features/practice/practice/practice.ts


+ 2 - 2
frontend/src/app/features/profile/profile/profile.html → frontend/src/app/mobile/features/profile/profile/profile.html

@@ -40,8 +40,8 @@
         </div>
         </div>
         <span>深色模式</span>
         <span>深色模式</span>
       </div>
       </div>
-      <button class="w-12 h-6 bg-gray-300 dark:bg-green-500 rounded-full relative transition-colors" (click)="toggleDarkMode()">
-        <span class="absolute top-1 left-1 w-4 h-4 bg-white rounded-full transition-transform" [class.translate-x-6]="isDark()"></span>
+      <button class="w-12 h-6 bg-gray-300 dark:bg-green-500 rounded-full relative" [class.transition-colors]="enableTransition" (click)="toggleDarkMode()">
+        <span class="absolute top-1 left-1 w-4 h-4 bg-white rounded-full" [class.transition-transform]="enableTransition" [class.translate-x-6]="isDark()"></span>
       </button>
       </button>
     </div>
     </div>
     <div class="px-4 py-4 flex items-center justify-between border-b border-gray-100 dark:border-gray-700">
     <div class="px-4 py-4 flex items-center justify-between border-b border-gray-100 dark:border-gray-700">

+ 0 - 0
frontend/src/app/features/profile/profile/profile.scss → frontend/src/app/mobile/features/profile/profile/profile.scss


+ 11 - 2
frontend/src/app/features/profile/profile/profile.ts → frontend/src/app/mobile/features/profile/profile/profile.ts

@@ -1,4 +1,4 @@
-import { Component, inject } from '@angular/core';
+import { Component, inject, OnInit } from '@angular/core';
 import { FaIconComponent } from '@fortawesome/angular-fontawesome';
 import { FaIconComponent } from '@fortawesome/angular-fontawesome';
 import {
 import {
   faUser,
   faUser,
@@ -19,9 +19,11 @@ import { ThemeService } from '../../../core/services/theme.service';
   templateUrl: './profile.html',
   templateUrl: './profile.html',
   styleUrl: './profile.scss',
   styleUrl: './profile.scss',
 })
 })
-export class Profile {
+export class Profile implements OnInit {
   private themeService = inject(ThemeService);
   private themeService = inject(ThemeService);
 
 
+  enableTransition = false;
+
   readonly faUser = faUser;
   readonly faUser = faUser;
   readonly faQrcode = faQrcode;
   readonly faQrcode = faQrcode;
   readonly faMoon = faMoon;
   readonly faMoon = faMoon;
@@ -32,6 +34,13 @@ export class Profile {
   readonly faInfoCircle = faInfoCircle;
   readonly faInfoCircle = faInfoCircle;
   readonly faChevronRight = faChevronRight;
   readonly faChevronRight = faChevronRight;
 
 
+  ngOnInit() {
+    // 延迟启用过渡动画,避免页面加载时开关状态跳动
+    setTimeout(() => {
+      this.enableTransition = true;
+    }, 50);
+  }
+
   isDark() {
   isDark() {
     return this.themeService.isDarkMode();
     return this.themeService.isDarkMode();
   }
   }

+ 0 - 0
frontend/src/app/features/qa/qa/qa.html → frontend/src/app/mobile/features/qa/qa/qa.html


+ 0 - 0
frontend/src/app/features/qa/qa/qa.scss → frontend/src/app/mobile/features/qa/qa/qa.scss


+ 0 - 0
frontend/src/app/features/qa/qa/qa.ts → frontend/src/app/mobile/features/qa/qa/qa.ts


+ 0 - 0
frontend/src/app/features/score/components/capability-radar/capability-radar.html → frontend/src/app/mobile/features/score/components/capability-radar/capability-radar.html


+ 0 - 0
frontend/src/app/features/score/components/capability-radar/capability-radar.scss → frontend/src/app/mobile/features/score/components/capability-radar/capability-radar.scss


+ 0 - 0
frontend/src/app/features/score/components/capability-radar/capability-radar.ts → frontend/src/app/mobile/features/score/components/capability-radar/capability-radar.ts


+ 0 - 0
frontend/src/app/features/score/components/learning-history/learning-history.html → frontend/src/app/mobile/features/score/components/learning-history/learning-history.html


+ 0 - 0
frontend/src/app/features/score/components/learning-history/learning-history.scss → frontend/src/app/mobile/features/score/components/learning-history/learning-history.scss


+ 0 - 0
frontend/src/app/features/score/components/learning-history/learning-history.ts → frontend/src/app/mobile/features/score/components/learning-history/learning-history.ts


+ 0 - 0
frontend/src/app/features/score/components/learning-stats/learning-stats.html → frontend/src/app/mobile/features/score/components/learning-stats/learning-stats.html


+ 0 - 0
frontend/src/app/features/score/components/learning-stats/learning-stats.scss → frontend/src/app/mobile/features/score/components/learning-stats/learning-stats.scss


+ 0 - 0
frontend/src/app/features/score/components/learning-stats/learning-stats.ts → frontend/src/app/mobile/features/score/components/learning-stats/learning-stats.ts


+ 0 - 0
frontend/src/app/features/score/score/score.html → frontend/src/app/mobile/features/score/score/score.html


+ 0 - 0
frontend/src/app/features/score/score/score.scss → frontend/src/app/mobile/features/score/score/score.scss


+ 0 - 0
frontend/src/app/features/score/score/score.ts → frontend/src/app/mobile/features/score/score/score.ts


+ 0 - 0
frontend/src/app/layouts/mobile-layout/mobile-layout.html → frontend/src/app/mobile/layouts/mobile-layout/mobile-layout.html


+ 0 - 0
frontend/src/app/layouts/mobile-layout/mobile-layout.scss → frontend/src/app/mobile/layouts/mobile-layout/mobile-layout.scss


+ 0 - 0
frontend/src/app/layouts/mobile-layout/mobile-layout.ts → frontend/src/app/mobile/layouts/mobile-layout/mobile-layout.ts


+ 2 - 2
frontend/src/app/pages/course-detail/course-detail.html → frontend/src/app/mobile/pages/course-detail/course-detail.html

@@ -3,7 +3,7 @@
     <div class="w-12 h-1 bg-gray-300 dark:bg-gray-600 rounded-full mx-auto mb-3"></div>
     <div class="w-12 h-1 bg-gray-300 dark:bg-gray-600 rounded-full mx-auto mb-3"></div>
     <div class="flex items-center justify-between">
     <div class="flex items-center justify-between">
       <h3 class="font-semibold text-lg">课程详情</h3>
       <h3 class="font-semibold text-lg">课程详情</h3>
-      <button routerLink="/learning" class="w-8 h-8 flex items-center justify-center">
+      <button routerLink="/mobile/learning" class="w-8 h-8 flex items-center justify-center">
         <fa-icon [icon]="faTimes" class="text-gray-500 dark:text-gray-400"></fa-icon>
         <fa-icon [icon]="faTimes" class="text-gray-500 dark:text-gray-400"></fa-icon>
       </button>
       </button>
     </div>
     </div>
@@ -46,6 +46,6 @@
     </div>
     </div>
   </div>
   </div>
   <div class="p-4 border-t border-gray-200 dark:border-gray-700 flex-shrink-0 safe-bottom">
   <div class="p-4 border-t border-gray-200 dark:border-gray-700 flex-shrink-0 safe-bottom">
-    <button routerLink="/video/1" class="w-full py-4 bg-green-500 text-white font-semibold rounded-xl shadow-lg shadow-green-500/30">开始学习</button>
+    <button routerLink="/mobile/video/1" class="w-full py-4 bg-green-500 text-white font-semibold rounded-xl shadow-lg shadow-green-500/30">开始学习</button>
   </div>
   </div>
 </div>
 </div>

+ 0 - 0
frontend/src/app/pages/course-detail/course-detail.scss → frontend/src/app/mobile/pages/course-detail/course-detail.scss


+ 0 - 0
frontend/src/app/pages/course-detail/course-detail.ts → frontend/src/app/mobile/pages/course-detail/course-detail.ts


+ 0 - 0
frontend/src/app/pages/login/login.html → frontend/src/app/mobile/pages/login/login.html


+ 0 - 0
frontend/src/app/pages/login/login.scss → frontend/src/app/mobile/pages/login/login.scss


+ 0 - 0
frontend/src/app/pages/login/login.ts → frontend/src/app/mobile/pages/login/login.ts


+ 2 - 2
frontend/src/app/pages/quiz-result/quiz-result.html → frontend/src/app/mobile/pages/quiz-result/quiz-result.html

@@ -18,8 +18,8 @@
   </div>
   </div>
   <div class="p-6 border-t border-gray-200 dark:border-gray-700 flex-shrink-0 safe-bottom">
   <div class="p-6 border-t border-gray-200 dark:border-gray-700 flex-shrink-0 safe-bottom">
     <div class="flex gap-3">
     <div class="flex gap-3">
-      <button routerLink="/quiz/1" class="flex-1 py-3.5 border border-gray-300 dark:border-gray-600 rounded-xl font-medium">下一题</button>
-      <button routerLink="/practice" class="flex-1 py-3.5 bg-green-500 text-white rounded-xl font-medium">进入AI陪练</button>
+      <button routerLink="/mobile/quiz/1" class="flex-1 py-3.5 border border-gray-300 dark:border-gray-600 rounded-xl font-medium">下一题</button>
+      <button routerLink="/mobile/practice" class="flex-1 py-3.5 bg-green-500 text-white rounded-xl font-medium">进入AI陪练</button>
     </div>
     </div>
   </div>
   </div>
 </div>
 </div>

+ 0 - 0
frontend/src/app/pages/quiz-result/quiz-result.scss → frontend/src/app/mobile/pages/quiz-result/quiz-result.scss


+ 0 - 0
frontend/src/app/pages/quiz-result/quiz-result.ts → frontend/src/app/mobile/pages/quiz-result/quiz-result.ts


+ 0 - 0
frontend/src/app/pages/quiz/quiz.html → frontend/src/app/mobile/pages/quiz/quiz.html


+ 0 - 0
frontend/src/app/pages/quiz/quiz.scss → frontend/src/app/mobile/pages/quiz/quiz.scss


+ 2 - 2
frontend/src/app/pages/quiz/quiz.ts → frontend/src/app/mobile/pages/quiz/quiz.ts

@@ -33,12 +33,12 @@ export class Quiz {
   }
   }
 
 
   goBack(): void {
   goBack(): void {
-    this.router.navigate(['/home']);
+    this.router.navigate(['/mobile/home']);
   }
   }
 
 
   submitAnswer(): void {
   submitAnswer(): void {
     if (this.selectedAnswer()) {
     if (this.selectedAnswer()) {
-      this.router.navigate(['/quiz-result']);
+      this.router.navigate(['/mobile/quiz-result']);
     }
     }
   }
   }
 }
 }

+ 0 - 0
frontend/src/app/pages/register/register.html → frontend/src/app/mobile/pages/register/register.html


+ 0 - 0
frontend/src/app/pages/register/register.scss → frontend/src/app/mobile/pages/register/register.scss


+ 1 - 1
frontend/src/app/pages/register/register.ts → frontend/src/app/mobile/pages/register/register.ts

@@ -83,7 +83,7 @@ export class Register {
     try {
     try {
       await this.authService.signUp(p, pw);
       await this.authService.signUp(p, pw);
       this.toast.success('注册成功,请登录');
       this.toast.success('注册成功,请登录');
-      this.router.navigate(['/login'], {
+      this.router.navigate(['/mobile/login'], {
         queryParams: { phone: p },
         queryParams: { phone: p },
       });
       });
     } catch (err: any) {
     } catch (err: any) {

+ 2 - 2
frontend/src/app/pages/score-report/score-report.html → frontend/src/app/mobile/pages/score-report/score-report.html

@@ -3,7 +3,7 @@
     <div class="w-12 h-1 bg-gray-300 dark:bg-gray-600 rounded-full mx-auto mb-3"></div>
     <div class="w-12 h-1 bg-gray-300 dark:bg-gray-600 rounded-full mx-auto mb-3"></div>
     <div class="flex items-center justify-between">
     <div class="flex items-center justify-between">
       <h3 class="font-semibold text-lg">陪练评分报告</h3>
       <h3 class="font-semibold text-lg">陪练评分报告</h3>
-      <button routerLink="/home" class="w-8 h-8 flex items-center justify-center">
+      <button routerLink="/mobile/home" class="w-8 h-8 flex items-center justify-center">
         <fa-icon [icon]="faTimes" class="text-gray-500 dark:text-gray-400"></fa-icon>
         <fa-icon [icon]="faTimes" class="text-gray-500 dark:text-gray-400"></fa-icon>
       </button>
       </button>
     </div>
     </div>
@@ -50,6 +50,6 @@
     </div>
     </div>
   </div>
   </div>
   <div class="p-4 border-t border-gray-200 dark:border-gray-700 flex-shrink-0 safe-bottom">
   <div class="p-4 border-t border-gray-200 dark:border-gray-700 flex-shrink-0 safe-bottom">
-    <button routerLink="/home" class="w-full py-4 bg-green-500 text-white font-semibold rounded-xl">完成</button>
+    <button routerLink="/mobile/home" class="w-full py-4 bg-green-500 text-white font-semibold rounded-xl">完成</button>
   </div>
   </div>
 </div>
 </div>

+ 0 - 0
frontend/src/app/pages/score-report/score-report.scss → frontend/src/app/mobile/pages/score-report/score-report.scss


+ 0 - 0
frontend/src/app/pages/score-report/score-report.ts → frontend/src/app/mobile/pages/score-report/score-report.ts


+ 2 - 2
frontend/src/app/pages/video-player/video-player.html → frontend/src/app/mobile/pages/video-player/video-player.html

@@ -1,6 +1,6 @@
 <div class="fixed inset-0 bg-black z-50 flex flex-col">
 <div class="fixed inset-0 bg-black z-50 flex flex-col">
   <div class="flex-1 video-player flex items-center justify-center relative">
   <div class="flex-1 video-player flex items-center justify-center relative">
-    <button class="absolute top-4 left-4 z-10 w-10 h-10 bg-black/50 rounded-full flex items-center justify-center text-white" routerLink="/learning">
+    <button class="absolute top-4 left-4 z-10 w-10 h-10 bg-black/50 rounded-full flex items-center justify-center text-white" routerLink="/mobile/learning">
       <fa-icon [icon]="faChevronDown"></fa-icon>
       <fa-icon [icon]="faChevronDown"></fa-icon>
     </button>
     </button>
     <div class="text-center text-white">
     <div class="text-center text-white">
@@ -21,6 +21,6 @@
   <div class="bg-gray-900 text-white p-4 safe-bottom">
   <div class="bg-gray-900 text-white p-4 safe-bottom">
     <h3 class="font-semibold mb-2">产品卖点提炼技巧</h3>
     <h3 class="font-semibold mb-2">产品卖点提炼技巧</h3>
     <p class="text-sm text-gray-400 mb-4">第3节:客户视角的卖点转化</p>
     <p class="text-sm text-gray-400 mb-4">第3节:客户视角的卖点转化</p>
-    <button class="w-full py-3.5 bg-green-500 rounded-xl font-medium" routerLink="/quiz/1">完成学习,进入答题</button>
+    <button class="w-full py-3.5 bg-green-500 rounded-xl font-medium" routerLink="/mobile/quiz/1">完成学习,进入答题</button>
   </div>
   </div>
 </div>
 </div>

+ 0 - 0
frontend/src/app/pages/video-player/video-player.scss → frontend/src/app/mobile/pages/video-player/video-player.scss


+ 0 - 0
frontend/src/app/pages/video-player/video-player.ts → frontend/src/app/mobile/pages/video-player/video-player.ts


+ 0 - 0
frontend/src/app/shared/components/bottom-nav/bottom-nav.html → frontend/src/app/mobile/shared/components/bottom-nav/bottom-nav.html


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio