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

feat: 创建独立圈子发现页 /pages/discover/circles

- pages.json: discover 分包注册 circles 页面(navigationStyle: custom)
- pages/discover/circles.vue: 新页面,含我的圈子+推荐列表、CircleCard/CircleDetail 组件复用、加入/退出功能
- action/index.vue: goDiscoverCircles 改为 navigateTo 跳转到新页面
Sisyphus 3 месяцев назад
Родитель
Сommit
62250ce158

+ 7 - 0
cfc-frontend/pages.json

@@ -1201,6 +1201,13 @@
           "style": {
             "navigationBarTitleText": "发现"
           }
+        },
+        {
+          "path": "circles",
+          "style": {
+            "navigationBarTitleText": "发现圈子",
+            "navigationStyle": "custom"
+          }
         }
       ]
     },

+ 1 - 1
cfc-frontend/pages/action/index.vue

@@ -415,7 +415,7 @@ export default {
       })
     },
     goDiscoverCircles: function() {
-      uni.switchTab({ url: '/pages/discover/index' })
+      uni.navigateTo({ url: '/pages/discover/circles' })
     },
     onFamilyMemberSelect: function(member) {
       this.selectedMemberId = member.id

+ 267 - 0
cfc-frontend/pages/discover/circles.vue

@@ -0,0 +1,267 @@
+<template>
+  <view class="circles-container">
+    <!-- 自定义导航栏 -->
+    <view class="custom-nav">
+      <view class="nav-header">
+        <view class="nav-back" @click="goBack">
+          <text class="nav-back-icon">‹</text>
+        </view>
+        <text class="nav-title">发现圈子</text>
+        <view class="nav-placeholder"></view>
+      </view>
+    </view>
+
+    <!-- 加载状态 -->
+    <view class="loading-state" v-if="loading">
+      <text class="loading-text">加载中...</text>
+    </view>
+
+    <!-- 圈子列表 -->
+    <scroll-view
+      v-else
+      class="circle-list"
+      scroll-y
+      refresher-enabled
+      :refresher-triggered="refreshing"
+      @refresherrefresh="onRefresh"
+      @refresherrestore="refreshing = false">
+      <!-- 我的圈子 -->
+      <view class="section" v-if="myCircles.length > 0">
+        <view class="section-header">
+          <text class="section-title">🤝 我的圈子</text>
+        </view>
+        <view class="circle-grid">
+          <view
+            v-for="item in myCircles"
+            :key="item.id || item.circleId"
+            class="circle-item"
+            @click="onCircleClick(item)">
+            <CircleCard :circle="item" compact />
+          </view>
+        </view>
+      </view>
+
+      <!-- 发现推荐 -->
+      <view class="section">
+        <view class="section-header">
+          <text class="section-title">🔍 推荐圈子</text>
+        </view>
+        <view class="circle-grid" v-if="discoverList.length > 0">
+          <view
+            v-for="item in discoverList"
+            :key="item.id || item.circleId"
+            class="circle-item"
+            @click="onCircleClick(item)">
+            <CircleCard :circle="item" />
+          </view>
+        </view>
+        <view class="empty-state" v-else-if="!loading">
+          <text class="empty-text">暂无推荐圈子</text>
+        </view>
+      </view>
+    </scroll-view>
+
+    <!-- 圈子详情弹窗 -->
+    <CircleDetail
+      :visible="detailVisible"
+      :circle="selectedCircle || {}"
+      :isMember="selectedIsMember"
+      @close="detailVisible = false"
+      @join="onJoin"
+      @leave="onLeave" />
+  </view>
+</template>
+
+<script>
+import CircleCard from '../../components/CircleCard.vue'
+import CircleDetail from '../../components/CircleDetail.vue'
+import { discoverCircles, getMyCircles, joinGeneralCircle, leaveGeneralCircle } from '../../utils/api.js'
+
+export default {
+  components: { CircleCard, CircleDetail },
+  data: function() {
+    return {
+      loading: true,
+      refreshing: false,
+      myCircles: [],
+      discoverList: [],
+      detailVisible: false,
+      selectedCircle: null,
+      selectedIsMember: false
+    }
+  },
+  onLoad: function() {
+    this.loadData()
+  },
+  methods: {
+    loadData: function() {
+      var self = this
+      self.loading = true
+      var childId = uni.getStorageSync('currentChildId') || ''
+      Promise.all([
+        new Promise(function(resolve) {
+          getMyCircles(childId, 'child').then(function(res) {
+            self.myCircles = res.code === 200 && Array.isArray(res.data) ? res.data : []
+            resolve()
+          }).catch(function() {
+            self.myCircles = []
+            resolve()
+          })
+        }),
+        new Promise(function(resolve) {
+          discoverCircles(childId).then(function(res) {
+            self.discoverList = res.code === 200 && Array.isArray(res.data) ? res.data : []
+            resolve()
+          }).catch(function() {
+            self.discoverList = []
+            resolve()
+          })
+        })
+      ]).then(function() {
+        self.loading = false
+        self.refreshing = false
+      })
+    },
+    onRefresh: function() {
+      this.refreshing = true
+      this.loadData()
+    },
+    onCircleClick: function(circle) {
+      var self = this
+      var isMember = self.myCircles.some(function(c) {
+        return (c.id || c.circleId) === (circle.id || circle.circleId)
+      })
+      self.selectedCircle = circle
+      self.selectedIsMember = isMember
+      self.detailVisible = true
+    },
+    onJoin: function(circleId) {
+      var self = this
+      var childId = uni.getStorageSync('currentChildId') || ''
+      joinGeneralCircle(circleId, childId, 'child').then(function(res) {
+        if (res.code === 200) {
+          uni.showToast({ title: '加入成功', icon: 'success' })
+          self.detailVisible = false
+          self.loadData()
+        } else {
+          uni.showToast({ title: res.message || '加入失败', icon: 'none' })
+        }
+      }).catch(function(e) {
+        console.log('加入圈子失败', e)
+      })
+    },
+    onLeave: function(circleId) {
+      var self = this
+      var childId = uni.getStorageSync('currentChildId') || ''
+      leaveGeneralCircle(circleId, childId, 'child').then(function(res) {
+        if (res.code === 200) {
+          uni.showToast({ title: '已退出', icon: 'success' })
+          self.detailVisible = false
+          self.loadData()
+        } else {
+          uni.showToast({ title: res.message || '退出失败', icon: 'none' })
+        }
+      }).catch(function(e) {
+        console.log('退出圈子失败', e)
+      })
+    },
+    goBack: function() {
+      uni.navigateBack()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.circles-container {
+  min-height: 100vh;
+  background: #FFF7ED;
+}
+/* 自定义导航栏 */
+.custom-nav {
+  padding-top: var(--status-bar-height);
+  background: #fff;
+}
+.nav-header {
+  display: flex;
+  align-items: center;
+  height: 88rpx;
+  position: relative;
+}
+.nav-back {
+  width: 88rpx;
+  height: 88rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+.nav-back:active {
+  opacity: 0.6;
+}
+.nav-back-icon {
+  font-size: 48rpx;
+  color: #333;
+  line-height: 1;
+}
+.nav-title {
+  flex: 1;
+  text-align: center;
+  font-size: 34rpx;
+  font-weight: 600;
+  color: #333;
+}
+.nav-placeholder {
+  width: 88rpx;
+  flex-shrink: 0;
+}
+/* 加载 */
+.loading-state {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 120rpx 0;
+}
+.loading-text {
+  font-size: 28rpx;
+  color: #999;
+}
+/* 列表 */
+.circle-list {
+  height: calc(100vh - var(--status-bar-height) - 88rpx);
+}
+.section {
+  margin: 20rpx 20rpx 0;
+}
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #333;
+}
+.circle-grid {
+  display: flex;
+  flex-direction: column;
+  gap: 0;
+}
+.circle-item {
+  margin-bottom: 12rpx;
+}
+.circle-item:active {
+  opacity: 0.8;
+}
+/* 空状态 */
+.empty-state {
+  padding: 80rpx 0;
+  text-align: center;
+}
+.empty-text {
+  font-size: 26rpx;
+  color: #ccc;
+}
+</style>