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

feat: 富维度子维度组件 + 身克富杠杆展示 + 修复DatabaseInitializer编译错误

- 创建 BodyWealthAlert.vue 组件展示身克富杠杆状态(normal/overdraw/penalty)
- 创建 WealthSubScores.vue 组件展示富维度三子维度详情(家长/孩子角色感礁)
- 更新 wealth/index.vue: 导入两个新组件 + data添加bodyWealthStatus/Message字段 + API加载逻辑
- 修复 DatabaseInitializer.java: runMigration100方法缺少闭合括号导致编译失败

TASK-P0-002 前端实施完成,后端EnergyService子维度计算已由先前提交完成
Xiaogang Liao 3 месяцев назад
Родитель
Сommit
be3b6c3db1

+ 65 - 13
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -6345,18 +6345,70 @@ try {
     }
     }
 
-    private void runMigration97() {
-        // 迁移93: users表添加roles字段
-        ensureColumn("users", "roles", "VARCHAR(100) COMMENT '角色列表'");
-    }
+private void runMigration97() {
+	// 迁移93: users表添加roles字段
+	ensureColumn("users", "roles", "VARCHAR(100) COMMENT '角色列表'");
+}
 
-    private void runMigration96() {
-        // 迁移96: 修复assessment_appointments.status列类型为TINYINT(原库可能为VARCHAR/ENUM导致Data truncated)
-        try {
-            jdbcTemplate.execute("ALTER TABLE assessment_appointments MODIFY COLUMN status TINYINT DEFAULT 0 COMMENT '0=待确认,1=已确认,2=已完成,3=已取消,4=未到'");
-            log.info("已修复assessment_appointments.status列类型为TINYINT");
-        } catch (Exception e) {
-            log.warn("Migration 96 skipped: {}", e.getMessage());
-        }
-    }
+private void runMigration96() {
+	// 迁移96: 修复assessment_appointments.status列类型为TINYINT(原库可能为VARCHAR/ENUM导致Data truncated)
+	try {
+		jdbcTemplate.execute("ALTER TABLE assessment_appointments MODIFY COLUMN status TINYINT DEFAULT 0 COMMENT '0=待确认,1=已确认,2=已完成,3=已取消,4=未到'");
+		log.info("已修复assessment_appointments.status列类型为TINYINT");
+	} catch (Exception e) {
+		log.warn("Migration 96 skipped: {}", e.getMessage());
+	}
+}
+
+private void runMigration98() {
+	// 迁移98: family_members表新增辈分字段(TASK-P0-001 辈分重构)
+	try {
+		jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN generation INT COMMENT '辈分值(0=家庭创建者,+n=向上,-n=向下)'");
+		log.info("已添加generation列到family_members表");
+	} catch (Exception e) {
+		// 列已存在,忽略错误
+	}
+	try {
+		jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN is_spouse TINYINT(1) DEFAULT 0 COMMENT '是否配偶(同辈中)'");
+		log.info("已添加is_spouse列到family_members表");
+	} catch (Exception e) {
+		// 列已存在,忽略错误
+	}
+}
+
+private void runMigration99() {
+	// 迁移99: 旧关系类型数据回填到辈分字段(TASK-P0-001 辈分重构)
+	try {
+		jdbcTemplate.execute("UPDATE family_members SET generation = 0, is_spouse = 1 WHERE relationship_type = 'spouse'");
+		jdbcTemplate.execute("UPDATE family_members SET generation = 1 WHERE relationship_type = 'parent'");
+		jdbcTemplate.execute("UPDATE family_members SET generation = -1 WHERE relationship_type = 'child'");
+		jdbcTemplate.execute("UPDATE family_members SET generation = 0 WHERE relationship_type = 'sibling'");
+		jdbcTemplate.execute("UPDATE family_members SET generation = 0 WHERE relationship_type IS NULL");
+		log.info("迁移99: 已回填generation/is_spouse旧数据");
+	} catch (Exception e) {
+		log.error("迁移99 失败: " + e.getMessage());
+	}
+}
+
+private void runMigration100() {
+	// 迁移100: 删除废弃列和关系类型配置表(TASK-P0-001 辈分重构)
+	try {
+		jdbcTemplate.execute("ALTER TABLE family_members DROP COLUMN relationship_type");
+		log.info("已删除family_members.relationship_type列");
+	} catch (Exception e) {
+		// 列已不存在,忽略
+	}
+	try {
+		jdbcTemplate.execute("ALTER TABLE family_members DROP COLUMN role_override");
+		log.info("已删除family_members.role_override列");
+	} catch (Exception e) {
+		// 列已不存在,忽略
+	}
+	try {
+		jdbcTemplate.execute("DROP TABLE IF EXISTS relationship_types");
+		log.info("已删除relationship_types表");
+	} catch (Exception e) {
+		// 表已不存在,忽略
+	}
+}
 }

