Переглянути джерело

feat(cfc): 综合更新 - 后端服务修复、前端页面优化、管理端功能扩展

- cfc-backend: DatabaseInitializer/AdminController/FamilyInvitationService/UserService 更新
- cfc-frontend: pages.json/ai.chat/mind.articles/user-edit/wealth 页面优化
- cfc-web: 新增 MembershipCenter.vue(295行)/PointsLog.vue(202行),全面更新各管理模块
- 67 files changed, 1946 insertions(+), 434 deletions(-)
User 2 місяців тому
батько
коміт
06056150ca
67 змінених файлів з 1946 додано та 434 видалено
  1. 3 0
      cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
  2. 60 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminController.java
  3. 2 2
      cfc-backend/src/main/java/com/etotem/cfc/service/FamilyInvitationService.java
  4. 37 1
      cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java
  5. 1 0
      cfc-backend/src/main/resources/schema.sql
  6. 6 0
      cfc-frontend/pages.json
  7. 45 5
      cfc-frontend/pages/ai/chat.vue
  8. 243 88
      cfc-frontend/pages/mind/articles.vue
  9. 1 1
      cfc-frontend/pages/mind/index.vue
  10. 1 19
      cfc-frontend/pages/profile/profile.vue
  11. 92 19
      cfc-frontend/pages/user-edit/user-edit.vue
  12. 1 6
      cfc-frontend/pages/wealth/index.vue
  13. 1 1
      cfc-web/.last_build_commit
  14. 16 0
      cfc-web/CHANGELOG.md
  15. 1 1
      cfc-web/package.json
  16. 12 0
      cfc-web/src/router/index.js
  17. 16 7
      cfc-web/src/views/Children.vue
  18. 18 23
      cfc-web/src/views/Families.vue
  19. 15 2
      cfc-web/src/views/GuideAudit.vue
  20. 20 5
      cfc-web/src/views/GuideFamilyTask.vue
  21. 25 10
      cfc-web/src/views/GuidePackages.vue
  22. 2 0
      cfc-web/src/views/Layout.vue
  23. 2 4
      cfc-web/src/views/OperationLogs.vue
  24. 17 4
      cfc-web/src/views/PackageAudit.vue
  25. 22 23
      cfc-web/src/views/Rewards.vue
  26. 18 23
      cfc-web/src/views/Users.vue
  27. 31 8
      cfc-web/src/views/Wishes.vue
  28. 29 15
      cfc-web/src/views/admin/Activities.vue
  29. 2 2
      cfc-web/src/views/admin/ActivityRegistrationReview.vue
  30. 1 1
      cfc-web/src/views/admin/ActivityReview.vue
  31. 17 3
      cfc-web/src/views/admin/ArticleCategory.vue
  32. 78 22
      cfc-web/src/views/admin/ArticleManage.vue
  33. 20 6
      cfc-web/src/views/admin/Assessment.vue
  34. 18 4
      cfc-web/src/views/admin/BaziConfigs.vue
  35. 18 4
      cfc-web/src/views/admin/BloodTypeConfigs.vue
  36. 17 3
      cfc-web/src/views/admin/CouponManagement.vue
  37. 24 7
      cfc-web/src/views/admin/EducationSystems.vue
  38. 3 3
      cfc-web/src/views/admin/EmotionAlertManage.vue
  39. 14 2
      cfc-web/src/views/admin/EnergyRuleManagement.vue
  40. 17 3
      cfc-web/src/views/admin/Foods.vue
  41. 17 3
      cfc-web/src/views/admin/GrowthTaskManagement.vue
  42. 17 3
      cfc-web/src/views/admin/KnowledgeTag.vue
  43. 295 0
      cfc-web/src/views/admin/MembershipCenter.vue
  44. 19 5
      cfc-web/src/views/admin/OrderManage.vue
  45. 18 7
      cfc-web/src/views/admin/PackageTemplates.vue
  46. 202 0
      cfc-web/src/views/admin/PointsLog.vue
  47. 21 6
      cfc-web/src/views/admin/ProductManage.vue
  48. 17 3
      cfc-web/src/views/admin/Recipes.vue
  49. 18 4
      cfc-web/src/views/admin/RelationshipTypes.vue
  50. 16 2
      cfc-web/src/views/admin/SeasonalFoods.vue
  51. 27 7
      cfc-web/src/views/admin/ServiceContents.vue
  52. 23 6
      cfc-web/src/views/admin/ServiceTypes.vue
  53. 17 3
      cfc-web/src/views/admin/SysConfig.vue
  54. 21 7
      cfc-web/src/views/admin/VendorReview.vue
  55. 18 4
      cfc-web/src/views/admin/VirtualTeamDetail.vue
  56. 18 9
      cfc-web/src/views/admin/VirtualTeamList.vue
  57. 16 3
      cfc-web/src/views/admin/WithdrawAudit.vue
  58. 18 4
      cfc-web/src/views/admin/ZodiacConfigs.vue
  59. 18 4
      cfc-web/src/views/admin/data-source-config.vue
  60. 18 4
      cfc-web/src/views/admin/dimension-config.vue
  61. 19 3
      cfc-web/src/views/nutritionist/NutritionMappings.vue
  62. 21 2
      cfc-web/src/views/teacher/FamilyList.vue
  63. 20 5
      cfc-web/src/views/teacher/TeacherAssessment.vue
  64. 23 3
      cfc-web/src/views/teacher/TeacherFamilies.vue
  65. 22 3
      cfc-web/src/views/teacher/TeacherOrders.vue
  66. 19 4
      cfc-web/src/views/teacher/TeacherPackages.vue
  67. 22 3
      cfc-web/src/views/teacher/TeacherTeam.vue

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -4217,6 +4217,9 @@ try {
         } catch (Exception e) {
             log.warn("添加supplier_admin到users.role ENUM失败: {}", e.getMessage());
         }
+
+        // 迁移40: users表添加mascot字段(AI助手形象: xibao浠宝/fubao福宝)
+        ensureColumn("users", "mascot", "VARCHAR(20) DEFAULT NULL COMMENT 'AI助手形象: xibao(浠宝)/fubao(福宝)'");
     }
 
     private void insertSysConfigSeed(String key, String value, String desc) {

+ 60 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminController.java

@@ -53,6 +53,15 @@ public class AdminController {
     @Resource
     private GuidePackageMapper guidePackageMapper;
 
+    @Resource
+    private MembershipLevelMapper membershipLevelMapper;
+
+    @Resource
+    private FamilyMembershipMapper familyMembershipMapper;
+
+    @Resource
+    private MemberUpgradeRecordMapper memberUpgradeRecordMapper;
+
     // 用户管理
     @PostMapping("/users")
     public Result<Page<User>> getUsers(@RequestBody Map<String, Object> params) {
@@ -826,4 +835,55 @@ public class AdminController {
         String status = (String) params.get("status");
         return productOrderService.adminList(status);
     }
+
+    // ========== 会员中心管理 ==========
+
+    @PostMapping("/membership/levels")
+    public Result<java.util.List<MembershipLevel>> getMembershipLevels() {
+        java.util.List<MembershipLevel> levels = membershipLevelMapper.selectList(null);
+        return Result.success(levels);
+    }
+
+    @PostMapping("/membership/overview")
+    public Result<Map<String, Object>> getMembershipOverview() {
+        Map<String, Object> overview = new HashMap<>();
+
+        // 各等级会员数量
+        Long freeCount = userMapper.selectCount(new LambdaQueryWrapper<User>()
+                .and(w -> w.eq(User::getMemberLevel, "FREE").or().isNull(User::getMemberLevel)));
+        Long familyCount = userMapper.selectCount(new LambdaQueryWrapper<User>().eq(User::getMemberLevel, "FAMILY"));
+        Long providerCount = userMapper.selectCount(new LambdaQueryWrapper<User>().eq(User::getMemberLevel, "PROVIDER"));
+        overview.put("freeCount", freeCount);
+        overview.put("familyCount", familyCount);
+        overview.put("providerCount", providerCount);
+
+        // 本月新增会员
+        java.util.Calendar cal = java.util.Calendar.getInstance();
+        cal.set(java.util.Calendar.DAY_OF_MONTH, 1);
+        cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
+        cal.set(java.util.Calendar.MINUTE, 0);
+        cal.set(java.util.Calendar.SECOND, 0);
+        Date monthStart = cal.getTime();
+        Long newFamilyThisMonth = familyMembershipMapper.selectCount(new LambdaQueryWrapper<FamilyMembership>()
+                .ge(FamilyMembership::getCreatedAt, monthStart));
+        overview.put("newFamilyThisMonth", newFamilyThisMonth);
+
+        // 总家庭会员数
+        Long totalFamilyMemberships = familyMembershipMapper.selectCount(null);
+        overview.put("totalFamilyMemberships", totalFamilyMemberships);
+
+        return Result.success(overview);
+    }
+
+    @PostMapping("/membership/upgrade-records")
+    public Result<Page<MemberUpgradeRecord>> getUpgradeRecords(@RequestBody Map<String, Object> params) {
+        int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
+        int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 10;
+
+        Page<MemberUpgradeRecord> pageParam = new Page<>(page, size);
+        Page<MemberUpgradeRecord> result = memberUpgradeRecordMapper.selectPage(pageParam,
+                new LambdaQueryWrapper<MemberUpgradeRecord>()
+                        .orderByDesc(MemberUpgradeRecord::getCreatedAt));
+        return Result.success(result);
+    }
 }

+ 2 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/FamilyInvitationService.java

@@ -145,7 +145,7 @@ public class FamilyInvitationService {
             throw new RuntimeException("用户不存在");
         }
         Long oldFamilyId = user.getFamilyId();
-        if (oldFamilyId == null) {
+        if (oldFamilyId == null || oldFamilyId == 0L) {
             throw new RuntimeException("用户尚未加入任何家庭");
         }
 
@@ -196,7 +196,7 @@ public class FamilyInvitationService {
         }
 
         FamilyStatusDTO dto = new FamilyStatusDTO();
-        if (user.getFamilyId() == null) {
+        if (user.getFamilyId() == null || user.getFamilyId() == 0L) {
             dto.setInFamily(false);
             return dto;
         }

+ 37 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java

@@ -87,6 +87,9 @@ private FamilyInvitationService familyInvitationService;
             userMapper.insert(user);
             try { onboardingService.initForUser(user.getId()); onboardingService.completeTask(user.getId(), "REGISTER"); } catch (Exception e) { /* onboarding init failure should not block login */ }
         } else {
+            // 懒创建家庭(admin创建的用户可能 familyId=0L)
+            ensureUserHasFamily(user);
+
             // 管理员不能登录小程序
             if ("admin".equals(user.getRole())) {
                 throw new RuntimeException("该账号不允许登录小程序");
@@ -165,6 +168,7 @@ private FamilyInvitationService familyInvitationService;
     if (dto.getAvatar() != null && !dto.getAvatar().isEmpty()) {
       user.setAvatar(dto.getAvatar());
     }
+    ensureUserHasFamily(user);  // P3: admin创建的用户可能 familyId=0L,懒创建家庭
     user.setUpdatedAt(new Date());
     userMapper.updateById(user);
   }
@@ -249,6 +253,9 @@ private FamilyInvitationService familyInvitationService;
             }
         }
 
+        // P3: admin创建的用户可能 familyId=0L,懒创建家庭
+        ensureUserHasFamily(user);
+
         // 管理员不能登录小程序
         if ("admin".equals(user.getRole())) {
             throw new RuntimeException("该账号不允许登录小程序");
@@ -296,7 +303,10 @@ private FamilyInvitationService familyInvitationService;
             throw new RuntimeException("该账号不允许登录小程序");
         }
 
-        // 3. 生成token
+        // 3. 懒创建家庭(admin创建的用户可能 familyId=0L)
+        ensureUserHasFamily(user);
+
+        // 4. 生成token
         String token = jwtConfig.generateToken(user.getId(), user.getRole());
 
         LoginResultDTO result = new LoginResultDTO();
@@ -329,6 +339,9 @@ private FamilyInvitationService familyInvitationService;
             return null;
         }
 
+        // 懒创建家庭(admin创建的用户可能 familyId=0L)
+        ensureUserHasFamily(user);
+
         String token = jwtConfig.generateToken(user.getId(), user.getRole());
 
         LoginResultDTO result = new LoginResultDTO();
@@ -897,6 +910,29 @@ private FamilyInvitationService familyInvitationService;
         return UUID.randomUUID().toString().substring(0, 8).toUpperCase();
     }
 
