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

feat(admin): 会员中心改为三个 Tab 页(等级配置/升级记录/会员维护)

iwt 2 месяцев назад
Родитель
Сommit
3fa605d6bf
1 измененных файлов с 148 добавлено и 148 удалено
  1. 148 148
      cfc-web/src/views/admin/MembershipCenter.vue

+ 148 - 148
cfc-web/src/views/admin/MembershipCenter.vue

@@ -20,142 +20,139 @@
       </el-col>
     </el-row>
 
-    <!-- 会员等级配置 -->
+    <!-- Tab 区域 -->
     <el-card class="admin-card">
-      <div slot="header" class="admin-page-header">
-        <span class="admin-page-title">会员等级配置</span>
-        <span class="text-muted" style="font-size:12px;">共 {{ safeLevels.length }} 个等级</span>
-      </div>
-      <div class="table-scroll-wrap-lg">
-        <el-table :data="safeLevels" v-loading="levelsLoading" border stripe size="small" empty-text="暂无等级配置">
-          <el-table-column prop="levelCode" label="等级编码" width="110">
-            <template slot-scope="{ row }">
-              <el-tag :type="levelTagType(row.levelCode)" size="small">{{ row.levelCode }}</el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column prop="levelName" label="等级名称" width="130" />
-          <el-table-column prop="levelDesc" label="描述" min-width="160" show-overflow-tooltip />
-          <el-table-column label="月费" width="80">
-            <template slot-scope="{ row }">{{ formatPrice(row.priceMonthly) }}</template>
-          </el-table-column>
-          <el-table-column label="季费" width="80">
-            <template slot-scope="{ row }">{{ formatPrice(row.priceQuarterly) }}</template>
-          </el-table-column>
-          <el-table-column label="年费" width="80">
-            <template slot-scope="{ row }">{{ formatPrice(row.priceYearly) }}</template>
-          </el-table-column>
-          <el-table-column label="孩子上限" width="90" align="center">
-            <template slot-scope="{ row }">{{ row.maxChildren || '—' }}</template>
-          </el-table-column>
-          <el-table-column label="每日任务" width="90" align="center">
-            <template slot-scope="{ row }">{{ row.maxTasksPerDay || '—' }}</template>
-          </el-table-column>
-          <el-table-column label="AI审核" width="80" align="center">
-            <template slot-scope="{ row }">
-              <el-tag :type="row.aiReviewEnabled ? 'success' : 'info'" size="small">{{ row.aiReviewEnabled ? '是' : '否' }}</el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column label="优先客服" width="80" align="center">
-            <template slot-scope="{ row }">
-              <el-tag :type="row.prioritySupport ? 'success' : 'info'" size="small">{{ row.prioritySupport ? '是' : '否' }}</el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column label="操作" width="100" fixed="right">
-            <template slot-scope="{ row }">
-              <el-button size="mini" type="primary" plain @click="handleViewFeatures(row)">权限</el-button>
-            </template>
-          </el-table-column>
-        </el-table>
-      </div>
-    </el-card>
+      <el-tabs v-model="activeTab" type="border-card" stretch>
+        
+        <!-- Tab 1: 会员等级配置 -->
+        <el-tab-pane label="会员等级配置" name="levels">
+          <div class="table-scroll-wrap-lg">
+            <el-table :data="safeLevels" v-loading="levelsLoading" border stripe size="small" empty-text="暂无等级配置">
+              <el-table-column prop="levelCode" label="等级编码" width="110">
+                <template slot-scope="{ row }">
+                  <el-tag :type="levelTagType(row.levelCode)" size="small">{{ row.levelCode }}</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column prop="levelName" label="等级名称" width="130" />
+              <el-table-column prop="levelDesc" label="描述" min-width="160" show-overflow-tooltip />
+              <el-table-column label="月费" width="80">
+                <template slot-scope="{ row }">{{ formatPrice(row.priceMonthly) }}</template>
+              </el-table-column>
+              <el-table-column label="季费" width="80">
+                <template slot-scope="{ row }">{{ formatPrice(row.priceQuarterly) }}</template>
+              </el-table-column>
+              <el-table-column label="年费" width="80">
+                <template slot-scope="{ row }">{{ formatPrice(row.priceYearly) }}</template>
+              </el-table-column>
+              <el-table-column label="孩子上限" width="90" align="center">
+                <template slot-scope="{ row }">{{ row.maxChildren || '—' }}</template>
+              </el-table-column>
+              <el-table-column label="每日任务" width="90" align="center">
+                <template slot-scope="{ row }">{{ row.maxTasksPerDay || '—' }}</template>
+              </el-table-column>
+              <el-table-column label="AI审核" width="80" align="center">
+                <template slot-scope="{ row }">
+                  <el-tag :type="row.aiReviewEnabled ? 'success' : 'info'" size="small">{{ row.aiReviewEnabled ? '是' : '否' }}</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="优先客服" width="80" align="center">
+                <template slot-scope="{ row }">
+                  <el-tag :type="row.prioritySupport ? 'success' : 'info'" size="small">{{ row.prioritySupport ? '是' : '否' }}</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="100" fixed="right">
+                <template slot-scope="{ row }">
+                  <el-button size="mini" type="primary" plain @click="handleViewFeatures(row)">权限</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+        </el-tab-pane>
 
