2026-06-19-mind-page-dual-tab.md 16 KB

心智页双Tab结构 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 为心智页 pages/mind/index.vue 添加心/智双Tab结构,用户可在心理与认知内容之间切换

Architecture: 单页面内通过 activeTab 数据属性 + v-if 控制心/智两个内容区块的显示隐藏,Tab栏位于图谱/快捷入口之后。推荐阅读、维度任务/活动/商品跟随activeTab切换维度参数。

Tech Stack: uni-app Vue 2, 微信小程序

Design Spec Ref: docs/superpowers/specs/2026-06-13-five-dimension-pages-redesign.md §4


注意事项(Agent执行时阅读,不写入文件)

  • 搜索范围限制: glob 仅在 cfc-frontend/components/cfc-frontend/pages/mind/cfc-frontend/utils/cfc-frontend/config/ 下搜索。禁止搜索 node_modules/unpackage/dist/
  • NEVER 在模板中使用可选链 ?.(微信小程序不支持)
  • NEVER git commitgit push,只修改文件
  • 所有模块必须 v-if 切换,不得使用 v-show(小程序 v-show 仍会渲染组件,影响性能)
  • 计算属性使用 function 关键字(Vue 2 Options API),不要使用箭头函数或 get 语法
  • v-for 中的元素如果有 v-if,用 <template v-for> 包裹或移到外层
  • 文章推荐阅读必须在两个Tab中都显示(心理Tab和心理相关,认知Tab和认知相关)

DO NOT 擅自创建/修改其他文件 — 只改 mind/index.vue


Task 1: Tab 状态 + Tab栏UI + 内容区块隔离

Files:

  • Modify: cfc-frontend/pages/mind/index.vue — 全文件(1392行)

做什么:

  1. data() 中添加 activeTab: 'emotion'
  2. FamilyRelationGraph / UserQuickEntry 之后、原心理能量概况之前,插入Tab栏
  3. 将原心理内容(能量概况~推荐阅读)包裹在 <template v-if="activeTab === 'emotion'">
  4. 将原认知内容(综合认知评分~阅读统计)包裹在 <template v-if="activeTab === 'cognitive'">
  5. 添加 switchTab(tab) 方法
  6. 添加 .tab-bar / .tab-item / .tab-item.active 样式

详细步骤:

  • Step 1: 在 data() 中添加 activeTab 属性

data() 中,dimensionProducts: [] 之后添加:

activeTab: 'emotion'
  • Step 2: 在template中插入Tab栏

找到(当前模板第53行):

    <!-- ===== 心理能量概况 ===== -->

在其之前插入Tab栏:

    <!-- ===== 心/智 Tab栏 ===== -->
    <view class="tab-bar">
      <view
        :class="['tab-item', activeTab === 'emotion' ? 'active' : '']"
        @click="switchTab('emotion')">
        <text class="tab-icon">&#x2764;</text>
        <text class="tab-label">心·心理</text>
      </view>
      <view
        :class="['tab-item', activeTab === 'cognitive' ? 'active' : '']"
        @click="switchTab('cognitive')">
        <text class="tab-icon">&#x1F9E0;</text>
        <text class="tab-label">智·认知</text>
      </view>
    </view>
  • Step 3: 包裹心理内容为 emotion Tab

在第53行 <!-- ===== 心理能量概况 ===== --> 之前插入:

    <!-- ===== 心·心理 Tab ===== -->
    <template v-if="activeTab === 'emotion'">

然后在原认知区块之前(第162行 <!-- ===== 认知区块 ===== -->)找到对应的闭合,替换为:

    </template>
  • Step 4: 包裹认知内容为 cognitive Tab

在第162行 <!-- ===== 认知区块 ===== --> 之前插入:

    <!-- ===== 智·认知 Tab ===== -->
    <template v-if="activeTab === 'cognitive'">

然后在 </template>(第311行 </view> 之前那里是底部区块)之前,找到认知区块的结束位置(第276行 <!-- ===== 底部:维度任务/活动/商品 ===== --> 之前),插入:

    </template>