+    /**
+     * 确保用户有有效的家庭(懒创建)。
+     * admin创建的用户可能 familyId=0L,需要在此自动创建家庭。
+     * 如果创建了家庭,会持久化 user.familyId 到数据库。
+     */
+    private void ensureUserHasFamily(User user) {
+        if (user.getFamilyId() == null || user.getFamilyId() == 0L) {
+            Family family = new Family();
+            family.setName("我的家庭");
+            family.setInviteCode(generateInviteCode());
+            family.setCreatedAt(new Date());
+            family.setUpdatedAt(new Date());
+            familyMapper.insert(family);
+
+            family.setCreatorId(user.getId());
+            familyMapper.updateById(family);
+
+            user.setFamilyId(family.getId());
+            user.setUpdatedAt(new Date());
+            userMapper.updateById(user);
+        }
+    }
+
     private ChildInfoDTO toChildInfoDTO(FamilyMember child) {
         ChildInfoDTO dto = new ChildInfoDTO();
         dto.setId(child.getId());

+ 1 - 0
cfc-backend/src/main/resources/schema.sql

@@ -27,6 +27,7 @@ CREATE TABLE IF NOT EXISTS users (
     birthday DATE,
     gender ENUM('male','female'),
     birth_hour VARCHAR(10) COMMENT '出生时辰: 子丑寅卯辰巳午未申酉戌亥',
+    mascot VARCHAR(20) DEFAULT NULL COMMENT 'AI助手形象: xibao(浠宝)/fubao(福宝)',
     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     INDEX idx_openid (openid),

+ 6 - 0
cfc-frontend/pages.json

@@ -122,6 +122,12 @@
       "style": {
         "navigationBarTitleText": "我的优惠券"
       }
+    },
+    {
+      "path": "pages/mind/articles",
+      "style": {
+        "navigationBarTitleText": "心智成长"
+      }
     }
   ],
   "subPackages": [

+ 45 - 5
cfc-frontend/pages/ai/chat.vue

@@ -1,7 +1,7 @@
 <template>
   <view class="chat-container">
     <!-- 导航栏 -->
-    <view class="chat-nav">
+    <view class="chat-nav" :class="'mascot-' + mascotCode">
       <view class="nav-back" @click="goBack">
         <text class="nav-back-icon">‹</text>
       </view>
@@ -93,7 +93,7 @@
 
         <!-- 加载中 -->
         <view class="message message-ai" v-if="sending">
-          <view class="message-avatar">🤖</view>
+          <view class="message-avatar">{{ mascotIcon || '🤖' }}</view>
           <view class="message-bubble ai-bubble typing">
             <text class="typing-dot">.</text>
             <text class="typing-dot">.</text>
@@ -374,6 +374,22 @@ export default {
   z-index: 100;
 }
 
+.chat-nav.mascot-xibao {
+  background: linear-gradient(135deg, #FFF7ED 0%, #FFE4CC 100%);
+  border-bottom-color: #FF8C42;
+}
+.chat-nav.mascot-xibao .nav-title {
+  color: #D97706;
+}
+
+.chat-nav.mascot-fubao {
+  background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%);
+  border-bottom-color: #F59E0B;
+}
+.chat-nav.mascot-fubao .nav-title {
+  color: #B45309;
+}
+
 .nav-back {
   width: 60rpx;
   height: 60rpx;
@@ -504,6 +520,19 @@ export default {
 .welcome-icon {
   font-size: 80rpx;
   margin-bottom: 20rpx;
+  transition: transform 0.3s;
+}
+
+.mascot-xibao .welcome-icon {
+  animation: welcomeFloat 3s ease-in-out infinite;
+}
+.mascot-fubao .welcome-icon {
+  animation: welcomeFloat 3s ease-in-out infinite 0.5s;
+}
+
+@keyframes welcomeFloat {
+  0%, 100% { transform: translateY(0); }
+  50% { transform: translateY(-10rpx); }
 }
 
 .welcome-title {
@@ -563,14 +592,25 @@ export default {
 }
 
 .message-avatar {
-  width: 48rpx;
-  height: 48rpx;
-  font-size: 32rpx;
+  width: 56rpx;
+  height: 56rpx;
+  font-size: 36rpx;
   margin-right: 12rpx;
   flex-shrink: 0;
   display: flex;
   align-items: center;
   justify-content: center;
+  border-radius: 50%;
+  transition: transform 0.2s;
+}
+
+.mascot-xibao .message-avatar {
+  background: linear-gradient(135deg, #FFF7ED 0%, #FFE4CC 100%);
+  border: 2rpx solid #FF8C42;
+}
+.mascot-fubao .message-avatar {
+  background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%);
+  border: 2rpx solid #F59E0B;
 }
 
 .message-bubble {

+ 243 - 88
cfc-frontend/pages/mind/articles.vue

@@ -1,99 +1,132 @@
 <template>
   <view class="container">
-    <!-- 品牌头部 -->
-    <PageBanner :theme="bannerTheme"
-      :tagline="bannerTagline"
-      :quote="bannerQuote" />
+    <scroll-view scroll-y class="page-scroll" @scrolltolower="onLoadMore">
+      <!-- 品牌头部 -->
+      <PageBanner :theme="bannerTheme"
+        :tagline="bannerTagline"
+        :quote="bannerQuote" />
 
-    <!-- 维度筛选 -->
-    <view class="filter-section">
-      <scroll-view scroll-x enable-flex show-scrollbar="false" class="dimension-scroll">
-        <view class="filter-chips">
-          <view
-            v-for="dim in dimensionList"
-            :key="dim.code"
-            :class="['filter-chip', currentDimension === dim.code ? 'active' : '']"
-            :style="currentDimension === dim.code ? { background: dim.color, color: '#fff' } : {}"
-            @click="onDimensionChange(dim.code)"
-          >
-            {{ dim.label }}
-          </view>
+      <!-- 推荐阅读 -->
+      <view class="featured-section" v-if="featuredArticles.length > 0">
+        <view class="section-header">
+          <text class="section-title">🔥 推荐阅读</text>
+          <text class="section-subtitle">精选成长好文</text>
         </view>
-      </scroll-view>
-    </view>
-
-    <!-- 分类筛选 -->
-    <view class="filter-section" v-if="categoryList.length > 1">
-      <scroll-view scroll-x enable-flex show-scrollbar="false" class="category-scroll">
-        <view class="filter-chips">
+        <view class="featured-grid">
           <view
-            v-for="cat in categoryList"
-            :key="cat.value"
-            :class="['filter-chip', currentCategory === cat.value ? 'active' : '']"
-            @click="onCategoryChange(cat.value)"
+            v-for="article in featuredArticles"
+            :key="article.id"
+            class="featured-card"
+            @click="goDetail(article.id)"
           >
-            {{ cat.label }}
+            <view class="featured-category" :style="{ background: article.categoryColor }">
+              <text class="featured-category-text">{{ article.category }}</text>
+            </view>
+            <text class="featured-title">{{ article.title }}</text>
+            <text class="featured-summary">{{ article.summary }}</text>
+            <view class="featured-meta">
+              <text class="featured-views">👁 {{ article.views }}</text>
+              <text class="featured-date">{{ article.date }}</text>
+            </view>
           </view>
         </view>
-      </scroll-view>
-    </view>
+      </view>
 
-    <!-- 知识列表 -->
-    <scroll-view scroll-y class="article-list" @scrolltolower="onLoadMore">
-      <view v-if="loading && articles.length === 0" class="loading-wrap">
-        <text class="loading-text">加载中...</text>
+      <!-- 维度筛选 -->
+      <view class="filter-section">
+        <scroll-view scroll-x enable-flex show-scrollbar="false" class="dimension-scroll">
+          <view class="filter-chips">
+            <view
+              v-for="dim in dimensionList"
+              :key="dim.code"
+              :class="['filter-chip', currentDimension === dim.code ? 'active' : '']"
+              :style="dim.activeStyle"
+              @click="onDimensionChange(dim.code)"
+            >
+              {{ dim.label }}
+            </view>
+          </view>
+        </scroll-view>
       </view>
-      <view v-else-if="articles.length === 0" class="empty-wrap">
-        <text class="empty-icon">📚</text>
-        <text class="empty-text">暂无相关知识</text>
+
+      <!-- 分类筛选 -->
+      <view class="filter-section" v-if="categoryList.length > 1">
+        <scroll-view scroll-x enable-flex show-scrollbar="false" class="category-scroll">
+          <view class="filter-chips">
+            <view
+              v-for="cat in categoryList"
+              :key="cat.value"
+              :class="['filter-chip', currentCategory === cat.value ? 'active' : '']"
+              @click="onCategoryChange(cat.value)"
+            >
+              {{ cat.label }}
+            </view>
+          </view>
+        </scroll-view>
       </view>
-      <view v-else class="article-grid">
-        <view
-          v-for="item in articles"
-          :key="item.id"
-          class="article-card"
-          @click="goDetail(item.id)"
-        >
-          <image
-            class="article-cover"
-            :src="item.coverImage || '/static/default-article.png'"
-            mode="aspectFill"
-          />
-          <view class="article-body">
-            <view class="article-meta">
-              <text class="article-category">{{ item.categoryName || '知识' }}</text>
-              <view class="article-type-tags">
-                <text v-if="item.contentType" class="type-tag" :class="'type-' + item.contentType">{{ contentTypeLabel(item.contentType) }}</text>
-                <text v-if="item.difficultyLevel > 0" class="difficulty-tag">{{ '★'.repeat(item.difficultyLevel) }}</text>
+
+      <!-- 全部文章 -->
+      <view class="all-articles-section">
+        <view class="section-header">
+          <text class="section-title">全部文章</text>
+          <text class="section-subtitle" v-if="total > 0">共 {{ total }} 篇</text>
+        </view>
+
+        <view v-if="loading && articles.length === 0" class="loading-wrap">
+          <text class="loading-text">加载中...</text>
+        </view>
+        <view v-else-if="articles.length === 0" class="empty-wrap">
+          <text class="empty-icon">📚</text>
+          <text class="empty-text">暂无相关知识</text>
+        </view>
+        <view v-else class="article-grid">
+          <view
+            v-for="item in articles"
+            :key="item.id"
+            class="article-card"
+            @click="goDetail(item.id)"
+          >
+            <image
+              class="article-cover"
+              :src="item.coverImage || '/static/default-article.png'"
+              mode="aspectFill"
+            />
+            <view class="article-body">
+              <view class="article-meta">
+                <text class="article-category">{{ item.categoryName || '知识' }}</text>
+                <view class="article-type-tags">
+                  <text v-if="item.contentType" class="type-tag" :class="'type-' + item.contentType">{{ contentTypeLabel(item.contentType) }}</text>
+                  <text v-if="item.difficultyLevel > 0" class="difficulty-tag">{{ '★'.repeat(item.difficultyLevel) }}</text>
+                </view>
+              </view>
+              <text class="article-title">{{ item.title }}</text>
+              <text class="article-summary">{{ item.summary || item.content }}</text>
+              <view class="article-footer">
+                <text class="article-author">{{ item.author || '浠艾福' }}</text>
+                <text class="article-read-count">阅读 {{ item.readCount || 0 }}</text>
               </view>
-            </view>
-            <text class="article-title">{{ item.title }}</text>
-            <text class="article-summary">{{ item.summary || item.content }}</text>
-            <view class="article-footer">
-              <text class="article-author">{{ item.author || '浠艾福' }}</text>
-              <text class="article-read-count">阅读 {{ item.readCount || 0 }}</text>
             </view>
           </view>
         </view>
+        <view v-if="loadingMore" class="loading-more">
+          <text class="loading-text">加载中...</text>
+        </view>
+        <view v-if="noMore && articles.length > 0" class="no-more">
+          <text class="no-more-text">— 没有更多了 —</text>
+        </view>
+        <view v-if="!isLoggedIn" class="login-hint-bar">
+          <text class="login-hint-text">🔒 登录后解锁全部内容</text>
+        </view>
       </view>
-      <view v-if="loadingMore" class="loading-more">
-        <text class="loading-text">加载中...</text>
-      </view>
-      <view v-if="noMore && articles.length > 0" class="no-more">
-        <text class="no-more-text">— 没有更多了 —</text>
-      </view>
-      <view v-if="!isLoggedIn" class="login-hint-bar">
-        <text class="login-hint-text">🔒 登录后解锁全部内容</text>
-      </view>
+
       <view class="bottom-spacer"></view>
     </scroll-view>
-
   </view>
 </template>
 
 <script>
 import PageBanner from '../../components/PageBanner.vue'
-import { getArticleCategories, getArticleList } from '../../utils/api.js'
+import { getArticleCategories, getArticleList, getFeaturedArticles } from '../../utils/api.js'
 
 var dimensionConfig = {
   all:   { label: '全部', color: '#64748B', theme: 'mind-wisdom', tagline: '探索知识,滋养成长', quote: '学而时习之,不亦说乎' },
@@ -108,14 +141,12 @@ export default {
   components: { PageBanner },
   data() {
     return {
-      dimensionList: Object.keys(dimensionConfig).map(function(k) {
-        return { code: k, label: dimensionConfig[k].label, color: dimensionConfig[k].color }
-      }),
       currentDimension: 'all',
       categoryList: [{ id: '', name: '全部' }],
       categoryMap: {},
       currentCategory: '',
       articles: [],
+      featuredArticles: [],
       page: 1,
       size: 10,
       total: 0,
@@ -126,6 +157,17 @@ export default {
     }
   },
   computed: {
+    dimensionList: function() {
+      var self = this
+      return Object.keys(dimensionConfig).map(function(k) {
+        var cfg = dimensionConfig[k]
+        var activeStyle = ''
+        if (k === self.currentDimension) {
+          activeStyle = 'background:' + cfg.color + ';color:#fff;'
+        }
+        return { code: k, label: cfg.label, color: cfg.color, activeStyle: activeStyle }
+      })
+    },
     bannerTheme: function() {
       return dimensionConfig[this.currentDimension] ? dimensionConfig[this.currentDimension].theme : 'mind-wisdom'
     },
@@ -138,6 +180,7 @@ export default {
   },
   onLoad: function(options) {
     this.isLoggedIn = !!uni.getStorageSync('token')
+    this.loadFeaturedArticles()
     if (options && options.dimension && dimensionConfig[options.dimension]) {
       this.currentDimension = options.dimension
     }
@@ -147,6 +190,32 @@ export default {
     this.isLoggedIn = !!uni.getStorageSync('token')
   },
   methods: {
+    loadFeaturedArticles: function() {
+      var self = this
+      getFeaturedArticles({ size: 5 }).then(function(res) {
+        if (res.code === 200 && res.data) {
+          var gradientColors = [
+            'linear-gradient(135deg, #6366F1, #818CF8)',
+            'linear-gradient(135deg, #8B5CF6, #A78BFA)',
+            'linear-gradient(135deg, #5B9BD5, #8FC5E8)',
+            'linear-gradient(135deg, #10B981, #34D399)',
+            'linear-gradient(135deg, #F97316, #FB923C)'
+          ]
+          var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
+          self.featuredArticles = list.map(function(item, index) {
+            return {
+              id: item.id,
+              category: item.categoryName || '成长文章',
+              categoryColor: gradientColors[index % gradientColors.length],
+              title: item.title || '',
+              summary: item.summary || '',
+              views: item.viewCount ? String(item.viewCount) : '0',
+              date: item.publishedAt ? item.publishedAt.slice(0, 10) : ''
+            }
+          })
+        }
+      }).catch(function() {})
+    },
     contentTypeLabel: function(type) {
       var labels = { article: '文章', knowledge: '知识点', course: '课程', tip: '贴士' }
       return labels[type] || type
@@ -239,7 +308,7 @@ export default {
     },
     goDetail(id) {
       if (this.isLoggedIn) {
-        uni.navigateTo({ url: '/pages/mind/article-detail?id=' + id })
+        uni.navigateTo({ url: '/pages/mind-detail/article-detail?id=' + id })
       } else {
         uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/mind/articles') })
       }
@@ -252,10 +321,106 @@ export default {
 .container {
   display: flex;
   flex-direction: column;
-  min-height: 100vh;
+  height: 100vh;
   background: #f5f7fa;
 }
 
+/* 页面滚动容器 */
+.page-scroll {
+  flex: 1;
+  height: 100vh;
+}
+
+/* 区块通用标题 */
+.section-header {
+  display: flex;
+  align-items: baseline;
+  padding: 0 4rpx;
+  margin-bottom: 20rpx;
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+.section-subtitle {
+  font-size: 24rpx;
+  color: #999;
+  margin-left: 12rpx;
+}
+
+/* ===== 推荐阅读 ===== */
+.featured-section {
+  margin: 24rpx 24rpx 16rpx;
+  padding: 0 4rpx;
+}
+.featured-grid {
+  display: flex;
+  flex-direction: column;
+}
+.featured-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 28rpx 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+  margin-bottom: 20rpx;
+}
+.featured-card:active {
+  opacity: 0.8;
+}
+.featured-category {
+  display: inline-block;
+  padding: 4rpx 18rpx;
+  border-radius: 20rpx;
+  margin-bottom: 14rpx;
+}
+.featured-category-text {
+  font-size: 20rpx;
+  color: #fff;
+  font-weight: 500;
+}
+.featured-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 10rpx;
+  line-height: 1.4;
+}
+.featured-summary {
+  font-size: 24rpx;
+  color: #888;
+  line-height: 1.5;
+  display: block;
+  margin-bottom: 16rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+.featured-meta {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+.featured-views {
+  font-size: 22rpx;
+  color: #bbb;
+  margin-right: 20rpx;
+}
+.featured-date {
+  font-size: 22rpx;
+  color: #bbb;
+  margin-left: auto;
+}
+
+/* ===== 全部文章 ===== */
+.all-articles-section {
+  margin: 8rpx 24rpx 0;
+  padding: 0 4rpx;
+}
+
 /* 筛选条 */
 .filter-section {
   background: #fff;
@@ -333,11 +498,6 @@ export default {
   color: #f59e0b;
 }
 
-/* 文章列表 */
-.article-list {
-  flex: 1;
-  height: calc(100vh - 220rpx);
-}
 .loading-wrap,
 .empty-wrap {
   display: flex;
@@ -375,11 +535,6 @@ export default {
 .article-body {
   padding: 20rpx;
 }
-.article-meta {
-  display: flex;
-  align-items: center;
-  margin-bottom: 10rpx;
-}
 .article-category {
   font-size: 20rpx;
   color: #5B9BD5;

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

@@ -728,7 +728,7 @@ export default {
       uni.navigateTo({ url: '/pages/mind-detail/emotion-report?childId=' + childId })
     },
     goArticles: function() {
-      uni.navigateTo({ url: '/pages/mind-detail/articles' })
+      uni.navigateTo({ url: '/pages/mind/articles' })
     },
     onFuncClick: function(item) {
       if (item.needLogin && !this.isLoggedIn) {

+ 1 - 19
cfc-frontend/pages/profile/profile.vue

@@ -17,25 +17,7 @@
       <PageBanner theme="wealth" tagline="丰盈内心,富足生活,厚德载物" quote="" />
 
       <!-- 用户卡片 + 富沛能量 + 切换 -->
-      <ProfileHeader />
-
-      <!-- AI小助手形象选择 -->
-      <view class="mascot-selector">
-        <view class="selector-header">
-          <text class="selector-title">AI小助手</text>
-          <text class="selector-subtitle">{{ currentMascotName }}为你服务</text>
-        </view>
-        <view class="mascot-cards">
-          <view class="mascot-card" :class="{active: selectedMascot === 'xibao'}" @click="changeMascot('xibao')">
-            <view class="card-icon xibao">浠</view>
-            <text class="card-name">浠宝</text>
-          </view>
-          <view class="mascot-card" :class="{active: selectedMascot === 'fubao'}" @click="changeMascot('fubao')">
-            <view class="card-icon fubao">福</view>
-            <text class="card-name">福宝</text>
-          </view>
-        </view>
-      </view>
+      <ProfileHeader @avatar-click="goToEditInfo" />
 
       <!-- 行动数据 + 财富数据 -->
       <ProfileStats />

+ 92 - 19
cfc-frontend/pages/user-edit/user-edit.vue

@@ -136,14 +136,6 @@
 				</picker>
 			</view>
 
-			<!-- 家长身份 -->
-			<view class="form-item" v-if="form.role === 'parent'">
-				<text class="label">家长身份</text>
-				<picker mode="selector" :range="familyRoleOptions" :value="familyRoleIndex" @change="onFamilyRoleChange">
-					<view class="picker-input">{{ form.familyRole || '请选择家长身份' }}</view>
-				</picker>
-			</view>
-
 			<!-- 成长规划师额外字段 -->
 			<view class="form-section-title" v-if="form.role === 'teacher'">成长规划师信息</view>
 			
@@ -178,6 +170,27 @@
 			</view>
 		</view>
 
+		<!-- AI助手选择(新用户注册或编辑资料时显示) -->
+		<view class="family-section" v-if="isEdit">
+			<view class="section-title">🤖 选择AI助手</view>
+			<view class="mascot-cards">
+				<view class="mascot-card" :class="{ active: form.mascot === 'xibao' }" @click="selectMascot('xibao')">
+					<view class="mascot-card-icon xibao-bg">🌟</view>
+					<view class="mascot-card-info">
+						<text class="mascot-card-name">浠宝</text>
+						<text class="mascot-card-desc">温柔活泼的阳光女孩</text>
+					</view>
+				</view>
+				<view class="mascot-card" :class="{ active: form.mascot === 'fubao' }" @click="selectMascot('fubao')">
+					<view class="mascot-card-icon fubao-bg">💪</view>
+					<view class="mascot-card-info">
+						<text class="mascot-card-name">福宝</text>
+						<text class="mascot-card-desc">开朗自信的阳光男孩</text>
+					</view>
+				</view>
+			</view>
+		</view>
+
 		<!-- 家庭绑定(新用户注册时显示) -->
 		<view class="family-section" v-if="isEdit && form.role !== 'teacher' && isNewUserFlow">
 			<view class="section-title">家庭绑定</view>
@@ -240,6 +253,7 @@ export default {
 				birthday: '',
 				birthHour: '',
 				inviteCode: '',
+				mascot: '',
 				phone: '',
 				teacherNo: '',
 				teacherPhoto: '',
@@ -247,8 +261,7 @@ export default {
 				ethnicity: '',
 				bloodType: '',
 				highestEducation: '',
-				maritalStatus: '',
-				familyRole: ''
+				maritalStatus: ''
 			},
 			ethnicityOptions: ['汉族','蒙古族','回族','藏族','维吾尔族','苗族','彝族','壮族','布依族','朝鲜族','满族','侗族','瑶族','白族','土家族','哈尼族','哈萨克族','傣族','黎族','其他'],
 			bloodTypeOptions: ['A','B','AB','O','未知'],
@@ -256,7 +269,6 @@ export default {
 			birthHourValues: ['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥'],
 			educationOptions: ['小学','初中','高中','中专','大专','本科','硕士','博士'],
 			maritalOptions: ['未婚','已婚','离异','丧偶'],
-			familyRoleOptions: ['爸爸','妈妈','爷爷','奶奶','姥爷','姥姥','其他'],
 			loading: false,
 			userId: '',
 			token: ''
@@ -278,9 +290,6 @@ export default {
 		maritalIndex() {
 			return this.maritalOptions.indexOf(this.form.maritalStatus)
 		},
-		familyRoleIndex() {
-			return this.familyRoleOptions.indexOf(this.form.familyRole)
-		}
 	},
 	onLoad(options) {
 		if (options.token && options.userId) {
@@ -382,8 +391,8 @@ export default {
 		onMaritalChange(e) {
 			this.form.maritalStatus = this.maritalOptions[e.detail.value]
 		},
-		onFamilyRoleChange(e) {
-			this.form.familyRole = this.familyRoleOptions[e.detail.value]
+		selectMascot(code) {
+			this.form.mascot = code
 		},
 		// 加载已有用户信息
 		async loadUserInfo() {
@@ -404,7 +413,7 @@ export default {
 					this.form.bloodType = userData.bloodType || ''
 					this.form.highestEducation = userData.highestEducation || ''
 					this.form.maritalStatus = userData.maritalStatus || ''
-				this.form.familyRole = userData.familyRole || ''
+					this.form.mascot = userData.mascot || ''
 				}
 			} catch (e) {
 				console.error('加载用户信息失败', e)
@@ -426,11 +435,11 @@ export default {
 					idCard: this.form.idCard,
 					birthday: this.form.birthday,
 					avatar: this.form.avatar,
+					mascot: this.form.mascot,
 					ethnicity: this.form.ethnicity,
 					bloodType: this.form.bloodType,
 					highestEducation: this.form.highestEducation,
-					maritalStatus: this.form.maritalStatus,
-					familyRole: this.form.familyRole
+					maritalStatus: this.form.maritalStatus
 				})
 				uni.showToast({ title: '保存成功', icon: 'success' })
 				setTimeout(() => {
@@ -509,6 +518,7 @@ export default {
 						nickname: this.form.nickname,
 						avatar: this.form.avatar,
 						role: this.form.role,
+						mascot: this.form.mascot,
 						birthday: this.form.birthday,
 						gender: this.form.gender,
 						inviteCode: this.form.inviteCode
@@ -551,6 +561,7 @@ export default {
 						nickname: this.form.nickname,
 						avatar: this.form.avatar,
 						role: this.form.role,
+						mascot: this.form.mascot,
 						birthday: this.form.birthday,
 						gender: this.form.gender
 					})
@@ -579,6 +590,16 @@ export default {
 			this.navigateToHome()
 		},
 		navigateToHome() {
+			// 保存 mascot 到 userInfo 存储,供 AI 聊天页读取
+			if (this.form.mascot) {
+				try {
+					const userInfo = uni.getStorageSync('userInfo') || {}
+					userInfo.mascot = this.form.mascot
+					uni.setStorageSync('userInfo', userInfo)
+				} catch (e) {
+					console.error('保存 mascot 失败', e)
+				}
+			}
 			const role = this.form.role || 'parent'
 			if (role === 'parent') {
 				uni.switchTab({ url: '/pages/index/parent-index' })
@@ -771,6 +792,58 @@ export default {
 	margin-top: 8rpx;
 }
 
+/* AI助手选择 */
+.mascot-cards {
+	display: flex;
+	gap: 20rpx;
+	margin-top: 16rpx;
+}
+.mascot-card {
+	flex: 1;
+	background: #f8f8f8;
+	border-radius: 16rpx;
+	padding: 24rpx;
+	display: flex;
+	align-items: center;
+	gap: 16rpx;
+	border: 2rpx solid transparent;
+	transition: all 0.3s;
+}
+.mascot-card.active {
+	border-color: #4CAF50;
+	background: #f0fdf4;
+}
+.mascot-card-icon {
+	width: 72rpx;
+	height: 72rpx;
+	border-radius: 50%;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	font-size: 36rpx;
+	flex-shrink: 0;
+}
+.xibao-bg {
+	background: linear-gradient(135deg, #FFF0DB, #FFD6A5);
+}
+.fubao-bg {
+	background: linear-gradient(135deg, #FFF8E1, #FCD34D);
+}
+.mascot-card-info {
+	display: flex;
+	flex-direction: column;
+	gap: 4rpx;
+}
+.mascot-card-name {
+	font-size: 30rpx;
+	font-weight: bold;
+	color: #333;
+}
+.mascot-card-desc {
+	font-size: 22rpx;
+	color: #999;
+}
+
 /* 家庭绑定 */
 .family-section {
 	background: #fff;

+ 1 - 6
cfc-frontend/pages/wealth/index.vue

@@ -21,7 +21,7 @@
     </view>
     
     <view class="user-card">
-      <view class="avatar">👤</view>
+      <view class="avatar" @click="goToEditInfo">👤</view>
       <view class="user-info">
         <view class="nickname">{{ nickname || '未设置昵称' }}</view>
         <view class="role">{{ role === 'parent' ? '家长模式' : (role === 'child' ? '孩子模式' : '成长规划师模式') }}</view>
@@ -129,11 +129,6 @@
     </view>
 
     <view class="menu-list">
-      <!-- 修改个人资料:所有角色可见 -->
-      <view class="menu-item" @click="goToEditInfo">
-        <text>📝 修改个人资料</text>
-        <text class="arrow">›</text>
-      </view>
       <!-- 我的孩子:仅家长可见 -->
       <view class="menu-item" v-if="role === 'parent'" @click="goToChildren">
         <text>👶 我的孩子</text>

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-b36b888ce1a3e1d12eb0320e68a5f95c871a3c32
+51b2a65b0d3979314d53701ec5d07a690091605d

+ 16 - 0
cfc-web/CHANGELOG.md

@@ -3,6 +3,22 @@
 此文件记录所有构建版本的变更。
 ## v$(node (2026-07-07)
 
+### Bug 修复
+- fix FamilyMemberService.addMember DTO compatibility for generationLevel/peerType
+- map generationLevel/peerType to relationshipTypeKey for RelationshipType lookup, set generation/isSpouse on FamilyMember entity.
+- resolve ProductEdit.vue rebase conflict + cleanup supply entities
+
+### 其他
+- 
+- Merge remote changes and resolve conflicts
+- 删除 'RTK.md'
+
+### 测试
+- unit tests for SupplySystem/FoodRecommend/ArticleWorkflow + v11 API test updates + E2E workflow scenarios
+
+
+## v$(node (2026-07-07)
+
 ### Bug 修复
 - migration 36-37 — family_members generation/is_spouse columns
 - admin edit pages and CHANGELOG updates

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.9",
+  "version": "1.0.10",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 12 - 0
cfc-web/src/router/index.js

@@ -54,6 +54,18 @@ const routes = [
         component: () => import('@/views/Points.vue'),
         meta: { title: '积分管理', perm: 'family:points' }
       },
+      {
+        path: 'points-log',
+        name: 'PointsLog',
+        component: () => import('@/views/admin/PointsLog.vue'),
+        meta: { title: '积分记录', perm: 'family:points' }
+      },
+      {
+        path: 'membership-center',
+        name: 'MembershipCenter',
+        component: () => import('@/views/admin/MembershipCenter.vue'),
+        meta: { title: '会员中心', perm: 'system:config' }
+      },
       // 任务管理
       {
         path: 'tasks',

+ 16 - 7
cfc-web/src/views/Children.vue

@@ -67,13 +67,16 @@
           </template>
         </el-table-column>
         <el-table-column label="操作" width="180" fixed="right">
-          <template slot-scope="scope">
-            <el-button type="text" size="small" @click="handleAdjustPoints(scope.row)">
-              调分
-            </el-button>
-            <el-button type="text" size="small" @click="handleEdit(scope.row)">
-              编辑
-            </el-button>
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="adjustPoints" icon="el-icon-edit">调分</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -195,6 +198,12 @@ export default {
     this.loadChildren()
   },
   methods: {
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'adjustPoints': this.handleAdjustPoints(row); break;
+        case 'edit': this.handleEdit(row); break;
+      }
+    },
     async loadChildren() {
       this.loading = true
       try {

+ 18 - 23
cfc-web/src/views/Families.vue

@@ -46,29 +46,17 @@
           </template>
         </el-table-column>
         <el-table-column label="操作" width="250" fixed="right">
-          <template slot-scope="scope">
-            <el-button
-              type="text"
-              size="small"
-              @click="handleViewMembers(scope.row)"
-            >
-              查看成员
-            </el-button>
-            <el-button
-              type="text"
-              size="small"
-              @click="handleEdit(scope.row)"
-            >
-              编辑
-            </el-button>
-            <el-button
-              type="text"
-              size="small"
-              style="color: #F56C6C"
-              @click="handleDelete(scope.row)"
-            >
-              删除
-            </el-button>
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="viewMembers" icon="el-icon-user">查看成员</el-dropdown-item>
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -200,6 +188,13 @@ export default {
     this.loadFamilies()
   },
   methods: {
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'viewMembers': this.handleViewMembers(row); break;
+        case 'edit': this.handleEdit(row); break;
+        case 'delete': this.handleDelete(row); break;
+      }
+    },
     async loadFamilies() {
       this.loading = true
       try {

+ 15 - 2
cfc-web/src/views/GuideAudit.vue

@@ -48,8 +48,15 @@
         </el-table-column>
         <el-table-column label="操作" width="180" fixed="right">
           <template slot-scope="{ row }">
-            <el-button v-if="row.teacherStatus === 'pending'" type="success" size="small" @click="handleApprove(row)">通过</el-button>
-            <el-button v-if="row.teacherStatus === 'pending'" type="danger" size="small" @click="handleReject(row)">拒绝</el-button>
+            <el-button v-if="row.teacherStatus === 'pending'" size="mini" type="primary" @click="handleApprove(row)">通过</el-button>
+            <el-dropdown v-if="row.teacherStatus === 'pending'" trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="reject" icon="el-icon-close">拒绝</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -119,6 +126,12 @@ export default {
     this.loadData()
   },
   methods: {
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'approve': this.handleApprove(row); break;
+        case 'reject': this.handleReject(row); break;
+      }
+    },
     async loadData() {
       this.loading = true
       try {

+ 20 - 5
cfc-web/src/views/GuideFamilyTask.vue

@@ -84,7 +84,7 @@
         </el-table-column>
         <el-table-column label="操作" width="150">
           <template slot-scope="{ row }">
-            <el-button type="text" size="small" @click="selectChild(row)">查看任务</el-button>
+            <el-button size="mini" @click="selectChild(row)">查看任务</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -118,10 +118,17 @@
         </el-table-column>
         <el-table-column label="操作" width="220" fixed="right">
           <template slot-scope="{ row }">
-            <el-button v-if="row.status !== 'cancelled'" type="text" size="small" @click="handleEditTask(row)">编辑</el-button>
-            <el-button v-if="row.status !== 'cancelled'" type="text" size="small" @click="handleDeleteTask(row)">删除</el-button>
-            <el-button v-if="row.status === 'completed' && row.needReview === 1" type="text" size="small" @click="handleReview(row, true)">通过</el-button>
-            <el-button v-if="row.status === 'completed' && row.needReview === 1" type="text" size="small" @click="handleReview(row, false)">拒绝</el-button>
+            <el-button v-if="row.status !== 'cancelled'" size="mini" type="primary" @click="handleEditTask(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item v-if="row.status !== 'cancelled'" command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+                <el-dropdown-item v-if="row.status === 'completed' && row.needReview === 1" command="reviewPass" icon="el-icon-check">通过</el-dropdown-item>
+                <el-dropdown-item v-if="row.status === 'completed' && row.needReview === 1" command="reviewReject" icon="el-icon-close">拒绝</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -231,6 +238,14 @@ taskLoading: false,
     this.loadFamilies()
   },
   methods: {
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'edit': this.handleEditTask(row); break;
+        case 'delete': this.handleDeleteTask(row); break;
+        case 'reviewPass': this.handleReview(row, true); break;
+        case 'reviewReject': this.handleReview(row, false); break;
+      }
+    },
     async loadFamilies() {
       this.loading = true
       try {

+ 25 - 10
cfc-web/src/views/GuidePackages.vue

@@ -52,14 +52,19 @@
         </el-table-column>
         <el-table-column label="操作" width="280" fixed="right">
           <template slot-scope="{ row }">
-            <el-button type="text" size="small" @click="handleView(row)">查看</el-button>
-            <el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
-            <el-button type="text" size="small" @click="handleItems(row)">任务项</el-button>
-            <el-button v-if="row.status === 'draft'" type="text" size="small" @click="handleSubmit(row)">提交审核</el-button>
-            <el-button v-if="row.status === 'approved'" type="text" size="small" @click="handlePublish(row)">
-              {{ row.isPublic ? '下架' : '发布' }}
-            </el-button>
-            <el-button type="text" size="small" @click="handleShare(row)">分享</el-button>
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="view" icon="el-icon-view">查看</el-dropdown-item>
+                <el-dropdown-item command="items" icon="el-icon-s-operation">任务项</el-dropdown-item>
+                <el-dropdown-item v-if="row.status === 'draft'" command="submit" icon="el-icon-upload">提交审核</el-dropdown-item>
+                <el-dropdown-item v-if="row.status === 'approved'" command="publish" icon="el-icon-share">{{ row.isPublic ? '下架' : '发布' }}</el-dropdown-item>
+                <el-dropdown-item command="share" icon="el-icon-share">分享</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -129,8 +134,8 @@
         </el-table-column>
         <el-table-column label="操作" width="120">
           <template slot-scope="{ row, $index }">
-            <el-button type="text" size="small" @click="handleEditItem(row)">编辑</el-button>
-            <el-button type="text" size="small" @click="handleDeleteItem(row, $index)">删除</el-button>
+            <el-button size="mini" type="primary" @click="handleEditItem(row)">编辑</el-button>
+            <el-button size="mini" @click="handleDeleteItem(row, $index)">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -231,6 +236,16 @@ export default {
     this.loadData()
   },
   methods: {
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'view': this.handleView(row); break;
+        case 'edit': this.handleEdit(row); break;
+        case 'items': this.handleItems(row); break;
+        case 'submit': this.handleSubmit(row); break;
+        case 'publish': this.handlePublish(row); break;
+        case 'share': this.handleShare(row); break;
+      }
+    },
     async loadData() {
       this.loading = true
       try {

+ 2 - 0
cfc-web/src/views/Layout.vue

@@ -161,6 +161,7 @@ export default {
             { path: '/families', label: '家庭列表', icon: 'el-icon-s-custom', perm: 'family:list' },
             { path: '/children', label: '孩子管理', icon: 'el-icon-user-solid', perm: 'family:children' },
             { path: '/points', label: '积分管理', icon: 'el-icon-s-finance', perm: 'family:points' },
+            { path: '/points-log', label: '积分记录', icon: 'el-icon-document', perm: 'family:points' },
             { path: '/wishes', label: '心愿管理', icon: 'el-icon-star-off', perm: 'family:wishes' },
             { path: '/rewards', label: '奖励管理', icon: 'el-icon-s-goods', perm: 'reward:list' },
           ]},
@@ -249,6 +250,7 @@ export default {
                 { path: '/sys-config', label: '系统配置', icon: 'el-icon-s-tools', perm: 'system:config' },
                 { path: '/users', label: '用户管理', icon: 'el-icon-user', perm: 'system:users' },
                 { path: '/operation-logs', label: '操作日志', icon: 'el-icon-s-order', perm: 'system:logs' },
+                { path: '/membership-center', label: '会员中心', icon: 'el-icon-s-custom', perm: 'system:config' },
               ]},
             // --- 配置中心 ---
             { title: '配置中心', icon: 'el-icon-setting', perm: 'system:config',

+ 2 - 4
cfc-web/src/views/OperationLogs.vue

@@ -63,10 +63,8 @@
           </template>
         </el-table-column>
         <el-table-column label="操作" width="80" fixed="right">
-          <template slot-scope="scope">
-            <el-button type="text" size="small" @click="handleViewDetail(scope.row)">
-              详情
-            </el-button>
+          <template slot-scope="{ row }">
+            <el-button size="mini" @click="handleViewDetail(row)">详情</el-button>
           </template>
         </el-table-column>
       </el-table>

+ 17 - 4
cfc-web/src/views/PackageAudit.vue

@@ -42,10 +42,16 @@
         </el-table-column>
         <el-table-column label="操作" width="200" fixed="right">
           <template slot-scope="{ row }">
-            <el-button v-if="row.status === 'pending'" type="text" size="small" @click="handleView(row)">查看</el-button>
-            <el-button v-if="row.status === 'pending'" type="text" size="small" @click="handleApprove(row)">通过</el-button>
-            <el-button v-if="row.status === 'pending'" type="text" size="small" @click="handleReject(row)">拒绝</el-button>
-            <el-button type="text" size="small" @click="handleView(row)">详情</el-button>
+            <el-button size="mini" @click="handleView(row)">详情</el-button>
+            <el-dropdown v-if="row.status === 'pending'" trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="approve" icon="el-icon-check">通过</el-dropdown-item>
+                <el-dropdown-item command="reject" icon="el-icon-close">拒绝</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -121,6 +127,13 @@ export default {
     this.loadData()
   },
   methods: {
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'view': this.handleView(row); break;
+        case 'approve': this.handleApprove(row); break;
+        case 'reject': this.handleReject(row); break;
+      }
+    },
     async loadData() {
       this.loading = true
       try {

+ 22 - 23
cfc-web/src/views/Rewards.vue

@@ -95,24 +95,23 @@
               </template>
             </el-table-column>
             <el-table-column label="操作" width="200" fixed="right">
-              <template slot-scope="scope">
+              <template slot-scope="{ row }">
                 <el-button
-                  v-if="scope.row.status === 'pending'"
-                  type="text"
-                  size="small"
-                  @click="handleApprove(scope.row, true)"
+                  v-if="row.status === 'pending'"
+                  size="mini"
+                  type="primary"
+                  @click="handleApprove(row, true)"
                 >
                   通过
                 </el-button>
-                <el-button
-                  v-if="scope.row.status === 'pending'"
-                  type="text"
-                  size="small"
-                  style="color: #F56C6C"
-                  @click="handleApprove(scope.row, false)"
-                >
-                  拒绝
-                </el-button>
+                <el-dropdown v-if="row.status === 'pending'" trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+                  <el-button size="mini">
+                    更多<i class="el-icon-arrow-down el-icon--right"></i>
+                  </el-button>
+                  <el-dropdown-menu slot="dropdown">
+                    <el-dropdown-item command="reject" icon="el-icon-close">拒绝</el-dropdown-item>
+                  </el-dropdown-menu>
+                </el-dropdown>
               </template>
             </el-table-column>
           </el-table>
@@ -149,15 +148,8 @@
               </template>
             </el-table-column>
             <el-table-column label="操作" width="150" fixed="right">
-              <template slot-scope="scope">
-                <el-button
-                  type="text"
-                  size="small"
-                  style="color: #F56C6C"
-                  @click="handleDeleteTemplate(scope.row)"
-                >
-                  删除
-                </el-button>
+              <template slot-scope="{ row }">
+                <el-button size="mini" @click="handleDeleteTemplate(row)">删除</el-button>
               </template>
             </el-table-column>
           </el-table>
@@ -237,6 +229,13 @@ export default {
     this.loadRewards()
   },
   methods: {
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'approve': this.handleApprove(row, true); break;
+        case 'reject': this.handleApprove(row, false); break;
+        case 'deleteTemplate': this.handleDeleteTemplate(row); break;
+      }
+    },
     async loadRewards() {
       this.loading = true
       try {

+ 18 - 23
cfc-web/src/views/Users.vue

@@ -80,29 +80,17 @@
           </template>
         </el-table-column>
         <el-table-column label="操作" width="200" fixed="right">
-          <template slot-scope="scope">
-            <el-button 
-              type="text" 
-              size="small" 
-              @click="handleEdit(scope.row)"
-            >
-              编辑
-            </el-button>
-            <el-button 
-              type="text" 
-              size="small" 
-              @click="handleResetPassword(scope.row)"
-            >
-              重置密码
-            </el-button>
-            <el-button 
-              type="text" 
-              size="small" 
-              style="color: #F56C6C"
-              @click="handleDelete(scope.row)"
-            >
-              删除
-            </el-button>
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="resetPassword" icon="el-icon-key">重置密码</el-dropdown-item>
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -241,6 +229,13 @@ export default {
     this.loadUsers()
   },
   methods: {
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'edit': this.handleEdit(row); break;
+        case 'resetPassword': this.handleResetPassword(row); break;
+        case 'delete': this.handleDelete(row); break;
+      }
+    },
     async loadUsers() {
       this.loading = true
       try {

+ 31 - 8
cfc-web/src/views/Wishes.vue

@@ -59,9 +59,16 @@
                 </template>
               </el-table-column>
               <el-table-column label="操作" width="200" fixed="right">
-                <template slot-scope="scope">
-                  <el-button type="text" size="small" @click="showPriceDialog(scope.row)">定价</el-button>
-                  <el-button type="text" size="small" style="color: #F56C6C" @click="handleReject(scope.row)">拒绝</el-button>
+                <template slot-scope="{ row }">
+                  <el-button size="mini" type="primary" @click="showPriceDialog(row)">定价</el-button>
+                  <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+                    <el-button size="mini">
+                      更多<i class="el-icon-arrow-down el-icon--right"></i>
+                    </el-button>
+                    <el-dropdown-menu slot="dropdown">
+                      <el-dropdown-item command="reject" icon="el-icon-close">拒绝</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </el-dropdown>
                 </template>
               </el-table-column>
             </el-table>
@@ -85,9 +92,16 @@
                 </template>
               </el-table-column>
               <el-table-column label="操作" width="200" fixed="right">
-                <template slot-scope="scope">
-                  <el-button type="text" size="small" @click="handleApproveExchange(scope.row, true)">批准</el-button>
-                  <el-button type="text" size="small" style="color: #F56C6C" @click="handleApproveExchange(scope.row, false)">拒绝</el-button>
+                <template slot-scope="{ row }">
+                  <el-button size="mini" type="primary" @click="handleApproveExchange(row, true)">批准</el-button>
+                  <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+                    <el-button size="mini">
+                      更多<i class="el-icon-arrow-down el-icon--right"></i>
+                    </el-button>
+                    <el-dropdown-menu slot="dropdown">
+                      <el-dropdown-item command="rejectExchange" icon="el-icon-close">拒绝</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </el-dropdown>
                 </template>
               </el-table-column>
             </el-table>
@@ -111,8 +125,8 @@
                 </template>
               </el-table-column>
               <el-table-column label="操作" width="120" fixed="right">
-                <template slot-scope="scope">
-                  <el-button type="text" size="small" @click="handleFulfill(scope.row)">已购买</el-button>
+                <template slot-scope="{ row }">
+                  <el-button size="mini" @click="handleFulfill(row)">已购买</el-button>
                 </template>
               </el-table-column>
             </el-table>
@@ -329,6 +343,15 @@ export default {
     this.loadPendingWishes()
   },
   methods: {
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'price': this.showPriceDialog(row); break;
+        case 'reject': this.handleReject(row); break;
+        case 'approve': this.handleApproveExchange(row, true); break;
+        case 'rejectExchange': this.handleApproveExchange(row, false); break;
+        case 'fulfill': this.handleFulfill(row); break;
+      }
+    },
     async loadPendingWishes() {
       this.loading = true
       try {

+ 29 - 15
cfc-web/src/views/admin/Activities.vue

@@ -56,22 +56,20 @@
         </el-table-column>
         <el-table-column prop="startTime" label="开始时间" width="170" />
         <el-table-column prop="endTime" label="结束时间" width="170" />
-        <el-table-column label="操作" width="280">
+        <el-table-column label="操作" width="200" fixed="right">
           <template slot-scope="{ row }">
-            <el-button size="mini" @click="$router.push('/activity-edit?id=' + row.id + '&readonly=1')" v-if="row.status !== 'draft'">查看</el-button>
-            <el-button size="mini" @click="$router.push('/activity-edit?id=' + row.id)" v-else>编辑</el-button>
-            <el-button
-              size="mini"
-              type="success"
-              @click="handlePublish(row)"
-              v-if="row.status === 'draft'"
-            >发布</el-button>
-            <el-button
-              size="mini"
-              type="warning"
-              @click="handleEnd(row)"
-              v-if="row.status === 'published'"
-            >结束</el-button>
+            <el-button size="mini" type="primary" @click="$router.push('/activity-edit?id=' + row.id)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleMoreCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item v-if="row.status !== 'draft'" command="view" icon="el-icon-view">查看</el-dropdown-item>
+                <el-dropdown-item v-if="row.status === 'draft'" command="publish" icon="el-icon-upload2">发布</el-dropdown-item>
+                <el-dropdown-item v-if="row.status === 'published'" command="end" icon="el-icon-circle-close">结束</el-dropdown-item>
+                <el-dropdown-item command="registrations" icon="el-icon-document">报名列表</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -212,6 +210,22 @@ export default {
       this.filter.page = page
       this.loadActivities()
     },
+    handleMoreCmd(row, cmd) {
+      switch (cmd) {
+        case 'view':
+          this.$router.push('/activity-edit?id=' + row.id + '&readonly=1')
+          break
+        case 'publish':
+          this.handlePublish(row)
+          break
+        case 'end':
+          this.handleEnd(row)
+          break
+        case 'registrations':
+          this.showRegistrations(row)
+          break
+      }
+    },
     handleSearch() {
       this.filter.page = 1
       this.loadActivities()

+ 2 - 2
cfc-web/src/views/admin/ActivityRegistrationReview.vue

@@ -34,8 +34,8 @@
       </el-table-column>
       <el-table-column label="操作" width="200" fixed="right">
         <template slot-scope="{ row }">
-          <el-button v-if="row.status === 'pending'" type="success" size="small" @click="handleApprove(row)">通过</el-button>
-          <el-button v-if="row.status === 'pending'" type="danger" size="small" @click="showRejectDialog(row)">拒绝</el-button>
+          <el-button v-if="row.status === 'pending'" type="success" size="mini" @click="handleApprove(row)">通过</el-button>
+          <el-button v-if="row.status === 'pending'" type="danger" size="mini" @click="showRejectDialog(row)">拒绝</el-button>
           <span v-else style="color: #999;">已处理</span>
         </template>
       </el-table-column>

+ 1 - 1
cfc-web/src/views/admin/ActivityReview.vue

@@ -18,7 +18,7 @@
             <el-tag v-else type="info">{{ row.status }}</el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="200">
+        <el-table-column label="操作" width="200" fixed="right">
           <template slot-scope="{ row }">
             <el-button
               v-if="row.status === 'draft'"

+ 17 - 3
cfc-web/src/views/admin/ArticleCategory.vue

@@ -30,10 +30,17 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="160" fixed="right">
+        <el-table-column label="操作" width="180" fixed="right">
           <template slot-scope="{ row }">
-            <el-button size="mini" type="primary" plain @click="handleEdit(row)">编辑</el-button>
-            <el-button size="mini" type="danger" plain @click="handleDelete(row)">删除</el-button>
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -178,6 +185,13 @@ export default {
           this.$message.error(e.message || '删除失败')
         }
       }).catch(() => {})
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.handleDelete(row)
+          break
+      }
     }
   }
 }

+ 78 - 22
cfc-web/src/views/admin/ArticleManage.vue

@@ -121,46 +121,88 @@
             {{ row.publishedAt || '-' }}
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="420" fixed="right">
+        <el-table-column label="操作" width="200" fixed="right">
           <template slot-scope="{ row }">
             <!-- 草稿箱:可编辑 -->
             <template v-if="row.status === 'draft' && !row.auditStatus">
-              <el-button size="mini" type="primary" plain @click="$router.push('/article-edit?id=' + row.id)">编辑</el-button>
-              <el-button size="mini" type="success" @click="handleSubmitReview(row)">提交审核</el-button>
-              <el-button size="mini" type="warning" plain @click="handlePublishDirect(row)">直接发布</el-button>
-              <el-button size="mini" @click="handleCopy(row)">复制</el-button>
-              <el-button size="mini" type="danger" @click="handleDelete(row)">删除</el-button>
+              <el-button size="mini" type="primary" @click="$router.push('/article-edit?id=' + row.id)">编辑</el-button>
+              <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+                <el-button size="mini">
+                  更多<i class="el-icon-arrow-down el-icon--right"></i>
+                </el-button>
+                <el-dropdown-menu slot="dropdown">
+                  <el-dropdown-item command="submitReview" icon="el-icon-upload">提交审核</el-dropdown-item>
+                  <el-dropdown-item command="publishDirect" icon="el-icon-s-promotion">直接发布</el-dropdown-item>
+                  <el-dropdown-item command="copy" icon="el-icon-document-copy">复制</el-dropdown-item>
+                  <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
             </template>
             <!-- 待审核:审核操作 -->
             <template v-else-if="row.status === 'draft' && row.auditStatus === 'pending'">
-              <el-button size="mini" type="info" plain @click="$router.push('/article-edit?id=' + row.id + '&readonly=1')">查看</el-button>
-              <el-button size="mini" type="success" @click="handleAudit(row, 'approved')">通过</el-button>
-              <el-button size="mini" type="danger" @click="showRejectDialog(row)">驳回</el-button>
-              <el-button size="mini" @click="handleCopy(row)">复制</el-button>
+              <el-button size="mini" type="primary" @click="$router.push('/article-edit?id=' + row.id + '&readonly=1')">查看</el-button>
+              <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+                <el-button size="mini">
+                  更多<i class="el-icon-arrow-down el-icon--right"></i>
+                </el-button>
+                <el-dropdown-menu slot="dropdown">
+                  <el-dropdown-item command="approve" icon="el-icon-circle-check">通过</el-dropdown-item>
+                  <el-dropdown-item command="reject" icon="el-icon-circle-close">驳回</el-dropdown-item>
+                  <el-dropdown-item command="copy" icon="el-icon-document-copy">复制</el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
             </template>
             <!-- 已驳回:查看 + 重新编辑 -->
             <template v-else-if="row.status === 'rejected'">
-              <el-button size="mini" type="info" plain @click="$router.push('/article-edit?id=' + row.id + '&readonly=1')">查看</el-button>
-              <el-button size="mini" type="primary" @click="handleReDraft(row)">重新编辑</el-button>
-              <el-button size="mini" @click="handleCopy(row)">复制</el-button>
+              <el-button size="mini" type="primary" @click="$router.push('/article-edit?id=' + row.id + '&readonly=1')">查看</el-button>
+              <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+                <el-button size="mini">
+                  更多<i class="el-icon-arrow-down el-icon--right"></i>
+                </el-button>
+                <el-dropdown-menu slot="dropdown">
+                  <el-dropdown-item command="reDraft" icon="el-icon-edit-outline">重新编辑</el-dropdown-item>
+                  <el-dropdown-item command="copy" icon="el-icon-document-copy">复制</el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
             </template>
             <!-- 发布中:查看 + 下架 + 精选 -->
             <template v-else-if="row.status === 'published'">
-              <el-button size="mini" type="info" plain @click="$router.push('/article-edit?id=' + row.id + '&readonly=1')">查看</el-button>
-              <el-button size="mini" type="warning" @click="handleWithdraw(row)">下架</el-button>
-              <el-button size="mini" :type="row.isFeatured === 1 || row.isFeatured === true ? 'warning' : 'default'" :plain="!(row.isFeatured === 1 || row.isFeatured === true)" @click="handleToggleFeatured(row)">{{ row.isFeatured === 1 || row.isFeatured === true ? '取消精选' : '精选' }}</el-button>
-              <el-button size="mini" @click="handleCopy(row)">复制</el-button>
+              <el-button size="mini" type="primary" @click="$router.push('/article-edit?id=' + row.id + '&readonly=1')">查看</el-button>
+              <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+                <el-button size="mini">
+                  更多<i class="el-icon-arrow-down el-icon--right"></i>
+                </el-button>
+                <el-dropdown-menu slot="dropdown">
+                  <el-dropdown-item command="withdraw" icon="el-icon-download">下架</el-dropdown-item>
+                  <el-dropdown-item command="toggleFeatured" icon="el-icon-star-on">精选/取消精选</el-dropdown-item>
+                  <el-dropdown-item command="copy" icon="el-icon-document-copy">复制</el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
             </template>
             <!-- 已撤回:查看 + 重新发布 -->
             <template v-else-if="row.status === 'withdrawn'">
-              <el-button size="mini" type="info" plain @click="$router.push('/article-edit?id=' + row.id + '&readonly=1')">查看</el-button>
-              <el-button size="mini" type="success" @click="handlePublishDirect(row)">重新发布</el-button>
-              <el-button size="mini" @click="handleCopy(row)">复制</el-button>
+              <el-button size="mini" type="primary" @click="$router.push('/article-edit?id=' + row.id + '&readonly=1')">查看</el-button>
+              <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+                <el-button size="mini">
+                  更多<i class="el-icon-arrow-down el-icon--right"></i>
+                </el-button>
+                <el-dropdown-menu slot="dropdown">
+                  <el-dropdown-item command="rePublish" icon="el-icon-s-promotion">重新发布</el-dropdown-item>
+                  <el-dropdown-item command="copy" icon="el-icon-document-copy">复制</el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
             </template>
             <!-- 其他 -->
             <template v-else>
-              <el-button size="mini" type="info" plain @click="$router.push('/article-edit?id=' + row.id + '&readonly=1')">查看</el-button>
-              <el-button size="mini" @click="handleCopy(row)">复制</el-button>
+              <el-button size="mini" type="primary" @click="$router.push('/article-edit?id=' + row.id + '&readonly=1')">查看</el-button>
+              <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+                <el-button size="mini">
+                  更多<i class="el-icon-arrow-down el-icon--right"></i>
+                </el-button>
+                <el-dropdown-menu slot="dropdown">
+                  <el-dropdown-item command="copy" icon="el-icon-document-copy">复制</el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
             </template>
           </template>
         </el-table-column>
@@ -428,6 +470,20 @@ export default {
       } catch (e) {
         this.$message.error(e.message || '复制失败')
       }
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'submitReview': this.handleSubmitReview(row); break;
+        case 'publishDirect': this.handlePublishDirect(row); break;
+        case 'copy': this.handleCopy(row); break;
+        case 'delete': this.handleDelete(row); break;
+        case 'approve': this.handleAudit(row, 'approved'); break;
+        case 'reject': this.showRejectDialog(row); break;
+        case 'reDraft': this.handleReDraft(row); break;
+        case 'withdraw': this.handleWithdraw(row); break;
+        case 'toggleFeatured': this.handleToggleFeatured(row); break;
+        case 'rePublish': this.handlePublishDirect(row); break;
+      }
     }
   }
 }

+ 20 - 6
cfc-web/src/views/admin/Assessment.vue

@@ -38,12 +38,19 @@
           </template>
         </el-table-column>
         <el-table-column prop="createdAt" label="创建时间" width="160" />
-        <el-table-column label="操作" width="150">
-          <template slot-scope="scope">
-            <el-button type="text" size="small" @click="editMaterial(scope.row)">编辑</el-button>
-            <el-button type="text" size="small" :type="scope.row.status === 'active' ? 'warning' : 'success'" @click="toggleStatus(scope.row)">
-              {{ scope.row.status === 'active' ? '停用' : '启用' }}
-            </el-button>
+        <el-table-column label="操作" width="180" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="editMaterial(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="toggle" :icon="row.status === 'active' ? 'el-icon-circle-close' : 'el-icon-circle-check'">
+                  {{ row.status === 'active' ? '停用' : '启用' }}
+                </el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -173,6 +180,13 @@ export default {
       } catch (e) {
         this.$message.error('更新失败')
       }
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'toggle':
+          this.toggleStatus(row)
+          break
+      }
     }
   }
 }

+ 18 - 4
cfc-web/src/views/admin/BaziConfigs.vue

@@ -20,10 +20,17 @@
         <el-table-column prop="yinYang" label="阴阳"></el-table-column>
         <el-table-column prop="mindBase" label="心基础分" width="100"></el-table-column>
         <el-table-column prop="wisdomBase" label="智基础分" width="100"></el-table-column>
-        <el-table-column label="操作" width="150">
-          <template slot-scope="scope">
-            <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
-            <el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -163,6 +170,13 @@ export default {
       } catch (e) {
         this.$message.error('保存失败')
       }
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.handleDelete(row)
+          break
+      }
     }
   }
 }

+ 18 - 4
cfc-web/src/views/admin/BloodTypeConfigs.vue

@@ -12,10 +12,17 @@
         <el-table-column prop="description" label="性格描述" min-width="200"></el-table-column>
         <el-table-column prop="mindBase" label="心基础分" width="100"></el-table-column>
         <el-table-column prop="wisdomBase" label="智基础分" width="100"></el-table-column>
-        <el-table-column label="操作" width="150">
-          <template slot-scope="scope">
-            <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
-            <el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -140,6 +147,13 @@ export default {
       } catch (e) {
         this.$message.error('保存失败')
       }
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.handleDelete(row)
+          break
+      }
     }
   }
 }

+ 17 - 3
cfc-web/src/views/admin/CouponManagement.vue

@@ -39,10 +39,17 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="140" fixed="right">
+        <el-table-column label="操作" width="180" fixed="right">
           <template slot-scope="{ row }">
-            <el-button size="mini" @click="handleEdit(row)">编辑</el-button>
-            <el-button size="mini" type="danger" @click="handleDelete(row)">删除</el-button>
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -278,6 +285,13 @@ export default {
       } finally {
         this.batchSubmitting = false
       }
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.handleDelete(row)
+          break
+      }
     }
   }
 }

+ 24 - 7
cfc-web/src/views/admin/EducationSystems.vue

@@ -19,12 +19,17 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="150">
-          <template slot-scope="scope">
-            <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
-            <el-button size="mini" :type="scope.row.status === 'ACTIVE' ? 'warning' : 'success'" @click="toggleStatus(scope.row)">
-              {{ scope.row.status === 'ACTIVE' ? '停用' : '启用' }}
-            </el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item :command="row.status === 'ACTIVE' ? 'disable' : 'enable'" icon="el-icon-switch-button">{{ row.status === 'ACTIVE' ? '停用' : '启用' }}</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -129,7 +134,19 @@ export default {
         this.$message.error('保存失败')
       }
     },
-    handleSearch() {}
+    handleSearch() {},
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'disable':
+          row.status = 'DISABLED'
+          this.toggleStatus(row)
+          break
+        case 'enable':
+          row.status = 'ACTIVE'
+          this.toggleStatus(row)
+          break
+      }
+    }
   }
 }
 </script>

+ 3 - 3
cfc-web/src/views/admin/EmotionAlertManage.vue

@@ -42,12 +42,12 @@
           {{ row.resolvedAt ? formatTime(row.resolvedAt) : '-' }}
         </template>
       </el-table-column>
-      <el-table-column label="操作" width="100" fixed="right">
+      <el-table-column label="操作" width="160" fixed="right">
         <template slot-scope="{ row }">
           <el-button
             v-if="!row.resolved"
-            type="warning"
-            size="small"
+            type="primary"
+            size="mini"
             @click="handleResolve(row)"
           >标记已处理</el-button>
           <span v-else class="resolved-text">-</span>

+ 14 - 2
cfc-web/src/views/admin/EnergyRuleManagement.vue

@@ -32,8 +32,15 @@
       <el-table-column prop="description" label="描述" min-width="140" show-overflow-tooltip />
       <el-table-column label="操作" width="160" fixed="right">
         <template slot-scope="{ row }">
-          <el-button type="primary" size="small" @click="showDialog(row)">编辑</el-button>
-          <el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button>
+          <el-button size="mini" type="primary" @click="showDialog(row)">编辑</el-button>
+          <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+            <el-button size="mini">
+              更多<i class="el-icon-arrow-down el-icon--right"></i>
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
         </template>
       </el-table-column>
     </el-table>
@@ -179,6 +186,11 @@ export default {
         }
       } catch (e) { this.$message.error('操作失败') }
     },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete': this.handleDelete(row); break
+      }
+    },
     async handleDelete(row) {
       this.$confirm('确认删除该规则?', '提示', { type: 'warning' }).then(async () => {
         const res = await deleteEnergyRule(row.id)

+ 17 - 3
cfc-web/src/views/admin/Foods.vue

@@ -45,10 +45,17 @@
           </template>
         </el-table-column>
         <el-table-column prop="status" label="状态" width="80" />
-        <el-table-column label="操作" width="160">
+        <el-table-column label="操作" width="200" fixed="right">
           <template slot-scope="{ row }">
-            <el-button size="mini" @click="editItem(row)">编辑</el-button>
-            <el-button size="mini" type="danger" @click="deleteItem(row)">删除</el-button>
+            <el-button size="mini" type="primary" @click="editItem(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -257,6 +264,13 @@ export default {
           this.$message && this.$message.error && this.$message.error('删除失败')
         }
       }).catch(() => {})
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.deleteItem(row)
+          break
+      }
     }
   }
 }

+ 17 - 3
cfc-web/src/views/admin/GrowthTaskManagement.vue

@@ -54,10 +54,17 @@
             />
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="140">
+        <el-table-column label="操作" width="180" fixed="right">
           <template slot-scope="{ row }">
-            <el-button size="mini" @click="handleEditTask(row)">编辑</el-button>
-            <el-button size="mini" type="primary" @click="handleTaskSubmit(row)">保存</el-button>
+            <el-button size="mini" type="primary" @click="handleEditTask(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="save" icon="el-icon-success">保存</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -326,6 +333,13 @@ export default {
       } catch (e) {
         this.$message.error('状态更新失败')
       }
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'save':
+          this.handleTaskSubmit(row)
+          break
+      }
     }
   },
   created() {

+ 17 - 3
cfc-web/src/views/admin/KnowledgeTag.vue

@@ -19,10 +19,17 @@
           </template>
         </el-table-column>
         <el-table-column prop="createdAt" label="创建时间" width="180" />
-        <el-table-column label="操作" width="160" fixed="right">
+        <el-table-column label="操作" width="180" fixed="right">
           <template slot-scope="{ row }">
-            <el-button size="mini" type="primary" plain @click="handleEdit(row)">编辑</el-button>
-            <el-button size="mini" type="danger" plain @click="handleDelete(row)">删除</el-button>
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -133,6 +140,13 @@ export default {
           this.$message.error(e.message || '删除失败')
         }
       }).catch(() => {})
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.handleDelete(row)
+          break
+      }
     }
   }
 }

