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
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 })
}
Read the surrounding context to confirm insertion point is correct and no syntax errors.
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 methodonShowImport methodonCloseImport methodonImportSuccess methodloadContacts methodloadRelationshipHealth methodgoInteractionLog methodgoRelationshipQuestionnaire methodRemove 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:
<OctopusGraph ...> from templateimport OctopusGraph from scriptOctopusGraph, from componentsDo 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:
<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:
<OctopusGraph> template tagOctopusGraph import and component registrationLet me revise the plan to be more surgical.
Remove lines 92-96:
<!-- ===== 重要关系维护 ===== -->
<OctopusGraph
:contactList="contactList"
:healthAlerts="healthAlerts"
:isLoggedIn="isLoggedIn" />
Replace with circle section template (see Step 3).
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)
In data() add after healthAlerts or similar:
myCircles: [],
discoverCirclesList: [],
circleDetailVisible: false,
selectedCircle: null,
selectedCircleIsMember: false,
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' })
},
In the import line at the top of <script>, add to the existing imports from ../../utils/api.js:
getMyCircles, discoverCircles, joinCircle, leaveCircle
In loadDimensionData method (line 274), add after this.loadDimensionProducts():
this.loadCircleData()
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" />
In components (line 139), add:
CircleCard,
CircleDetail,
Add in the import section:
import CircleCard from '../../components/CircleCard.vue'
import CircleDetail from '../../components/CircleDetail.vue'
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;
}