Parcourir la source

chore: auto bump version and changelog [skip ci]

iwt il y a 1 mois
Parent
commit
d73cb0192b

+ 32 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/CircleChallengeController.java

@@ -0,0 +1,32 @@
+package com.etotem.cfc.controller;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.CircleChallenge;
+import com.etotem.cfc.service.CircleChallengeService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/health/circle/challenge")
+public class CircleChallengeController {
+
+    @Resource
+    private CircleChallengeService circleChallengeService;
+
+    @PostMapping("/list")
+    public Result<List<CircleChallenge>> getList(@RequestBody Map<String, Object> params) {
+        Long circleId = params.get("circleId") != null
+                ? Long.valueOf(params.get("circleId").toString()) : null;
+        return Result.success(circleChallengeService.getActiveChallenges(circleId));
+    }
+
+    @PostMapping("/history")
+    public Result<List<CircleChallenge>> getHistory(@RequestBody Map<String, Object> params) {
+        Long circleId = params.get("circleId") != null
+                ? Long.valueOf(params.get("circleId").toString()) : null;
+        return Result.success(circleChallengeService.getChallengeHistory(circleId));
+    }
+}

+ 58 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthCircleController.java

@@ -0,0 +1,58 @@
+package com.etotem.cfc.controller;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.HealthCircle;
+import com.etotem.cfc.service.HealthCircleService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/health/circle")
+public class HealthCircleController {
+
+    @Resource
+    private HealthCircleService healthCircleService;
+
+    @PostMapping("/list")
+    public Result<List<HealthCircle>> list(@RequestBody Map<String, Object> params) {
+        Long familyId = params.get("familyId") != null
+                ? Long.valueOf(params.get("familyId").toString()) : null;
+        return Result.success(healthCircleService.getUserCircles(familyId));
+    }
+
+    @PostMapping("/create")
+    public Result<HealthCircle> create(@RequestBody Map<String, Object> params) {
+        String name = params.get("name") != null ? params.get("name").toString() : null;
+        String description = params.get("description") != null ? params.get("description").toString() : null;
+        Long familyId = params.get("familyId") != null
+                ? Long.valueOf(params.get("familyId").toString()) : null;
+        return healthCircleService.createCircle(name, description, familyId);
+    }
+
+    @PostMapping("/join")
+    public Result<Void> join(@RequestBody Map<String, Object> params) {
+        String inviteCode = params.get("inviteCode") != null ? params.get("inviteCode").toString() : null;
+        Long familyId = params.get("familyId") != null
+                ? Long.valueOf(params.get("familyId").toString()) : null;
+        return healthCircleService.joinCircle(inviteCode, familyId);
+    }
+
+    @PostMapping("/leave")
+    public Result<Void> leave(@RequestBody Map<String, Object> params) {
+        Long circleId = params.get("circleId") != null
+                ? Long.valueOf(params.get("circleId").toString()) : null;
+        Long familyId = params.get("familyId") != null
+                ? Long.valueOf(params.get("familyId").toString()) : null;
+        return healthCircleService.leaveCircle(circleId, familyId);
+    }
+
+    @PostMapping("/ranking")
+    public Result<Map<String, Object>> ranking(@RequestBody Map<String, Object> params) {
+        Long circleId = params.get("circleId") != null
+                ? Long.valueOf(params.get("circleId").toString()) : null;
+        return Result.success(healthCircleService.getCircleRanking(circleId));
+    }
+}

+ 8 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/ShareController.java

@@ -100,4 +100,12 @@ public class ShareController {
         Map<String, Object> stats = shareService.getShareStats(userId);
         return Result.success(stats);
     }
+
+    @Operation(summary = "获取家庭健康足迹(P3分享页)")
+    @PostMapping("/family-footprint")
+    public Result<Map<String, Object>> familyFootprint(@RequestBody Map<String, Object> params) {
+        Long familyId = params.get("familyId") != null
+                ? Long.valueOf(params.get("familyId").toString()) : null;
+        return Result.success(shareService.getFamilyFootprint(familyId));
+    }
 }