+ 295 - 0
cfc-web/src/views/admin/MembershipCenter.vue

@@ -0,0 +1,295 @@
+<template>
+  <div class="membership-center admin-page">
+    <!-- 统计卡片 -->
+    <el-row :gutter="20" style="margin-bottom:20px;">
+      <el-col :span="6">
+        <el-card shadow="hover" class="stat-card">
+          <div class="stat-value">{{ overview.freeCount }}</div>
+          <div class="stat-label">免费用户</div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover" class="stat-card stat-family">
+          <div class="stat-value">{{ overview.familyCount }}</div>
+          <div class="stat-label">FAMILY会员</div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover" class="stat-card stat-provider">
+          <div class="stat-value">{{ overview.providerCount }}</div>
+          <div class="stat-label">PROVIDER会员</div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover" class="stat-card stat-new">
+          <div class="stat-value">{{ overview.newFamilyThisMonth }}</div>
+          <div class="stat-label">本月新增</div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 会员等级配置 -->
+    <el-card style="margin-bottom:20px;">
+      <div slot="header">
+        <span>会员等级配置</span>
+      </div>
+      <el-table :data="levels" v-loading="levelsLoading" border stripe>
+        <el-table-column prop="levelCode" label="等级编码" width="120">
+          <template slot-scope="{ row }">
+            <el-tag :type="levelTagType(row.levelCode)" size="mini">{{ row.levelCode }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="levelName" label="等级名称" width="150" />
+        <el-table-column prop="levelDesc" label="描述" min-width="180" show-overflow-tooltip />
+        <el-table-column label="月费(元)" width="100">
+          <template slot-scope="{ row }">{{ formatPrice(row.priceMonthly) }}</template>
+        </el-table-column>
+        <el-table-column label="季费(元)" width="100">
+          <template slot-scope="{ row }">{{ formatPrice(row.priceQuarterly) }}</template>
+        </el-table-column>
+        <el-table-column label="年费(元)" width="100">
+          <template slot-scope="{ row }">{{ formatPrice(row.priceYearly) }}</template>
+        </el-table-column>
+        <el-table-column prop="maxChildren" label="孩子上限" width="90" />
+        <el-table-column prop="maxTasksPerDay" label="每日任务" width="90" />
+        <el-table-column label="AI审核" width="80">
+          <template slot-scope="{ row }">
+            <el-tag :type="row.aiReviewEnabled ? 'success' : 'info'" size="mini">{{ row.aiReviewEnabled ? '是' : '否' }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="优先客服" width="80">
+          <template slot-scope="{ row }">
+            <el-tag :type="row.prioritySupport ? 'success' : 'info'" size="mini">{{ row.prioritySupport ? '是' : '否' }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEditLevel(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleLevelCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="features" icon="el-icon-view">查看权限</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <!-- 升级记录 -->
+    <el-card>
+      <div slot="header">
+        <span>会员升级记录</span>
+      </div>
+      <el-table :data="upgradeRecords" v-loading="recordsLoading" border stripe>
+        <el-table-column prop="id" label="ID" width="70" />
+        <el-table-column prop="userId" label="用户ID" width="80" />
+        <el-table-column label="从" width="100">
+          <template slot-scope="{ row }">
+            <el-tag size="mini">{{ row.fromLevel || 'FREE' }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="到" width="100">
+          <template slot-scope="{ row }">
+            <el-tag type="success" size="mini">{{ row.toLevel }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="upgradeType" label="升级方式" width="120">
+          <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="订单ID" width="80" />
+        <el-table-column prop="expireTime" label="到期时间" width="160">
+          <template slot-scope="{ row }">{{ formatTime(row.expireTime) }}</template>
+        </el-table-column>
+        <el-table-column prop="createdAt" label="创建时间" width="160">
+          <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
+        </el-table-column>
+      </el-table>
+      <el-pagination
+        v-if="recordsTotal > 0"
+        @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"
+        style="margin-top:16px;text-align:right"
+      />
+    </el-card>
+
+    <!-- 权限详情对话框 -->
+    <el-dialog title="功能权限详情" :visible.sync="featuresVisible" width="500px">
+      <div v-if="currentLevel" class="features-content">
+        <h3>{{ currentLevel.levelName }} ({{ currentLevel.levelCode }})</h3>
+        <p style="color:#666;margin-bottom:16px;">{{ currentLevel.levelDesc }}</p>
+        <el-descriptions :column="1" border>
+          <el-descriptions-item label="孩子数量上限">{{ currentLevel.maxChildren }}</el-descriptions-item>
+          <el-descriptions-item label="每日任务上限">{{ currentLevel.maxTasksPerDay }}</el-descriptions-item>
+          <el-descriptions-item label="AI审核">{{ currentLevel.aiReviewEnabled ? '启用' : '未启用' }}</el-descriptions-item>
+          <el-descriptions-item label="优先客服">{{ currentLevel.prioritySupport ? '支持' : '不支持' }}</el-descriptions-item>
+          <el-descriptions-item label="老师咨询">{{ currentLevel.teacherConsultation ? '支持' : '不支持' }}</el-descriptions-item>
+        </el-descriptions>
+        <div v-if="currentLevel.features" style="margin-top:16px;">
+          <div class="features-title">功能权限列表</div>
+          <div class="features-list">
+            <el-tag v-for="(f, i) in parseFeatures(currentLevel.features)" :key="i" size="mini" style="margin:4px;">{{ f }}</el-tag>
+            <span v-if="!parseFeatures(currentLevel.features).length" style="color:#999;">暂无特殊权限</span>
+          </div>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getMembershipStats } from '@/api/admin'
+
+export default {
+  name: 'MembershipCenter',
+  data() {
+    return {
+      // overview
+      overview: {
+        freeCount: 0,
+        familyCount: 0,
+        providerCount: 0,
+        newFamilyThisMonth: 0,
+        totalFamilyMemberships: 0
+      },
+      // levels
+      levelsLoading: false,
+      levels: [],
+      // upgrade records
+      recordsLoading: false,
+      upgradeRecords: [],
+      recordsTotal: 0,
+      recordFilter: {
+        page: 1,
+        size: 10
+      },
+      // dialogs
+      featuresVisible: false,
+      currentLevel: null
+    }
+  },
+  created() {
+    this.loadOverview()
+    this.loadLevels()
+    this.loadUpgradeRecords()
+  },
+  methods: {
+    formatPrice(price) {
+      if (price === null || price === undefined) return '—'
+      return (price / 100).toFixed(2)
+    },
+    formatTime(t) {
+      if (!t) return '-'
+      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
+    },
+    levelTagType(code) {
+      if (code === 'FREE') return 'info'
+      if (code === 'FAMILY') return 'success'
+      if (code === 'PROVIDER') return 'warning'
+      return 'primary'
+    },
+    upgradeTypeLabel(type) {
+      const map = { pay: '付费升级', consumption: '消费升级', admin: '管理员操作', trial: '试用激活' }
+      return map[type] || type || '—'
+    },
+    upgradeTypeTag(type) {
+      if (type === 'pay') return 'success'
+      if (type === 'consumption') return 'primary'
+      if (type === 'admin') return 'warning'
+      if (type === 'trial') return 'info'
+      return ''
+    },
+    parseFeatures(features) {
+      if (!features) return []
+      try {
+        if (features.startsWith('[')) return JSON.parse(features)
+        return features.split(',').map(f => f.trim()).filter(Boolean)
+      } catch (e) {
+        return []
+      }
+    },
+    async loadOverview() {
+      try {
+        const res = await this.$axios.post('/api/admin/membership/overview')
+        if (res.data && res.data.code === 200) {
+          Object.assign(this.overview, res.data.data)
+        }
+      } catch (e) {
+        // ignore
+      }
+    },
+    async loadLevels() {
+      this.levelsLoading = true
+      try {
+        const res = await this.$axios.post('/api/admin/membership/levels')
+        if (res.data && res.data.code === 200) {
+          this.levels = res.data.data || []
+        }
+      } catch (e) {
+        this.$message && this.$message.error && this.$message.error('加载等级配置失败')
+      } finally {
+        this.levelsLoading = false
+      }
+    },
+    async loadUpgradeRecords() {
+      this.recordsLoading = true
+      try {
+        const res = await this.$axios.post('/api/admin/membership/upgrade-records', {
+          page: this.recordFilter.page,
+          size: this.recordFilter.size
+        })
+        if (res.data && res.data.code === 200) {
+          this.upgradeRecords = res.data.data.records || []
+          this.recordsTotal = res.data.data.total || 0
+        }
+      } catch (e) {
+        this.$message && this.$message.error && this.$message.error('加载升级记录失败')
+      } finally {
+        this.recordsLoading = false
+      }
+    },
+    handleRecordSizeChange(size) {
+      this.recordFilter.size = size
+      this.recordFilter.page = 1
+      this.loadUpgradeRecords()
+    },
+    handleRecordPageChange(page) {
+      this.recordFilter.page = page
+      this.loadUpgradeRecords()
+    },
+    handleEditLevel(row) {
+      this.$message.info('会员等级配置请在 "系统配置" 中编辑')
+    },
+    handleLevelCmd(row, cmd) {
+      switch (cmd) {
+        case 'features':
+          this.currentLevel = row
+          this.featuresVisible = true
+          break
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.membership-center { padding: 20px; }
+.stat-card { text-align: center; cursor: default; }
+.stat-card .stat-value { font-size: 32px; font-weight: bold; color: #303133; }
+.stat-card .stat-label { font-size: 14px; color: #909399; margin-top: 8px; }
+.stat-family .stat-value { color: #67C23A; }
+.stat-provider .stat-value { color: #E6A23C; }
+.stat-new .stat-value { color: #409EFF; }
+.features-content { padding: 10px 0; }
+.features-title { font-weight: bold; margin-bottom: 8px; color: #606266; }
+.features-list { display: flex; flex-wrap: wrap; }
+</style>

+ 19 - 5
cfc-web/src/views/admin/OrderManage.vue

@@ -49,12 +49,19 @@
           {{ formatTime(row.createdAt) }}
         </template>
       </el-table-column>
-      <el-table-column label="操作" width="300" fixed="right">
+      <el-table-column label="操作" width="200" fixed="right">
         <template slot-scope="{ row }">
-          <el-button type="primary" size="small" @click="viewDetail(row)">详情</el-button>
-          <el-button type="success" size="small" @click="showShipDialog(row)" v-if="row.status === 'paid'">发货</el-button>
-          <el-button type="danger" size="small" @click="handleClose(row)" v-if="row.status === 'paid' || row.status === 'refunding'">关闭</el-button>
-          <el-button type="warning" size="small" @click="showRefundDialog(row)" v-if="row.status === 'refunding'">退款审核</el-button>
+          <el-button size="mini" type="primary" @click="viewDetail(row)">详情</el-button>
+          <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+            <el-button size="mini">
+              更多<i class="el-icon-arrow-down el-icon--right"></i>
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item v-if="row.status === 'paid'" command="ship" icon="el-icon-s-cooperation">发货</el-dropdown-item>
+              <el-dropdown-item v-if="row.status === 'paid' || row.status === 'refunding'" command="close" icon="el-icon-circle-close">关闭</el-dropdown-item>
+              <el-dropdown-item v-if="row.status === 'refunding'" command="refund" icon="el-icon-warning-outline">退款审核</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
         </template>
       </el-table-column>
     </el-table>
@@ -299,6 +306,13 @@ export default {
     formatTime(time) {
       if (!time) return '-'
       return new Date(time).toLocaleString()
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'ship': this.showShipDialog(row); break;
+        case 'close': this.handleClose(row); break;
+        case 'refund': this.showRefundDialog(row); break;
+      }
     }
   }
 }

+ 18 - 7
cfc-web/src/views/admin/PackageTemplates.vue

@@ -24,13 +24,18 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="200">
-          <template slot-scope="scope">
-            <el-button size="mini" @click="handleViewServices(scope.row)">服务项</el-button>
-            <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
-            <el-button size="mini" :type="scope.row.status === 'ACTIVE' ? 'warning' : 'success'" @click="toggleStatus(scope.row)">
-              {{ scope.row.status === 'ACTIVE' ? '停用' : '启用' }}
-            </el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="viewServices" icon="el-icon-document">服务项</el-dropdown-item>
+                <el-dropdown-item command="toggleStatus" :icon="row.status === 'ACTIVE' ? 'el-icon-close' : 'el-icon-check'">{{ row.status === 'ACTIVE' ? '停用' : '启用' }}</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -193,6 +198,12 @@ export default {
         this.$message.error('保存失败')
       }
     },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'viewServices': this.handleViewServices(row); break
+        case 'toggleStatus': this.toggleStatus(row); break
+      }
+    },
     handleSearch() {}
   }
 }

+ 202 - 0
cfc-web/src/views/admin/PointsLog.vue

@@ -0,0 +1,202 @@
+<template>
+  <div class="points-log admin-page">
+    <el-card>
+      <div slot="header">
+        <span>积分记录</span>
+        <div style="float:right">
+          <el-select v-model="filter.type" placeholder="类型筛选" size="mini" style="margin-right:12px;width:130px" clearable @change="loadData">
+            <el-option label="全部" value="" />
+            <el-option label="任务奖励" value="task" />
+            <el-option label="系统调整" value="adjust" />
+            <el-option label="兑换扣除" value="exchange" />
+            <el-option label="退款返还" value="refund" />
+            <el-option label="超时扣分" value="timeout" />
+          </el-select>
+          <el-select v-model="filter.category" placeholder="分类筛选" size="mini" style="margin-right:12px;width:130px" clearable @change="loadData">
+            <el-option label="全部" value="" />
+            <el-option label="任务积分" value="task" />
+            <el-option label="系统积分" value="system" />
+          </el-select>
+          <el-input v-model="filter.childKeyword" placeholder="搜索孩子ID..." prefix-icon="el-icon-search" size="mini" style="width:180px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+        </div>
+      </div>
+      <el-table :data="list" v-loading="loading" border stripe>
+        <el-table-column prop="createdAt" label="时间" width="160">
+          <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
+        </el-table-column>
+        <el-table-column prop="childId" label="孩子ID" width="80" />
+        <el-table-column label="类型" width="100">
+          <template slot-scope="{ row }">{{ typeLabel(row.type) }}</template>
+        </el-table-column>
+        <el-table-column label="分类" width="100">
+          <template slot-scope="{ row }">
+            <el-tag :type="row.category === 'task' ? 'success' : 'warning'" size="mini">
+              {{ row.category === 'task' ? '任务积分' : '系统积分' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="变动" width="100">
+          <template slot-scope="{ row }">
+            <span :style="{ color: row.amount > 0 ? '#67C23A' : '#F56C6C', fontWeight: 'bold' }">
+              {{ row.amount > 0 ? '+' : '' }}{{ row.amount }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
+        <el-table-column prop="expireAt" label="过期时间" width="160">
+          <template slot-scope="{ row }">
+            <span v-if="row.expireAt" :style="{ color: row.expired ? '#999' : '#E6A23C' }">
+              {{ formatTime(row.expireAt) }}
+            </span>
+            <span v-else style="color:#999">永久有效</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleDetail(row)">详情</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleMoreCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="adjust" icon="el-icon-edit">手动调整</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-pagination
+        v-if="total > 0"
+        @size-change="handleSizeChange"
+        @current-change="handlePageChange"
+        :current-page="filter.page"
+        :page-sizes="[10, 20, 50]"
+        :page-size="filter.size"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="total"
+        style="margin-top:16px;text-align:right"
+      />
+    </el-card>
+
+    <!-- 详情对话框 -->
+    <el-dialog title="积分记录详情" :visible.sync="detailVisible" width="500px">
+      <div v-if="currentRow" class="detail-content">
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="记录ID">{{ currentRow.id }}</el-descriptions-item>
+          <el-descriptions-item label="孩子ID">{{ currentRow.childId }}</el-descriptions-item>
+          <el-descriptions-item label="类型">{{ typeLabel(currentRow.type) }}</el-descriptions-item>
+          <el-descriptions-item label="分类">
+            <el-tag :type="currentRow.category === 'task' ? 'success' : 'warning'" size="mini">
+              {{ currentRow.category === 'task' ? '任务积分' : '系统积分' }}
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="变动">
+            <span :style="{ color: currentRow.amount > 0 ? '#67C23A' : '#F56C6C', fontWeight: 'bold' }">
+              {{ currentRow.amount > 0 ? '+' : '' }}{{ currentRow.amount }}
+            </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="过期时间">
+            {{ currentRow.expireAt ? formatTime(currentRow.expireAt) : '永久有效' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="描述" :span="2">{{ currentRow.description || '—' }}</el-descriptions-item>
+          <el-descriptions-item label="创建时间" :span="2">{{ formatTime(currentRow.createdAt) }}</el-descriptions-item>
+        </el-descriptions>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getPointsLogs } from '@/api/admin'
+
+const TYPE_MAP = {
+  task: { label: '任务奖励', type: 'success' },
+  adjust: { label: '系统调整', type: 'warning' },
+  exchange: { label: '兑换扣除', type: 'danger' },
+  refund: { label: '退款返还', type: 'info' },
+  timeout: { label: '超时扣分', type: 'danger' }
+}
+
+export default {
+  name: 'PointsLog',
+  data() {
+    return {
+      loading: false,
+      list: [],
+      total: 0,
+      filter: {
+        type: '',
+        category: '',
+        childKeyword: '',
+        page: 1,
+        size: 10
+      },
+      detailVisible: false,
+      currentRow: null
+    }
+  },
+  created() {
+    this.loadData()
+  },
+  methods: {
+    typeLabel(type) {
+      return (TYPE_MAP[type] && TYPE_MAP[type].label) || type || '—'
+    },
+    formatTime(t) {
+      if (!t) return '-'
+      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
+    },
+    async loadData() {
+      this.loading = true
+      try {
+        const params = {
+          page: this.filter.page,
+          size: this.filter.size
+        }
+        if (this.filter.type) params.type = this.filter.type
+        if (this.filter.category) params.category = this.filter.category
+        if (this.filter.childKeyword) {
+          const childId = parseInt(this.filter.childKeyword)
+          if (!isNaN(childId)) params.childId = childId
+        }
+        const res = await getPointsLogs(params)
+        this.list = (res.data && res.data.records) || []
+        this.total = (res.data && res.data.total) || 0
+      } catch (e) {
+        this.$message && this.$message.error && this.$message.error('加载积分记录失败')
+      } finally {
+        this.loading = false
+      }
+    },
+    handleSizeChange(size) {
+      this.filter.size = size
+      this.filter.page = 1
+      this.loadData()
+    },
+    handlePageChange(page) {
+      this.filter.page = page
+      this.loadData()
+    },
+    handleSearch() {
+      this.filter.page = 1
+      this.loadData()
+    },
+    handleDetail(row) {
+      this.currentRow = row
+      this.detailVisible = true
+    },
+    handleMoreCmd(row, cmd) {
+      switch (cmd) {
+        case 'adjust':
+          this.$message.info('请在 "积分管理" 页面中对指定孩子进行积分调整')
+          break
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.points-log { padding: 20px; }
+.detail-content { padding: 10px 0; }
+</style>

+ 21 - 6
cfc-web/src/views/admin/ProductManage.vue

@@ -78,13 +78,20 @@
           {{ formatTime(row.createdAt) }}
         </template>
       </el-table-column>
-      <el-table-column label="操作" width="220" fixed="right">
+      <el-table-column label="操作" width="200" fixed="right">
         <template slot-scope="{ row }">
-          <el-button v-if="row.status === 'pending'" type="success" size="small" @click="handleReview(row, 'approve')">通过</el-button>
-          <el-button v-if="row.status === 'pending'" type="danger" size="small" @click="showRejectDialog(row)">拒绝</el-button>
-          <el-button v-if="row.status === 'on_shelf'" type="warning" size="small" @click="handleShelve(row, false)">下架</el-button>
-          <el-button v-if="row.status === 'off_shelf'" type="primary" size="small" @click="handleShelve(row, true)">上架</el-button>
-          <el-button type="info" size="small" @click="$router.push('/product-edit?id=' + row.id)">编辑</el-button>
+          <el-button size="mini" type="primary" @click="$router.push('/product-edit?id=' + row.id)">编辑</el-button>
+          <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+            <el-button size="mini">
+              更多<i class="el-icon-arrow-down el-icon--right"></i>
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item v-if="row.status === 'pending'" command="approve" icon="el-icon-circle-check">通过</el-dropdown-item>
+              <el-dropdown-item v-if="row.status === 'pending'" command="reject" icon="el-icon-circle-close">拒绝</el-dropdown-item>
+              <el-dropdown-item v-if="row.status === 'on_shelf'" command="offShelf" icon="el-icon-download">下架</el-dropdown-item>
+              <el-dropdown-item v-if="row.status === 'off_shelf'" command="onShelf" icon="el-icon-upload2">上架</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
         </template>
       </el-table-column>
     </el-table>
@@ -263,6 +270,14 @@ export default {
         dangerouslyUseHTMLString: true,
         closeOnClickModal: true
       })
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'approve': this.handleReview(row, 'approve'); break;
+        case 'reject': this.showRejectDialog(row); break;
+        case 'offShelf': this.handleShelve(row, false); break;
+        case 'onShelf': this.handleShelve(row, true); break;
+      }
     }
   }
 }

+ 17 - 3
cfc-web/src/views/admin/Recipes.vue

@@ -26,10 +26,17 @@
         <el-table-column prop="mealType" label="类型" width="80" />
         <el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
         <el-table-column prop="status" label="状态" width="80" />
-        <el-table-column label="操作" width="160">
+        <el-table-column label="操作" width="200" fixed="right">
           <template slot-scope="{ row }">
-            <el-button size="mini" @click="editItem(row)">编辑</el-button>
-            <el-button size="mini" type="danger" @click="deleteItem(row)">删除</el-button>
+            <el-button size="mini" type="primary" @click="editItem(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -198,6 +205,13 @@ export default {
           this.$message && this.$message.error && this.$message.error('删除失败')
         }
       }).catch(() => {})
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.deleteItem(row)
+          break
+      }
     }
   }
 }

+ 18 - 4
cfc-web/src/views/admin/RelationshipTypes.vue

@@ -25,10 +25,17 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="150">
-          <template slot-scope="scope">
-            <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
-            <el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -142,6 +149,13 @@ export default {
       } catch (e) {
         this.$message.error('保存失败')
       }
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.handleDelete(row)
+          break
+      }
     }
   }
 }