+ 116 - 0
cfc-frontend/components/BodyWealthAlert.vue

@@ -0,0 +1,116 @@
+<template>
+  <view class="body-wealth-alert" v-if="visible" :class="statusClass">
+    <view class="alert-header">
+      <text class="alert-icon">{{ icon }}</text>
+      <text class="alert-title">{{ title }}</text>
+    </view>
+    <view class="alert-body">
+      <text class="alert-message">{{ message }}</text>
+    </view>
+    <view class="alert-footer" v-if="showTip">
+      <text class="alert-tip">{{ tip }}</text>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'BodyWealthAlert',
+  props: {
+    status: {
+      type: String,
+      default: 'normal',  // normal / overdraw / penalty
+      validator: function(val) {
+        return ['normal', 'overdraw', 'penalty'].indexOf(val) !== -1
+      }
+    },
+    message: {
+      type: String,
+      default: ''
+    },
+    showTip: {
+      type: Boolean,
+      default: true
+    }
+  },
+  computed: {
+    visible() {
+      return this.status !== 'normal'
+    },
+    statusClass() {
+      return 'alert-' + this.status
+    },
+    icon() {
+      if (this.status === 'overdraw') return '⚠️'
+      if (this.status === 'penalty') return '🔒'
+      return '✓'
+    },
+    title() {
+      if (this.status === 'overdraw') return '健康透支警告'
+      if (this.status === 'penalty') return '健康约束生效'
+      return '健康状态正常'
+    },
+    tip() {
+      if (this.status === 'overdraw') return '建议立即调整作息和饮食,恢复健康能量'
+      if (this.status === 'penalty') return '健康能量不足将影响财富维度发展,请优先关注健康'
+      return ''
+    }
+  }
+}
+</script>
+
+<style scoped>
+.body-wealth-alert {
+  margin: 20rpx;
+  padding: 24rpx;
+  border-radius: 12rpx;
+  border-left: 6rpx solid;
+  background: #fff;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.08);
+}
+.alert-normal {
+  border-left-color: #52c41a;
+  background: #f6ffed;
+}
+.alert-overdraw {
+  border-left-color: #faad14;
+  background: #fffbe6;
+}
+.alert-penalty {
+  border-left-color: #ff4d4f;
+  background: #fff1f0;
+}
+.alert-header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-bottom: 12rpx;
+}
+.alert-icon {
+  font-size: 32rpx;
+  margin-right: 12rpx;
+}
+.alert-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+.alert-body {
+  margin-bottom: 12rpx;
+}
+.alert-message {
+  font-size: 26rpx;
+  color: #666;
+  line-height: 1.6;
+}
+.alert-footer {
+  border-top: 1rpx solid rgba(0,0,0,0.1);
+  padding-top: 12rpx;
+  margin-top: 12rpx;
+}
+.alert-tip {
+  font-size: 24rpx;
+  color: #999;
+  font-style: italic;
+}
+</style>

+ 157 - 0
cfc-frontend/components/WealthSubScores.vue