-    <!-- 会员升级记录 -->
-    <el-card class="admin-card">
-      <div slot="header" class="admin-page-header">
-        <span class="admin-page-title">升级记录</span>
-      </div>
-      <div class="table-scroll-wrap-lg">
-        <el-table :data="upgradeRecords" v-loading="recordsLoading" border stripe size="small" empty-text="暂无升级记录">
-          <el-table-column prop="id" label="ID" width="60" />
-          <el-table-column label="用户" width="110">
-            <template slot-scope="{ row }">{{ row.userName || ('用户' + row.userId) }}</template>
-          </el-table-column>
-          <el-table-column label="升级路径" width="140">
-            <template slot-scope="{ row }">
-              <span class="level-path">
-                <el-tag size="mini" type="info">{{ row.fromLevel || 'FREE' }}</el-tag>
-                <i class="el-icon-right"></i>
-                <el-tag size="mini" type="success">{{ row.toLevel || '—' }}</el-tag>
-              </span>
-            </template>
-          </el-table-column>
-          <el-table-column label="升级方式" width="110">
-            <template slot-scope="{ row }">
-              <el-tag :type="upgradeTypeTag(row.upgradeType)" size="mini">{{ upgradeTypeLabel(row.upgradeType) }}</el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column prop="orderId" label="订单号" width="120" show-overflow-tooltip />
-          <el-table-column label="到期时间" width="150">
-            <template slot-scope="{ row }">{{ formatTime(row.expireTime) }}</template>
-          </el-table-column>
-          <el-table-column label="创建时间" width="150">
-            <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
-          </el-table-column>
-        </el-table>
-      </div>
-      <div class="pagination-wrap" v-if="recordsTotal > 0">
-        <el-pagination
-          @size-change="handleRecordSizeChange"
-          @current-change="handleRecordPageChange"
-          :current-page="recordFilter.page"
-          :page-sizes="[10, 20, 50]"
-          :page-size="recordFilter.size"
-          layout="total, sizes, prev, pager, next, jumper"
-          :total="recordsTotal"
-        />
-      </div>
-    </el-card>
+        <!-- Tab 2: 升级记录 -->
+        <el-tab-pane label="升级记录" name="records">
+          <div class="table-scroll-wrap-lg">
+            <el-table :data="upgradeRecords" v-loading="recordsLoading" border stripe size="small" empty-text="暂无升级记录">
+              <el-table-column prop="id" label="ID" width="60" />
+              <el-table-column label="用户" width="110">
+                <template slot-scope="{ row }">{{ row.userName || ('用户' + row.userId) }}</template>
+              </el-table-column>
+              <el-table-column label="升级路径" width="140">
+                <template slot-scope="{ row }">
+                  <span class="level-path">
+                    <el-tag size="mini" type="info">{{ row.fromLevel || 'FREE' }}</el-tag>
+                    <i class="el-icon-right"></i>
+                    <el-tag size="mini" type="success">{{ row.toLevel || '—' }}</el-tag>
+                  </span>
+                </template>
+              </el-table-column>
+              <el-table-column label="升级方式" width="110">
+                <template slot-scope="{ row }">
+                  <el-tag :type="upgradeTypeTag(row.upgradeType)" size="mini">{{ upgradeTypeLabel(row.upgradeType) }}</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column prop="orderId" label="订单号" width="120" show-overflow-tooltip />
+              <el-table-column label="到期时间" width="150">
+                <template slot-scope="{ row }">{{ formatTime(row.expireTime) }}</template>
+              </el-table-column>
+              <el-table-column label="创建时间" width="150">
+                <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
+              </el-table-column>
+            </el-table>
+          </div>
+          <div class="pagination-wrap" v-if="recordsTotal > 0">
+            <el-pagination
+              @size-change="handleRecordSizeChange"
+              @current-change="handleRecordPageChange"
+              :current-page="recordFilter.page"
+              :page-sizes="[10, 20, 50]"
+              :page-size="recordFilter.size"
+              layout="total, sizes, prev, pager, next, jumper"
+              :total="recordsTotal"
+            />
+          </div>
+        </el-tab-pane>
 