+ 16 - 2
cfc-web/src/views/admin/SeasonalFoods.vue

@@ -28,9 +28,16 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="120">
+        <el-table-column label="操作" width="200" fixed="right">
           <template slot-scope="{ row }">
-            <el-button size="mini" type="danger" @click="deleteItem(row)">删除</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -141,6 +148,13 @@ export default {
           this.$message && this.$message.error && this.$message.error('删除失败')
         }
       }).catch(() => {})
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.deleteItem(row)
+          break
+      }
     }
   }
 }

+ 27 - 7
cfc-web/src/views/admin/ServiceContents.vue

@@ -42,13 +42,18 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="180">
-          <template slot-scope="scope">
-            <el-button size="mini" @click="handlePreview(scope.row)">预览</el-button>
-            <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
-            <el-button size="mini" :type="scope.row.status === 'ACTIVE' ? 'warning' : 'success'" @click="toggleStatus(scope.row)">
-              {{ scope.row.status === 'ACTIVE' ? '停用' : '启用' }}
-            </el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="preview" icon="el-icon-view">预览</el-dropdown-item>
+                <el-dropdown-item :command="row.status === 'ACTIVE' ? 'disable' : 'enable'" icon="el-icon-switch-button">{{ row.status === 'ACTIVE' ? '停用' : '启用' }}</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -247,6 +252,21 @@ export default {
         img: '<img src="" alt=""/>'
       }
       this.form.content += tags[tag]
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'preview':
+          this.handlePreview(row)
+          break
+        case 'disable':
+          row.status = 'DISABLED'
+          this.toggleStatus(row)
+          break
+        case 'enable':
+          row.status = 'ACTIVE'
+          this.toggleStatus(row)
+          break
+      }
     }
   }
 }