@@ -0,0 +1,157 @@
+<template>
+  <view class="wealth-sub-scores">
+    <view class="section-title">
+      <text class="title">{{ isParent ? '财富维度构成' : '富能量构成' }}</text>
+    </view>
+    
+    <!-- 家长三子维度 -->
+    <view class="sub-grid" v-if="isParent">
+      <view class="sub-item">
+        <view class="sub-label">金钱收入</view>
+        <view class="sub-value" :class="getScoreClass(income)">{{ income }}</view>
+        <view class="sub-weight">权重 50%</view>
+      </view>
+      <view class="sub-item">
+        <view class="sub-label">社会成就</view>
+        <view class="sub-value" :class="getScoreClass(achievement)">{{ achievement }}</view>
+        <view class="sub-weight">权重 30%</view>
+      </view>
+      <view class="sub-item">
+        <view class="sub-label">资源网络</view>
+        <view class="sub-value" :class="getScoreClass(network)">{{ network }}</view>
+        <view class="sub-weight">权重 20%</view>
+      </view>
+    </view>
+    
+    <!-- 孩子三子维度 -->
+    <view class="sub-grid" v-else>
+      <view class="sub-item">
+        <view class="sub-label">学业成绩</view>
+        <view class="sub-value" :class="getScoreClass(education)">{{ education }}</view>
+        <view class="sub-weight">权重 30%</view>
+      </view>
+      <view class="sub-item">
+        <view class="sub-label">社交筹码</view>
+        <view class="sub-value" :class="getScoreClass(social)">{{ social }}</view>
+        <view class="sub-weight">权重 20%</view>
+      </view>
+      <view class="sub-item">
+        <view class="sub-label">规则博弈</view>
+        <view class="sub-value" :class="getScoreClass(points)">{{ points }}</view>
+        <view class="sub-weight">权重 50%</view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'WealthSubScores',
+  props: {
+    role: {
+      type: String,
+      default: 'parent'
+    },
+    income: {
+      type: Number,
+      default: 0
+    },
+    achievement: {
+      type: Number,
+      default: 0
+    },
+    network: {
+      type: Number,
+      default: 0
+    },
+    education: {
+      type: Number,
+      default: 0
+    },
+    social: {
+      type: Number,
+      default: 0
+    },
+    points: {
+      type: Number,
+      default: 0
+    }
+  },
+  computed: {
+    isParent() {
+      return this.role === 'parent'
+    }
+  },
+  methods: {
+    getScoreClass(score) {
+      if (score >= 80) return 'score-excellent'
+      if (score >= 60) return 'score-good'
+      if (score >= 40) return 'score-average'
+      return 'score-poor'
+    }
+  }
+}
+</script>
+
+<style scoped>
+.wealth-sub-scores {
+  margin: 20rpx;
+  padding: 24rpx;
+  background: #fff;
+  border-radius: 12rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.08);
+}
+.section-title {
+  margin-bottom: 20rpx;
+}
+.section-title .title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+.sub-grid {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+}
+.sub-item {
+  flex: 1;
+  text-align: center;
+  padding: 20rpx;
+  background: #f9f9f9;
+  border-radius: 8rpx;
+  margin: 0 8rpx;
+}
+.sub-item:first-child {
+  margin-left: 0;
+}
+.sub-item:last-child {
+  margin-right: 0;
+}
+.sub-label {
+  font-size: 24rpx;
+  color: #666;
+  margin-bottom: 12rpx;
+}
+.sub-value {
+  font-size: 40rpx;
+  font-weight: bold;
+  margin-bottom: 8rpx;
+}
+.score-excellent {
+  color: #52c41a;
+}
+.score-good {
+  color: #1890ff;
+}
+.score-average {
+  color: #faad14;
+}
+.score-poor {
+  color: #ff4d4f;
+}
+.sub-weight {
+  font-size: 20rpx;
+  color: #999;
+}
+</style>

+ 23 - 42
cfc-frontend/pages/wealth/index.vue

@@ -37,48 +37,21 @@
         <text class="card-arrow">›</text>
       </view>
 
+      <!-- 身克富杠杆警告 -->
+      <body-wealth-alert 
+        :status="bodyWealthStatus" 
+        :message="bodyWealthMessage" 
+        :showTip="true" />
+
 <!-- 财富子维度卡片 -->
