Bläddra i källkod

feat: 行页面整体美化

1. 家庭关系/章鱼图/珍珠图/能力图标题前加 ? 按钮,点击弹出说明弹窗
2. 章鱼图添加入口从'记录成效'改为'感恩记录',并跳转至新建的维护列表页
3. 三个图添加入口按钮统一美化(渐变圆角 + 彩色阴影)
4. 章鱼图 Canvas 背景添加低透明度章鱼触手+头部装饰
5. 新增 octopus-effects-list.vue:感恩记录维护页(列表展示/删除/FAB 添加)
6. 修复 octopus-add-effect.vue 关键人'能添加但不能选'bug:补 onShow 刷新

- 新增:pages/action-detail/octopus-effects-list.vue
- 修改:OctopusDiagram.vue / pages.json / index.vue / octopus-add-effect.vue
Sisyphus 11 timmar sedan
förälder
incheckning
43573f9f64

+ 48 - 3
cfc-frontend/components/OctopusDiagram.vue

@@ -41,9 +41,9 @@
   <!-- 空状态 -->
   <view class="octopus-empty" v-else-if="effects && effects.list && effects.list.length === 0">
     <text class="empty-icon">🧭</text>
-    <text class="empty-text">暂无成效记录</text>
-      <text class="empty-hint">记录你的成交成效,画出章鱼图</text>
-    <text class="action-btn action-btn-primary" @tap="$emit('add-effect')">+ 记录成效</text>
+    <text class="empty-text">暂无感恩记录</text>
+      <text class="empty-hint">记录你的感恩时刻,画出章鱼图</text>
+    <text class="action-btn action-btn-primary" @tap="$emit('add-effect')">+ 感恩记录</text>
   </view>
   <!-- 加载状态 -->
   <view class="octopus-loading" v-else>