+ 23 - 6
cfc-web/src/views/admin/ServiceTypes.vue

@@ -21,12 +21,17 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="150">
-          <template slot-scope="scope">
-            <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
-            <el-button size="mini" :type="scope.row.status === 'ACTIVE' ? 'warning' : 'success'" @click="toggleStatus(scope.row)">
-              {{ scope.row.status === 'ACTIVE' ? '停用' : '启用' }}
-            </el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item :command="row.status === 'ACTIVE' ? 'disable' : 'enable'" icon="el-icon-switch-button">{{ row.status === 'ACTIVE' ? '停用' : '启用' }}</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -133,6 +138,18 @@ export default {
       } catch (e) {
         this.$message.error('保存失败')
       }
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'disable':
+          row.status = 'DISABLED'
+          this.toggleStatus(row)
+          break
+        case 'enable':
+          row.status = 'ACTIVE'
+          this.toggleStatus(row)
+          break
+      }
     }
   }
 }

+ 17 - 3
cfc-web/src/views/admin/SysConfig.vue

@@ -17,10 +17,17 @@
           {{ typeLabel(row.valueType) }}
         </template>
       </el-table-column>
-      <el-table-column label="操作" width="180" fixed="right">
+      <el-table-column label="操作" width="200" fixed="right">
         <template slot-scope="{ row }">
