2026-07-27-circle-replace-octopus.md 12 KB

行首页 OctopusGraph → 圈子区块 实现计划

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: Replace OctopusGraph component with "我的圈子 + 发现推荐" circle section on the action dimension homepage

Architecture: Add 2 API wrappers to api.js, then modify action/index.vue to inline the circle section replacing OctopusGraph import + template + data + methods + CSS. Reuse existing CircleCard.vue and CircleDetail.vue components.

Tech Stack: uni-app Vue 2, WeChat mini-program


Task 1: Add circle API functions to api.js

Files:

  • Modify: cfc-frontend/utils/api.js (insert after existing circle functions around line 1710)

  • [ ] Step 1: Add API functions

Find the existing circle functions block around line 1704-1710:

export const getCircleChallengeHistory = (data) => request('/api/health/circle/challenge/history', 'POST', data)

After that line, add:

export const getMyCircles = (memberId, memberType) => {
  return request('/api/circle/my-circles', 'POST', { memberId: memberId, memberType: memberType || 'child' })
}

export const discoverCircles = (childId) => {
  return request('/api/circle/discover', 'POST', { childId: childId })
}
  • Step 2: Verify

Read the surrounding context to confirm insertion point is correct and no syntax errors.


Task 2: Modify action/index.vue — remove OctopusGraph, add circle section

Files:

  • Modify: cfc-frontend/pages/action/index.vue

  • [ ] Step 1: Remove OctopusGraph import and component registration

In the <script> section:

Remove import line (line 132):

import OctopusGraph from '../../components/OctopusGraph.vue'

Remove from components registration (line 139):

OctopusGraph,

Remove from the import line the getContactsList and getHealthAlerts if they were only used by OctopusGraph. Actually, check: loadContacts is called separately in loadChildren, and loadRelationshipHealth is called in onShow. These methods call getContactList and getHealthAlerts respectively. The OctopusGraph component just receives the data. So the data loading methods should stay — they may be needed for other parts.

Actually, let me look at the actual usage: contactList and healthAlerts data are passed to <OctopusGraph> via props. If we remove OctopusGraph, the data loading and storage are still there but unused unless we remove them too. Remove loadContacts() call, loadRelationshipHealth() call, and their data variables to clean up dead code.

Remove from loadChildren method (after self.loadDimensionData()):

self.loadContacts()

Remove from onShow method (after this.loadFamilyMembersVisible()):

this.loadRelationshipHealth()

Remove data properties that were only used by OctopusGraph (around lines 159-161):

contactList: [],
showImportModal: false,
healthAlerts: [],

Remove computed/methods that were only used by OctopusGraph:

  • onContactClick method
  • onShowImport method
  • onCloseImport method
  • onImportSuccess method
  • loadContacts method
  • loadRelationshipHealth method
  • goInteractionLog method
  • goRelationshipQuestionnaire method

Remove ContactCard and ContactImport imports (lines 130-131):

import ContactCard from '../../components/ContactCard.vue'
import ContactImport from '../../components/ContactImport.vue'

Update import of API functions — remove getContactList, getHealthAlerts if they were only used by the removed methods.

Actually, rather than doing this deletion-by-deletion which is error prone, let me take a surgical approach: just remove the OctopusGraph component usage and its data that's ONLY props for it, but keep the imports and methods since removing them is a bigger refactor than needed.

Let me be more precise — the cleanest approach:

  1. Remove <OctopusGraph ...> from template
  2. Remove import OctopusGraph from script
  3. Remove OctopusGraph, from components
  4. Add circle section template + data + methods + CSS

Do NOT remove ContactCard, ContactImport, loadContacts, loadRelationshipHealth, etc. unless they're confirmed unused elsewhere. This is safer and follows "surgical changes" principle.

Actually wait, let me re-read the current code more carefully. The onContactClick, onShowImport, onCloseImport, onImportSuccess methods and ContactImport component, ContactCard component, showImportModal data, contactList data are all related to the OctopusGraph / contact section. But they might also be used elsewhere on the page.

Looking at the template:

  • Line 111-114: <ContactImport :visible="showImportModal" @close="onCloseImport" @success="onImportSuccess" />

This ContactImport is still in the template! So it's page-level, not just OctopusGraph. If we remove OctopusGraph, the ContactImport modal stays if the user wants to import contacts elsewhere.

Actually, ContactImport is a standalone component in the template, not inside OctopusGraph. So removing OctopusGraph component doesn't automatically remove ContactImport. The ContactImport modal is shown from the OctopusGraph component's "import" button via event emission.

Given the user wants to replace the entire OctopusGraph with circles, I should:

  1. Remove <OctopusGraph> template tag
  2. Add circle section template
  3. Remove OctopusGraph import and component registration
  4. Keep everything else — the ContactImport and related methods are page-level and don't hurt to keep. They might be reused later. The important thing is OctopusGraph is gone and circles are in.

Let me revise the plan to be more surgical.

  • Step 2: Remove OctopusGraph from template

Remove lines 92-96:

        <!-- ===== 重要关系维护 ===== -->
        <OctopusGraph
          :contactList="contactList"
          :healthAlerts="healthAlerts"
          :isLoggedIn="isLoggedIn" />

Replace with circle section template (see Step 3).

  • Step 3: Remove OctopusGraph import and component

Remove line 132:

import OctopusGraph from '../../components/OctopusGraph.vue'

Remove from line 139:

OctopusGraph,

Remove from components list (line 139):

'OctopusGraph': OctopusGraph,