@@ -205,6 +205,7 @@ export default {
       var w = this.canvasWidth
       var h = this.canvasHeight
       ctx.clearRect(0, 0, w, h)
+      this._drawOctopusBackground(ctx)
       this._drawCenterNode(ctx)
       this._drawEdges(ctx)
       for (var i = 0; i < this.effectNodes.length; i++) {
@@ -215,6 +216,50 @@ export default {
       }
       this._drawLabels(ctx)
     },
+    /** 绘制章鱼背景:头部 + 8 条波浪触手(低透明度,衬托节点) */
+    _drawOctopusBackground: function(ctx) {
+      var cx = this.canvasWidth / 2
+      var cy = this.canvasHeight / 2
+      var minDim = Math.min(this.canvasWidth, this.canvasHeight)
+      var headR = minDim * 0.12
+      // 触手:8 条波浪曲线向外延伸
+      for (var i = 0; i < 8; i++) {
+        var angle = (i / 8) * Math.PI * 2 - Math.PI / 2
+        var len = minDim * 0.36
+        var sx = cx + Math.cos(angle) * headR
+        var sy = cy + Math.sin(angle) * headR
+        var ex = cx + Math.cos(angle) * (headR + len)
+        var ey = cy + Math.sin(angle) * (headR + len)
+        // 控制点:垂直偏移制造波浪
+        var perp = angle + Math.PI / 2
+        var mid = headR + len * 0.5
+        var wave = Math.sin(i * 2.2) * len * 0.16
+        var mx = cx + Math.cos(angle) * mid + Math.cos(perp) * wave
+        var my = cy + Math.sin(angle) * mid + Math.sin(perp) * wave
+        ctx.beginPath()
+        ctx.moveTo(sx, sy)
+        ctx.quadraticCurveTo(mx, my, ex, ey)
+        ctx.strokeStyle = 'rgba(16, 185, 129, 0.12)'
+        ctx.lineWidth = 6
+        ctx.lineCap = 'round'
+        ctx.stroke()
+      }
+      // 头部(半透明圆)
+      ctx.beginPath()
+      ctx.arc(cx, cy, headR, 0, Math.PI * 2)
+      ctx.fillStyle = 'rgba(16, 185, 129, 0.08)'
+      ctx.fill()
+      // 眼睛
+      var eyeOff = headR * 0.35
+      var eyeR = Math.max(3, headR * 0.09)
+      ctx.beginPath()
+      ctx.arc(cx - eyeOff, cy - headR * 0.12, eyeR, 0, Math.PI * 2)
+      ctx.fillStyle = 'rgba(16, 185, 129, 0.22)'
+      ctx.fill()
+      ctx.beginPath()
+      ctx.arc(cx + eyeOff, cy - headR * 0.12, eyeR, 0, Math.PI * 2)
+      ctx.fill()
+    },
     _drawCenterNode: function(ctx) {
       var cx = this.canvasWidth / 2
       var cy = this.canvasHeight / 2

+ 7 - 1
cfc-frontend/pages.json

@@ -1268,7 +1268,13 @@
         {
           "path": "octopus-add-effect",
           "style": {
-            "navigationBarTitleText": "记录成效"
+            "navigationBarTitleText": "感恩记录"
+          }
+        },
+        {
+          "path": "octopus-effects-list",
+          "style": {
+            "navigationBarTitleText": "感恩记录"
           }
         },
         {

+ 148 - 13
cfc-frontend/pages/action-detail/index.vue

@@ -53,7 +53,10 @@
     <!-- 登录后:家庭关系(行维度核心) -->
     <view class="section" v-if="isLoggedIn">
       <view class="section-header">
-        <text class="section-title">家庭关系</text>
+        <view class="section-title-wrap">
+          <text class="help-icon" @tap="showHelp('家庭关系', '了解每位家人的关系质量,改善沟通方式。点击家人节点,查看关系详情。')">?</text>
+          <text class="section-title">家庭关系</text>
+        </view>
         <text class="section-sub">了解每位家人的关系质量,改善沟通方式</text>
         <text class="section-sub">点击家人节点,查看关系详情</text>
       </view>
@@ -69,11 +72,14 @@
     <!-- 登录后:章鱼图(关键人拓展) -->
     <view class="section" v-if="isLoggedIn">
       <view class="section-header">
-        <view>
-          <text class="section-title">🐙 章鱼图</text>
-          <text class="section-sub">从成效出发,挖掘关键人,画出拓展路径</text>
+        <view class="section-title-wrap">
+          <text class="help-icon" @tap="showHelp('章鱼图', '从成效出发,挖掘关键人,画出拓展路径。记录感恩时刻,建立有温度的人脉网络。')">?</text>
+          <view>
+            <text class="section-title">🐙 章鱼图</text>
+            <text class="section-sub">从成效出发,挖掘关键人,画出拓展路径</text>
+          </view>
         </view>
-        <text class="section-link" @tap="goOctopusAddEffect">+ 记录成效</text>
+        <text class="section-link section-link-octopus" @tap="goOctopusEffectsList">+ 感恩记录</text>
       </view>
       <OctopusDiagram
         ref="octopusDiagram"
@@ -90,11 +96,14 @@
     <!-- 登录后:珍珠图(我有什么) -->
     <view class="section" v-if="isLoggedIn">
       <view class="section-header">
-        <view>
-          <text class="section-title">🫧 珍珠图</text>
-          <text class="section-sub">把可调用的资源串成珍珠项链——盘点我有什么</text>
+        <view class="section-title-wrap">
+          <text class="help-icon" @tap="showHelp('珍珠图', '把可调用的资源串成珍珠项链——盘点我有什么。登记人脉、资源、信息缺口,画出一条清晰的成长珍珠链。')">?</text>
+          <view>
+            <text class="section-title">🫧 珍珠图</text>
+            <text class="section-sub">把可调用的资源串成珍珠项链——盘点我有什么</text>
+          </view>
         </view>
-        <text class="section-link" @tap="goPearlAddResource">+ 登记</text>
+        <text class="section-link section-link-pearl" @tap="goPearlAddResource">+ 登记</text>
       </view>
       <PearlDiagram
         ref="pearlDiagram"
@@ -109,10 +118,14 @@
     <!-- 登录后:能力图(谁会什么) -->
     <view class="section" v-if="isLoggedIn">
       <view class="section-header">
-        <view>
-          <text class="section-title">💡 能力图</text>
-          <text class="section-sub">身边藏着什么技能——谁有能力帮到你</text>
+        <view class="section-title-wrap">
+          <text class="help-icon" @tap="showHelp('能力图', '身边藏着什么技能——谁有能力帮到你。记录技能类帮助后,这里会画出大家的能力图谱。')">?</text>
+          <view>
+            <text class="section-title">💡 能力图</text>
+            <text class="section-sub">身边藏着什么技能——谁有能力帮到你</text>
+          </view>
         </view>
+        <text class="section-link section-link-ability" @tap="goInteractionLog">+ 记录帮助</text>
       </view>
       <AbilityDiagram
         ref="abilityDiagram"
@@ -177,6 +190,17 @@
     <!-- 底部占位 -->
     <view class="bottom-spacer"></view>
     <FloatingAvatar />
+
+    <!-- 帮助说明弹窗 -->
+    <view class="help-mask" v-if="helpPopup.visible" @tap="closeHelp">
+      <view class="help-dialog" @tap.stop>
+        <view class="help-dialog-header">
+          <text class="help-dialog-title">{{ helpPopup.title }}</text>
+          <text class="help-dialog-close" @tap="closeHelp">✕</text>
+        </view>
+        <text class="help-dialog-content">{{ helpPopup.content }}</text>
+      </view>
+    </view>
   </view>
 </template>
 
@@ -226,6 +250,8 @@ export default {
       octopusExpansions: [],
       octopusKeyPersons: {},
       overdueMap: null,
+      // 帮助弹窗
+      helpPopup: { visible: false, title: '', content: '' },
       funcList: [
         { icon: '\u{1F4AC}', label: '记录互动', needLogin: true, page: '/pages/action-detail/interaction-log' },
         { icon: '\u{1F4DD}', label: '关系问卷', needLogin: true, page: '/pages/action-detail/relationship-questionnaire' },
@@ -559,6 +585,10 @@ export default {
     goPearlAddResource: function() {
       uni.navigateTo({ url: '/pages/action-detail/pearl-add-resource' })
     },
+    /** 能力图记录帮助入口(跳转互动记录,记录技能类帮助) */
+    goInteractionLog: function() {
+      uni.navigateTo({ url: '/pages/action-detail/interaction-log' })
+    },
     /** 删除珍珠图资源 */
     onDeletePearlResource: function(item) {
       if (!item || !item.id) return
@@ -613,6 +643,10 @@ export default {
     goOctopusAddEffect: function() {
       uni.navigateTo({ url: '/pages/action-detail/octopus-add-effect' })
     },
+    /** 跳转感恩记录维护列表 */
+    goOctopusEffectsList: function() {
+      uni.navigateTo({ url: '/pages/action-detail/octopus-effects-list' })
+    },
     /** 点击成效节点 */
     onEffectClick: function(effect) {
       var self = this
@@ -631,6 +665,13 @@ export default {
     onReanalyze: function() {
       this.loadOctopusData()
     },
+    /** 显示帮助弹窗 */
+    showHelp: function(title, content) {
+      this.helpPopup = { visible: true, title: title, content: content }
+    },
+    closeHelp: function() {
+      this.helpPopup.visible = false
+    },
     onTaskClick: function(task) {
       uni.navigateTo({ url: '/pages/tasks/tasks' })
     },
@@ -717,6 +758,30 @@ export default {
   justify-content: space-between;
   margin-bottom: 16rpx;
 }
+.section-title-wrap {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 8rpx;
+}
+.help-icon {
+  width: 36rpx;
+  height: 36rpx;
+  border-radius: 50%;
+  border: 2rpx solid #CBD5E1;
+  color: #94A3B8;
+  font-size: 22rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  transition: all 0.2s;
+}
+.help-icon:active {
+  border-color: #F97316;
+  color: #F97316;
+  background: #FFF7ED;
+}
 .section-title {
   font-size: 32rpx;
   font-weight: bold;
@@ -726,8 +791,78 @@ export default {
   font-size: 22rpx;
   color: #999;
 }
+.section-link {
+  display: inline-flex;
+  align-items: center;
+  padding: 10rpx 28rpx;
+  border-radius: 40rpx;
+  font-size: 24rpx;
+  font-weight: 500;
+  white-space: nowrap;
+  transition: opacity 0.2s;
+}
+.section-link:active {
+  opacity: 0.7;
+}
+.section-link-octopus {
+  background: linear-gradient(135deg, #F97316, #FB923C);
+  color: #fff;
+  box-shadow: 0 4rpx 16rpx rgba(249,115,22,0.25);
+}
+.section-link-pearl {
+  background: linear-gradient(135deg, #0EA5E9, #38BDF8);
+  color: #fff;
+  box-shadow: 0 4rpx 16rpx rgba(14,165,233,0.25);
+}
+.section-link-ability {
+  background: linear-gradient(135deg, #10B981, #34D399);
+  color: #fff;
+  box-shadow: 0 4rpx 16rpx rgba(16,185,129,0.25);
+}
 
-/* ===== 功能入口 ===== */
+/* 帮助说明弹窗 */
+.help-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.4);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.help-dialog {
+  background: #fff;
+  border-radius: 20rpx;
+  width: 90%;
+  max-width: 500rpx;
+  max-height: 80vh;
+  overflow: hidden;
+}
+.help-dialog-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20rpx 24rpx;
+  border-bottom: 1rpx solid #E5E7EB;
+}
+.help-dialog-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+.help-dialog-close {
+  font-size: 32rpx;
+  color: #94A3B8;
+  cursor: pointer;
+}
+.help-dialog-content {
+  padding: 24rpx;
+  font-size: 26rpx;
+  color: #666;
+  line-height: 1.6;
+}
 .func-section {
   margin: 20rpx 30rpx 0;
 }

+ 12 - 3
cfc-frontend/pages/action-detail/octopus-add-effect.vue

@@ -1,7 +1,7 @@
 <template>
   <view class="container">
     <view class="page-header">
-      <text class="page-title">记录成效</text>
+      <text class="page-title">感恩记录</text>
     </view>
 
     <!-- 成交金额 -->
@@ -57,7 +57,7 @@
 
     <!-- 提交按钮 -->
     <view class="submit-bar">
-      <button class="btn-submit" @click="onSubmit" :disabled="!canSubmit">保存成效</button>
+      <button class="btn-submit" @click="onSubmit" :disabled="!canSubmit">保存感恩记录</button>
     </view>
   </view>
 </template>
@@ -80,7 +80,8 @@ export default {
       happenedTime: h + ':' + min,
       keyPersons: [],
       selectedMap: {},   // kpId -> true
-      roleMap: {}        // kpId -> role (1引荐/2决策/3其他)
+      roleMap: {},        // kpId -> role (1引荐/2决策/3其他)
+      _onLoadFired: false
     }
   },
   computed: {
@@ -90,6 +91,14 @@ export default {
   },
   onLoad() {
     this.loadKeyPersons()
+    this._onLoadFired = true
+  },
+  onShow() {
+    if (this._onLoadFired) {
+      this._onLoadFired = false
+    } else {
+      this.loadKeyPersons()
+    }
   },
   methods: {
     async loadKeyPersons() {

+ 224 - 0
cfc-frontend/pages/action-detail/octopus-effects-list.vue

@@ -0,0 +1,224 @@
+<template>
+  <view class="container">
+    <view class="page-header">
+      <text class="page-title">感恩记录</text>
+      <text class="page-sub">管理你的感恩时刻与成效记录</text>
+    </view>
+
+    <!-- 记录列表 -->
+    <view class="record-list" v-if="records && records.length > 0">
+      <view class="record-item" v-for="r in records" :key="r.id">
+        <view class="record-main">
+          <view class="record-amount">{{ formatAmount(r.effectAmount) }} 元</view>
+          <view class="record-tags">
+            <text class="tag" v-for="t in (r.effectTypeList || [])" :key="t">{{ t }}</text>
+          </view>
+        </view>
+        <view class="record-date">{{ formatTime(r.createdAt) }}</view>
+        <view class="record-desc" v-if="r.effectDesc">{{ r.effectDesc }}</view>
+        <view class="record-actions">
+          <text class="action-btn-del" @tap="onDelete(r.id)">删除</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 空状态 -->
+    <view class="empty-state" v-else>
+      <text class="empty-icon">🙏</text>
+      <text class="empty-text">暂无感恩记录</text>
+      <text class="empty-hint">记录你的感恩时刻,画出章鱼图</text>
+    </view>
+
+    <!-- 添加按钮 -->
+    <view class="fab" @tap="goAddEffect">
+      <text class="fab-icon">+</text>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getOctopusEffects, deleteOctopusEffect } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      records: [],
+      _onLoadFired: false
+    }
+  },
+  onLoad() {
+    this.loadRecords()
+    this._onLoadFired = true
+  },
+  onShow() {
+    if (this._onLoadFired) {
+      this._onLoadFired = false
+    } else {
+      this.loadRecords()
+    }
+  },
+  methods: {
+    async loadRecords() {
+      try {
+        var res = await getOctopusEffects()
+        if (res.code === 200 && res.data && res.data.list) {
+          this.records = res.data.list
+        } else {
+          this.records = []
+        }
+      } catch (e) { /* 静默 */ }
+    },
+    formatAmount: function(val) {
+      if (val == null) return '0'
+      return String(val)
+    },
+    formatTime: function(ts) {
+      if (!ts) return ''
+      return String(ts).substring(0, 10)
+    },
+    onDelete: function(id) {
+      var self = this
+      uni.showModal({
+        title: '提示',
+        content: '确定删除这条感恩记录吗?',
+        success: function(res) {
+          if (!res.confirm) return
+          deleteOctopusEffect({ effectId: id }).then(function(r) {
+            if (r.code === 200) {
+              uni.showToast({ title: '已删除', icon: 'success' })
+              self.loadRecords()
+            } else {
+              uni.showToast({ title: r.message || '删除失败', icon: 'none' })
+            }
+          }).catch(function(e) {
+            uni.showToast({ title: '删除失败', icon: 'none' })
+          })
+        }
+      })
+    },
+    goAddEffect: function() {
+      uni.navigateTo({ url: '/pages/action-detail/octopus-add-effect' })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding: 24rpx;
+  padding-bottom: 120rpx;
+}
+.page-header {
+  padding: 20rpx 0 30rpx;
+}
+.page-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+}
+.page-sub {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 4rpx;
+  display: block;
+}
+.record-list {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+.record-item {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
+  position: relative;
+}
+.record-main {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.record-amount {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #F97316;
+}
+.record-tags {
+  display: flex;
+  gap: 8rpx;
+}
+.tag {
+  font-size: 20rpx;
+  padding: 4rpx 12rpx;
+  background: #FFF7ED;
+  color: #F97316;
+  border-radius: 8rpx;
+}
+.record-date {
+  font-size: 24rpx;
+  color: #94A3B8;
+  margin-top: 12rpx;
+}
+.record-desc {
+  font-size: 26rpx;
+  color: #666;
+  margin-top: 8rpx;
+}
+.record-actions {
+  position: absolute;
+  top: 24rpx;
+  right: 24rpx;
+}
+.action-btn-del {
+  font-size: 24rpx;
+  color: #CBD5E1;
+  padding: 4rpx 12rpx;
+  border-radius: 8rpx;
+}
+.action-btn-del:active {
+  color: #EF4444;
+  background: #FEF2F2;
+}
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 80rpx 20rpx;
+}
+.empty-icon {
+  font-size: 80rpx;
+  margin-bottom: 16rpx;
+}
+.empty-text {
+  font-size: 28rpx;
+  color: #333;
+  font-weight: 500;
+  margin-bottom: 8rpx;
+}
+.empty-hint {
+  font-size: 24rpx;
+  color: #999;
+}
+.fab {
+  position: fixed;
+  bottom: 80rpx;
+  right: 40rpx;
+  width: 100rpx;
+  height: 100rpx;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #F97316, #FB923C);
+  box-shadow: 0 4rpx 20rpx rgba(249,115,22,0.35);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.fab-icon {
+  font-size: 48rpx;
+  color: #fff;
+  font-weight: 300;
+}
+</style>