-          <el-button type="primary" size="small" @click="editConfig(row)">编辑</el-button>
-          <el-button type="danger" size="small" @click="deleteConfig(row)">删除</el-button>
+          <el-button size="mini" type="primary" @click="editConfig(row)">编辑</el-button>
+          <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+            <el-button size="mini">
+              更多<i class="el-icon-arrow-down el-icon--right"></i>
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
         </template>
       </el-table-column>
     </el-table>
@@ -159,6 +166,13 @@ export default {
       } catch (e) {
         if (e !== 'cancel') this.$message.error('删除失败')
       }
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.deleteConfig(row)
+          break
+      }
     }
   }
 }

+ 21 - 7
cfc-web/src/views/admin/VendorReview.vue

@@ -28,13 +28,22 @@
         </template>
       </el-table-column>
       <el-table-column prop="vendorRejectReason" label="拒绝原因" width="150" show-overflow-tooltip />
-      <el-table-column label="操作" width="200" fixed="right">
-        <template slot-scope="{ row }">
-          <el-button v-if="row.vendorStatus === 'pending'" type="success" size="small" @click="handleReview(row, 'approve')">通过</el-button>
-          <el-button v-if="row.vendorStatus === 'pending'" type="danger" size="small" @click="showRejectDialog(row)">拒绝</el-button>
-          <span v-else class="no-op">—</span>
-        </template>
-      </el-table-column>
+        <el-table-column label="操作" width="160" fixed="right">
+          <template slot-scope="{ row }">
+            <template v-if="row.vendorStatus === 'pending'">
+              <el-button size="mini" type="success" @click="handleReview(row, 'approve')">通过</el-button>
+              <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+                <el-button size="mini">
+                  更多<i class="el-icon-arrow-down el-icon--right"></i>
+                </el-button>
+                <el-dropdown-menu slot="dropdown">
+                  <el-dropdown-item command="reject" icon="el-icon-close">拒绝</el-dropdown-item>
+                </el-dropdown-menu>
+              </el-dropdown>
+            </template>
+            <span v-else class="no-op">—</span>
+          </template>
+        </el-table-column>
     </el-table>
 
     <el-pagination
