Преглед изворни кода

feat: 小程序管家选择页与接口封装

iwt пре 3 недеља
родитељ
комит
00bb0481b9
3 измењених фајлова са 189 додато и 0 уклоњено
  1. 6 0
      cfc-frontend/pages.json
  2. 172 0
      cfc-frontend/pages/butler/select.vue
  3. 11 0
      cfc-frontend/utils/api.js

+ 6 - 0
cfc-frontend/pages.json

@@ -629,6 +629,12 @@
           "style": {
             "navigationBarTitleText": "管家入驻"
           }
+        },
+        {
+          "path": "select",
+          "style": {
+            "navigationBarTitleText": "选择管家"
+          }
         }
       ]
     },

+ 172 - 0
cfc-frontend/pages/butler/select.vue

@@ -0,0 +1,172 @@
+<template>
+  <view class="select-page">
+    <!-- 顶部状态区 -->
+    <view class="status-card" v-if="pageState === 'bound'">
+      <view class="current-label">我的管家</view>
+      <view class="current-row">
+        <image class="current-avatar" :src="myButler.avatar || '/static/logo.png'" mode="aspectFill" />
+        <view class="current-info">
+          <text class="current-name">{{ myButler.nickname }}</text>
+          <text class="current-date">{{ formatDate(myButler.assignedAt) }} 绑定</text>
+        </view>
+      </view>
+      <view class="current-desc">{{ myButler.description }}</view>
+      <button class="btn-change" @click="scrollToList">更换管家</button>
+    </view>
+    <view class="upgrade-banner" v-if="pageState === 'locked'" @click="goUpgrade">
+      <text class="upgrade-text">久久一生(L2)会员专享,升级后可选择专属管家</text>
+      <text class="upgrade-arrow">›</text>
+    </view>
+
+    <!-- 管家列表 -->
+    <view class="list-section" id="butlerList">
+      <view class="section-title">可选管家</view>
+      <view class="butler-card" v-for="(item, idx) in butlers" :key="getItemKey(idx)"
+        :class="{ active: myButler && myButler.butlerUserId === item.butlerUserId }">
+        <image class="card-avatar" :src="item.avatar || '/static/logo.png'" mode="aspectFill" />
+        <view class="card-body">
+          <view class="card-top">
+            <text class="card-name">{{ item.nickname }}</text>
+            <text class="card-slots">剩余名额 {{ item.remainingSlots }}</text>
+          </view>
+          <text class="card-desc">{{ item.description || '专业家庭健康管家' }}</text>
+        </view>
+        <button class="btn-select"
+          :disabled="pageState === 'locked' || item.remainingSlots <= 0"
+          @click="onSelect(item)">
+          {{ myButler && myButler.butlerUserId === item.butlerUserId ? '当前' : '选择' }}
+        </button>
+      </view>
+      <view class="empty-tip" v-if="butlers.length === 0 && !loading">暂无可选管家</view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getAvailableButlers, getMyButler, selectButler } from '@/utils/api.js'
+
+export default {
+  data() {
+    return {
+      loading: false,
+      pageState: 'browse', // browse=可浏览未绑定 / bound=已绑定 / locked=非L2
+      lockReason: '',
+      myButler: null,
+      butlers: [],
+      keySeq: 0
+    }
+  },
+  onLoad() {
+    this.initPage()
+  },
+  onShow() {
+    if (this.myButler || this.pageState === 'bound') {
+      this.refreshMyButler()
+    }
+  },
+  methods: {
+    getItemKey(idx) {
+      var item = this.butlers[idx]
+      return item && item.butlerUserId ? 'b_' + item.butlerUserId : 'idx_' + idx
+    },
+    formatDate(iso) {
+      if (!iso) return ''
+      return iso.substring(0, 19).replace('T', ' ').substring(0, 16)
+    },
+    initPage() {
+      var that = this
+      that.loading = true
+      Promise.all([getAvailableButlers(), getMyButler()])
+        .then(function (resArr) {
+          var listRes = resArr[0]
+          var myRes = resArr[1]
+          if (listRes && listRes.code === 200) {
+            that.butlers = listRes.data || []
+          }
+          if (myRes && myRes.code === 200 && myRes.data) {
+            that.myButler = myRes.data
+            that.pageState = 'bound'
+          }
+        })
+        .catch(function () {
+          uni.showToast({ title: '加载失败', icon: 'none' })
+        })
+        .then(function () { that.loading = false })
+    },
+    refreshMyButler() {
+      var that = this
+      getMyButler().then(function (res) {
+        if (res && res.code === 200) {
+          that.myButler = res.data
+          that.pageState = res.data ? 'bound' : 'browse'
+        }
+      })
+    },
+    markLocked(msg) {
+      this.pageState = 'locked'
+      this.lockReason = msg || ''
+    },
+    onSelect(item) {
+      var that = this
+      if (that.myButler && that.myButler.butlerUserId === item.butlerUserId) return
+      uni.showModal({
+        title: '确认选择',
+        content: '确定选择「' + item.nickname + '」作为您的专属管家?',
+        success: function (mr) {
+          if (!mr.confirm) return
+          selectButler(item.butlerUserId).then(function (res) {
+            if (res && res.code === 200) {
+              uni.showToast({ title: '绑定成功', icon: 'success' })
+              that.initPage()
+            } else if (res && res.message && res.message.indexOf('久久一生') !== -1) {
+              that.markLocked(res.message)
+              uni.showModal({
+                title: '会员权益',
+                content: res.message,
+                showCancel: false,
+                success: function () { that.goUpgrade() }
+              })
+            } else {
+              uni.showToast({ title: (res && res.message) || '绑定失败', icon: 'none' })
+            }
+          })
+        }
+      })
+    },
+    scrollToList() {
+      uni.pageScrollTo({ selector: '#butlerList', duration: 300 })
+    },
+    goUpgrade() {
+      uni.navigateTo({ url: '/pages/membership/upgrade' })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.select-page { min-height: 100vh; background: #f5fafe; padding: 24rpx; box-sizing: border-box; display: flex; flex-direction: column; }
+.status-card { background: #fff; border-radius: 20rpx; padding: 32rpx; margin-bottom: 24rpx; }
+.current-label { font-size: 24rpx; color: #999; margin-bottom: 16rpx; }
+.current-row { display: flex; align-items: center; }
+.current-avatar { width: 96rpx; height: 96rpx; border-radius: 50%; margin-right: 20rpx; }
+.current-info { display: flex; flex-direction: column; }
+.current-name { font-size: 34rpx; font-weight: bold; color: #333; }
+.current-date { font-size: 22rpx; color: #999; margin-top: 6rpx; }
+.current-desc { font-size: 26rpx; color: #666; margin-top: 20rpx; line-height: 1.6; }
+.btn-change { margin-top: 24rpx; background: #4a9bd7; color: #fff; font-size: 28rpx; border-radius: 40rpx; }
+.upgrade-banner { display: flex; justify-content: space-between; align-items: center; background: #fff7ed; border: 1rpx solid #ffb56b; border-radius: 16rpx; padding: 24rpx; margin-bottom: 24rpx; }
+.upgrade-text { font-size: 26rpx; color: #d4770a; flex: 1; }
+.upgrade-arrow { font-size: 36rpx; color: #d4770a; margin-left: 12rpx; }
+.section-title { font-size: 30rpx; font-weight: bold; color: #333; margin: 8rpx 0 20rpx; }
+.butler-card { display: flex; align-items: center; background: #fff; border-radius: 20rpx; padding: 28rpx; margin-bottom: 20rpx; }
+.butler-card.active { border: 2rpx solid #4a9bd7; }
+.card-avatar { width: 88rpx; height: 88rpx; border-radius: 50%; margin-right: 20rpx; flex-shrink: 0; }
+.card-body { flex: 1; min-width: 0; }
+.card-top { display: flex; align-items: center; justify-content: space-between; }
+.card-name { font-size: 30rpx; font-weight: bold; color: #333; }
+.card-slots { font-size: 22rpx; color: #10b981; }
+.card-desc { display: block; font-size: 24rpx; color: #888; margin-top: 8rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.btn-select { width: 120rpx; height: 60rpx; line-height: 60rpx; padding: 0; font-size: 26rpx; background: #4a9bd7; color: #fff; border-radius: 30rpx; flex-shrink: 0; margin-left: 16rpx; }
+.btn-select[disabled] { background: #ccc; color: #fff; }
+.empty-tip { text-align: center; color: #999; font-size: 26rpx; padding: 60rpx 0; }
+</style>

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

@@ -2711,3 +2711,14 @@ export function getChallengeProgress(memberId) {
     })
   })
 }
+
+// ── 管家自助选择 ──
+export function getAvailableButlers() {
+  return request('/api/butler/available-list', 'POST', {})
+}
+export function getMyButler() {
+  return request('/api/butler/my-butler', 'POST', {})
+}
+export function selectButler(butlerUserId) {
+  return request('/api/butler/select', 'POST', { butlerUserId })
+}