Ver Fonte

feat: subpackage split - move non-TabBar pages out of main package to reduce size

- pages.json: move share/profile/login/member-home-detail/child-index/parent-index
  to subpackages (pages/index, pages/login, pages/share, pages/profile)
- index/index.vue: remove static import of ParentIndex/ChildIndex, redirect
  authenticated users via redirectTo instead of rendering as sub-components
- AGENTS.md: document subpackage structure (45 subpackages)
- api.js: add console.log for /api/auth requests for debugging
- family/add-member.vue: minor fix
liaoxg há 1 mês atrás
pai
commit
08a494f1f0

+ 51 - 11
cfc-frontend/AGENTS.md

@@ -16,17 +16,57 @@ uni-app 微信小程序前端,Vue 2 语法,家庭教育任务管理。
 
 ## STRUCTURE
 
-```
-pages/
-├── index/          # 首页(角色分流)
-├── login/          # 登录页
-├── tasks/          # 任务管理
-├── rewards/        # 心愿单
-├── profile/        # 个人中心
-├── guide/          # 成长规划师功能
-├── games/          # 小游戏
-└── parent/         # 家长专属功能
-```
+### 主包(主包 = `pages.json` 的 `pages` 数组)
+
+仅含 5 个 TabBar 页面,主包大小已压缩至 ~1MB 以下:
+
+| 路径 | 说明 |
+|------|------|
+| `pages/index/index` | 首页(未登录态引流页 + 角色路由跳转) |
+| `pages/mind/index` | 心维度首页 |
+| `pages/body/index` | 身维度首页 |
+| `pages/wisdom/index` | 智维度首页 |
+| `pages/wealth/index` | 富维度首页 |
+
+> **登录用户路由**:`pages/index/index` 中 `parent-index` 和 `child-index` 已从静态组件改为 `redirectTo` 跳转到分包页面,不再计入主包体积。
+
+### 分包(`pages.json` 的 `subPackages` 数组,共 45 个)
+
+主要分包(按 `root`):
+
+| Root | 内容 |
+|------|------|
+| `pages/teacher` | 成长规划师中心(7 页) |
+| `pages/guide` | 任务模板、邀请、绑定等(11 页) |
+| `pages/parent` | 家长端市场、孩子详情(3 页) |
+| `pages/child` | 孩子端任务、心愿(2 页) |
+| `pages/tasks` | 任务列表/创建/审核(4 页) |
+| `pages/wishes` | 心愿管理(3 页) |
+| `pages/rewards` | 心愿单/勋章墙(2 页) |
+| `pages/games` | 小游戏(5 页) |
+| `pages/assessment` | 测评申请/报告(5 页) |
+| `pages/dan-assessment` | DAN 测评上传(1 页) |
+| `pages/growth` | 成长档案(6 页) |
+| `pages/shop` | 商城(15 页) |
+| `pages/vendor` | 服务商入驻/管理(5 页) |
+| `pages/health` | 健康功能(18 页) |
+| `pages/mind-detail` | 心智详情(11 页) |
+| `pages/body-detail` | 身体详情(6 页) |
+| `pages/wisdom-detail` | 智慧详情(4 页) |
+| `pages/action-detail` | 行动详情(4 页) |
+| `pages/tianpan` | 家庭天盘(6 页) |
+| `pages/article-center` | 文章中心(4 页) |
+| `pages/activity` | 活动(3 页) |
+| `pages/promotion` | 财富中心/推广(6 页) |
+| `pages/membership` | 会员(3 页) |
+| `pages/index` | 原主包页面(parent-index, child-index, member-home-detail) |
+| `pages/login` | 登录页 |
+| `pages/share` | 健康足迹 |
+| `pages/profile` | 个人中心(profile + index) |
+| `pages/profile-extra` | 孩子管理/优惠券(6 页) |
+| ... 共 45 个分包 | 详见 `pages.json` |
+
+> **注意**:分包页面在首次访问时异步下载,主包只加载必要的 TabBar 页面,确保主包 < 1.5MB。
 
 ## TabBar 配置
 

+ 65 - 45
cfc-frontend/pages.json

@@ -6,25 +6,6 @@
         "navigationBarTitleText": "首页"
       }
     },