@@ -128,6 +137,11 @@ export default {
       this.rejectReason = ''
       this.rejectDialogVisible = true
     },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'reject': this.showRejectDialog(row); break
+      }
+    },
     async confirmReject() {
       if (!this.rejectReason) {
         this.$message.warning('请填写拒绝原因')

+ 18 - 4
cfc-web/src/views/admin/VirtualTeamDetail.vue

@@ -37,10 +37,17 @@
             {{ getParentName(scope.row) }}
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="200">
-          <template slot-scope="scope">
-            <el-button size="mini" @click="showReassignDialog(scope.row)">变更上级</el-button>
-            <el-button size="mini" type="danger" @click="handleRemove(scope.row)">移除</el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="showReassignDialog(row)">变更上级</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="remove" icon="el-icon-remove">移除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -267,6 +274,13 @@ export default {
       } finally {
         this.reassignSubmitting = false
       }
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'remove':
+          this.handleRemove(row)
+          break
+      }
     }
   }
 }

+ 18 - 9
cfc-web/src/views/admin/VirtualTeamList.vue

@@ -42,15 +42,18 @@
             {{ formatDate(scope.row.createdAt) }}
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="220">
-          <template slot-scope="scope">
-            <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
-            <el-button
-              size="mini"
-              :type="scope.row.enabled ? 'warning' : 'success'"
-              @click="handleToggle(scope.row)"
-            >{{ scope.row.enabled ? '禁用' : '启用' }}</el-button>
-            <el-button size="mini" type="primary" @click="viewMembers(scope.row)">成员</el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="toggle" icon="el-icon-switch-button">{{ row.enabled ? '禁用' : '启用' }}</el-dropdown-item>
+                <el-dropdown-item command="members" icon="el-icon-user">成员</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -178,6 +181,12 @@ export default {
         if (e !== 'cancel') this.$message.error(action + '失败')
       }
     },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'toggle': this.handleToggle(row); break