+ 70 - 0
cfc-frontend/components/CircleRanking.vue

@@ -0,0 +1,70 @@
+<template>
+  <view class="circle-ranking" v-if="circleName">
+    <view class="cr-header">
+      <text class="cr-title">🏅 {{ circleName }}</text>
+    </view>
+    <view class="cr-list">
+      <view class="cr-item" v-for="(item, idx) in rankings" :key="idx">
+        <text class="cr-rank">{{ '🥇🥈🥉'[idx] || '#' + (idx + 1) }}</text>
+        <view class="cr-info">
+          <text class="cr-family">家庭 {{ idx + 1 }}</text>
+          <text class="cr-members">{{ item.memberCount || 0 }} 位成员</text>
+        </view>
+        <text class="cr-score">{{ item.totalScore || 0 }} 分</text>
+      </view>
+    </view>
+  </view>
+  <view v-else-if="!circleName && !loading" class="cr-empty">
+    <text class="cr-empty-text">暂无健康圈数据</text>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    circleName: { type: String, default: '' },
+    rankings: { type: Array, default: function() { return [] } },
+    loading: { type: Boolean, default: false }
+  }
+}
+</script>
+
+<style scoped>
+.circle-ranking {
+  background: #fff;
+  border-radius: 20rpx;
+  margin: 0 20rpx 20rpx 20rpx;
+  padding: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.cr-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+.cr-title {
+  font-size: 30rpx;
+  font-weight: 700;
+  color: #1E293B;
+}
+.cr-item {
+  display: flex;
+  align-items: center;
+  gap: 12rpx;
+  padding: 12rpx 0;
+  border-bottom: 1rpx solid #F1F5F9;
+}
+.cr-item:last-child { border-bottom: none; }
+.cr-rank { font-size: 32rpx; width: 48rpx; text-align: center; }
+.cr-info { flex: 1; min-width: 0; }
+.cr-family { font-size: 26rpx; font-weight: 600; color: #334155; display: block; }
+.cr-members { font-size: 20rpx; color: #94A3B8; margin-top: 2rpx; display: block; }
+.cr-score { font-size: 30rpx; font-weight: 700; color: #6366F1; }
+.cr-empty {
+  padding: 60rpx 20rpx;
+  display: flex;
+  justify-content: center;
+}
+.cr-empty-text { font-size: 26rpx; color: #94A3B8; }
+</style>

+ 146 - 0
cfc-frontend/pages/share/index.vue

@@ -0,0 +1,146 @@
+<template>
+  <view class="share-page">
+    <view class="sp-header" v-if="familyName">
+      <text class="sp-title">{{ familyName }} · 健康足迹</text>
+    </view>
+
+    <view class="sp-body" v-if="members && members.length > 0">
+      <view class="sp-member" v-for="m in members" :key="m.name">
+        <image class="sp-avatar" :src="m.avatar || '/static/default-avatar.png'" mode="aspectFill"></image>
+        <view class="sp-member-info">
+          <text class="sp-member-name">{{ m.name }}</text>
+          <view class="sp-member-stats">
+            <text class="sp-stat">打卡 {{ m.streakDays || 0 }} 天</text>
+            <text class="sp-stat" v-if="m.score > 0">积分 {{ m.score }}</text>
+          </view>
+        </view>
+        <text class="sp-badge" v-if="m.streakDays && m.streakDays >= 30">🏆</text>
+        <text class="sp-badge" v-else-if="m.streakDays && m.streakDays >= 7">🎯</text>
+      </view>
+    </view>
+
+    <view class="sp-footer" v-if="totalCheckinDays">
+      <text class="sp-slogan">我们坚持健康打卡 {{ totalCheckinDays }} 天了!</text>
+    </view>
+
+    <view class="sp-qr">
+      <text class="sp-qr-text">扫码加入我们一起健康</text>
+    </view>
+
+    <view class="sp-empty" v-if="!members || members.length === 0">
+      <text class="sp-empty-text">暂无健康数据</text>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getFamilyFootprint } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      familyName: '',
+      members: [],
+      totalScore: 0,
+      totalCheckinDays: 0
+    }
+  },
+  onLoad(params) {
+    var familyId = params.familyId || ''
+    if (familyId) {
+      this.loadData(familyId)
+    }
+  },
+  methods: {
+    loadData: function(familyId) {
+      var self = this
+      getFamilyFootprint({ familyId: familyId }).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.members = res.data.members || []
+          self.totalScore = res.data.totalScore || 0
+          self.totalCheckinDays = res.data.totalCheckinDays || 0
+        }
+      }).catch(function() {})
+    }
+  }
+}
+</script>
+
+<style scoped>
+.share-page {
+  min-height: 100vh;
+  background: linear-gradient(180deg, #F0FDF4 0%, #ECFDF5 100%);
+  padding: 40rpx 30rpx;
+}
+.sp-header { text-align: center; margin-bottom: 40rpx; }
+.sp-title {
+  font-size: 36rpx;
+  font-weight: 700;
+  color: #065F46;
+}
+.sp-body {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 24rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.08);
+}
+.sp-member {
+  display: flex;
+  align-items: center;
+  gap: 16rpx;
+  padding: 16rpx 0;
+  border-bottom: 1rpx solid #F1F5F9;
+}
+.sp-member:last-child { border-bottom: none; }
+.sp-avatar {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 50%;
+  flex-shrink: 0;
+}
+.sp-member-info { flex: 1; min-width: 0; }
+.sp-member-name {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #1E293B;
+}
+.sp-member-stats {
+  display: flex;
+  gap: 16rpx;
+  margin-top: 4rpx;
+}
+.sp-stat {
+  font-size: 24rpx;
+  color: #64748B;
+}
+.sp-badge { font-size: 40rpx; }
+.sp-footer {
+  text-align: center;
+  margin-top: 40rpx;
+  padding: 24rpx;
+  background: rgba(16,185,129,0.1);
+  border-radius: 16rpx;
+}
+.sp-slogan {
+  font-size: 28rpx;
+  color: #065F46;
+  font-weight: 600;
+}
+.sp-qr {
+  text-align: center;
+  margin-top: 40rpx;
+  padding: 40rpx;
+  background: #fff;
+  border-radius: 16rpx;
+  border: 2rpx dashed #D1FAE5;
+}
+.sp-qr-text {
+  font-size: 26rpx;
+  color: #10B981;
+}
+.sp-empty {
+  text-align: center;
+  padding: 100rpx 0;
+}
+.sp-empty-text { font-size: 28rpx; color: #94A3B8; }
+</style>

+ 10 - 0
cfc-frontend/utils/api.js

@@ -1484,6 +1484,16 @@ export const getChallengeHistory = (data) => request('/api/health/challenge/hist
 export const updateChallengeProgress = (data) => request('/api/health/challenge/progress', 'POST', data)
 export const sendLike = (data) => request('/api/health/interact/like', 'POST', data)
 
+// ===== 健康社区扩散(P3 健康圈/排行/分享) =====
+export const getCircleList = (data) => request('/api/health/circle/list', 'POST', data)
+export const createCircle = (data) => request('/api/health/circle/create', 'POST', data)
+export const joinCircle = (data) => request('/api/health/circle/join', 'POST', data)
+export const leaveCircle = (data) => request('/api/health/circle/leave', 'POST', data)
+export const getCircleRanking = (data) => request('/api/health/circle/ranking', 'POST', data)
+export const getCircleChallengeList = (data) => request('/api/health/circle/challenge/list', 'POST', data)
+export const getCircleChallengeHistory = (data) => request('/api/health/circle/challenge/history', 'POST', data)
+export const getFamilyFootprint = (data) => request('/api/share/family-footprint', 'POST', data)
+
 // ===== 娲诲姩妯″潡锛堢淮搴︾瓫閫夛級 =====
 export const getActivityList = (data) => request('/api/activity/list', 'POST', data)
 export const getActivityDetail = (id) => request('/api/activity/detail', 'POST', { id })

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-269a6ebfaa249880c7eda6aab6a630c42b1e3873
+3af0887427d05aa8f093247ebaeb115b51cc394c