Просмотр исходного кода

删除废弃的 pages/action 分包并修复引用

- 删除 pages/action/ 分包(index.vue 等 5 个文件,已在 pages.json 中取消注册)
- parent-index.vue: /pages/action/contact-detail → /pages/action-detail/contact-detail
- action-section.vue: activity-detail 跳转修正为 /pages/activity/activity-detail/activity-detail
- action-section.vue: task-detail 标记 TODO(该页面不存在,为死代码)
- pages.json: 移除 pages/action 分包注册
Xiaogang Liao 3 месяцев назад
Родитель
Сommit
c226b6ab9a

+ 4 - 4
cfc-frontend/components/member-detail-sections/action-section.vue

@@ -142,13 +142,13 @@ export default {
       return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate()
     },
     goTaskDetail: function(task) {
-      uni.navigateTo({
-        url: '/pages/action/task-detail?id=' + task.id
-      })
+      // TODO: task-detail 页面尚未实现,暂不跳转
+      console.log('task-detail not implemented', task)
     },
     goActivityDetail: function(act) {
+      if (!act || !act.id) return
       uni.navigateTo({
-        url: '/pages/action/activity-detail?id=' + act.id
+        url: '/pages/activity/activity-detail/activity-detail?id=' + act.id
       })
     }
   }

+ 0 - 11
cfc-frontend/pages.json

@@ -1212,17 +1212,6 @@
         }
       ]
     },