(may be just OctopusGraph depending on how it's written)

  • Step 4: Add circle data properties

In data() add after healthAlerts or similar:

myCircles: [],
discoverCirclesList: [],
circleDetailVisible: false,
selectedCircle: null,
selectedCircleIsMember: false,
  • Step 5: Add circle loading methods

In methods, add after loadRelationshipHealth or end of methods:

loadCircleData: function() {
  var self = this
  if (!this.currentChildId) return
  getMyCircles(this.currentChildId, 'child').then(function(res) {
    if (res.code === 200 && res.data) {
      self.myCircles = Array.isArray(res.data) ? res.data : []
    }
  }).catch(function(e) {
    console.log('获取我的圈子失败', e)
  })
  discoverCircles(this.currentChildId).then(function(res) {
    if (res.code === 200 && res.data) {
      self.discoverCirclesList = Array.isArray(res.data) ? res.data.slice(0, 3) : []
    }
  }).catch(function(e) {
    console.log('获取推荐圈子失败', e)
  })
},
onCircleClick: function(circle) {
  var isMember = this.myCircles.some(function(c) {
    return (c.id || c.circleId) === (circle.id || circle.circleId)
  })
  this.selectedCircle = circle
  this.selectedCircleIsMember = isMember
  this.circleDetailVisible = true
},
onCircleJoin: function(circleId) {
  var self = this
  joinCircle({ circleId: circleId, memberId: this.currentChildId, memberType: 'child' }).then(function(res) {
    if (res.code === 200) {
      uni.showToast({ title: '加入成功', icon: 'success' })
      self.circleDetailVisible = false
      self.loadCircleData()
    } else {
      uni.showToast({ title: res.message || '加入失败', icon: 'none' })
    }
  }).catch(function(e) {
    console.log('加入圈子失败', e)
  })
},
onCircleLeave: function(circleId) {
  var self = this
  leaveCircle({ circleId: circleId, memberId: this.currentChildId, memberType: 'child' }).then(function(res) {
    if (res.code === 200) {
      uni.showToast({ title: '已退出', icon: 'success' })
      self.circleDetailVisible = false
      self.loadCircleData()
    } else {
      uni.showToast({ title: res.message || '退出失败', icon: 'none' })
    }
  }).catch(function(e) {
    console.log('退出圈子失败', e)
  })
},
goDiscoverCircles: function() {
  uni.navigateTo({ url: '/pages/discover/circles' })
},
  • Step 6: Add import for circle API functions

In the import line at the top of <script>, add to the existing imports from ../../utils/api.js:

getMyCircles, discoverCircles, joinCircle, leaveCircle
  • Step 7: Call loadCircleData

In loadDimensionData method (line 274), add after this.loadDimensionProducts():

this.loadCircleData()
  • Step 8: Add circle section template

Add this HTML template in place of the removed <OctopusGraph>:

    <!-- ===== 我的圈子 ===== -->
    <view class="circle-section" v-if="isLoggedIn">
      <view class="circle-header">
        <text class="circle-section-title">🤝 我的圈子</text>
        <text class="circle-header-link" @click="goDiscoverCircles">发现更多 ›</text>
      </view>
      <scroll-view class="circle-scroll" scroll-x enable-flex v-if="myCircles.length > 0">
        <view class="circle-scroll-inner">
          <view class="circle-card-wrap" v-for="item in myCircles" :key="item.id || item.circleId" @click="onCircleClick(item)">
            <CircleCard :circle="item" compact />
          </view>
        </view>
      </scroll-view>
      <view class="circle-empty" v-else>
        <text class="circle-empty-text">暂未加入圈子,去发现看看</text>
      </view>

      <!-- ===== 发现推荐 ===== -->
      <view class="circle-discover" v-if="discoverCirclesList.length > 0">
        <view class="circle-header">
          <text class="circle-section-title">🔍 发现推荐</text>
          <text class="circle-header-link" @click="goDiscoverCircles">查看更多 ›</text>
        </view>
        <view class="discover-list">
          <view class="discover-item" v-for="item in discoverCirclesList" :key="item.id || item.circleId" @click="onCircleClick(item)">
            <CircleCard :circle="item" />
          </view>
        </view>
      </view>
    </view>

    <!-- 圈子详情弹窗 -->
    <CircleDetail
      :visible="circleDetailVisible"
      :circle="selectedCircle || {}"
      :isMember="selectedCircleIsMember"
      @close="circleDetailVisible = false"
      @join="onCircleJoin"
      @leave="onCircleLeave" />
  • Step 9: Add component registration

In components (line 139), add:

CircleCard,
CircleDetail,
  • Step 10: Add component imports

Add in the import section:

import CircleCard from '../../components/CircleCard.vue'
import CircleDetail from '../../components/CircleDetail.vue'
  • Step 11: Add CSS styles

Add to <style scoped>:

/* ===== 圈子区块 ===== */
.circle-section {
  margin: 20rpx 20rpx 0;
}
.circle-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16rpx;
}
.circle-section-title {
  font-size: 30rpx;
  font-weight: 600;
  color: #333;
}
.circle-header-link {
  font-size: 24rpx;
  color: #F97316;
  font-weight: 500;
}
.circle-scroll {
  white-space: nowrap;
  overflow: hidden;
}
.circle-scroll-inner {
  display: flex;
  flex-direction: row;
  gap: 16rpx;
  padding: 8rpx 0 16rpx;
}
.circle-card-wrap {
  flex-shrink: 0;
  width: 220rpx;
}
.circle-card-wrap:active {
  opacity: 0.8;
}
.circle-empty {
  padding: 40rpx 0;
  text-align: center;
}
.circle-empty-text {
  font-size: 26rpx;
  color: #ccc;
}
.circle-discover {
  margin-top: 8rpx;
}
.discover-list {
  display: flex;
  flex-direction: column;
  gap: 12rpx;
}
.discover-item:active {
  opacity: 0.8;
}