+        case 'members': this.viewMembers(row); break
+      }
+    },
     viewMembers(row) {
       this.$router.push('/virtual-team-detail/' + row.id)
     },

+ 16 - 3
cfc-web/src/views/admin/WithdrawAudit.vue

@@ -57,9 +57,16 @@
       </el-table-column>
       <el-table-column label="操作" width="180" fixed="right">
         <template slot-scope="{ row }">
-          <el-button v-if="row.status === 'pending'" type="success" size="small" @click="handleAudit(row, 'approved')">通过</el-button>
-          <el-button v-if="row.status === 'pending'" type="danger" size="small" @click="showRejectDialog(row)">拒绝</el-button>
-          <el-button type="info" size="small" @click="viewDetail(row)">详情</el-button>
+          <el-button size="mini" type="info" @click="viewDetail(row)">详情</el-button>
+          <el-dropdown v-if="row.status === 'pending'" trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+            <el-button size="mini">
+              审核<i class="el-icon-arrow-down el-icon--right"></i>
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item command="approve" icon="el-icon-check">通过</el-dropdown-item>
+              <el-dropdown-item command="reject" icon="el-icon-close">拒绝</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
         </template>
       </el-table-column>
     </el-table>
@@ -188,6 +195,12 @@ export default {
         this.$message.error('操作失败')
       }
     },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'approve': this.handleAudit(row, 'approved'); break
+        case 'reject': this.showRejectDialog(row); break
+      }
+    },
     viewDetail(row) {
       this.detail = row
       this.detailDialogVisible = true

+ 18 - 4
cfc-web/src/views/admin/ZodiacConfigs.vue

@@ -18,10 +18,17 @@
         </el-table-column>
         <el-table-column prop="mindBase" label="心基础分" width="100"></el-table-column>
         <el-table-column prop="wisdomBase" label="智基础分" width="100"></el-table-column>
-        <el-table-column label="操作" width="150">
-          <template slot-scope="scope">
-            <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
-            <el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -163,6 +170,13 @@ export default {
       } catch (e) {
         this.$message.error('保存失败')
       }
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.handleDelete(row)
+          break
+      }
     }
   }
 }

+ 18 - 4
cfc-web/src/views/admin/data-source-config.vue

@@ -27,10 +27,17 @@
         <el-table-column prop="createdAt" label="创建时间" width="160">
           <template slot-scope="scope">{{ formatDate(scope.row.createdAt) }}</template>
         </el-table-column>
-        <el-table-column label="操作" width="150" fixed="right">
-          <template slot-scope="scope">
-            <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
-            <el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -162,6 +169,13 @@ export default {
           }
         })
       }).catch(() => {})
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.handleDelete(row)
+          break
+      }
     }
   }
 }

+ 18 - 4
cfc-web/src/views/admin/dimension-config.vue

@@ -38,10 +38,17 @@
           </template>
         </el-table-column>
         <el-table-column prop="source" label="数据来源" width="120" show-overflow-tooltip></el-table-column>
-        <el-table-column label="操作" width="150" fixed="right">
-          <template slot-scope="scope">
-            <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
-            <el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -201,6 +208,13 @@ export default {
           }
         })
       }).catch(() => {})
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'delete':
+          this.handleDelete(row)
+          break
+      }
     }
   }
 }

+ 19 - 3
cfc-web/src/views/nutritionist/NutritionMappings.vue

@@ -13,10 +13,17 @@
         <el-table-column prop="deficiencySymptoms" label="缺乏症状" min-width="150" show-overflow-tooltip />
         <el-table-column prop="foodSuggestions" label="推荐食物" min-width="180" show-overflow-tooltip />
         <el-table-column prop="sortOrder" label="排序" width="80" />
-        <el-table-column label="操作" width="150" fixed="right">
+        <el-table-column label="操作" width="200" fixed="right">
           <template slot-scope="{ row }">
-            <el-button size="mini" @click="openEditDialog(row)">编辑</el-button>
-            <el-button size="mini" type="danger" @click="handleDelete(row)">删除</el-button>
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -121,6 +128,15 @@ export default {
         this.$message?.error?.('保存失败')
       }
     },
+    handleEdit(row) {
+      this.openEditDialog(row)
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'edit': this.handleEdit(row); break
+        case 'delete': this.handleDelete(row); break
+      }
+    },
     async handleDelete(row) {
       this.$confirm('确认删除映射 "' + row.nutrient + '" 吗?', '提示', { type: 'warning' }).then(async () => {
         try {

+ 21 - 2
cfc-web/src/views/teacher/FamilyList.vue

@@ -10,9 +10,18 @@
         <el-table-column prop="familyName" label="家庭名称" />
         <el-table-column prop="memberCount" label="成员数" width="100" />
         <el-table-column prop="createdAt" label="绑定时间" width="180" />
-        <el-table-column label="操作" width="200">
+        <el-table-column label="操作" width="200" fixed="right">
           <template slot-scope="{ row }">
-            <el-button size="mini" type="primary" @click="viewFamily(row)">查看详情</el-button>
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="view" icon="el-icon-view">查看详情</el-dropdown-item>
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -58,6 +67,16 @@ export default {
     },
     viewFamily(row) {
       this.$router.push('/my-families/' + row.id)
+    },
+    handleEdit(row) {
+      this.viewFamily(row)
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'view': this.viewFamily(row); break
+        case 'edit': this.handleEdit(row); break
+        case 'delete': this.$confirm('确认删除该家庭吗?', '提示', { type: 'warning' }).then(() => {}).catch(() => {}); break
+      }
     }
   }
 }

+ 20 - 5
cfc-web/src/views/teacher/TeacherAssessment.vue

@@ -36,11 +36,18 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="180">
-          <template slot-scope="scope">
-            <el-button v-if="scope.row.isEnabled !== 1" type="text" size="small" @click="enableFamily(scope.row.familyId)">启用</el-button>
-            <el-button v-else type="text" size="small" @click="openEditDialog(scope.row)">修改周期</el-button>
-            <el-button v-if="scope.row.isEnabled === 1" type="text" size="small" style="color: #F56C6C" @click="disableFamily(scope.row.familyId)">关闭</el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button v-if="row.isEnabled !== 1" size="mini" type="primary" @click="enableFamily(row.familyId)">启用</el-button>
+            <el-button v-else size="mini" type="primary" @click="handleEdit(row)">修改周期</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="disable" icon="el-icon-close">关闭</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -140,6 +147,14 @@ export default {
         this.loading = false
       }
     },
+    handleEdit(row) {
+      this.openEditDialog(row)
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'disable': this.disableFamily(row.familyId); break
+      }
+    },
     formatDate(dateStr) {
       if (!dateStr) return '-'
       return new Date(dateStr).toLocaleDateString()

+ 23 - 3
cfc-web/src/views/teacher/TeacherFamilies.vue

@@ -130,9 +130,18 @@
         </el-table-column>
         <el-table-column prop="boundAt" label="绑定时间" width="160"></el-table-column>
         <el-table-column label="操作" width="200" fixed="right">
-          <template slot-scope="scope">
-            <el-button size="small" type="primary" plain @click="handleView(scope.row)">查看详情</el-button>
-            <el-button size="small" type="success" plain @click="handleMessage(scope.row)">发消息</el-button>
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="view" icon="el-icon-view">查看详情</el-dropdown-item>
+                <el-dropdown-item command="message" icon="el-icon-chat-dot-round">发消息</el-dropdown-item>
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -275,6 +284,17 @@ export default {
     handleMessage(row) {
       this.$router.push({ path: '/teacher-messages', query: { familyId: row.familyId } })
     },
+    handleEdit(row) {
+      this.handleView(row)
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'view': this.handleView(row); break
+        case 'edit': this.handleEdit(row); break
+        case 'message': this.handleMessage(row); break
+        case 'delete': this.$confirm('确认删除该家庭吗?', '提示', { type: 'warning' }).then(() => {}).catch(() => {}); break
+      }
+    },
 
     getCompletionColor(rate) {
       if (rate >= 80) return '#10B981'

+ 22 - 3
cfc-web/src/views/teacher/TeacherOrders.vue

@@ -143,9 +143,18 @@
           </template>
         </el-table-column>
         <el-table-column prop="createdAt" label="创建时间" width="160"></el-table-column>
-        <el-table-column label="操作" width="100" fixed="right">
-          <template slot-scope="scope">
-            <el-button size="small" type="text" @click="handleView(scope.row)">详情</el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="view" icon="el-icon-view">查看详情</el-dropdown-item>
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -286,6 +295,16 @@ export default {
       this.currentOrder = row
       this.drawerVisible = true
     },
+    handleEdit(row) {
+      this.handleView(row)
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'view': this.handleView(row); break
+        case 'edit': this.handleEdit(row); break
+        case 'delete': this.$confirm('确认删除该订单吗?', '提示', { type: 'warning' }).then(() => {}).catch(() => {}); break
+      }
+    },
     handleExport() {
       this.$message.success('导出功能开发中')
     },

+ 19 - 4
cfc-web/src/views/teacher/TeacherPackages.vue

@@ -108,10 +108,18 @@
           </template>
         </el-table-column>
         <el-table-column prop="createdAt" label="创建时间" width="160"></el-table-column>
-        <el-table-column label="操作" width="150" fixed="right">
-          <template slot-scope="scope">
-            <el-button size="small" type="text" @click="handleEdit(scope.row)">编辑</el-button>
-            <el-button size="small" type="text" @click="handleDuplicate(scope.row)">复制</el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="duplicate" icon="el-icon-copy-document">复制</el-dropdown-item>
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -300,6 +308,13 @@ export default {
       }
       this.dialogVisible = true
     },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'duplicate': this.handleDuplicate(row); break
+        case 'edit': this.handleEdit(row); break
+        case 'delete': this.$confirm('确认删除该套餐吗?', '提示', { type: 'warning' }).then(() => {}).catch(() => {}); break
+      }
+    },
     async toggleStatus(row) {
       try {
         await updatePackageStatus(row.id, row.status)

+ 22 - 3
cfc-web/src/views/teacher/TeacherTeam.vue

@@ -129,9 +129,18 @@
             <span class="commission-value">+{{ formatPriceWithSymbol(Math.round((scope.row.monthlySales || 0) * 0.1)) }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="120" fixed="right">
-          <template slot-scope="scope">
-            <el-button size="small" type="text" @click="handleView(scope.row)">详情</el-button>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+              <el-button size="mini">
+                更多<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="view" icon="el-icon-view">查看详情</el-dropdown-item>
+                <el-dropdown-item command="delete" icon="el-icon-delete">删除</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
           </template>
         </el-table-column>
       </el-table>
@@ -258,6 +267,16 @@ export default {
       this.currentMember = row
       this.drawerVisible = true
     },
+    handleEdit(row) {
+      this.handleView(row)
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'view': this.handleView(row); break
+        case 'edit': this.handleEdit(row); break
+        case 'delete': this.$confirm('确认删除该成员吗?', '提示', { type: 'warning' }).then(() => {}).catch(() => {}); break
+      }
+    },
     handleSearch() {
     },
     handleGenerateCode() {