等一下 — 我需要更精确地确认包裹范围。

当前认知内容范围:162行(<!-- ===== 认知区块 ===== -->)到276行(底部:维度任务/活动/商品之前)。

所以替换为:

    <!-- ===== 智·认知 Tab ===== -->
    <template v-if="activeTab === 'cognitive'">

    <!-- 原有认知内容... -->

    </template>

    <!-- ===== 底部:维度任务/活动/商品 ===== -->
  • Step 5: 添加 switchTab 方法

methods 中,goAssessment 方法之前添加:

switchTab: function(tab) {
  this.activeTab = tab
}
  • Step 6: 添加Tab栏样式

<style scoped> 中,.container 样式之后添加:

/* ===== 心/智 Tab 栏 ===== */
.tab-bar {
  display: flex;
  flex-direction: row;
  margin: 20rpx 30rpx;
  background: #fff;
  border-radius: 20rpx;
  padding: 8rpx;
  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
}
.tab-item {
  flex: 1;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  padding: 20rpx 0;
  border-radius: 16rpx;
  transition: all 0.3s;
}
.tab-item.active {
  background: linear-gradient(135deg, #8B5CF6, #A78BFA);
  box-shadow: 0 4rpx 12rpx rgba(139,92,246,0.3);
}
.tab-icon {
  font-size: 28rpx;
  margin-right: 8rpx;
}
.tab-item.active .tab-icon {
  color: #fff;
}
.tab-label {
  font-size: 28rpx;
  font-weight: 500;
  color: #999;
}
.tab-item.active .tab-label {
  color: #fff;
  font-weight: bold;
}

Task 2: 推荐阅读按Tab筛选维度

做什么:

  1. 在文章映射时保留 relatedDimensions 字段
  2. 添加 emotionArticles / cognitiveArticles 计算属性,按 activeTab 过滤
  3. 推荐阅读区域切换使用对应的文章列表
  4. 推荐阅读已登录限制移除
  • Step 1: 文章映射保留 relatedDimensions

loadFeaturedArticles 中,list.map 回调内添加 relatedDimensions

self.articles = list.map(function(item, index) {
  return {
    id: item.id,
    category: item.categoryName || '成长文章',
    categoryColor: gradientColors[index % gradientColors.length],
    title: item.title || '',
    summary: item.summary || '',
    views: item.viewCount ? String(item.viewCount) : '0',
    likes: '0',
    date: item.publishedAt ? item.publishedAt.slice(0, 10) : '',
    relatedDimensions: item.relatedDimensions || []  // 保留维度标签用于过滤
  }
})
  • Step 2: 添加文章计算属性

computed 中,intimacyMapForGraph 之后添加:

emotionArticles: function() {
  if (!this.articles || this.articles.length === 0) return []
  var result = []
  for (var i = 0; i < this.articles.length; i++) {
    var a = this.articles[i]
    if (a.relatedDimensions && a.relatedDimensions.indexOf('mind') !== -1) {
      result.push(a)
    }
  }
  // 如果没有匹配 dimension 的文章,回退显示全部
  if (result.length === 0) return this.articles.slice(0, 5)
  return result.slice(0, 5)
},
cognitiveArticles: function() {
  if (!this.articles || this.articles.length === 0) return []
  var result = []
  for (var i = 0; i < this.articles.length; i++) {
    var a = this.articles[i]
    if (a.relatedDimensions && a.relatedDimensions.indexOf('wisdom') !== -1) {
      result.push(a)
    }
  }
  if (result.length === 0) return this.articles.slice(0, 5)
  return result.slice(0, 5)
}
  • Step 3: 推荐阅读区域使用对应的文章列表

心理Tab中的推荐阅读(第136-160行),v-for="article in articles" 改为 v-for="article in emotionArticles"

<!-- 推荐阅读(心理Tab内) -->
<view class="section" v-if="sectionVisible('recommended_reading')">
  <view class="section-header">
    <text class="section-title">推荐阅读</text>
  </view>
  <view class="article-list">
    <view class="article-card" v-for="article in emotionArticles" :key="article.id" @click="goArticleDetail(article.id)">

在认知Tab内的推荐阅读部分(需要认知Tab也有一份推荐阅读),在当前认知内容的最底部、认知Tab闭合之前插入:

  <!-- 推荐阅读(认知Tab内) -->
  <view class="section" v-if="sectionVisible('recommended_reading')">
    <view class="section-header">
      <text class="section-title">推荐阅读</text>
    </view>
    <view class="article-list">
      <view class="article-card" v-for="article in cognitiveArticles" :key="article.id" @click="goArticleDetail(article.id)">
        <view class="article-category" :style="{ background: article.categoryColor }">
          <text class="article-category-text">{{ article.category }}</text>
        </view>
        <text class="article-title">{{ article.title }}</text>
        <text class="article-summary">{{ article.summary }}</text>
        <view class="article-meta">
          <view class="meta-item">
            <text class="meta-icon">&#x1F4D6;</text>
            <text class="meta-text">{{ article.views }}</text>
          </view>
          <view class="meta-item">
            <text class="meta-icon">&#x1F44D;</text>
            <text class="meta-text">{{ article.likes }}</text>
          </view>
          <text class="article-date">{{ article.date }}</text>
        </view>
      </view>
    </view>
  </view>

Task 3: 维度数据跟随Tab切换

做什么:

  1. 添加 currentDimensionCode 计算属性,根据 activeTab 返回 mindwisdom
  2. DimensionTasks / DimensionActivities / DimensionProductsdimensionCode 绑定动态值
  3. loadDimensionActivities / loadDimensionProducts 方法改为使用动态维度
  4. moreActivities / moreProducts 导航URL使用动态维度
  • Step 1: 添加 currentDimensionCode 计算属性

computed 中,cognitiveArticles 之后添加:

currentDimensionCode: function() {
  return this.activeTab === 'emotion' ? 'mind' : 'wisdom'
}
  • Step 2: 底部维度区块绑定动态 dimensionCode

在模板中找到(第278-295行):

    <DimensionTasks
      v-if="isLoggedIn"
      dimensionCode="mind"
      ... />

    <DimensionActivities
      dimensionCode="mind"
      ... />

    <DimensionProducts
      dimensionCode="mind"
      ... />

改为:

    <DimensionTasks
      v-if="isLoggedIn"
      :dimensionCode="currentDimensionCode"
      ... />

    <DimensionActivities
      :dimensionCode="currentDimensionCode"
      ... />

    <DimensionProducts
      :dimensionCode="currentDimensionCode"
      ... />

注意:DimensionTasksdimensionCode 前加 : 冒号(动态绑定)。

  • Step 3: 数据加载方法使用动态维度

loadDimensionActivities 方法(第750-758行):

loadDimensionActivities: function() {
  var self = this
  var dimCode = self.currentDimensionCode
  getActivityList({ dimensionCode: dimCode, page: 1, size: 3 }).then(function(res) {
    if (res && res.data) {
      var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
      self.dimensionActivities = list.slice(0, 3)
    }
  }).catch(function() { self.dimensionActivities = [] })
}

loadDimensionProducts 方法(第759-767行):

loadDimensionProducts: function() {
  var self = this
  var dimCode = self.currentDimensionCode
  getProductsByDomain(dimCode, 1, 4).then(function(res) {
    if (res && res.data) {
      var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
      self.dimensionProducts = list.slice(0, 4)
    }
  }).catch(function() { self.dimensionProducts = [] })
}
  • Step 4: switchTab 时重载维度数据

更新 switchTab 方法:

switchTab: function(tab) {
  if (tab === this.activeTab) return
  this.activeTab = tab
  // 切换维度后重载对应的活动/商品数据
  this.loadDimensionActivities()
  this.loadDimensionProducts()
}
  • Step 5: 更新更多链接

goMoreActivities 方法(第804-806行):

goMoreActivities: function() {
  var dimCode = this.currentDimensionCode
  uni.navigateTo({ url: '/pages/discover/index?type=activity&dimension=' + dimCode })
}

goMoreProducts 方法(第810-812行):

goMoreProducts: function() {
  var dimCode = this.currentDimensionCode
  uni.navigateTo({ url: '/pages/discover/index?type=product&domain=' + dimCode })
}

Task 4: 登录限制调整(商品/活动/文章始终显示)

做什么:

  1. 移除 DimensionActivitiesDimensionProductsv-if="isLoggedIn"(它们已在onShow中无条件加载)
  2. 推荐阅读已在上一步中调整(无条件显示)
  3. 确保 loadFeaturedArticles 在未登录时也调用(当前已调用)
  • Step 1: 移除活动/商品的登录限制

当前(第285-295行):

<DimensionActivities
  dimensionCode="mind"
  :activities="..."
  @activityClick="..."
  @moreActivities="..." />

<DimensionProducts
  dimensionCode="mind"
  :products="..."
  @productClick="..."
  @moreProducts="..." />

确认这些没有 v-if="isLoggedIn" — 它们当前确实没有(只有 DimensionTasksv-if="isLoggedIn")。无需改动。

但需要确认 loadDimensionActivitiesloadDimensionProductsonShow 中未登录时也被调用 — 当前代码第455-456行已做:

    this.loadDimensionActivities()
    this.loadDimensionProducts()

这些在 if (this.isLoggedIn) { ... } 块之外,所以没问题。

  • Step 2: 确认推荐阅读始终加载

当前 loadFeaturedArticles()onShow 中无条件调用(第442行),且在未登录时 sectionVisible('recommended_reading') 默认返回 true,没问题。


Task 5: 编译验证

做什么:

  1. 验证编译通过
  2. 修复 LSP 诊断错误
  • [ ] Step 1: 检查LSP诊断

    # 检查修改后的文件有无诊断错误
    

使用 lsp_diagnostics 工具检查 cfc-frontend/pages/mind/index.vue

  • Step 2: 验证Tab切换逻辑

手动验证清单:

  • Tab栏正确渲染(心·心理 / 智·认知)
  • 默认显示心理Tab内容
  • 点击「智·认知」Tab显示认知内容
  • 点击「心·心理」Tab回到心理内容
  • 推荐阅读在两个Tab中均显示
  • 活动/商品跟随Tab切换维度

设计补充说明:Tab改造后的页面流

PageBanner
  ↓
FamilyMemberStrip (登录后)
  ↓
LoginGuideCard (未登录)
  ↓
FamilyEnergyBar (登录后, dualDimension)
  ↓
FamilyRelationGraph (登录后)
  ↓
UserQuickEntry (登录后)
  ↓
┌─ Tab栏 ──────────────────────────────┐
│  [❤ 心·心理]    [🧠 智·认知]         │
└──────────────────────────────────────┘
  ↓ activeTab === 'emotion'              ↓ activeTab === 'cognitive'
┌─ 心理能量概况 ┐                    ┌─ 综合认知评分 ┐
│ EMI 四维条形图 │                    │ 认知六维雷达图 │
│ 大五人格雷达图 │                    │ 认知训练       │
│ 每日心理       │                    │ 本月阅读统计   │
│ 推荐阅读(mind) │                    │ 推荐阅读(wisdom)│
└────────────────┘                    └────────────────┘
  ↓                                    ↓
┌─ DimensionTasks (isLoggedIn) ────────┐
│  (currentDimensionCode=mind/wisdom)  │
├──────────────────────────────────────┤
│  DimensionActivities                 │
│  (currentDimensionCode=mind/wisdom)  │
├──────────────────────────────────────┤
│  DimensionProducts                   │
│  (currentDimensionCode=mind/wisdom)  │
└──────────────────────────────────────┘
  ↓
功能入口 Grid

执行确认

Agent 注意:

  • 只修改 cfc-frontend/pages/mind/index.vue 一个文件
  • 使用 edit 工具精确替换
  • 每次替换后用 lsp_diagnostics 验证
  • 不 commit, 不 push