-    <!-- 会员维护 -->
-    <el-card class="admin-card">
-      <div slot="header" class="admin-page-header">
-        <span class="admin-page-title">会员维护</span>
-      </div>
-      <div class="filter-bar">
-        <el-input
-          v-model="maintainSearch"
-          placeholder="手机号 / 昵称 / 用户ID"
-          clearable
-          style="width:240px;"
-          @keyup.enter.native="handleMaintainSearch"
-        />
-        <el-button type="primary" size="small" icon="el-icon-search" @click="handleMaintainSearch" :loading="maintainSearching">搜索</el-button>
-        <span v-if="safeMaintainUsers.length" class="search-result-count">找到 {{ safeMaintainUsers.length }} 个用户</span>
-      </div>
-      <div v-if="safeMaintainUsers.length" class="table-scroll-wrap-sm">
-        <el-table :data="safeMaintainUsers" border stripe size="small">
-          <el-table-column prop="id" label="ID" width="70" />
-          <el-table-column prop="nickname" label="昵称" width="130" />
-          <el-table-column prop="phone" label="手机号" width="130" />
-          <el-table-column label="当前等级" width="110">
-            <template slot-scope="{ row }">
-              <el-tag :type="levelTagType(row.memberLevel)" size="mini">{{ row.memberLevel || 'FREE' }}</el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column label="推荐人ID" width="100">
-            <template slot-scope="{ row }">{{ row.referrerId || '—' }}</template>
-          </el-table-column>
-          <el-table-column label="操作" width="100" fixed="right">
-            <template slot-scope="{ row }">
-              <el-button size="mini" type="warning" @click="openMaintainDialog(row)">维护</el-button>
-            </template>
-          </el-table-column>
-        </el-table>
-      </div>
-      <div v-else-if="maintainSearched" class="empty-state">
-        <i class="el-icon-user-solid"></i>
-        <span>未找到匹配用户</span>
-      </div>
+        <!-- Tab 3: 会员维护 -->
+        <el-tab-pane label="会员维护" name="maintain">
+          <div class="filter-bar">
+            <el-input
+              v-model="maintainSearch"
+              placeholder="手机号 / 昵称 / 用户ID"
+              clearable
+              style="width:240px;"
+              @keyup.enter.native="handleMaintainSearch"
+            />
+            <el-button type="primary" size="small" icon="el-icon-search" @click="handleMaintainSearch" :loading="maintainSearching">搜索</el-button>
+            <span v-if="safeMaintainUsers.length" class="search-result-count">找到 {{ safeMaintainUsers.length }} 个用户</span>
+          </div>
+          <div v-if="safeMaintainUsers.length" class="table-scroll-wrap-sm">
+            <el-table :data="safeMaintainUsers" border stripe size="small">
+              <el-table-column prop="id" label="ID" width="70" />
+              <el-table-column prop="nickname" label="昵称" width="130" />
+              <el-table-column prop="phone" label="手机号" width="130" />
+              <el-table-column label="当前等级" width="110">
+                <template slot-scope="{ row }">
+                  <el-tag :type="levelTagType(row.memberLevel)" size="mini">{{ row.memberLevel || 'FREE' }}</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="推荐人ID" width="100">
+                <template slot-scope="{ row }">{{ row.referrerId || '—' }}</template>
+              </el-table-column>
+              <el-table-column label="操作" width="100" fixed="right">
+                <template slot-scope="{ row }">
+                  <el-button size="mini" type="warning" @click="openMaintainDialog(row)">维护</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+          <div v-else-if="maintainSearched" class="empty-state">
+            <i class="el-icon-user-solid"></i>
+            <span>未找到匹配用户</span>
+          </div>
+        </el-tab-pane>
+
+      </el-tabs>
     </el-card>
 
     <!-- 权限详情对话框 -->