-    {
-      "root": "pages/action",
-      "pages": [
-        {
-          "path": "index",
-          "style": {
-            "navigationBarTitleText": "行动"
-          }
-        }
-      ]
-    },
     {
       "root": "pages/member-detail",
       "pages": [

+ 0 - 400
cfc-frontend/pages/action/contact-detail.vue

@@ -1,400 +0,0 @@
-<template>
-  <view class="detail-page">
-    <!-- 头部 -->
-    <view class="detail-header">
-      <view class="header-top">
-        <text class="back-btn" @click="goBack">← 返回</text>
-        <text class="page-title">关系详情</text>
-        <text class="edit-btn" @click="toggleEdit">{{ isEditing ? '取消' : '编辑' }}</text>
-      </view>
-      <view class="header-avatar" :style="{ background: avatarColor }">
-        <text class="avatar-text">{{ initial }}</text>
-      </view>
-      <text class="header-name">{{ contact.name || '未知' }}</text>
-      <text class="header-meta">{{ typeLabel }}</text>
-      <text class="header-known" v-if="contact.knownSinceLabel">{{ contact.knownSinceLabel }}</text>
-      <view class="header-intimacy">
-        <text class="intimacy-heart">❤️</text>
-        <text class="intimacy-num">{{ contact.intimacyLevel || 0 }}</text>
-        <text class="intimacy-label">亲密度</text>
-      </view>
-    </view>
-
-    <!-- 基本信息 -->
-    <view class="info-section">
-      <text class="section-title">基本信息</text>
-      <view class="info-row" v-if="!isEditing">
-        <text class="info-icon">📞</text>
-        <text class="info-value">{{ contact.phone || '未填写' }}</text>
-      </view>
-      <view class="info-row" v-if="!isEditing && contact.birthdayLabel">
-        <text class="info-icon">🎂</text>
-        <text class="info-value">{{ contact.birthdayLabel }}
-          <text v-if="contact.birthdayCountdown" class="countdown"> · {{ contact.birthdayCountdown }}</text>
-        </text>
-      </view>
-      <view class="info-row" v-if="!isEditing && contact.bio">
-        <text class="info-icon">📝</text>
-        <text class="info-value">{{ contact.bio }}</text>
-      </view>
-      <view class="info-row" v-if="!isEditing && contact.tags">
-        <text class="info-icon">🏷️</text>
-        <text class="info-value">{{ contact.tags }}</text>
-      </view>
-      <view class="info-row" v-if="!isEditing && contact.notes">
-        <text class="info-icon">💬</text>
-        <text class="info-value">{{ contact.notes }}</text>
-      </view>
-
-      <!-- 编辑模式 -->
-      <view v-if="isEditing">
-        <view class="edit-row">
-          <text class="edit-label">姓名</text>
-          <input class="edit-input" v-model="editForm.name" />
-        </view>
-        <view class="edit-row">
-          <text class="edit-label">手机号</text>
-          <input class="edit-input" v-model="editForm.phone" />
-        </view>
-        <view class="edit-row">
-          <text class="edit-label">关系类型</text>
-          <picker mode="selector" :range="typeOptions" @change="onTypeChange">
-            <view class="edit-picker">{{ editForm.relationshipTypeLabel || '请选择' }}</view>
-          </picker>
-        </view>
-        <view class="edit-row">
-          <text class="edit-label">认识时间</text>
-          <picker mode="date" @change="onDateChange">
-            <view class="edit-picker">{{ editForm.knownSince || '请选择' }}</view>
-          </picker>
-        </view>
-        <view class="edit-row">
-          <text class="edit-label">生日</text>
-          <picker mode="date" @change="onBirthdayChange">
-            <view class="edit-picker">{{ editForm.birthday || '请选择' }}</view>
-          </picker>
-        </view>
-        <view class="edit-row">
-          <text class="edit-label">个人介绍</text>
-          <textarea class="edit-textarea" v-model="editForm.bio" placeholder="选填" />
-        </view>
-        <view class="edit-row">
-          <text class="edit-label">备注</text>
-          <input class="edit-input" v-model="editForm.notes" placeholder="选填" />
-        </view>
-        <button class="btn-save" @click="saveEdit">保存</button>
-      </view>
-    </view>
-
-    <!-- 互动记录 -->
-    <view class="interaction-section">
-      <text class="section-title">互动记录</text>
-      <view class="interaction-item" v-if="contact.lastContactAt">
-        <text class="interaction-date">{{ formatDate(contact.lastContactAt) }}</text>
-        <text class="interaction-desc">标记了联系</text>
-      </view>
-      <view class="interaction-item" v-if="contact.createdAt">
-        <text class="interaction-date">{{ formatDate(contact.createdAt) }}</text>
-        <text class="interaction-desc">添加了联系人</text>
-      </view>
-      <view class="interaction-empty" v-if="!contact.lastContactAt && !contact.updatedAt">
-        <text>暂无互动记录</text>
-      </view>
-      <button class="btn-interaction" @click="markInteraction">📞 标记联系</button>
-    </view>
-
-    <!-- 删除 -->
-    <view class="danger-section">
-      <button class="btn-delete" @click="confirmDelete">删除联系人</button>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getContactDetail, updateContact, deleteContact, recordContactInteraction } from '../../utils/api.js'
-
-export default {
-  data: function() {
-    return {
-      contactId: null,
-      contact: {},
-      isEditing: false,
-      editForm: {},
-      typeOptions: ['家人', '朋友', '伴侣', '同事', '其他'],
-      typeValues: { '家人': 'family', '朋友': 'friend', '伴侣': 'partner', '同事': 'colleague', '其他': 'other' },
-      reverseTypeValues: { 'family': '家人', 'friend': '朋友', 'partner': '伴侣', 'colleague': '同事', 'other': '其他' }
-    }
-  },
-  computed: {
-    initial: function() {
-      return this.contact.name ? this.contact.name[0] : '?'
-    },
-    avatarColor: function() {
-      var colors = ['#FF6B35', '#667eea', '#10B981', '#F59E0B', '#8B5CF6', '#EC4899']
-      var idx = (this.contact.id || 0) % colors.length
-      return colors[idx]
-    },
-    typeLabel: function() {
-      var map = { family: '家人', friend: '朋友', partner: '伴侣', colleague: '同事', other: '其他' }
-      return map[this.contact.relationshipType] || '其他'
-    }
-  },
-  onLoad: function(options) {
-    if (options.id) {
-      this.contactId = options.id
-      this.loadDetail()
-    }
-  },
-  methods: {
-    loadDetail: function() {
-      var self = this
-      if (!this.contactId) return
-      getContactDetail(this.contactId).then(function(res) {
-        if (res.code === 200 && res.data) {
-          self.contact = res.data
-        } else {
-          uni.showToast({ title: res.message || '加载失败', icon: 'none' })
-        }
-      }).catch(function() {
-        uni.showToast({ title: '网络错误', icon: 'none' })
-      })
-    },
-    toggleEdit: function() {
-      if (this.isEditing) {
-        this.isEditing = false
-      } else {
-        this.editForm = {
-          name: this.contact.name || '',
-          phone: this.contact.phone || '',
-          relationshipType: this.contact.relationshipType || '',
-          relationshipTypeLabel: this.reverseTypeValues[this.contact.relationshipType] || '其他',
-          knownSince: this.contact.knownSince ? this.contact.knownSince.substring(0, 10) : '',
-          birthday: this.contact.birthday ? this.contact.birthday.substring(0, 10) : '',
-          bio: this.contact.bio || '',
-          notes: this.contact.notes || ''
-        }
-        this.isEditing = true
-      }
-    },
-    onTypeChange: function(e) {
-      var label = this.typeOptions[e.detail.value]
-      this.editForm.relationshipType = this.typeValues[label] || 'other'
-      this.editForm.relationshipTypeLabel = label
-    },
-    onDateChange: function(e) {
-      this.editForm.knownSince = e.detail.value
-    },
-    onBirthdayChange: function(e) {
-      this.editForm.birthday = e.detail.value
-    },
-    saveEdit: function() {
-      var self = this
-      var payload = { id: this.contact.id, name: this.editForm.name, phone: this.editForm.phone }
-      if (this.editForm.relationshipType) payload.relationshipType = this.editForm.relationshipType
-      if (this.editForm.knownSince) payload.knownSince = this.editForm.knownSince
-      if (this.editForm.birthday) payload.birthday = this.editForm.birthday
-      if (this.editForm.bio !== undefined) payload.bio = this.editForm.bio
-      if (this.editForm.notes !== undefined) payload.notes = this.editForm.notes
-      updateContact(payload).then(function(res) {
-        if (res.code === 200) {
-          uni.showToast({ title: '保存成功', icon: 'success' })
-          self.isEditing = false
-          self.loadDetail()
-        } else {
-          uni.showToast({ title: res.message || '保存失败', icon: 'none' })
-        }
-      }).catch(function() {
-        uni.showToast({ title: '网络错误', icon: 'none' })
-      })
-    },
-    markInteraction: function() {
-      var self = this
-      recordContactInteraction(this.contact.id).then(function(res) {
-        if (res.code === 200) {
-          uni.showToast({ title: '已标记联系', icon: 'success' })
-          self.loadDetail()
-        } else {
-          uni.showToast({ title: res.message || '操作失败', icon: 'none' })
-        }
-      }).catch(function() {
-        uni.showToast({ title: '网络错误', icon: 'none' })
-      })
-    },
-    confirmDelete: function() {
-      var self = this
-      uni.showModal({
-        title: '确认删除',
-        content: '确定要删除该联系人吗?',
-        success: function(r) {
-          if (r.confirm) {
-            deleteContact({ id: self.contact.id }).then(function(res) {
-              if (res.code === 200) {
-                uni.showToast({ title: '已删除', icon: 'success' })
-                setTimeout(function() { uni.navigateBack() }, 500)
-              } else {
-                uni.showToast({ title: res.message || '删除失败', icon: 'none' })
-              }
-            }).catch(function() {
-              uni.showToast({ title: '网络错误', icon: 'none' })
-            })
-          }
-        }
-      })
-    },
-    formatDate: function(dateStr) {
-      if (!dateStr) return '-'
-      return dateStr.substring(0, 10)
-    },
-    goBack: function() {
-      uni.navigateBack()
-    }
-  }
-}
-</script>
-
-<style scoped>
-.detail-page {
-  min-height: 100vh;
-  background: #f5f7fa;
-  padding-bottom: 40rpx;
-}
-.detail-header {
-  background: linear-gradient(135deg, #667eea, #764ba2);
-  padding: 60rpx 30rpx 40rpx;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  color: #fff;
-}
-.header-top {
-  width: 100%;
-  display: flex;
-  justify-content: space-between;
-  margin-bottom: 30rpx;
-}
-.back-btn, .edit-btn {
-  font-size: 28rpx;
-  padding: 10rpx 20rpx;
-}
-.page-title { font-size: 32rpx; font-weight: bold; }
-.header-avatar {
-  width: 120rpx;
-  height: 120rpx;
-  border-radius: 60rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 16rpx;
-  border: 4rpx solid rgba(255,255,255,0.4);
-}
-.avatar-text { font-size: 48rpx; font-weight: bold; color: #fff; }
-.header-name { font-size: 36rpx; font-weight: bold; margin-bottom: 8rpx; }
-.header-meta { font-size: 26rpx; opacity: 0.9; margin-bottom: 4rpx; }
-.header-known { font-size: 24rpx; opacity: 0.7; margin-bottom: 12rpx; }
-.header-intimacy { display: flex; align-items: center; gap: 8rpx; }
-.intimacy-heart { font-size: 32rpx; }
-.intimacy-num { font-size: 40rpx; font-weight: bold; }
-.intimacy-label { font-size: 24rpx; opacity: 0.8; }
-
-.info-section, .interaction-section {
-  background: #fff;
-  margin: 20rpx;
-  border-radius: 20rpx;
-  padding: 30rpx;
-}
-.section-title {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #333;
-  display: block;
-  margin-bottom: 20rpx;
-}
-.info-row {
-  display: flex;
-  align-items: flex-start;
-  margin-bottom: 16rpx;
-}
-.info-icon { font-size: 28rpx; margin-right: 12rpx; width: 40rpx; }
-.info-value { font-size: 26rpx; color: #555; flex: 1; }
-.countdown { color: #EC4899; }
-
-.edit-row {
-  margin-bottom: 20rpx;
-}
-.edit-label {
-  font-size: 26rpx;
-  color: #666;
-  display: block;
-  margin-bottom: 8rpx;
-}
-.edit-input {
-  border: 1rpx solid #ddd;
-  border-radius: 12rpx;
-  padding: 24rpx 20rpx;
-  font-size: 28rpx;
-  width: 100%;
-  box-sizing: border-box;
-  min-height: 80rpx;
-}
-.edit-textarea {
-  border: 1rpx solid #ddd;
-  border-radius: 12rpx;
-  padding: 24rpx 20rpx;
-  font-size: 28rpx;
-  width: 100%;
-  box-sizing: border-box;
-  min-height: 160rpx;
-}
-.edit-picker {
-  border: 1rpx solid #ddd;
-  border-radius: 12rpx;
-  padding: 24rpx 20rpx;
-  font-size: 28rpx;
-  min-height: 80rpx;
-  display: flex;
-  align-items: center;
-}
-.btn-save {
-  width: 100%;
-  padding: 24rpx;
-  border-radius: 12rpx;
-  background: linear-gradient(135deg, #FF6B35, #F97316);
-  font-size: 30rpx;
-  color: #fff;
-  text-align: center;
-  border: none;
-  margin-top: 20rpx;
-}
-
-.interaction-item {
-  display: flex;
-  justify-content: space-between;
-  padding: 16rpx 0;
-  border-bottom: 1rpx solid #f0f0f0;
-}
-.interaction-date { font-size: 24rpx; color: #999; }
-.interaction-desc { font-size: 26rpx; color: #555; }
-.interaction-empty { text-align: center; padding: 30rpx; color: #999; font-size: 26rpx; }
-.btn-interaction {
-  width: 100%;
-  padding: 24rpx;
-  border-radius: 12rpx;
-  background: linear-gradient(135deg, #667eea, #764ba2);
-  font-size: 30rpx;
-  color: #fff;
-  text-align: center;
-  border: none;
-  margin-top: 20rpx;
-}
-.danger-section { margin: 20rpx; }
-.btn-delete {
-  width: 100%;
-  padding: 24rpx;
-  border-radius: 12rpx;
-  background: #fff;
-  border: 1rpx solid #ff4d4f;
-  font-size: 28rpx;
-  color: #ff4d4f;
-  text-align: center;
-}
-</style>

+ 0 - 678
cfc-frontend/pages/action/index.vue

@@ -1,678 +0,0 @@
-<template>
-  <view class="container">
-    <!-- Tabbar 切换过渡页 -->
-    <tab-transition v-if="showTabTransition" dimCode="action" waitingFor="action" ref="tabTransition" @close="showTabTransition = false" />
-
-    <!-- 品牌头部 -->
-    <PageBanner theme="action"
-      tagline="知行合一 · 快乐成长"
-      quote="千里之行,始于足下" />
-
-    <!-- 家庭成员选择条 -->
-    <FamilyMemberStrip
-      v-if="isLoggedIn"
-      :members="familyMembersVisible"
-      :selectedMemberId="selectedMemberId"
-      @select="onFamilyMemberSelect"
-      @removed="loadFamilyMembersVisible" />
-
-    <!-- 游客登录引导卡 -->
-    <template v-if="!isLoggedIn">
-      <LoginGuideCard
-        title="登录后查看任务和活动记录"
-        :items="[
-          '查看今日任务,完成打卡获取行动能量',
-          '报名参加热门活动,记录成长足迹',
-          '查看专属商品和活动价格'
-        ]" />
-    </template>
-
-    <!-- 登录后:五维能量条 -->
-    <FamilyEnergyBar
-      v-if="isLoggedIn"
-      dimensionCode="action"
-      :sandboxData="sandboxData"
-      :dualDimension="dualDimension" />
-
-    <!-- 能量沙盘(登录后可见) — Canvas 五行星图 -->
-    <WuxingSandbox
-      v-if="isLoggedIn && sandboxData"
-      mode="live"
-      :scores="sandboxScores"
-      :overallScore="sandboxData.overallScore || 0"
-      :members="sandboxData.members || []"
-      showDimensionBar
-      starHeight="480rpx" />
-
-    <!-- 关系图谱(登录后可见) — 家庭关系网络 -->
-    <FamilyRelationGraph
-      v-if="isLoggedIn && familyMembersVisible.length > 0"
-      dimensionCode="action"
-      :selfId="activeChildId"
-      :members="familyMembersVisible"
-      :energyMap="energyMapForGraph"
-      :intimacyMap="intimacyMapForGraph"
-      @memberTap="goMemberDetail" />
-
-    <!-- ===== 维度任务/活动/商品 ===== -->
-    <DimensionTasks
-      v-if="isLoggedIn"
-      dimensionCode="action"
-      :tasks="dimensionTasks"
-      @taskClick="onTaskClick"
-      @moreTasks="goTasks" />
-
-    <DimensionActivities
-      dimensionCode="action"
-      :activities="dimensionActivities"
-      :isLoggedIn="isLoggedIn"
-      @activityClick="goActivityDetail"
-      @moreActivities="goMoreActivities" />
-
-    <DimensionProducts
-      dimensionCode="action"
-      :familyId="activeChildId"
-      :products="dimensionProducts"
-      title="推荐商品"
-      @productClick="goProductDetail"
-      @moreProducts="goMoreProducts" />
-
-    <!-- 推荐阅读 -->
-    <ArticleBookshelf
-      :articles="actionArticles"
-      :isLoggedIn="isLoggedIn"
-      @articleClick="goArticleDetail"
-      @moreArticles="goMoreArticles" />
-
-        <!-- ===== 我的圈子 ===== -->
-        <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="getCircleKey(item)" @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="getCircleKey(item)" @click="onCircleClick(item)">
-                <CircleCard :circle="item" />
-              </view>
-            </view>
-          </view>
-        </view>
-
-        <!-- 圈子详情弹窗 -->
-        <CircleDetail
-          :visible="circleDetailVisible"
-          :circle="detailCircle"
-          :isMember="selectedCircleIsMember"
-          @close="circleDetailVisible = false"
-          @join="onCircleJoin"
-          @leave="onCircleLeave" />
-
-    <!-- 功能入口(4个:活动、商品、课程) -->
-    <view class="func-section" v-if="sectionVisible('func_entries')">
-      <view class="func-grid">
-        <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
-          <view class="func-icon-wrap">
-            <text class="func-icon">{{ item.icon }}</text>
-          </view>
-          <text class="func-label">{{ item.label }}</text>
-        </view>
-      </view>
-    </view>
-
-    <!-- 底部占位 -->
-    <view class="bottom-spacer"></view>
-  </view>
-</template>
-
-<script>
-import TabTransition from '../../components/tab-transition.vue'
-import PageBanner from '../../components/PageBanner.vue'
-import LoginGuideCard from '../../components/LoginGuideCard.vue'
-import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
-import DimensionTasks from '../../components/DimensionTasks.vue'
-import DimensionActivities from '../../components/DimensionActivities.vue'
-import DimensionProducts from '../../components/DimensionProducts.vue'
-import ArticleBookshelf from '../../components/ArticleBookshelf.vue'
-import CircleCard from '../../components/CircleCard.vue'
-import CircleDetail from '../../components/CircleDetail.vue'
-import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
-import WuxingSandbox from '../../components/wuxing-sandbox.vue'
-import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
-import { getVisibleSections, getEnergyOverview, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren, getVisibleFamilyMembers, getFeaturedArticles, getMyCircles, discoverCircles, joinGeneralCircle, leaveGeneralCircle } from '../../utils/api.js'
-
-export default {
-  components: { TabTransition, PageBanner, LoginGuideCard, FamilyEnergyBar, DimensionTasks, DimensionActivities, DimensionProducts, ArticleBookshelf, FamilyRelationGraph, WuxingSandbox, FamilyMemberStrip, CircleCard, CircleDetail },
-  data() {
-    return {
-      showTabTransition: true,
-      isLoggedIn: false,
-      role: null,
-      currentChildId: null,
-      activeChildId: null,
-      selectedMemberId: null,
-      currentChildName: '',
-      userName: '',
-      children: [],
-      familyMembersVisible: [],
-      sandboxData: null,
-      dualDimension: null,
-      visibleSections: [],
-      dimensionTasks: [],
-      dimensionActivities: [],
-      dimensionProducts: [],
-      articles: [],
-      myCircles: [],
-      discoverCirclesList: [],
-      circleDetailVisible: false,
-      selectedCircle: null,
-      selectedCircleIsMember: false,
-    
-      funcList: [
-        { icon: '\u{1F3CB}', label: '任务', needLogin: true, page: '/pages/tasks/tasks' },
-        { icon: '\u{1F3C0}', label: '活动', needLogin: false, page: '/pages/discover/index?type=activity' },
-        { icon: '\u{1F6CD}', label: '商城', needLogin: false, page: '/pages/discover/index?type=product' },
-        { icon: '\u{1F3EB}', label: '课程', needLogin: false, page: '/pages/discover/index?type=course' }
-      ]
-    }
-  },
-  computed: {
-    sandboxScores: function() {
-      var d = this.sandboxData || {}
-      return {
-        body: d.bodyScore || 0,
-        mind: d.mindScore || 0,
-        wisdom: d.wisdomScore || 0,
-        action: d.actionScore || 0,
-        wealth: d.wealthScore || 0
-      }
-    },
-    detailCircle: function() {
-      return this.selectedCircle || {}
-    },
-    energyMapForGraph: function() {
-      var map = {}
-      if (this.sandboxData && this.sandboxData.members) {
-        for (var i = 0; i < this.sandboxData.members.length; i++) {
-          var m = this.sandboxData.members[i]
-          map[m.memberId || m.id] = {
-            bodyScore: m.bodyScore || 0,
-            mindScore: m.mindScore || 0,
-            actionScore: m.actionScore || 0
-          }
-        }
-      }
-      return map
-    },
-    intimacyMapForGraph: function() {
-      return {}
-    },
-    actionArticles: 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('action') !== -1) {
-          result.push(a)
-        }
-      }
-      if (result.length === 0) return this.articles.slice(0, 5)
-      return result.slice(0, 5)
-    }
-  },
-  onShow() {
-    this.showTabTransition = true
-
-    var token = uni.getStorageSync('token')
-    this.isLoggedIn = !!token
-    if (this.isLoggedIn) {
-      this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
-      this.currentChildId = uni.getStorageSync('currentChildId') || null
-      this.userName = uni.getStorageSync('nickname') || uni.getStorageSync('userName') || '家长'
-    }
-    this.loadSectionConfig()
-    if (this.isLoggedIn) {
-      var self = this
-      this.loadChildren().finally(function() {
-        uni.setStorageSync('actionReadyTime', Date.now())
-      })
-      this.loadFamilyMembersVisible()
-    }
-    // 游客也能浏览活动和商品
-    this.loadDimensionActivities()
-    this.loadDimensionProducts()
-    // 游客也能浏览文章
-    this.loadFeaturedArticles()
-    this._watchPageReady()
-  },
-  methods: {
-    getCircleKey: function(item) {
-      return item.id || item.circleId
-    },
-    loadSectionConfig: function() {
-      var self = this
-      var baseSections = ['func_entries']
-      if (!uni.getStorageSync('token')) {
-        self.visibleSections = baseSections
-        return
-      }
-      var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
-      getVisibleSections({ pageKey: 'action', role: role }).then(function(res) {
-        if (res.code === 200 && res.data) {
-          var keys = res.data.map(function(s) { return s.sectionKey })
-          var merged = baseSections.slice()
-          keys.forEach(function(k) { if (merged.indexOf(k) === -1) merged.push(k) })
-          self.visibleSections = merged
-        }
-      }).catch(function() {
-        self.visibleSections = ['func_entries']
-      })
-    },
-    loadChildren: function() {
-      var self = this
-      getChildren().then(function(res) {
-        if (res.code === 200 && res.data) {
-          self.children = res.data
-          var savedChildId = uni.getStorageSync('currentChildId')
-          var found = false
-          for (var i = 0; i < self.children.length; i++) {
-            if (self.children[i].childId === savedChildId) {
-              self.currentChildId = savedChildId
-              self.activeChildId = savedChildId
-              self.currentChildName = self.children[i].name || self.children[i].nickname || '孩子'
-              found = true
-              break
-            }
-          }
-          // 孩子选择完全是手动的,不自动默认选
-          if (found) {
-            self.loadDimensionData()
-          }
-        }
-      }).catch(function(e) {
-        console.log('获取孩子列表失败', e)
-      })
-    },
-    loadDimensionData: function() {
-      this.loadEnergyData()
-      this.loadSandboxData()
-      this.loadDimensionTasks()
-      this.loadDimensionActivities()
-      this.loadDimensionProducts()
-      this.loadCircleData()
-    },
-    loadEnergyData: function() {
-      var self = this
-      if (!this.currentChildId) return
-      getEnergyOverview(this.currentChildId).then(function(res) {
-        if (res && res.data) {
-          var dims = res.data.dimensions
-          if (dims && dims.length > 0) {
-            for (var i = 0; i < dims.length; i++) {
-              if (dims[i].code === 'action') {
-                self.dualDimension = dims[i]
-                break
-              }
-            }
-          }
-        }
-      }).catch(function(e) {
-        console.log('获取能量概览失败', e)
-      })
-    },
-    loadSandboxData: function() {
-      var self = this
-      getFamilyEnergySandbox().then(function(res) {
-        if (res && res.data) {
-          self.sandboxData = res.data
-        }
-      }).catch(function() {})
-    },
-    loadDimensionTasks: function() {
-      var self = this
-      if (!this.currentChildId) return
-      getTodayTasksByCategory(this.currentChildId, 'action').then(function(res) {
-        if (res && res.data) {
-          self.dimensionTasks = Array.isArray(res.data) ? res.data.slice(0, 5) : []
-        }
-      }).catch(function() { self.dimensionTasks = [] })
-    },
-    loadDimensionActivities: function() {
-      var self = this
-      getActivityList({ dimensionCode: 'action', 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: function() {
-      var self = this
-      getProductsByDomain('action', 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 = [] })
-    },
-    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
-      joinGeneralCircle(circleId, this.currentChildId, '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
-      leaveGeneralCircle(circleId, this.currentChildId, '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' })
-    },
-    onFamilyMemberSelect: function(member) {
-      this.selectedMemberId = member.id
-    },
-    onChildChanged: function(child) {
-      this.currentChildId = child.childId
-      this.currentChildName = child.name || child.nickname || '孩子'
-      // FIX: 同步提交到 Vuex store
-      this.$store.commit('switchToChild', child.childId)
-      this.loadDimensionData()
-    },
-    loadFamilyMembersVisible: function() {
-      var self = this
-      getVisibleFamilyMembers().then(function(res) {
-        self.familyMembersVisible = res.data || []
-      }).catch(function(e) {
-        console.log('获取可见家庭成员失败', e)
-        self.familyMembersVisible = []
-      })
-    },
-    scrollToSection: function(section) {
-      uni.pageScrollTo({ selector: '#' + section, duration: 300 })
-    },
-    sectionVisible: function(key) {
-      return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
-    },
-    onTaskClick: function(task) {
-      uni.navigateTo({ url: '/pages/tasks/tasks' })
-    },
-    goActivityDetail: function(act) {
-      if (!act || !act.id) return
-      uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id })
-    },
-    goMoreActivities: function() {
-      uni.navigateTo({ url: '/pages/activity/index' })
-    },
-    goProductDetail: function(prod) {
-      uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + prod.id })
-    },
-    goMoreProducts: function() {
-      uni.navigateTo({ url: '/pages/shop/index/index' })
-    },
-    onFuncClick: function(item) {
-      if (item.needLogin && !this.isLoggedIn) {
-        uni.showModal({
-          title: '提示',
-          content: '请先登录后使用此功能',
-          success: function(res) { if (res.confirm) uni.navigateTo({ url: '/pages/login/login' }) }
-        })
-        return
-      }
-      if (item.page) {
-        var nativeTabs = ['/pages/index-home/index', '/pages/body/index', '/pages/mind/index', '/pages/wisdom/index', '/pages/wealth/index']
-        if (nativeTabs.indexOf(item.page) !== -1) {
-          uni.switchTab({ url: item.page })
-        } else {
-          uni.navigateTo({ url: item.page })
-        }
-      }
-    },
-    goTasks: function() { uni.navigateTo({ url: '/pages/tasks/tasks' }) },
-    goMemberDetail: function(member) {
-      if (!member || !member.memberId) return
-      uni.navigateTo({
-        url: '/pages/member-detail/member-detail?memberId=' + member.memberId + '&memberType=' + (member.memberType || 'child') + '&entrySource=relation-graph&dimensionCode=action'
-      })
-    },
-    goLogin: function() { uni.navigateTo({ url: '/pages/login/login' }) },
-    parseRelatedDimensions: function(val) {
-      if (!val) return []
-      if (Array.isArray(val)) return val
-      if (typeof val === 'string') {
-        try {
-          var parsed = JSON.parse(val)
-          return Array.isArray(parsed) ? parsed : []
-        } catch (e) {
-          // 逗号分隔格式 fallback: "action,mind"
-          return val.split(',').map(function(s) { return s.trim() }).filter(function(s) { return s })
-        }
-      }
-      return []
-    },
-    loadFeaturedArticles: function() {
-      var self = this
-      getFeaturedArticles({ size: 5, dimensionCode: 'action' }).then(function(res) {
-        if (res.code === 200 && res.data) {
-          var gradientColors = [
-            'linear-gradient(135deg, #10B981, #34D399)',
-            'linear-gradient(135deg, #5B9BD5, #8FC5E8)',
-            'linear-gradient(135deg, #8B5CF6, #C084FC)',
-            'linear-gradient(135deg, #F97316, #FB923C)',
-            'linear-gradient(135deg, #6366F1, #818CF8)'
-          ]
-          var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
-          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 || '',
-              dimensionWeights: item.dimensionWeights || null,
-              views: item.viewCount ? String(item.viewCount) : '0',
-              likes: '0',
-              date: item.publishedAt ? item.publishedAt.slice(0, 10) : '',
-              relatedDimensions: self.parseRelatedDimensions(item.relatedDimensions),
-              coverImage: item.coverImage
-            }
-          })
-        }
-      }).catch(function() {})
-    },
-    goArticleDetail: function(article) {
-      var id = article.id || article
-      uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + id })
-    },
-    goMoreArticles: function() {
-      uni.navigateTo({ url: '/pages/article-center/index?dimension=action' })
-    },
-
-    _watchPageReady() {
-      var self = this
-      var unwatch = this.$watch(function() {
-        if (self.isLoggedIn) {
-          return !!(self.dualDimension || self.sandboxData || (self.dimensionTasks && self.dimensionTasks.length > 0))
-        }
-        return !!(self.dimensionActivities && self.dimensionActivities.length > 0) ||
-               !!(self.dimensionProducts && self.dimensionProducts.length > 0)
-      }, function(val) {
-        if (val) {
-          if (unwatch) unwatch()
-          if (self.$refs.tabTransition) self.$refs.tabTransition.markReady()
-        }
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container {
-  min-height: 100vh;
-  background: #f5f7fa;
-  padding-bottom: 120rpx;
-}
-
-/* ===== 功能入口 ===== */
-.func-section {
-  margin: -40rpx 20rpx 0;
-  position: relative;
-  z-index: 2;
-}
-.func-grid {
-  background: #fff;
-  border-radius: 24rpx;
-  padding: 30rpx 16rpx;
-  display: flex;
-  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
-}
-.func-item {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-}
-.func-item:active {
-  opacity: 0.7;
-}
-.func-icon-wrap {
-  width: 88rpx;
-  height: 88rpx;
-  border-radius: 50%;
-  background: #FFF7ED;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 10rpx;
-}
-.func-icon {
-  font-size: 40rpx;
-}
-.func-label {
-  font-size: 24rpx;
-  color: #333;
-  font-weight: 500;
-}
-
-/* ===== 圈子区块 ===== */
-.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;
-}
-
-/* ===== 底部 ===== */
-.bottom-spacer {
-  height: 20rpx;
-}
-</style>

+ 0 - 282
cfc-frontend/pages/action/interaction-log.vue

@@ -1,282 +0,0 @@
-<template>
-  <view class="container">
-    <view class="page-header">
-      <text class="page-title">记录互动</text>
-    </view>
-
-    <!-- 选择互动对象 -->
-    <view class="form-section">
-      <text class="section-label">互动对象</text>
-      <view class="member-select">
-        <view class="member-item" v-for="m in members" :key="m.id"
-              :class="{selected: selectedMemberId === m.id}"
-              @click="selectMember(m)">
-          <text class="member-name">{{ m.nickname }}</text>
-          <text class="member-rel">{{ m.relationshipTypeName || m.relationshipType }}</text>
-        </view>
-      </view>
-    </view>
-
-    <!-- 选择互动类型 -->
-    <view class="form-section">
-      <text class="section-label">互动类型</text>
-      <view class="type-grid">
-        <view class="type-item" v-for="(name, key) in interactionTypes" :key="key"
-              :class="{selected: selectedType === key}"
-              @click="selectedType = key">
-          <text class="type-icon">{{ typeIcons[key] || '💬' }}</text>
-          <text class="type-name">{{ name }}</text>
-        </view>
-      </view>
-    </view>
-
-    <!-- 互动描述 -->
-    <view class="form-section">
-      <text class="section-label">描述(选填)</text>
-      <textarea class="desc-input" v-model="description"
-                 placeholder="简单描述一下这次互动..." maxlength="256"></textarea>
-    </view>
-
-    <!-- 互动时间 -->
-    <view class="form-section">
-      <text class="section-label">互动时间</text>
-      <view class="time-selector">
-        <view class="time-option" :class="{selected: timeType === 'now'}" @click="timeType = 'now'">现在</view>
-        <view class="time-option" :class="{selected: timeType === 'custom'}" @click="timeType = 'custom'">选择时间</view>
-      </view>
-      <view v-if="timeType === 'custom'" class="custom-time">
-        <picker mode="dateTime" :value="customTime" @change="onTimeChange">
-          <view class="time-display">{{ customTime || '选择时间' }}</view>
-        </picker>
-      </view>
-    </view>
-
-    <!-- 提交按钮 -->
-    <view class="submit-bar">
-      <button class="btn-submit" @click="onSubmit" :disabled="!canSubmit">保存记录</button>
-    </view>
-  </view>
-</template>
-
-<script>
-import { listFamilyMembers, addInteraction, getInteractionTypes } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      members: [],
-      selectedMemberId: null,
-      selectedType: '',
-      description: '',
-      timeType: 'now',
-      customTime: '',
-      interactionTypes: {
-        dining: '一起用餐',
-        outgoing: '外出活动',
-        call: '电话联系',
-        video: '视频通话',
-        chat: '聊天交流',
-        play: '一起游戏',
-        gift: '赠送礼物'
-      },
-      typeIcons: {
-        dining: '🍽️',
-        outgoing: '🚗',
-        call: '📞',
-        video: '📹',
-        chat: '💬',
-        play: '🎮',
-        gift: '🎁'
-      }
-    }
-  },
-  computed: {
-    canSubmit() {
-      return this.selectedMemberId && this.selectedType
-    }
-  },
-  onLoad(options) {
-    if (options.memberId) {
-      this.selectedMemberId = Number(options.memberId)
-    }
-    this.loadMembers()
-  },
-  methods: {
-    async loadMembers() {
-      try {
-        var res = await listFamilyMembers()
-        if (res.code === 200 && res.data) {
-          this.members = res.data || []
-        }
-      } catch (e) {
-        uni.showToast({ title: '加载成员失败', icon: 'none' })
-      }
-    },
-    selectMember(member) {
-      this.selectedMemberId = member.id
-    },
-    onTimeChange(e) {
-      this.customTime = e.detail.value
-    },
-    async onSubmit() {
-      if (!this.canSubmit) {
-        uni.showToast({ title: '请选择对象和类型', icon: 'none' })
-        return
-      }
-      try {
-        var happenedAt = ''
-        if (this.timeType === 'custom' && this.customTime) {
-          happenedAt = this.customTime
-        }
-        var res = await addInteraction({
-          fromMemberId: this.selectedMemberId,
-          toMemberId: this.selectedMemberId,
-          interactionType: this.selectedType,
-          description: this.description,
-          happenedAt: happenedAt
-        })
-        if (res.code === 200) {
-          uni.showToast({ title: '记录成功', icon: 'success' })
-          setTimeout(function() {
-            uni.navigateBack()
-          }, 1500)
-        } else {
-          uni.showToast({ title: res.message || '保存失败', icon: 'none' })
-        }
-      } catch (e) {
-        uni.showToast({ title: '保存失败', icon: 'none' })
-      }
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container {
-  min-height: 100vh;
-  background: #f5f5f5;
-  padding: 24rpx;
-}
-.page-header {
-  padding: 20rpx 0 30rpx;
-}
-.page-title {
-  font-size: 36rpx;
-  font-weight: bold;
-  color: #333;
-}
-.form-section {
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 24rpx;
-  margin-bottom: 20rpx;
-}
-.section-label {
-  font-size: 28rpx;
-  color: #333;
-  font-weight: 500;
-  display: block;
-  margin-bottom: 16rpx;
-}
-.member-select {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 16rpx;
-}
-.member-item {
-  padding: 16rpx 24rpx;
-  border: 2rpx solid #e0e0e0;
-  border-radius: 12rpx;
-  background: #fafafa;
-}
-.member-item.selected {
-  border-color: #F97316;
-  background: #FFF7ED;
-}
-.member-name {
-  font-size: 28rpx;
-  color: #333;
-  display: block;
-}
-.member-rel {
-  font-size: 22rpx;
-  color: #999;
-}
-.type-grid {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 16rpx;
-}
-.type-item {
-  width: calc(25% - 12rpx);
-  padding: 20rpx 0;
-  text-align: center;
-  border: 2rpx solid #e0e0e0;
-  border-radius: 12rpx;
-  background: #fafafa;
-}
-.type-item.selected {
-  border-color: #F97316;
-  background: #FFF7ED;
-}
-.type-icon {
-  font-size: 40rpx;
-  display: block;
-  margin-bottom: 8rpx;
-}
-.type-name {
-  font-size: 22rpx;
-  color: #666;
-}
-.desc-input {
-  width: 100%;
-  height: 160rpx;
-  background: #fafafa;
-  border-radius: 12rpx;
-  padding: 20rpx;
-  font-size: 28rpx;
-  box-sizing: border-box;
-}
-.time-selector {
-  display: flex;
-  gap: 16rpx;
-  margin-bottom: 16rpx;
-}
-.time-option {
-  padding: 16rpx 32rpx;
-  border: 2rpx solid #e0e0e0;
-  border-radius: 12rpx;
-  font-size: 28rpx;
-  color: #666;
-}
-.time-option.selected {
-  border-color: #F97316;
-  color: #F97316;
-  background: #FFF7ED;
-}
-.custom-time {
-  margin-top: 12rpx;
-}
-.time-display {
-  padding: 20rpx;
-  background: #fafafa;
-  border-radius: 12rpx;
-  font-size: 28rpx;
-  color: #333;
-}
-.submit-bar {
-  margin-top: 40rpx;
-}
-.btn-submit {
-  width: 100%;
-  background: #F97316;
-  color: #fff;
-  border: none;
-  border-radius: 12rpx;
-  padding: 24rpx 0;
-  font-size: 32rpx;
-}
-.btn-submit[disabled] {
-  background: #ccc;
-}
-</style>

+ 0 - 308
cfc-frontend/pages/action/member-action-detail.vue

@@ -1,308 +0,0 @@
-<template>
-  <view class="detail-container">
-    <!-- 顶部导航 -->
-    <view class="nav-bar">
-      <view class="nav-back" @click="goBack">
-        <text class="nav-back-icon">&#x2190;</text>
-      </view>
-      <text class="nav-title">行 · 成员详情</text>
-    </view>
-
-    <!-- 五维能量条 -->
-    <FamilyEnergyBar
-      dimensionCode="action"
-      :sandboxData="sandboxData"
-      :dualDimension="dualDimension" />
-
-    <!-- 成员信息卡 -->
-    <view class="member-card" v-if="memberInfo">
-      <view class="member-avatar action-avatar">
-        <text class="avatar-text">{{ memberInfo.name && memberInfo.name.charAt(0) || '孩' }}</text>
-      </view>
-      <view class="member-info">
-        <text class="member-name">{{ memberInfo.name || '孩子' }}</text>
-        <text class="member-role">行动维度</text>
-      </view>
-      <view class="member-score" v-if="dualDimension">
-        <text class="score-value action-score">{{ dualDimension.energy || 0 }}</text>
-        <text class="score-label">能量值</text>
-      </view>
-    </view>
-
-    <!-- 家庭成员关系图谱(只读) -->
-    <FamilyRelationGraph
-      dimensionCode="action"
-      :selfId="selfId"
-      :members="graphMembers"
-      :energyMap="energyMapForGraph"
-      :intimacyMap="intimacyMapForGraph"
-      :interactive="false" />
-
-    <!-- 今日任务 -->
-    <DimensionTasks
-      dimensionCode="action"
-      :tasks="dimensionTasks"
-      @taskClick="onTaskClick"
-      @moreTasks="goTasks" />
-
-    <!-- 活动 -->
-    <DimensionActivities
-      dimensionCode="action"
-      :activities="dimensionActivities"
-      @activityClick="goActivityDetail"
-      @moreActivities="goMoreActivities" />
-
-    <!-- 商品 -->
-    <DimensionProducts
-      dimensionCode="action"
-      :products="dimensionProducts"
-      @productClick="goProductDetail"
-      @moreProducts="goMoreProducts" />
-
-    <!-- 底部占位 -->
-    <view class="bottom-spacer"></view>
-  </view>
-</template>
-
-<script>
-import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
-import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
-import DimensionTasks from '../../components/DimensionTasks.vue'
-import DimensionActivities from '../../components/DimensionActivities.vue'
-import DimensionProducts from '../../components/DimensionProducts.vue'
-import { getEnergyOverview, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren } from '../../utils/api.js'
-
-export default {
-  components: { FamilyEnergyBar, FamilyRelationGraph, DimensionTasks, DimensionActivities, DimensionProducts },
-  data() {
-    return {
-      childId: null,
-      selfId: null,
-      memberInfo: null,
-      sandboxData: null,
-      dualDimension: null,
-      dimensionTasks: [],
-      dimensionActivities: [],
-      dimensionProducts: []
-    }
-  },
-  computed: {
-    graphMembers: function() {
-      if (!this.sandboxData || !this.sandboxData.members) return []
-      return this.sandboxData.members.map(function(m) {
-        return {
-          id: m.memberId || m.id,
-          nickname: m.name || m.nickname || '成员',
-          memberType: m.memberType || 'child'
-        }
-      })
-    },
-    energyMapForGraph: function() {
-      var map = {}
-      if (this.sandboxData && this.sandboxData.members) {
-        for (var i = 0; i < this.sandboxData.members.length; i++) {
-          var m = this.sandboxData.members[i]
-          map[m.memberId || m.id] = {
-            bodyScore: m.bodyScore || 0,
-            mindScore: m.mindScore || 0,
-            actionScore: m.actionScore || 0
-          }
-        }
-      }
-      return map
-    },
-    intimacyMapForGraph: function() {
-      return {}
-    }
-  },
-  onLoad: function(options) {
-    this.childId = options.childId || uni.getStorageSync('currentChildId') || null
-  },
-  onShow: function() {
-    this.selfId = uni.getStorageSync('userId') || null
-    this.loadChildren()
-    this.loadSandboxData()
-    if (this.childId) {
-      this.loadEnergyData()
-      this.loadDimensionTasks()
-      this.loadDimensionActivities()
-      this.loadDimensionProducts()
-    }
-  },
-  methods: {
-    loadChildren: function() {
-      var self = this
-      getChildren().then(function(res) {
-        if (res.code === 200 && res.data) {
-          for (var i = 0; i < res.data.length; i++) {
-            if (res.data[i].childId === self.childId) {
-              self.memberInfo = res.data[i]
-              break
-            }
-          }
-          if (!self.memberInfo && res.data.length > 0) {
-            self.memberInfo = res.data[0]
-            self.childId = res.data[0].childId
-          }
-        }
-      }).catch(function() {})
-    },
-    loadSandboxData: function() {
-      var self = this
-      getFamilyEnergySandbox().then(function(res) {
-        if (res && res.data) self.sandboxData = res.data
-      }).catch(function() {})
-    },
-    loadEnergyData: function() {
-      var self = this
-      getEnergyOverview(this.childId).then(function(res) {
-        if (res && res.data) {
-          var dims = res.data.dimensions
-          if (dims && dims.length > 0) {
-            for (var i = 0; i < dims.length; i++) {
-              if (dims[i].code === 'action') {
-                self.dualDimension = dims[i]
-                break
-              }
-            }
-          }
-        }
-      }).catch(function() {})
-    },
-    loadDimensionTasks: function() {
-      var self = this
-      getTodayTasksByCategory(this.childId, 'action').then(function(res) {
-        if (res && res.data) {
-          self.dimensionTasks = Array.isArray(res.data) ? res.data.slice(0, 5) : []
-        }
-      }).catch(function() { self.dimensionTasks = [] })
-    },
-    loadDimensionActivities: function() {
-      var self = this
-      getActivityList({ dimensionCode: 'action', 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: function() {
-      var self = this
-      getProductsByDomain('action', 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 = [] })
-    },
-    onTaskClick: function(task) {
-      uni.navigateTo({ url: '/pages/tasks/tasks' })
-    },
-    goActivityDetail: function(act) {
-      if (act && act.id) uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id })
-    },
-    goMoreActivities: function() {
-      uni.navigateTo({ url: '/pages/activity/index' })
-    },
-    goProductDetail: function(prod) {
-      if (prod && prod.id) uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + prod.id })
-    },
-    goMoreProducts: function() {
-      uni.navigateTo({ url: '/pages/shop/index/index' })
-    },
-    goTasks: function() { uni.navigateTo({ url: '/pages/tasks/tasks' }) },
-    goBack: function() { uni.navigateBack() }
-  }
-}
-</script>
-
-<style scoped>
-.detail-container {
-  min-height: 100vh;
-  background: #f5f7fa;
-  padding-bottom: 40rpx;
-}
-.nav-bar {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding: 20rpx 30rpx;
-  background: #fff;
-}
-.nav-back {
-  padding: 10rpx;
-}
-.nav-back-icon {
-  font-size: 36rpx;
-  color: #333;
-}
-.nav-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #333;
-  margin-left: 16rpx;
-}
-.member-card {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  margin: 20rpx 30rpx;
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 24rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
-}
-.member-avatar {
-  width: 80rpx;
-  height: 80rpx;
-  border-radius: 40rpx;
-  background: linear-gradient(135deg, #4CAF50, #81C784);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 20rpx;
-}
-.action-avatar {
-  background: linear-gradient(135deg, #10B981, #34D399);
-}
-.avatar-text {
-  font-size: 32rpx;
-  color: #fff;
-  font-weight: bold;
-}
-.member-info {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-}
-.member-name {
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #333;
-}
-.member-role {
-  font-size: 24rpx;
-  color: #999;
-  margin-top: 4rpx;
-}
-.member-score {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-}
-.score-value {
-  font-size: 40rpx;
-  font-weight: bold;
-  color: #4CAF50;
-}
-.action-score {
-  color: #10B981;
-}
-.score-label {
-  font-size: 22rpx;
-  color: #999;
-}
-.bottom-spacer {
-  height: 40rpx;
-}
-</style>

+ 0 - 372
cfc-frontend/pages/action/relationship-questionnaire.vue

@@ -1,372 +0,0 @@
-<template>
-  <view class="container">
-    <view class="page-header">
-      <text class="page-title">关系问卷</text>
-      <text class="page-subtitle" v-if="memberName">为「{{ memberName }}」评估关系质量</text>
-    </view>
-
-    <!-- 加载状态 -->
-    <view v-if="loading" class="loading-wrap">
-      <text class="loading-text">AI 正在生成问卷...</text>
-    </view>
-
-    <!-- 问卷表单 -->
-    <view v-else-if="questions.length > 0" class="questionnaire-form">
-      <view class="question-item" v-for="(q, index) in questions" :key="q.id">
-        <view class="question-header">
-          <text class="question-dimension dimension-tag" :class="q.dimension">{{ dimensionLabels[q.dimension] }}</text>
-          <text class="question-text">{{ q.question }}</text>
-        </view>
-        <view class="option-list">
-          <view class="option-item" v-for="(opt, oi) in q.options" :key="oi"
-                :class="{selected: answers[q.id] === opt}"
-                @click="selectOption(q.id, opt)">
-            <text class="option-letter">{{ optionLetters[oi] }}</text>
-            <text class="option-text">{{ opt }}</text>
-          </view>
-        </view>
-      </view>
-
-      <view class="submit-bar">
-        <button class="btn-submit" @click="onSubmit" :disabled="!allAnswered">提交问卷</button>
-      </view>
-    </view>
-
-    <!-- 无问卷 -->
-    <view v-else class="empty-wrap">
-      <text class="empty-text">暂无可用问卷</text>
-      <button class="btn-generate" @click="generateQuestionnaire">生成新问卷</button>
-    </view>
-
-    <!-- 结果展示 -->
-    <view v-if="showResult" class="result-overlay" @click="showResult = false">
-      <view class="result-card" @click.stop>
-        <text class="result-title">关系质量评估结果</text>
-        <view class="result-scores">
-          <view class="result-score-item">
-            <text class="result-score-label">信任</text>
-            <view class="result-bar-bg">
-              <view class="result-bar-fill trust" :style="{width: (result.trustScore || 0) + '%'}"></view>
-            </view>
-            <text class="result-score-value">{{ Math.round(result.trustScore || 0) }}</text>
-          </view>
-          <view class="result-score-item">
-            <text class="result-score-label">亲密</text>
-            <view class="result-bar-bg">
-              <view class="result-bar-fill intimacy" :style="{width: (result.intimacyScore || 0) + '%'}"></view>
-            </view>
-            <text class="result-score-value">{{ Math.round(result.intimacyScore || 0) }}</text>
-          </view>
-          <view class="result-score-item">
-            <text class="result-score-label">沟通</text>
-            <view class="result-bar-bg">
-              <view class="result-bar-fill communication" :style="{width: (result.communicationScore || 0) + '%'}"></view>
-            </view>
-            <text class="result-score-value">{{ Math.round(result.communicationScore || 0) }}</text>
-          </view>
-        </view>
-        <button class="btn-close" @click="showResult = false">关闭</button>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { generateQuestionnaire, submitQuestionnaire, getQuestionnaireSnapshot } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      memberId: null,
-      memberName: '',
-      snapshotId: null,
-      questions: [],
-      answers: {},
-      loading: false,
-      showResult: false,
-      result: {},
-      dimensionLabels: {
-        trust: '信任',
-        intimacy: '亲密',
-        communication: '沟通'
-      },
-      optionLetters: ['A', 'B', 'C', 'D', 'E']
-    }
-  },
-  computed: {
-    allAnswered() {
-      return this.questions.length > 0 && this.questions.every(function(q) {
-        return this.answers[q.id]
-      }, this)
-    }
-  },
-  onLoad(options) {
-    if (options.memberId) {
-      this.memberId = Number(options.memberId)
-    }
-    this.loadSnapshot()
-  },
-  methods: {
-    async loadSnapshot() {
-      if (!this.memberId) return
-      this.loading = true
-      try {
-        var res = await getQuestionnaireSnapshot(this.memberId)
-        if (res.code === 200 && res.data) {
-          this.snapshotId = res.data.id
-          this.memberName = res.data.memberName
-          try {
-            this.questions = JSON.parse(res.data.aiGeneratedJson || '[]')
-          } catch (e) {
-            this.questions = []
-          }
-        } else {
-          // 没有问卷,生成新的
-          await this.generateQuestionnaire()
-        }
-      } catch (e) {
-        uni.showToast({ title: '加载失败', icon: 'none' })
-      } finally {
-        this.loading = false
-      }
-    },
-    async generateQuestionnaire() {
-      if (!this.memberId) return
-      this.loading = true
-      try {
-        var res = await generateQuestionnaire(this.memberId)
-        if (res.code === 200 && res.data) {
-          this.snapshotId = res.data.id
-          this.memberName = res.data.memberName
-          try {
-            this.questions = JSON.parse(res.data.aiGeneratedJson || '[]')
-          } catch (e) {
-            this.questions = []
-          }
-        } else {
-          uni.showToast({ title: res.message || '生成失败', icon: 'none' })
-        }
-      } catch (e) {
-        uni.showToast({ title: '生成失败', icon: 'none' })
-      } finally {
-        this.loading = false
-      }
-    },
-    selectOption(questionId, option) {
-      this.answers[questionId] = option
-    },
-    async onSubmit() {
-      if (!this.allAnswered || !this.snapshotId) {
-        uni.showToast({ title: '请回答所有问题', icon: 'none' })
-        return
-      }
-      try {
-        var answersJson = JSON.stringify(this.answers)
-        var res = await submitQuestionnaire({
-          snapshotId: this.snapshotId,
-          answersJson: answersJson
-        })
-        if (res.code === 200 && res.data) {
-          this.result = res.data
-          this.showResult = true
-        } else {
-          uni.showToast({ title: res.message || '提交失败', icon: 'none' })
-        }
-      } catch (e) {
-        uni.showToast({ title: '提交失败', icon: 'none' })
-      }
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container {
-  min-height: 100vh;
-  background: #f5f5f5;
-  padding: 24rpx;
-}
-.page-header {
-  padding: 20rpx 0 30rpx;
-}
-.page-title {
-  font-size: 36rpx;
-  font-weight: bold;
-  color: #333;
-  display: block;
-}
-.page-subtitle {
-  font-size: 26rpx;
-  color: #999;
-  margin-top: 8rpx;
-  display: block;
-}
-.loading-wrap, .empty-wrap {
-  text-align: center;
-  padding: 100rpx 0;
-}
-.loading-text {
-  font-size: 28rpx;
-  color: #999;
-}
-.empty-text {
-  font-size: 28rpx;
-  color: #999;
-  display: block;
-  margin-bottom: 30rpx;
-}
-.btn-generate, .btn-submit {
-  background: #F97316;
-  color: #fff;
-  border: none;
-  border-radius: 12rpx;
-  padding: 24rpx 60rpx;
-  font-size: 30rpx;
-}
-.questionnaire-form {
-  display: flex;
-  flex-direction: column;
-  gap: 24rpx;
-}
-.question-item {
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 28rpx;
-}
-.question-header {
-  margin-bottom: 20rpx;
-}
-.dimension-tag {
-  font-size: 22rpx;
-  padding: 4rpx 12rpx;
-  border-radius: 6rpx;
-  margin-right: 12rpx;
-}
-.dimension-tag.trust { background: #D1FAE5; color: #059669; }
-.dimension-tag.intimacy { background: #FCE7F3; color: #DB2777; }
-.dimension-tag.communication { background: #E0E7FF; color: #4338CA; }
-.question-text {
-  font-size: 30rpx;
-  color: #333;
-  line-height: 1.6;
-  display: block;
-  margin-top: 12rpx;
-}
-.option-list {
-  display: flex;
-  flex-direction: column;
-  gap: 12rpx;
-}
-.option-item {
-  display: flex;
-  align-items: center;
-  gap: 16rpx;
-  padding: 20rpx;
-  border: 2rpx solid #e0e0e0;
-  border-radius: 12rpx;
-  background: #fafafa;
-}
-.option-item.selected {
-  border-color: #F97316;
-  background: #FFF7ED;
-}
-.option-letter {
-  width: 44rpx;
-  height: 44rpx;
-  line-height: 44rpx;
-  text-align: center;
-  background: #f0f0f0;
-  border-radius: 50%;
-  font-size: 24rpx;
-  color: #666;
-}
-.option-item.selected .option-letter {
-  background: #F97316;
-  color: #fff;
-}
-.option-text {
-  flex: 1;
-  font-size: 28rpx;
-  color: #333;
-}
-.submit-bar {
-  margin-top: 20rpx;
-}
-.btn-submit {
-  width: 100%;
-}
-.btn-submit[disabled] {
-  background: #ccc;
-}
-.result-overlay {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0,0,0,0.5);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  z-index: 100;
-}
-.result-card {
-  width: 600rpx;
-  background: #fff;
-  border-radius: 24rpx;
-  padding: 40rpx;
-}
-.result-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #333;
-  display: block;
-  text-align: center;
-  margin-bottom: 30rpx;
-}
-.result-scores {
-  display: flex;
-  flex-direction: column;
-  gap: 20rpx;
-  margin-bottom: 30rpx;
-}
-.result-score-item {
-  display: flex;
-  align-items: center;
-  gap: 12rpx;
-}
-.result-score-label {
-  width: 60rpx;
-  font-size: 26rpx;
-  color: #666;
-}
-.result-bar-bg {
-  flex: 1;
-  height: 16rpx;
-  background: #f0f0f0;
-  border-radius: 8rpx;
-  overflow: hidden;
-}
-.result-bar-fill {
-  height: 100%;
-  border-radius: 8rpx;
-}
-.result-bar-fill.trust { background: #10B981; }
-.result-bar-fill.intimacy { background: #FF6B9D; }
-.result-bar-fill.communication { background: #6366F1; }
-.result-score-value {
-  width: 50rpx;
-  font-size: 26rpx;
-  color: #333;
-  text-align: right;
-}
-.btn-close {
-  width: 100%;
-  background: #F97316;
-  color: #fff;
-  border: none;
-  border-radius: 12rpx;
-  padding: 20rpx 0;
-  font-size: 30rpx;
-}
-</style>

+ 1 - 1
cfc-frontend/pages/home-pages/parent-index.vue

@@ -1045,7 +1045,7 @@ export default {
       })
     },
     onContactClick: function(item) {
-      uni.navigateTo({ url: '/pages/action/contact-detail?id=' + item.id })
+      uni.navigateTo({ url: '/pages/action-detail/contact-detail?id=' + item.id })
     },
     onShowImport: function() {
       this.showImportModal = true