-    {
-      "path": "pages/share/index",
-      "style": {
-        "navigationBarTitleText": "健康足迹"
-      }
-    },
-    {
-      "path": "pages/index/child-index",
-      "style": {
-        "navigationBarTitleText": "首页"
-      }
-    },
-    {
-      "path": "pages/index/parent-index",
-      "style": {
-        "navigationBarTitleText": "首页",
-        "enablePullDownRefresh": true
-      }
-    },
     {
       "path": "pages/mind/index",
       "style": {
@@ -48,32 +29,6 @@
       "style": {
         "navigationBarTitleText": "富"
       }
-    },
-    {
-      "path": "pages/profile/index",
-      "style": {
-        "navigationBarTitleText": "我的"
-      }
-    },
-    {
-      "path": "pages/profile/profile",
-      "style": {
-        "navigationBarTitleText": "我的"
-      }
-    },
-    {
-      "path": "pages/login/login",
-      "style": {
-        "navigationBarTitleText": "登录",
-        "navigationStyle": "custom"
-      }
-    },
-    {
-      "path": "pages/index/member-home-detail",
-      "style": {
-        "navigationBarTitleText": "成员详情",
-        "navigationStyle": "custom"
-      }
     }
   ],
   "subPackages": [
@@ -1297,6 +1252,71 @@
           }
         }
       ]