@@ -221,7 +218,7 @@
 </template>
 
 <script>
-import { searchUsers, updateMembershipUser, getMembershipStats } from '@/api/admin'
+import { searchUsers, updateMembershipUser, getMembershipStats, listMembershipLevels } from '@/api/admin'
 
 const LEVEL_TAG_TYPE = { FREE: 'info', FAMILY: 'success', PROVIDER: 'warning', LIFETIME: 'danger' }
 const UPGRADE_TYPE_LABEL = { pay: '付费升级', consumption: '消费升级', admin: '管理员操作', trial: '试用激活', lifetime: '终身会员授予' }
@@ -231,6 +228,7 @@ export default {
   name: 'MembershipCenter',
   data() {
     return {
+      activeTab: 'levels',
       // 统计卡片
       overviewLoading: false,
       statCards: [
@@ -286,17 +284,17 @@ export default {
           getMembershipStats(),
           this._request('/api/admin/membership/overview')
         ])
-        if (statsRes.code === 200 && statsRes.data) {
-          const s = statsRes.data
-          this.statCards[0].value = s.freeCount ?? 0
-          this.statCards[1].value = s.familyCount ?? 0
-          this.statCards[2].value = s.providerCount ?? 0
+        if (statsRes.data && statsRes.data.code === 200) {
+          const s = statsRes.data.data
+          this.statCards[0].value = s.freeCount || 0
+          this.statCards[1].value = s.familyCount || 0
+          this.statCards[2].value = s.providerCount || 0
         }
         if (overviewRes.data && overviewRes.data.code === 200) {
           const o = overviewRes.data.data || {}
-          this.statCards[3].value = o.newFamilyThisMonth ?? 0
-          this.statCards[4].value = o.lifetimeCount ?? 0
-          this.statCards[5].value = o.totalFamilyMemberships ?? 0
+          this.statCards[3].value = o.newFamilyThisMonth || 0
+          this.statCards[4].value = o.lifetimeCount || 0
+          this.statCards[5].value = o.totalFamilyMemberships || 0
         }
       } catch (e) {
         console.error('加载会员统计失败', e)
@@ -307,11 +305,13 @@ export default {
       this.levelsLoading = true
       try {
         const res = await listMembershipLevels()
-        if (res.code === 200) {
-          this.levels = Array.isArray(res.data) ? res.data.filter(function(l) { return l && typeof l === 'object' }) : []
+        if (res.data && res.data.code === 200) {
+          const list = res.data.data || []
+          this.levels = Array.isArray(list) ? list.filter(function(l) { return l && typeof l === 'object' }) : []
         }
       } catch (e) {
-        this.$message.error('加载等级配置失败')
+        console.error('加载等级配置失败', e)
+        this.$message && this.$message.error && this.$message.error('加载等级配置失败')
       } finally {
         this.levelsLoading = false
       }
@@ -422,13 +422,13 @@ export default {
         if (refStr === '0') payload.referrerId = ''
         else if (refStr !== '') payload.referrerId = refStr
         const res = await updateMembershipUser(payload)
-        if (res.code === 200) {
-          this.$message && this.$message.success && this.$message.success(res.data || '更新成功')
+        if (res.data && res.data.code === 200) {
+          this.$message && this.$message.success && this.$message.success(res.data.data || '更新成功')
           this.maintainVisible = false
           this.handleMaintainSearch()
           this.refreshAll()
         } else {
-          this.$message && this.$message.error && this.$message.error(res.message || '更新失败')
+          this.$message && this.$message.error && this.$message.error(res.data && res.data.message || '更新失败')
         }
       } catch (e) {
         this.$message && this.$message.error && this.$message.error('更新失败')