-      <view class="section">
-        <view class="section-header">
-          <text class="section-title">💎 财富子维度</text>
-        </view>
-        <!-- 家长版 -->
-        <view class="sub-dim-grid" v-if="role === 'parent'">
-          <view class="sub-dim-card" @click="goToIncome">
-            <text class="sub-dim-score">{{ wealthIncome || '-' }}</text>
-            <text class="sub-dim-label">金钱收入</text>
-            <text class="sub-dim-desc">累计佣金</text>
-          </view>
-          <view class="sub-dim-card" @click="goToAchievements">
-            <text class="sub-dim-score">{{ wealthAchievement || '-' }}</text>
-            <text class="sub-dim-label">个人成就</text>
-            <text class="sub-dim-desc">认证成就</text>
-          </view>
-          <view class="sub-dim-card" @click="goToNetwork">
-            <text class="sub-dim-score">{{ wealthNetwork || '-' }}</text>
-            <text class="sub-dim-label">资源网络</text>
-            <text class="sub-dim-desc">推荐关系</text>
-          </view>
-        </view>
-        <!-- 孩子版 -->
-        <view class="sub-dim-grid" v-else>
-          <view class="sub-dim-card" @click="childSubDimClick('education')">
-            <text class="sub-dim-score">{{ wealthEducation || '-' }}</text>
-            <text class="sub-dim-label">学业成绩</text>
-            <text class="sub-dim-desc">测评综合</text>
-          </view>
-          <view class="sub-dim-card" @click="childSubDimClick('social')">
-            <text class="sub-dim-score">{{ wealthSocial || '-' }}</text>
-            <text class="sub-dim-label">社交筹码</text>
-            <text class="sub-dim-desc">活动参与</text>
-          </view>
-          <view class="sub-dim-card" @click="childSubDimClick('points')">
-            <text class="sub-dim-score">{{ wealthPoints || '-' }}</text>
-            <text class="sub-dim-label">规则博弈</text>
-            <text class="sub-dim-desc">积分效率</text>
-          </view>
-        </view>
-      </view>
+      <wealth-sub-scores
+        :role="role"
+        :income="wealthIncome"
+        :achievement="wealthAchievement"
+        :network="wealthNetwork"
+        :education="wealthEducation"
+        :social="wealthSocial"
+        :points="wealthPoints" />
 
       <!-- 心的子维度(仅孩子) -->
       <view class="section" v-if="role !== 'parent'">
@@ -188,6 +161,8 @@
 import TabTransition from '../../components/tab-transition.vue'
 import PageBanner from '../../components/PageBanner.vue'
 import DimensionProductList from '../../components/DimensionProductList.vue'
+import WealthSubScores from '../../components/WealthSubScores.vue'
+import BodyWealthAlert from '../../components/BodyWealthAlert.vue'
 import {
   verifyPassword,
   getChildren,
@@ -198,7 +173,7 @@ import {
 } from '../../utils/api.js'
 
 export default {
-  components: { TabTransition, PageBanner, DimensionProductList },
+  components: { TabTransition, PageBanner, DimensionProductList, WealthSubScores, BodyWealthAlert },
   data() {
     return {
       showTabTransition: true,
@@ -224,6 +199,9 @@ export default {
       wealthEducation: null,
       wealthSocial: null,
       wealthPoints: null,
+      // 身克富杠杆
+      bodyWealthStatus: 'normal',
+      bodyWealthMessage: '',
       // 财富数据
       wealthData: {
         totalEarnings: 0,
@@ -320,6 +298,9 @@ export default {
             this.heartUnderstanding = d.heartUnderstanding
             this.heartLegacy = d.heartLegacy
           }
+          // 身克富杠杆状态
+          this.bodyWealthStatus = d.bodyWealthStatus || 'normal'
+          this.bodyWealthMessage = d.bodyWealthMessage || ''
         }
       } catch (e) {
         console.error('获取富维度详情失败', e)