+    },
+    {
+      "root": "pages/index",
+      "pages": [
+        {
+          "path": "parent-index",
+          "style": {
+            "navigationBarTitleText": "首页",
+            "enablePullDownRefresh": true
+          }
+        },
+        {
+          "path": "child-index",
+          "style": {
+            "navigationBarTitleText": "首页"
+          }
+        },
+        {
+          "path": "member-home-detail",
+          "style": {
+            "navigationBarTitleText": "成员详情",
+            "navigationStyle": "custom"
+          }
+        }
+      ]
+    },
+    {
+      "root": "pages/login",
+      "pages": [
+        {
+          "path": "login",
+          "style": {
+            "navigationBarTitleText": "登录",
+            "navigationStyle": "custom"
+          }
+        }
+      ]
+    },
+    {
+      "root": "pages/share",
+      "pages": [
+        {
+          "path": "index",
+          "style": {
+            "navigationBarTitleText": "健康足迹"
+          }
+        }
+      ]
+    },
+    {
+      "root": "pages/profile",
+      "pages": [
+        {
+          "path": "profile",
+          "style": {
+            "navigationBarTitleText": "我的"
+          }
+        },
+        {
+          "path": "index",
+          "style": {
+            "navigationBarTitleText": "我的"
+          }
+        }
+      ]
     }
   ],
   "globalStyle": {

+ 8 - 6
cfc-frontend/pages/family/add-member.vue

@@ -12,7 +12,7 @@
 <!-- 辈分关系(必填,替代关系类型) -->
 <view class="form-item required">
 <text class="label"><text class="required-mark">*</text>辈分关系</text>
-<generation-picker v-model:generationLevel="form.generationLevel" v-model:peerType="form.peerType" :showLabel="false" />
+<generation-picker ref="generationPicker" v-model:generationLevel="form.generationLevel" v-model:peerType="form.peerType" :showLabel="false" />
 </view>
 
 <!-- 性别(必填) -->
@@ -169,9 +169,9 @@ if (!this.form.nickname || !this.form.nickname.trim()) {
 uni.showToast({ title: '请输入成员昵称', icon: 'none' })
 return
 }
-if (!this.form.generationLevel) {
-uni.showToast({ title: '请选择辈分关系', icon: 'none' })
-return
+if (!this.$refs.generationPicker || !this.$refs.generationPicker.selectedGen) {
+  uni.showToast({ title: '请选择辈分关系', icon: 'none' })
+  return
 }
 if (!this.form.gender) {
 uni.showToast({ title: '请选择性别', icon: 'none' })
@@ -188,10 +188,12 @@ return
 
 try {
 this.submitting = true
+var genLevel = (this.$refs.generationPicker && this.$refs.generationPicker.selectedGen) || this.form.generationLevel
+var genPeerType = (this.$refs.generationPicker && this.$refs.generationPicker.selectedPeer) || this.form.peerType
 var payload = {
 nickname: this.form.nickname.trim(),
-generationLevel: this.form.generationLevel,
-peerType: this.form.peerType || null,
+generationLevel: genLevel,
+peerType: genPeerType || null,
 gender: this.form.gender,
 birthday: this.form.birthday
 }

+ 17 - 30
cfc-frontend/pages/index/index.vue

@@ -211,29 +211,14 @@
     <view style="height: 120rpx;"></view>
   </view>
 
-  <!-- 登录态 — 按角色渲染(带淡入淡出过渡)-->
-  <view v-else class="role-switch-wrapper">
-    <transition name="fade" mode="out-in">
-      <parent-index v-if="currentRole === 'parent'" key="parent" @ready="onChildReady" />
-      <child-index v-else-if="currentRole === 'child'" key="child" @ready="onChildReady" />
-      <!-- teacher-index 已废弃,教师功能折入 parent-index -->
-      <view v-else class="loading-container" key="loading">
-        <text>加载中...</text>
-      </view>
-    </transition>
-    <AIFloatingAvatar />
-  </view>
+  <!-- 登录态 — 已迁移到 home 分包,通过 redirectTo 跳转,不再静态渲染 -->
 </view>
 </template>
 
 <script>
-import ParentIndex from './parent-index.vue'
-import ChildIndex from './child-index.vue'
-// TeacherIndex 已废弃,教师功能折入 parent-index
 import PageBanner from '../../components/PageBanner.vue'
 import WuxingSandbox from '../../components/wuxing-sandbox.vue'
 import TabTransition from '../../components/tab-transition.vue'
-import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
 import { acceptParentInvite, productList, getActivityList, getFeaturedArticles } from '../../utils/api.js'
 
 var DIMENSIONS = [
@@ -246,12 +231,9 @@ var DIMENSIONS = [
 
 export default {
   components: {
-    ParentIndex,
-    ChildIndex,
     PageBanner,
     WuxingSandbox,
-    TabTransition,
-    AIFloatingAvatar
+    TabTransition
   },
   data() {
     // 从 storage 预取登录态/角色,确保首次渲染即正确(WeChat 渲染层/逻辑层分离架构下 onLoad 执行前已渲染)
@@ -354,8 +336,7 @@ export default {
     },
 
     onChildReady() {
-      this._splashReady = true
-      this._tryHideSplash()
+      // 已废弃:登录用户不再通过此页面渲染子组件
     },
 
     // 首页 TabTransition 动态关闭:检测关键内容是否就绪
@@ -394,15 +375,21 @@ export default {
       this.syncRoleAndTabBar()
       if (options.inviteCode && options.type === 'parent_invite') {
         this.handleParentInvite(options.inviteCode)
-      } else {
-        var storedCode = uni.getStorageSync('inviteCode')
-        var storedType = uni.getStorageSync('inviteType')
-        if (storedCode && storedType === 'parent_invite') {
-          this.handleParentInvite(storedCode)
-          uni.removeStorageSync('inviteCode')
-          uni.removeStorageSync('inviteType')
-        }
+        return
+      }
+      var storedCode = uni.getStorageSync('inviteCode')
+      var storedType = uni.getStorageSync('inviteType')
+      if (storedCode && storedType === 'parent_invite') {
+        this.handleParentInvite(storedCode)
+        uni.removeStorageSync('inviteCode')
+        uni.removeStorageSync('inviteType')
+        return
       }
+      // 登录用户:跳转到 home 分包页面,不再静态渲染组件
+      var targetUrl = this.currentRole === 'child'
+        ? '/pages/index/child-index'
+        : '/pages/index/parent-index'
+      uni.redirectTo({ url: targetUrl })
     },
     isLoggedIn() {
       return !!uni.getStorageSync('token')

+ 5 - 0
cfc-frontend/utils/api.js

@@ -23,6 +23,11 @@ function _clearAuthAndRedirect() {
 }
 
 const request = (url, method = 'POST', data = {}, options = {}) => {
+  // 调试:打印完整请求地址
+  var fullUrl = BASE_URL + url
+  if (url.indexOf('/api/auth') === 0) {
+    console.log('[API] ' + method + ' ' + fullUrl, data)
+  }
   // Append query params from options to URL if provided (e.g., { guideId } for GET requests)
   if (options && Object.keys(options).length > 0) {
     const separator = url.includes('?') ? '&' : '?'