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

Merge remote-tracking branch 'origin/cfclub' into cfclub

# Conflicts:
#	cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
Sisyphus пре 3 дана
родитељ
комит
c0bc46b1d8
100 измењених фајлова са 964 додато и 211 уклоњено
  1. 113 23
      cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
  2. 1 0
      cfc-backend/src/main/java/com/etotem/cfc/config/JwtInterceptor.java
  3. 55 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/auth/AuthController.java
  4. 44 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/SsoTicket.java
  5. 9 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/SsoTicketMapper.java
  6. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/service/DietIngredientService.java
  7. 95 0
      cfc-backend/src/main/java/com/etotem/cfc/service/SsoService.java
  8. 39 0
      cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java
  9. 15 0
      cfc-backend/src/main/resources/schema.sql
  10. 30 2
      cfc-frontend/App.vue
  11. 35 0
      cfc-frontend/pages/profile/index.vue
  12. 9 0
      cfc-frontend/utils/api.js
  13. 34 0
      cfc-frontend/utils/sso.js
  14. 86 0
      cfc-web/src/styles/admin-tokens.css
  15. 3 1
      cfc-web/src/views/Children.vue
  16. 3 1
      cfc-web/src/views/Dashboard.vue
  17. 12 5
      cfc-web/src/views/Families.vue
  18. 8 4
      cfc-web/src/views/GuideFamilyTask.vue
  19. 5 2
      cfc-web/src/views/GuidePackages.vue
  20. 3 1
      cfc-web/src/views/OperationLogs.vue
  21. 3 1
      cfc-web/src/views/Points.vue
  22. 9 5
      cfc-web/src/views/Rewards.vue
  23. 4 2
      cfc-web/src/views/TaskTemplates.vue
  24. 5 2
      cfc-web/src/views/Tasks.vue
  25. 3 1
      cfc-web/src/views/TeacherConsult.vue
  26. 3 1
      cfc-web/src/views/TeacherDashboard.vue
  27. 5 1
      cfc-web/src/views/Users.vue
  28. 22 6
      cfc-web/src/views/Wishes.vue
  29. 7 2
      cfc-web/src/views/admin/Activities.vue
  30. 3 3
      cfc-web/src/views/admin/ActivityRegistrationReview.vue
  31. 3 1
      cfc-web/src/views/admin/ActivityReview.vue
  32. 5 4
      cfc-web/src/views/admin/ArticleCategory.vue
  33. 4 3
      cfc-web/src/views/admin/ArticleManage.vue
  34. 2 2
      cfc-web/src/views/admin/Assessment.vue
  35. 3 1
      cfc-web/src/views/admin/AssessmentAssign.vue
  36. 3 1
      cfc-web/src/views/admin/AssessmentOrders.vue
  37. 3 1
      cfc-web/src/views/admin/AssessmentPlanRules.vue
  38. 3 1
      cfc-web/src/views/admin/AssessmentProducts.vue
  39. 3 1
      cfc-web/src/views/admin/BadgeManage.vue
  40. 5 4
      cfc-web/src/views/admin/BaziConfigs.vue
  41. 5 3
      cfc-web/src/views/admin/BloodTypeConfigs.vue
  42. 3 1
      cfc-web/src/views/admin/ButlerAssignments.vue
  43. 6 2
      cfc-web/src/views/admin/CfValueManage.vue
  44. 3 1
      cfc-web/src/views/admin/ChallengeScoreConfig.vue
  45. 3 1
      cfc-web/src/views/admin/CommentReview.vue
  46. 3 3
      cfc-web/src/views/admin/CouponGrantLog.vue
  47. 3 3
      cfc-web/src/views/admin/CouponManagement.vue
  48. 3 1
      cfc-web/src/views/admin/DanExecution.vue
  49. 3 1
      cfc-web/src/views/admin/DanSettlement.vue
  50. 3 1
      cfc-web/src/views/admin/Dashboard.vue
  51. 3 3
      cfc-web/src/views/admin/DiscountRuleManagement.vue
  52. 5 4
      cfc-web/src/views/admin/EcomSupplierManage.vue
  53. 3 1
      cfc-web/src/views/admin/EmotionAlertManage.vue
  54. 3 1
      cfc-web/src/views/admin/EnergyBehaviorConfig.vue
  55. 3 1
      cfc-web/src/views/admin/EnergyRuleManagement.vue
  56. 3 1
      cfc-web/src/views/admin/EnergySandbox.vue
  57. 5 4
      cfc-web/src/views/admin/FamilyEarnings.vue
  58. 3 3
      cfc-web/src/views/admin/FamilyEarningsWithdraw.vue
  59. 3 1
      cfc-web/src/views/admin/FileManage.vue
  60. 4 2
      cfc-web/src/views/admin/Foods.vue
  61. 3 1
      cfc-web/src/views/admin/GrowthTaskManagement.vue
  62. 3 1
      cfc-web/src/views/admin/GuidePopupConfig.vue
  63. 3 1
      cfc-web/src/views/admin/GuidePopupStats.vue
  64. 3 1
      cfc-web/src/views/admin/HealthCheckinManage.vue
  65. 5 2
      cfc-web/src/views/admin/HealthPlanManage.vue
  66. 12 5
      cfc-web/src/views/admin/HealthReportEdit.vue
  67. 3 1
      cfc-web/src/views/admin/HealthReportReassign.vue
  68. 3 3
      cfc-web/src/views/admin/HomeBannerManage.vue
  69. 3 1
      cfc-web/src/views/admin/InnatePortraitConfig.vue
  70. 27 6
      cfc-web/src/views/admin/InventoryManage.vue
  71. 3 1
      cfc-web/src/views/admin/KnowledgeTag.vue
  72. 12 4
      cfc-web/src/views/admin/LangGraphAdmin.vue
  73. 3 1
      cfc-web/src/views/admin/MbtiProfiles.vue
  74. 10 9
      cfc-web/src/views/admin/MembershipCenter.vue
  75. 3 3
      cfc-web/src/views/admin/MenuManage.vue
  76. 3 3
      cfc-web/src/views/admin/NoticeManage.vue
  77. 3 1
      cfc-web/src/views/admin/NotificationTemplateManage.vue
  78. 3 1
      cfc-web/src/views/admin/NumSoulDetailConfig.vue
  79. 8 4
      cfc-web/src/views/admin/NutritionProducts.vue
  80. 1 1
      cfc-web/src/views/admin/OrderManage.vue
  81. 3 3
      cfc-web/src/views/admin/PendingRefund.vue
  82. 3 1
      cfc-web/src/views/admin/PeriodicServiceConfig.vue
  83. 3 1
      cfc-web/src/views/admin/PlanManagement.vue
  84. 3 3
      cfc-web/src/views/admin/PointsLog.vue
  85. 3 1
      cfc-web/src/views/admin/ProblemDomainManage.vue
  86. 7 3
      cfc-web/src/views/admin/ProductManage.vue
  87. 3 1
      cfc-web/src/views/admin/ProductPpointManage.vue
  88. 3 1
      cfc-web/src/views/admin/ProductProfitRate.vue
  89. 3 1
      cfc-web/src/views/admin/ProfileManagement.vue
  90. 8 4
      cfc-web/src/views/admin/PromotionManagement.vue
  91. 3 1
      cfc-web/src/views/admin/PurchaseManagement.vue
  92. 5 4
      cfc-web/src/views/admin/Recipes.vue
  93. 3 3
      cfc-web/src/views/admin/RecommendationRules.vue
  94. 3 1
      cfc-web/src/views/admin/ReportAutoLearn.vue
  95. 8 4
      cfc-web/src/views/admin/ReportCollectorTrainer.vue
  96. 3 1
      cfc-web/src/views/admin/ReportManagement.vue
  97. 5 2
      cfc-web/src/views/admin/ReportParserImport.vue
  98. 3 1
      cfc-web/src/views/admin/ReportTemplateManage.vue
  99. 5 2
      cfc-web/src/views/admin/ReportTypeManagement.vue
  100. 5 2
      cfc-web/src/views/admin/ReportUnknownCluster.vue

+ 113 - 23
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -2983,6 +2983,12 @@ insertSysConfigSeed("gift_membership_pay_price", "198000", "送会员商品达
 
         runSystemMigrations();
         registerCollectExpectedIndicators();
+
+        // 减脂专题表创建
+        createFatWeightRecordTable();
+        createFatDietRecordTable();
+        createFatExerciseRecordTable();
+        createFatGoalTable();
     }
 
     private void migrateHealthDimensionsTable() {
@@ -10946,6 +10952,7 @@ log.info("迁移298: 已为无 openid 的 child 账号补齐 family_members 记
         createTaskReminderConfigTable();
         addMissingColumnsToRecommendationLog();
         createDietFamilySelectedIngredientsTable();
+        createSsoTicketTable();
 
         // 迁移341: BASIC 体验会员 features JSON 补 referral_commission(迁移315 未包含,前端对比表已显示可参与)
         try {
@@ -11853,30 +11860,26 @@ log.info("迁移298: 已为无 openid 的 child 账号补齐 family_members 记
         }
     }
 
-    // 迁移354: 家庭共餐选中食材表(持久化共餐场景食材选择)
-    private void createDietFamilySelectedIngredientsTable() {
+    // 迁移359: 跨小程序单点登录一次性票据表(cfc ↔ train 双向免登)
+    // 详见 schema.sql。此处自愈建表,避免 schema.sql 建表语句因故静默失败导致登录报错。
+    private void createSsoTicketTable() {
         try {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS diet_family_selected_ingredients (" +
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS sso_ticket (" +
                     "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID'," +
-                    "family_id BIGINT NOT NULL COMMENT '家庭ID'," +
-                    "food_id BIGINT NOT NULL COMMENT '食材ID'," +
-                    "food_name VARCHAR(100) COMMENT '食材名称'," +
-                    "score INT COMMENT '推荐分'," +
-                    "reason VARCHAR(255) COMMENT '推荐理由'," +
-                    "category VARCHAR(50) COMMENT '分类'," +
-                    "calories_per_100g INT COMMENT '每100克卡路里'," +
-                    "cooking_level TINYINT COMMENT '烹饪难度(1=快手,2=中等,3=耗时)'," +
-                    "nutrition_score INT COMMENT '营养密度分(0-100)'," +
-                    "balance_reason VARCHAR(255) COMMENT '均衡化推荐理由'," +
-                    "sort_order INT DEFAULT 0 COMMENT '排序'," +
+                    "ticket VARCHAR(64) NOT NULL COMMENT '一次性票据(SecureRandom 生成)'," +
+                    "source_system VARCHAR(16) NOT NULL COMMENT '签发方系统: cfc / train'," +
+                    "target_system VARCHAR(16) NOT NULL COMMENT '目标系统: cfc / train'," +
+                    "source_user_id BIGINT NOT NULL COMMENT '签发方用户ID(cfc=users.id / train=train_user.id)'," +
+                    "cfc_user_id BIGINT NOT NULL COMMENT 'cfc 统一会员ID(users.id),跨系统换票依据'," +
+                    "used TINYINT NOT NULL DEFAULT 0 COMMENT '是否已使用: 0=未用 1=已用'," +
+                    "expire_at DATETIME NOT NULL COMMENT '过期时间'," +
                     "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'," +
-                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'," +
-                    "UNIQUE KEY uk_family_food (family_id, food_id)," +
-                    "INDEX idx_family (family_id)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭共餐选中食材'");
-            log.info("迁移354: 已创建 diet_family_selected_ingredients 表");
+                    "UNIQUE KEY uk_ticket (ticket)," +
+                    "INDEX idx_expire (expire_at)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='跨小程序单点登录一次性票据'");
+            log.info("迁移359: 已创建 sso_ticket 表");
         } catch (Exception e) {
-            log.warn("迁移354: 创建 diet_family_selected_ingredients 表失败: {}", e.getMessage());
+            log.warn("迁移359: 创建 sso_ticket 表失败: {}", e.getMessage());
         }
     }
 
@@ -12049,7 +12052,7 @@ log.info("迁移298: 已为无 openid 的 child 账号补齐 family_members 记
         return sb.append(']').toString();
     }
 
-    // 迁移354: 家庭共餐选中食材表(持久化共餐场景食材选择)
+    // 迁移359: 家庭共餐选中食材表(持久化共餐场景食材选择)
     private void createDietFamilySelectedIngredientsTable() {
         try {
             jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS diet_family_selected_ingredients (" +
@@ -12070,9 +12073,96 @@ log.info("迁移298: 已为无 openid 的 child 账号补齐 family_members 记
                     "UNIQUE KEY uk_family_food (family_id, food_id)," +
                     "INDEX idx_family (family_id)" +
                     ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭共餐选中食材'");
-            log.info("迁移354: 已创建 diet_family_selected_ingredients 表");
+            log.info("迁移359: 已创建 diet_family_selected_ingredients 表");
+        } catch (Exception e) {
+            log.warn("迁移359: 创建 diet_family_selected_ingredients 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移355: 创建减脂专题体重记录表
+    private void createFatWeightRecordTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS fat_weight_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL, " +
+                    "weight DECIMAL(5,2) NOT NULL, " +
+                    "body_fat_percent DECIMAL(4,2) DEFAULT NULL, " +
+                    "waist_circumference DECIMAL(4,2) DEFAULT NULL, " +
+                    "measure_date DATE NOT NULL, " +
+                    "remark VARCHAR(255) DEFAULT NULL, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_fat_weight_member (member_id, measure_date)) " +
+                    "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='减脂专题-体重体脂记录'");
+            log.info("迁移355: 已创建fat_weight_record表");
+        } catch (Exception e) {
+            log.warn("迁移355: 创建fat_weight_record表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移356: 创建减脂专题饮食记录表
+    private void createFatDietRecordTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS fat_diet_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL, " +
+                    "meal_type VARCHAR(20) NOT NULL, " +
+                    "food_name VARCHAR(255) NOT NULL, " +
+                    "food_weight DECIMAL(6,2) DEFAULT NULL, " +
+                    "calories INT DEFAULT 0, " +
+                    "protein DECIMAL(6,2) DEFAULT 0, " +
+                    "fat DECIMAL(6,2) DEFAULT 0, " +
+                    "carbs DECIMAL(6,2) DEFAULT 0, " +
+                    "eat_date DATE NOT NULL, " +
+                    "remark VARCHAR(255) DEFAULT NULL, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_fat_diet_member (member_id, eat_date)) " +
+                    "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='减脂专题-饮食记录'");
+            log.info("迁移356: 已创建fat_diet_record表");
+        } catch (Exception e) {
+            log.warn("迁移356: 创建fat_diet_record表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移357: 创建减脂专题运动记录表
+    private void createFatExerciseRecordTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS fat_exercise_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL, " +
+                    "exercise_type VARCHAR(50) NOT NULL, " +
+                    "duration_min INT DEFAULT 0, " +
+                    "distance_km DECIMAL(5,2) DEFAULT NULL, " +
+                    "calories_burned INT DEFAULT 0, " +
+                    "exercise_date DATE NOT NULL, " +
+                    "remark VARCHAR(255) DEFAULT NULL, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_fat_exercise_member (member_id, exercise_date)) " +
+                    "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='减脂专题-运动记录'");
+            log.info("迁移357: 已创建fat_exercise_record表");
+        } catch (Exception e) {
+            log.warn("迁移357: 创建fat_exercise_record表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移358: 创建减脂专题目标表
+    private void createFatGoalTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS fat_goal (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL, " +
+                    "target_weight DECIMAL(5,2) NOT NULL, " +
+                    "start_weight DECIMAL(5,2) NOT NULL, " +
+                    "target_body_fat DECIMAL(4,2) DEFAULT NULL, " +
+                    "target_date DATE DEFAULT NULL, " +
+                    "status VARCHAR(20) DEFAULT 'active', " +
+                    "start_date DATE NOT NULL, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_fat_goal_member (member_id)) " +
+                    "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='减脂专题-减脂目标'");
+            log.info("迁移358: 已创建fat_goal表");
         } catch (Exception e) {
-            log.warn("迁移354: 创建 diet_family_selected_ingredients 表失败: {}", e.getMessage());
+            log.warn("迁移358: 创建fat_goal表失败: {}", e.getMessage());
         }
     }
 }

+ 1 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/JwtInterceptor.java

@@ -34,6 +34,7 @@ private static final String[] PUBLIC_PATHS = {
         "/api/auth/phone-login",
         "/api/auth/wechat-phone-login",
         "/api/auth/silent-login",
+        "/api/auth/sso-login",
         "/api/auth/auto-login",
         "/api/auth/register-with-idcard",
         "/api/auth/wechat-login",

+ 55 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/auth/AuthController.java

@@ -7,6 +7,7 @@ import com.etotem.cfc.dto.RegisterWithIdCardDTO;
 import com.etotem.cfc.dto.RegisterWithInviteCodeDTO;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.mapper.UserMapper;
+import com.etotem.cfc.service.SsoService;
 import com.etotem.cfc.service.UserService;
 import com.etotem.cfc.service.VerificationCodeService;
 import io.jsonwebtoken.Claims;
@@ -39,6 +40,9 @@ public class AuthController {
     @Resource
     private UserMapper userMapper;
 
+    @Resource
+    private SsoService ssoService;
+
     /**
      * 发送验证码
      */
@@ -336,4 +340,55 @@ public class AuthController {
         }
     }
 
+    /**
+     * 跨小程序单点登录 - 签发一次性票据(需登录)。
+     * 请求体: { "target": "train" };返回 { ticket, target }。
+     * 前端拿到 ticket 后通过 uni.navigateToMiniProgram 跳转到目标小程序的落地页。
+     */
+    @Operation(summary = "SSO 签发一次性票据")
+    @PostMapping("/sso-ticket")
+    public Result<Map<String, Object>> ssoTicket(@RequestBody Map<String, String> body,
+                                                 @RequestAttribute("userId") Long userId) {
+        if (userId == null || userId <= 0) {
+            return Result.error(401, "请先登录");
+        }
+        String target = body != null ? body.get("target") : null;
+        if (!SsoService.SYS_TRAIN.equals(target)) {
+            return Result.error("不支持的目标系统");
+        }
+        try {
+            String ticket = ssoService.issueTicket(SsoService.SYS_CFC, target, userId, userId);
+            Map<String, Object> data = new HashMap<>();
+            data.put("ticket", ticket);
+            data.put("target", target);
+            return Result.success(data);
+        } catch (Exception e) {
+            log.error("签发 SSO 票据失败: userId={}", userId, e);
+            return Result.error("签发票据失败,请稍后重试");
+        }
+    }
+
+    /**
+     * 跨小程序单点登录 - 消费一次性票据换取 cfc 登录态(公开接口)。
+     * 请求体: { "ticket": "..." }。
+     */
+    @Operation(summary = "SSO 票据换取登录态")
+    @PostMapping("/sso-login")
+    public Result<LoginResultDTO> ssoLogin(@RequestBody Map<String, String> body) {
+        String ticket = body != null ? body.get("ticket") : null;
+        if (ticket == null || ticket.trim().isEmpty()) {
+            return Result.error("票据不能为空");
+        }
+        Long cfcUserId = ssoService.consumeTicket(ticket, SsoService.SYS_CFC);
+        if (cfcUserId == null) {
+            return Result.error("票据无效或已过期,请重新进入");
+        }
+        try {
+            LoginResultDTO result = userService.ssoLogin(cfcUserId);
+            return Result.success(result);
+        } catch (RuntimeException e) {
+            return Result.error(e.getMessage());
+        }
+    }
+
 }

+ 44 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/SsoTicket.java

@@ -0,0 +1,44 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 跨小程序单点登录一次性票据(cfc ↔ train 双向免登)。
+ * cfc 与 train 共用本库(zxyj),签发方写入,目标方原子占用(used 0→1)。
+ */
+@Data
+@TableName("sso_ticket")
+public class SsoTicket implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 一次性票据(SecureRandom 生成) */
+    private String ticket;
+
+    /** 签发方系统: cfc / train */
+    private String sourceSystem;
+
+    /** 目标系统: cfc / train */
+    private String targetSystem;
+
+    /** 签发方用户ID(cfc=users.id / train=train_user.id) */
+    private Long sourceUserId;
+
+    /** cfc 统一会员ID(users.id),跨系统换票依据 */
+    private Long cfcUserId;
+
+    /** 是否已使用: 0=未用 1=已用 */
+    private Integer used;
+
+    /** 过期时间 */
+    private Date expireAt;
+
+    private Date createdAt;
+}

+ 9 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/SsoTicketMapper.java

@@ -0,0 +1,9 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.SsoTicket;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface SsoTicketMapper extends BaseMapper<SsoTicket> {
+}

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

@@ -80,7 +80,7 @@ public class DietIngredientService {
 
     public Map<String, Object> addIngredient(Long familyId, Long foodId, String name) {
         // 幂等插入
-        Integer count = selectedIngredientMapper.selectCount(
+        Long count = selectedIngredientMapper.selectCount(
                 new LambdaQueryWrapper<DietFamilySelectedIngredient>()
                         .eq(DietFamilySelectedIngredient::getFamilyId, familyId)
                         .eq(DietFamilySelectedIngredient::getFoodId, foodId));

+ 95 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/SsoService.java

@@ -0,0 +1,95 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.etotem.cfc.entity.SsoTicket;
+import com.etotem.cfc.mapper.SsoTicketMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.security.SecureRandom;
+import java.util.Date;
+
+/**
+ * 跨小程序单点登录一次性票据服务(cfc ↔ train 双向免登)。
+ * cfc 与 train 共用本库(zxyj),ticket 由签发方写入,目标方原子占用(used 0→1)。
+ */
+@Slf4j
+@Service
+public class SsoService {
+
+    /** 系统标识:cfc */
+    public static final String SYS_CFC = "cfc";
+    /** 系统标识:train */
+    public static final String SYS_TRAIN = "train";
+
+    private static final SecureRandom RANDOM = new SecureRandom();
+
+    @Resource
+    private SsoTicketMapper ssoTicketMapper;
+
+    /** 票据有效期(毫秒),默认 5 分钟 */
+    @Value("${sso.ticket-ttl-ms:300000}")
+    private long ticketTtlMs;
+
+    /**
+     * 签发一次性票据。
+     *
+     * @param sourceSystem 签发方系统标识(cfc / train)
+     * @param targetSystem 目标系统标识(cfc / train)
+     * @param sourceUserId 签发方用户ID(cfc=users.id / train=train_user.id)
+     * @param cfcUserId    cfc 统一会员ID(users.id),跨系统换票依据
+     * @return 票据字符串(64 位十六进制)
+     */
+    public String issueTicket(String sourceSystem, String targetSystem, Long sourceUserId, Long cfcUserId) {
+        SsoTicket t = new SsoTicket();
+        t.setTicket(generateTicket());
+        t.setSourceSystem(sourceSystem);
+        t.setTargetSystem(targetSystem);
+        t.setSourceUserId(sourceUserId);
+        t.setCfcUserId(cfcUserId);
+        t.setUsed(0);
+        t.setExpireAt(new Date(System.currentTimeMillis() + ticketTtlMs));
+        t.setCreatedAt(new Date());
+        ssoTicketMapper.insert(t);
+        return t.getTicket();
+    }
+
+    /**
+     * 原子消费票据:仅当「票据未使用 + 未过期 + 目标系统匹配」时置 used=1。
+     * 并发/重放场景下若多请求同时命中,只有 update 影响行数=1 的那一个成功。
+     *
+     * @return 成功返回 cfc_user_id;无效/已用/过期/目标不符返回 null
+     */
+    public Long consumeTicket(String ticket, String targetSystem) {
+        if (ticket == null || ticket.trim().isEmpty()) {
+            return null;
+        }
+        int updated = ssoTicketMapper.update(null, new LambdaUpdateWrapper<SsoTicket>()
+                .eq(SsoTicket::getTicket, ticket)
+                .eq(SsoTicket::getTargetSystem, targetSystem)
+                .eq(SsoTicket::getUsed, 0)
+                .gt(SsoTicket::getExpireAt, new Date())
+                .set(SsoTicket::getUsed, 1));
+        if (updated != 1) {
+            log.warn("SSO 票据消费失败(无效/已用/过期/目标不符): target={}", targetSystem);
+            return null;
+        }
+        SsoTicket t = ssoTicketMapper.selectOne(new LambdaQueryWrapper<SsoTicket>()
+                .eq(SsoTicket::getTicket, ticket));
+        return t != null ? t.getCfcUserId() : null;
+    }
+
+    private String generateTicket() {
+        byte[] bytes = new byte[32];
+        RANDOM.nextBytes(bytes);
+        StringBuilder sb = new StringBuilder(64);
+        for (byte b : bytes) {
+            sb.append(Character.forDigit((b >> 4) & 0xF, 16));
+            sb.append(Character.forDigit(b & 0xF, 16));
+        }
+        return sb.toString();
+    }
+}

+ 39 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java

@@ -655,6 +655,45 @@ private FamilyInvitationService familyInvitationService;
         return result;
     }
 
+    /**
+     * 跨小程序单点登录(SSO):凭一次性票据换取的 cfc_user_id 直接建立登录态。
+     *
+     * <p>身份可信度来自票据本身:短时效(默认 5 分钟)+ 一次性(消费即置 used=1)+ 签发方鉴权
+     * (签发时必须携带有效 cfc 登录态)。票据只在本服务与 train 共享的库中传递,不依赖 wx.login。
+     *
+     * @param cfcUserId cfc 统一会员ID(users.id)
+     */
+    public LoginResultDTO ssoLogin(Long cfcUserId) {
+        if (cfcUserId == null) {
+            throw new RuntimeException("SSO 票据无效");
+        }
+        User user = userMapper.selectById(cfcUserId);
+        if (user == null) {
+            throw new RuntimeException("用户不存在");
+        }
+        // 管理员不能登录小程序
+        if ("admin".equals(user.getRole())) {
+            throw new RuntimeException("该账号不允许登录小程序");
+        }
+        ensureUserHasFamily(user);
+
+        String token = jwtConfig.generateToken(user.getId(), user.getRole());
+        LoginResultDTO result = new LoginResultDTO();
+        result.setToken(token);
+        result.setUserId(user.getId());
+        result.setRole(user.getRole());
+        result.setRoles(user.getRoles());
+        result.setFamilyId(user.getFamilyId());
+        result.setNickname(user.getNickname());
+        result.setPhone(user.getPhone());
+        result.setIsNewUser(false);
+        result.setOpenid(user.getOpenid());
+        result.setNeedsFamily(user.getFamilyId() == null || user.getFamilyId() == 0L);
+        populateTeacherFields(result, user);
+        populateActivityProviderFields(result, user);
+        return result;
+    }
+
     /**
      * 获取用户所有角色列表
      */

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

@@ -6172,3 +6172,18 @@ CREATE TABLE IF NOT EXISTS fat_goal (
     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
     INDEX idx_fat_goal_member (member_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='减脂专题-减脂目标';
+-- 迁移359: 跨小程序单点登录一次性票据(cfc ↔ train 双向免登)
+-- cfc 与 train 共用本库(zxyj)。签发方写入 ticket,目标方原子占用(used 0→1)后按 cfc_user_id 建立登录态。
+CREATE TABLE IF NOT EXISTS sso_ticket (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    ticket VARCHAR(64) NOT NULL COMMENT '一次性票据(SecureRandom 生成)',
+    source_system VARCHAR(16) NOT NULL COMMENT '签发方系统: cfc / train',
+    target_system VARCHAR(16) NOT NULL COMMENT '目标系统: cfc / train',
+    source_user_id BIGINT NOT NULL COMMENT '签发方用户ID(cfc=users.id / train=train_user.id)',
+    cfc_user_id BIGINT NOT NULL COMMENT 'cfc 统一会员ID(users.id),跨系统换票依据',
+    used TINYINT NOT NULL DEFAULT 0 COMMENT '是否已使用: 0=未用 1=已用',
+    expire_at DATETIME NOT NULL COMMENT '过期时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    UNIQUE KEY uk_ticket (ticket),
+    INDEX idx_expire (expire_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='跨小程序单点登录一次性票据';

+ 30 - 2
cfc-frontend/App.vue

@@ -1,7 +1,7 @@
 <script>
 import config from '@/config.js'
 import Vue from 'vue'
-import { checkUserFamily, verifyToken, bindReferralIfNotSelf, silentRelogin } from './utils/api.js'
+import { checkUserFamily, verifyToken, bindReferralIfNotSelf, silentRelogin, ssoLogin } from './utils/api.js'
 Vue.prototype.$config = config
 
 export default {
@@ -19,7 +19,7 @@ export default {
     // inviteCode=推荐码(文章/活动/商品分享)| refCode=推广码 | token/familyInviteCode=家庭邀请令牌 | id=内容详情
     var hasDeepLink = options && options.query && (
       options.query.inviteCode || options.query.refCode || options.query.token ||
-      options.query.familyInviteCode || options.query.id
+      options.query.familyInviteCode || options.query.id || options.query.ssoTicket
     )
     // 冷启动:跳转到 splash 页展示过渡动画,2秒后自动进入首页
     if (options.scene !== 1047 && options.scene !== 1048 && options.scene !== 1049 && !hasDeepLink) {
@@ -27,6 +27,8 @@ export default {
       uni.redirectTo({ url: '/pages/splash/index' })
       return
     }
+    // 跨小程序单点登录:携带 ssoTicket 进入 → 换取本地登录态
+    this.handleSsoLogin(options)
     // 处理小程序码场景值(从 wxacode 扫码进入,跳过 splash)
     this.handleScene(options)
   },
@@ -34,6 +36,8 @@ export default {
     console.log('App Show', options)
     // 热启动(含从分享卡重新进入):同样捕获分享参数
     this.captureReferralParams(options)
+    // 跨小程序单点登录:热启动携带 ssoTicket 进入同样处理
+    this.handleSsoLogin(options)
     // 每次 app 展示时也检查场景值(小程序码可能从冷启动后的 onShow 传入)
     this.handleScene(options)
     
@@ -167,6 +171,30 @@ bindReferralIfNotSelf(code).then(function(res) {
       // 同步 familyId 到 store(hasFamily getter 依赖;静默续期路径)
       this.$store.commit('setFamilyId', data.familyId)
     },
+    // 跨小程序单点登录:从 train 跳转进入时携带 ssoTicket → 换取本地登录态。
+    // 一次性票据(后端原子消费),用 _ssoHandledTicket 去重,避免 onLaunch/onShow 双处理。
+    handleSsoLogin(options) {
+      if (!options || !options.query) return
+      var ticket = options.query.ssoTicket
+      if (!ticket) return
+      if (this._ssoHandledTicket === ticket) return
+      this._ssoHandledTicket = ticket
+      var self = this
+      uni.showLoading({ title: '登录中...' })
+      ssoLogin(ticket).then(function(res) {
+        uni.hideLoading()
+        if (res && res.code === 200 && res.data) {
+          self.saveLoginInfo(res.data)
+          // 清空路由票据(防转发泄漏)并落地首页;index/index 会按角色重新分流
+          uni.reLaunch({ url: '/pages/index/index' })
+        } else {
+          uni.showToast({ title: (res && res.message) || '单点登录失败', icon: 'none' })
+        }
+      }).catch(function(e) {
+        uni.hideLoading()
+        uni.showToast({ title: (e && e.message) || '单点登录失败', icon: 'none' })
+      })
+    },
     // 处理小程序码场景值
     handleScene(options) {
       if (!options || !options.query) return

+ 35 - 0
cfc-frontend/pages/profile/index.vue

@@ -14,6 +14,11 @@
       </view>
       <ProfileHeader @avatar-click="goToEditInfo" />
       <ProfileMenu :role="role" @invite-generate="onInviteGenerate" />
+      <view class="sso-entry" @click="goTrain">
+        <text class="sso-entry-icon">🎓</text>
+        <text class="sso-entry-text">进入训练营</text>
+        <text class="sso-entry-arrow">›</text>
+      </view>
     </template>
   </view>
 </template>
@@ -22,6 +27,7 @@
 import ProfileHeader from '@/pages/profile/components/ProfileHeader.vue'
 import ProfileMenu from '@/pages/profile/components/ProfileMenu.vue'
 import { getReferralCode, extractReferralCode } from '../../utils/api.js'
+import { jumpToTrain } from '../../utils/sso.js'
 
 export default {
   components: {
@@ -107,6 +113,9 @@ export default {
         url: '/pages/user-edit/user-edit'
       })
     },
+    goTrain() {
+      jumpToTrain()
+    },
     onInviteGenerate(data) {
       this.shareData = data
       uni.showToast({
@@ -189,4 +198,30 @@ export default {
   font-size: 28rpx;
   color: #666;
 }
+/* 跨小程序单点登录入口 */
+.sso-entry {
+  display: flex;
+  align-items: center;
+  margin: 20rpx 30rpx 0;
+  padding: 28rpx 24rpx;
+  background: #fff;
+  border-radius: 16rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05);
+}
+.sso-entry:active {
+  background: #f8fafc;
+}
+.sso-entry-icon {
+  font-size: 40rpx;
+  margin-right: 16rpx;
+}
+.sso-entry-text {
+  flex: 1;
+  font-size: 30rpx;
+  color: #333;
+}
+.sso-entry-arrow {
+  font-size: 40rpx;
+  color: #cbd5e1;
+}
 </style>

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

@@ -3385,3 +3385,12 @@ export const getVendorTeamCustomers = (params) =>
 
 export const bindVendorCustomer = (params) =>
   request('/api/admin/supply-team/bind-customer', 'POST', params)
+
+// ===================== 跨小程序单点登录(SSO:cfc ↔ train)=====================
+/** 签发跳转到 train 的一次性票据。返回 { ticket, target } */
+export const createSsoTicket = (target) =>
+  request('/api/auth/sso-ticket', 'POST', { target: target || 'train' })
+
+/** 用一次性票据换取登录态。返回登录信息(结构同 wechatLogin 的 data) */
+export const ssoLogin = (ticket) =>
+  request('/api/auth/sso-login', 'POST', { ticket })

+ 34 - 0
cfc-frontend/utils/sso.js

@@ -0,0 +1,34 @@
+// 跨小程序单点登录(SSO):从 cfc 跳转到训练营(train)小程序。
+// 复用方式:页面按钮 @click 调 jumpToTrain() 即可;后端已用一次性票据免二次微信授权。
+import { createSsoTicket } from './api.js'
+
+// 训练营小程序 AppID
+const TRAIN_APPID = 'wxcf0ed181fc3bce50'
+// 训练营落地页(App.onLaunch 会读取 query.ssoTicket 换票登录)
+const TRAIN_LANDING_PATH = 'pages/index/index'
+
+/** 已登录用户跳到训练营:签发一次性票据 → navigateToMiniProgram */
+export function jumpToTrain() {
+  if (!uni.getStorageSync('token')) {
+    uni.showToast({ title: '请先登录', icon: 'none' })
+    return
+  }
+  uni.showLoading({ title: '跳转中...' })
+  createSsoTicket('train').then(function(res) {
+    uni.hideLoading()
+    var ticket = res && res.data && res.data.ticket
+    if (!ticket) {
+      uni.showToast({ title: '签发失败,请稍后重试', icon: 'none' })
+      return
+    }
+    uni.navigateToMiniProgram({
+      appId: TRAIN_APPID,
+      path: TRAIN_LANDING_PATH + '?ssoTicket=' + encodeURIComponent(ticket),
+      fail: function() {
+        uni.showToast({ title: '跳转失败,请确认已关联训练营小程序', icon: 'none' })
+      }
+    })
+  }).catch(function() {
+    uni.hideLoading()
+  })
+}

+ 86 - 0
cfc-web/src/styles/admin-tokens.css

@@ -581,3 +581,89 @@
 .admin-page .text-muted { color: var(--adm-text-45); }
 .admin-page .adm-num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }
 .admin-page > .header { flex-shrink: 0; }
+
+/* ==== 弹性填充链增强(自动填充,兼容任意层级卡片/标签页嵌套) ==== */
+/* 让 admin-page / page-container 内任意层级的卡片成为弹性填充容器(覆盖 Layout.vue 的直接子级选择器) */
+.admin-page .el-card.el-card,
+.page-container .el-card.el-card {
+  flex: 1 1 auto;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+}
+.admin-page .el-card__body.el-card__body,
+.page-container .el-card__body.el-card__body {
+  flex: 1 1 auto;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+}
+
+/* adm-table-wrap 全局化:表格容器弹性撑满,仅表格自身滚动 */
+.adm-table-wrap {
+  flex: 1 1 auto;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
+}
+.adm-table-wrap > .el-table {
+  flex: 1 1 auto;
+  min-height: 0;
+  height: 100%;
+}
+
+/* 标签页场景:让 el-tabs 内容区成为弹性填充容器 */
+.admin-page .el-tabs,
+.page-container .el-tabs {
+  flex: 1 1 auto;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+}
+.admin-page .el-tabs__content,
+.page-container .el-tabs__content {
+  flex: 1 1 auto;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
+}
+.admin-page .el-tab-pane,
+.page-container .el-tab-pane {
+  flex: 1 1 auto;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
+}
+
+/* 兜底:未包裹的 el-table 若直接为卡片 body 子级也填充 */
+.admin-page .el-card__body > .el-table,
+.page-container .el-card__body > .el-table,
+.admin-page .el-tab-pane > .el-table,
+.page-container .el-tab-pane > .el-table {
+  flex: 1 1 auto;
+  min-height: 0;
+}
+
+/* 防止页面级横向滚动:限定内部横向溢出由表格自身处理 */
+.admin-page,
+.page-container {
+  overflow-x: hidden;
+}
+
+/* ==== 弹性填充链 :has 全自动传播(兼容任意层级嵌套 + 自定义根 class) ==== */
+/* 仅作用于真正含有填充表格容器的祖先,对话框(portal 在 main-wrapper 之外且不带 wrap)不受影响 */
+/* 1) 页面根容器:含有填充表格的页面根直接撑满 main-wrapper */
+.main-wrapper > *:has(.adm-table-wrap) {
+  flex: 1 1 auto;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+}
+/* 2) 表格容器内任意祖先容器:自动成为弹性列,确保表格撑满且不产生多余滚动条
+      排除横向栅格(.el-row/.el-col),避免误把横向布局翻转为纵向 */
+.main-wrapper *:has(.adm-table-wrap):not(.el-row):not(.el-col) {
+  display: flex;
+  flex-direction: column;
+  flex: 1 1 auto;
+  min-height: 0;
+}

+ 3 - 1
cfc-web/src/views/Children.vue

@@ -39,7 +39,8 @@
         </el-form>
 
         <!-- 孩子列表 -->
-        <el-table :data="tableData" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
+                <div class="adm-table-wrap">
+<el-table :data="tableData" border v-loading="loading" style="width: 100%" height="100%">
         <el-table-column prop="id" label="ID" width="80">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -102,6 +103,7 @@
           </template>
         </el-table-column>
         </el-table>
+        </div>
 
         <!-- 分页 -->
       <el-pagination

+ 3 - 1
cfc-web/src/views/Dashboard.vue

@@ -142,7 +142,8 @@
                 查看全部 →
               </el-button>
             </div>
-            <el-table :data="recentTasks" size="small" class="custom-table">
+                        <div class="adm-table-wrap">
+<el-table :data="recentTasks" size="small" class="custom-table" height="100%">
               <el-table-column prop="title" label="任务名称" min-width="180"></el-table-column>
               <el-table-column prop="childName" label="孩子" width="100"></el-table-column>
               <el-table-column prop="status" label="状态" width="100">
@@ -162,6 +163,7 @@
                 </template>
               </el-table-column>
             </el-table>
+            </div>
           </el-card>
         </el-col>
 

+ 12 - 5
cfc-web/src/views/Families.vue

@@ -31,12 +31,13 @@
         </el-form>
 
         <!-- 家庭列表 -->
-        <el-table
+                <div class="adm-table-wrap">
+<el-table
           :data="tableData"
           border
           v-loading="loading"
           style="width: 100%"
-          :max-height="tableHeight"
+          height="100%"
         >
         <el-table-column prop="id" label="ID" width="80">
           <template slot="header">
@@ -78,7 +79,10 @@
             </el-dropdown>
           </template>
         </el-table-column>
+        
+        
         </el-table>
+        </div>
 
         <!-- 分页 -->
       <el-pagination
@@ -165,8 +169,9 @@
               </el-button>
             </template>
           </el-table-column>
-        </el-table>
         <el-empty v-else description="暂无成员" :image-size="60"></el-empty>
+      
+      </el-table>
       </div>
     </el-dialog>
 
@@ -242,7 +247,6 @@
         <el-table-column prop="usedAt" label="使用时间" width="160">
           <template slot-scope="scope">{{ formatTime(scope.row.usedAt) }}</template>
         </el-table-column>
-      </el-table>
       <el-pagination
         style="margin-top: 12px; text-align: right"
         @size-change="onCouponSizeChange"
@@ -281,7 +285,6 @@
         <el-table-column prop="usedAt" label="使用时间" width="160">
           <template slot-scope="scope">{{ formatTime(scope.row.usedAt) }}</template>
         </el-table-column>
-      </el-table>
       <el-pagination
         style="margin-top: 12px; text-align: right"
         @size-change="onUsageSizeChange"
@@ -293,6 +296,10 @@
         layout="total, sizes, prev, pager, next"
       ></el-pagination>
     </el-dialog>
+  
+  </el-table>
+  
+  </el-table>
   </div>
 </template>
 

+ 8 - 4
cfc-web/src/views/GuideFamilyTask.vue

@@ -70,7 +70,8 @@
       </div>
 
       <!-- 孩子列表 -->
-      <el-table :max-height="tableHeight" v-if="familyOverview && !searchForm.memberId" :data="familyOverview.children" border>
+            <div class="adm-table-wrap">
+<el-table height="100%" v-if="familyOverview && !searchForm.memberId" :data="familyOverview.children" border>
         <el-table-column prop="childName" label="孩子姓名" width="120"></el-table-column>
         <el-table-column prop="totalPoints" label="总积分" width="100"></el-table-column>
         <el-table-column prop="streakDays" label="连续打卡" width="100">
@@ -89,9 +90,11 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <!-- 任务列表 -->
-      <el-table v-if="searchForm.memberId" :data="tasks" border v-loading="taskLoading" :max-height="tableHeight">
+            <div class="adm-table-wrap">
+<el-table v-if="searchForm.memberId" :data="tasks" border v-loading="taskLoading" height="100%">
         <template slot="empty">
           <div class="empty-table-state">
             <i class="el-icon-tickets" style="font-size: 48px; color: #ccc; margin-bottom: 16px;"></i>
@@ -156,7 +159,6 @@
             </el-dropdown>
           </template>
         </el-table-column>
-      </el-table>
 
       <!-- 分页 -->
       <el-pagination
@@ -216,8 +218,10 @@
         <el-button type="primary" @click="submitEditTask" :loading="editLoading">保存</el-button>
       </div>
     </el-dialog>
+  </el-table>
   </div>
-</template>
+
+</div></template>
 
 <script>
 import SortMixin from '@/mixins/SortMixin'

+ 5 - 2
cfc-web/src/views/GuidePackages.vue

@@ -26,7 +26,8 @@
       </el-form>
 
       <!-- 任务模板列表 -->
-      <el-table :max-height="tableHeight" :data="tableData" border v-loading="loading">
+            <div class="adm-table-wrap">
+<el-table height="100%" :data="tableData" border v-loading="loading">
         <el-table-column prop="id" label="ID" width="60">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -95,6 +96,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <!-- 编辑弹窗 -->
@@ -165,7 +167,6 @@
             <el-button size="mini" @click="handleDeleteItem(row, $index)">删除</el-button>
           </template>
         </el-table-column>
-      </el-table>
       <div slot="footer">
         <el-button @click="itemsDialogVisible = false">关闭</el-button>
       </div>
@@ -212,6 +213,8 @@
         <el-button type="primary" @click="copyShareCode">复制分享码</el-button>
       </div>
     </el-dialog>
+  
+  </el-table>
   </div>
 </template>
 

+ 3 - 1
cfc-web/src/views/OperationLogs.vue

@@ -35,7 +35,8 @@
 
     <!-- 日志列表 -->
     <el-card>
-      <el-table :max-height="tableHeight" :data="logs" v-loading="loading" stripe>
+            <div class="adm-table-wrap">
+<el-table height="100%" :data="logs" v-loading="loading" stripe>
         <el-table-column prop="id" label="ID" width="80">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -100,6 +101,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <!-- 分页 -->
       <el-pagination

+ 3 - 1
cfc-web/src/views/Points.vue

@@ -41,7 +41,8 @@
       </el-form>
 
       <!-- 积分流水列表 -->
-      <el-table :data="tableData" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
+            <div class="adm-table-wrap">
+<el-table :data="tableData" border v-loading="loading" style="width: 100%" height="100%">
         <el-table-column prop="id" label="ID" width="80">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -94,6 +95,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <!-- 分页 -->
       <el-pagination

+ 9 - 5
cfc-web/src/views/Rewards.vue

@@ -61,7 +61,6 @@
                   {{ formatDate(scope.row.createdAt) }}
                 </template>
               </el-table-column>
-            </el-table>
             <el-pagination
               style="margin-top: 15px; text-align: right"
               @current-change="loadFamilies"
@@ -72,7 +71,8 @@
             ></el-pagination>
           </el-dialog>
 
-          <el-table :data="tableData" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
+                    <div class="adm-table-wrap">
+<el-table :data="tableData" border v-loading="loading" style="width: 100%" height="100%">
              <el-table-column prop="id" label="ID" width="80">
                <template slot="header">
                  <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -140,6 +140,7 @@
               </template>
             </el-table-column>
           </el-table>
+          </div>
 
           <el-pagination
             style="margin-top: 20px; text-align: right"
@@ -158,7 +159,8 @@
           <el-button type="primary" size="small" style="margin-bottom: 15px" @click="showCreateTemplate">
             新建模板
           </el-button>
-          <el-table :data="templates" border v-loading="loadingTemplates" style="width: 100%" :max-height="tableHeight">
+                    <div class="adm-table-wrap">
+<el-table :data="templates" border v-loading="loadingTemplates" style="width: 100%" height="100%">
             <el-table-column prop="id" label="ID" width="80"></el-table-column>
             <el-table-column prop="title" label="模板名称" min-width="200"></el-table-column>
             <el-table-column prop="pointsRequired" label="所需积分" width="120">
@@ -177,7 +179,6 @@
                 <el-button size="mini" @click="handleDeleteTemplate(row)">删除</el-button>
               </template>
             </el-table-column>
-          </el-table>
         </el-tab-pane>
       </el-tabs>
     </el-card>
@@ -206,8 +207,11 @@
         <el-button type="primary" @click="handleCreateTemplate" :loading="creating">确定</el-button>
       </div>
     </el-dialog>
+  </el-table>
   </div>
-</template>
+
+</el-table>
+</div></template>
 
 <script>
 import SortMixin from '@/mixins/SortMixin'

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

@@ -54,12 +54,13 @@
       </el-form>
 
       <!-- 模板列表 -->
-      <el-table
+            <div class="adm-table-wrap">
+<el-table
         :data="tableData"
         border
         v-loading="loading"
         style="width: 100%"
-        :max-height="tableHeight"
+        height="100%"
       >
         <el-table-column prop="id" label="ID" width="80">
           <template slot="header">
@@ -176,6 +177,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <!-- 分页 -->
       <el-pagination

+ 5 - 2
cfc-web/src/views/Tasks.vue

@@ -67,7 +67,6 @@
               {{ formatDate(scope.row.createdAt) }}
             </template>
           </el-table-column>
-        </el-table>
         <el-pagination
           style="margin-top: 15px; text-align: right"
           @current-change="loadFamilies"
@@ -95,7 +94,8 @@
       </el-row>
 
       <!-- 任务列表 -->
-      <el-table :data="tableData" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
+            <div class="adm-table-wrap">
+<el-table :data="tableData" border v-loading="loading" style="width: 100%" height="100%">
         <el-table-column prop="id" label="ID" width="80">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -170,6 +170,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <!-- 分页 -->
       <el-pagination
@@ -198,6 +199,8 @@
         <el-button type="primary" @click="confirmReview(true)">通过</el-button>
       </div>
     </el-dialog>
+  
+  </el-table>
   </div>
 </template>
 

+ 3 - 1
cfc-web/src/views/TeacherConsult.vue

@@ -20,7 +20,8 @@
       </el-form>
 
       <!-- 咨询列表 -->
-      <el-table :max-height="tableHeight" :data="messages" border v-loading="loading">
+            <div class="adm-table-wrap">
+<el-table height="100%" :data="messages" border v-loading="loading">
         <el-table-column prop="id" label="ID" width="60">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -60,6 +61,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <!-- 分页 -->
       <el-pagination

+ 3 - 1
cfc-web/src/views/TeacherDashboard.vue

@@ -186,7 +186,8 @@
             <span class="td-card-title">最近订单</span>
             <el-button type="text" @click="$router.push('/teacher-orders')">查看全部 <i class="el-icon-arrow-right"></i></el-button>
           </div>
-          <el-table :data="recentOrders" stripe style="width: 100%" class="td-orders-table">
+                    <div class="adm-table-wrap">
+<el-table :data="recentOrders" stripe style="width: 100%" class="td-orders-table" height="100%">
             <el-table-column prop="orderNo" label="订单号" width="200">
               <template slot-scope="scope">
                 <span class="td-order-no">{{ scope.row.orderNo }}</span>
@@ -214,6 +215,7 @@
             </el-table-column>
             <el-table-column prop="createdAt" label="时间" width="160"></el-table-column>
           </el-table>
+          </div>
         </el-card>
       </el-col>
     </el-row>

+ 5 - 1
cfc-web/src/views/Users.vue

@@ -64,12 +64,13 @@
       </el-form>
 
       <!-- 用户列表 -->
+<div class="adm-table-wrap">
 <el-table 
         :data="tableData" 
         border 
         v-loading="loading"
         style="width: 100%"
-        :max-height="tableHeight"
+        height="100%"
       >
         <el-table-column prop="id" label="ID" width="80">
           <template slot="header">
@@ -139,7 +140,10 @@
             </el-dropdown>
           </template>
         </el-table-column>
+      
+      
       </el-table>
+      </div>
 
       <!-- 分页 -->
       <el-pagination

+ 22 - 6
cfc-web/src/views/Wishes.vue

@@ -48,7 +48,8 @@
           <!-- 待定价列表 -->
           <div v-if="draftWishes.length > 0" class="wish-section">
             <div class="section-title">📝 待定价</div>
-<el-table style="width: 100%" :max-height="tableHeight" :data="draftWishes" border v-loading="loading">
+<div class="adm-table-wrap">
+<el-table style="width: 100%" height="100%" :data="draftWishes" border v-loading="loading">
               <el-table-column prop="id" label="ID" width="80"></el-table-column>
               <el-table-column prop="childName" label="孩子" width="100"></el-table-column>
               <el-table-column prop="title" label="心愿名称" min-width="200"></el-table-column>
@@ -72,12 +73,14 @@
                 </template>
               </el-table-column>
             </el-table>
+            </div>
           </div>
 
           <!-- 待审批兑换列表 -->
           <div v-if="pendingExchangeWishes.length > 0" class="wish-section">
             <div class="section-title">⏳ 待审批兑换</div>
-            <el-table :data="pendingExchangeWishes" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
+                        <div class="adm-table-wrap">
+<el-table :data="pendingExchangeWishes" border v-loading="loading" style="width: 100%" height="100%">
               <el-table-column prop="id" label="ID" width="80"></el-table-column>
               <el-table-column prop="childName" label="孩子" width="100"></el-table-column>
               <el-table-column prop="title" label="心愿名称" min-width="200"></el-table-column>
@@ -105,12 +108,14 @@
                 </template>
               </el-table-column>
             </el-table>
+            </div>
           </div>
 
           <!-- 待兑现列表 -->
           <div v-if="approvedWishes.length > 0" class="wish-section">
             <div class="section-title">📦 待兑现</div>
-            <el-table :data="approvedWishes" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
+                        <div class="adm-table-wrap">
+<el-table :data="approvedWishes" border v-loading="loading" style="width: 100%" height="100%">
               <el-table-column prop="id" label="ID" width="80"></el-table-column>
               <el-table-column prop="childName" label="孩子" width="100"></el-table-column>
               <el-table-column prop="title" label="心愿名称" min-width="200"></el-table-column>
@@ -130,12 +135,14 @@
                 </template>
               </el-table-column>
             </el-table>
+            </div>
           </div>
 
           <!-- 待收货列表 -->
           <div v-if="exchangingWishes.length > 0" class="wish-section">
             <div class="section-title">🎁 待收货确认</div>
-            <el-table :data="exchangingWishes" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
+                        <div class="adm-table-wrap">
+<el-table :data="exchangingWishes" border v-loading="loading" style="width: 100%" height="100%">
               <el-table-column prop="id" label="ID" width="80"></el-table-column>
               <el-table-column prop="childName" label="孩子" width="100"></el-table-column>
               <el-table-column prop="title" label="心愿名称" min-width="200"></el-table-column>
@@ -150,6 +157,7 @@
                 </template>
               </el-table-column>
             </el-table>
+            </div>
           </div>
 
           <!-- 空状态 -->
@@ -201,7 +209,6 @@
               <el-table-column prop="id" label="ID" width="80"></el-table-column>
               <el-table-column prop="name" label="家庭名称" min-width="150"></el-table-column>
               <el-table-column prop="inviteCode" label="邀请码" width="120"></el-table-column>
-            </el-table>
             <el-pagination
               style="margin-top: 15px; text-align: right"
               @current-change="loadFamilies"
@@ -212,7 +219,8 @@
             ></el-pagination>
           </el-dialog>
 
-          <el-table :data="tableData" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
+                    <div class="adm-table-wrap">
+<el-table :data="tableData" border v-loading="loading" style="width: 100%" height="100%">
             <el-table-column prop="id" label="ID" width="80">
               <template slot="header">
                 <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -271,6 +279,12 @@
               </template>
             </el-table-column>
           </el-table>
+          </div>
+          <
+          <
+          <
+          <
+          <
 
           <el-pagination
             style="margin-top: 20px; text-align: right"
@@ -301,6 +315,8 @@
         <el-button type="primary" @click="submitPrice" :loading="submitting">确定</el-button>
       </div>
     </el-dialog>
+  
+  </el-table>
   </div>
 </template>
 

+ 7 - 2
cfc-web/src/views/admin/Activities.vue

@@ -57,7 +57,8 @@
         <el-button type="primary" size="mini" @click="handleSearch">查询</el-button>
         <el-button size="mini" @click="resetSearch">重置</el-button>
       </div>
-        <el-table :data="activities" v-loading="loading" border stripe style="min-width:900px;" :max-height="tableHeight">
+                <div class="adm-table-wrap">
+<el-table :data="activities" v-loading="loading" border stripe style="min-width:900px;" height="100%">
         <el-table-column prop="id" label="ID" width="80">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -134,7 +135,10 @@
             </el-dropdown>
           </template>
         </el-table-column>
+        
+        
         </el-table>
+        </div>
       <el-pagination
         v-if="total > 0"
         @size-change="handleSizeChange"
@@ -177,9 +181,10 @@
             <span v-else style="color: #999;">已处理</span>
           </template>
         </el-table-column>
-      </el-table>
       <p v-if="!registrations.length && !regLoading" style="text-align:center;color:#999;">暂无报名记录</p>
     </el-dialog>
+  
+  </el-table>
   </div>
 </template>
 

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

@@ -16,8 +16,8 @@
       </div>
     </div>
 
-    <div class="table-scroll-wrap-sm">
-      <el-table :max-height="tableHeight" :data="filteredList" v-loading="loading" border stripe>
+    <div class="adm-table-wrap">
+      <el-table height="100%" :data="filteredList" v-loading="loading" border stripe>
       <el-table-column prop="id" label="ID" width="60">
         <template slot="header">
           <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -58,7 +58,7 @@
           <span v-else style="color: #999;">已处理</span>
         </template>
       </el-table-column>
-      </el-table>
+    </el-table>
     </div>
 
     <el-dialog title="拒绝原因" :visible.sync="rejectDialogVisible" width="400px">

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

@@ -23,7 +23,8 @@
           <el-button size="mini" @click="resetSearch">重置</el-button>
         </div>
       </div>
-        <el-table :data="pendingActivities" v-loading="loading" border stripe style="min-width:900px;" :max-height="tableHeight">
+                <div class="adm-table-wrap">
+<el-table :data="pendingActivities" v-loading="loading" border stripe style="min-width:900px;" height="100%">
       <el-table-column prop="id" label="ID" width="80">
         <template slot="header">
           <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -70,6 +71,7 @@
           </template>
       </el-table-column>
     </el-table>
+    </div>
     <el-pagination
         v-if="total > 0"
         @current-change="handlePageChange"

+ 5 - 4
cfc-web/src/views/admin/ArticleCategory.vue

@@ -8,8 +8,8 @@
           <el-button type="primary" size="small" @click="handleCreate">+ 新增分类</el-button>
         </div>
       </div>
-      <div class="table-scroll-wrap-sm">
-      <el-table :data="filteredData" v-loading="loading" border stripe :max-height="tableHeight">
+      <div class="adm-table-wrap">
+      <el-table :data="filteredData" v-loading="loading" border stripe height="100%">
         <el-table-column prop="id" label="ID" width="70">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
@@ -56,7 +56,6 @@
             </el-dropdown>
           </template>
         </el-table-column>
-      </el-table></div>
     </el-card>
 
     <el-dialog :visible.sync="dialogVisible" :title="isEdit ? '编辑分类' : '新增分类'" width="500px" :close-on-click-modal="false">
@@ -93,8 +92,10 @@
         <el-button @click="handleClose">关闭</el-button>
       </div>
     </el-dialog>
+  </el-table>
   </div>
-</template>
+
+</div></template>
 
 <script>
 import {

+ 4 - 3
cfc-web/src/views/admin/ArticleManage.vue

@@ -55,8 +55,8 @@
         <el-button type="primary" icon="el-icon-search" @click="loadList">搜索</el-button>
       </div>
 
-      <div class="table-scroll-wrap-lg">
-        <el-table :data="list" v-loading="loading" border stripe :max-height="tableHeight">
+      <div class="adm-table-wrap">
+        <el-table :data="list" v-loading="loading" border stripe height="100%">
           <el-table-column prop="id" label="ID" width="70">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -270,7 +270,6 @@
             {{ formatTime(row.last_read_at) }}
           </template>
         </el-table-column>
-      </el-table>
       <el-pagination
         v-if="readRecordTotal > 0"
         layout="total, prev, pager, next"
@@ -280,6 +279,8 @@
         @current-change="onReadRecordPageChange"
         style="margin-top: 16px; text-align: right;"
       />
+    
+    </el-table>
     </div>
     <span slot="footer">
       <el-button @click="readRecordVisible = false">关闭</el-button>

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

@@ -27,8 +27,8 @@
           <el-button type="primary" size="small" @click="showCreateDialog">添加资料</el-button>
         </div>
       </div>
-      <div class="table-scroll-wrap-sm">
-      <el-table :max-height="tableHeight" :data="filteredData" border stripe>
+      <div class="adm-table-wrap">
+      <el-table height="100%" :data="filteredData" border stripe>
         <el-table-column prop="id" label="ID" width="60" />
         <el-table-column prop="title" label="标题" />
         <el-table-column prop="content" label="内容" show-overflow-tooltip />

+ 3 - 1
cfc-web/src/views/admin/AssessmentAssign.vue

@@ -6,7 +6,8 @@
         <el-button type="primary" size="small" icon="el-icon-refresh" @click="loadList">刷新</el-button>
       </div>
 
-        <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe empty-text="暂无可分配记录">
+                <div class="adm-table-wrap">
+<el-table height="100%" :data="list" v-loading="loading" border stripe empty-text="暂无可分配记录">
           <el-table-column prop="id" label="ID" width="70">
             <template slot="header">
               <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -44,6 +45,7 @@
             </template>
           </el-table-column>
         </el-table>
+        </div>
 
       <el-pagination
         @size-change="handleSizeChange"

+ 3 - 1
cfc-web/src/views/admin/AssessmentOrders.vue

@@ -25,7 +25,8 @@
         </div>
       </div>
 
-      <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe empty-text="暂无测评预约">
+            <div class="adm-table-wrap">
+<el-table height="100%" :data="list" v-loading="loading" border stripe empty-text="暂无测评预约">
         <el-table-column label="用户" width="110">
           <template slot-scope="{ row }">{{ row.userName || '-' }}</template>
         </el-table-column>
@@ -73,6 +74,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <el-pagination
         @size-change="handleSizeChange"

+ 3 - 1
cfc-web/src/views/admin/AssessmentPlanRules.vue

@@ -5,7 +5,8 @@
       <el-button type="primary" @click="showDialog(null)">新增规则</el-button>
     </div>
 
-    <el-table :data="rules" border stripe style="width: 100%" :max-height="tableHeight">
+        <div class="adm-table-wrap">
+<el-table :data="rules" border stripe style="width: 100%" height="100%">
       <el-table-column prop="dimension" label="维度" width="120" />
       <el-table-column label="分数区间" width="140">
         <template slot-scope="{ row }">{{ row.scoreMin }} - {{ row.scoreMax }}</template>
@@ -25,6 +26,7 @@
         </template>
       </el-table-column>
     </el-table>
+    </div>
 
     <el-dialog title="规则编辑" :visible.sync="dialogVisible" width="500px">
       <el-form :model="form" label-width="100px">

+ 3 - 1
cfc-web/src/views/admin/AssessmentProducts.vue

@@ -26,7 +26,8 @@
         </div>
       </div>
 
-      <el-table :data="list" v-loading="loading" border stripe :max-height="tableHeight">
+            <div class="adm-table-wrap">
+<el-table :data="list" v-loading="loading" border stripe height="100%">
         <el-table-column prop="id" label="ID" width="70">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -69,6 +70,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <div class="pagination-wrap">
         <el-pagination

+ 3 - 1
cfc-web/src/views/admin/BadgeManage.vue

@@ -25,7 +25,8 @@
         </el-form-item>
       </el-form>
 
-      <el-table :data="list" border stripe v-loading="loading" empty-text="暂无数据" style="width:100%" :max-height="tableHeight">
+            <div class="adm-table-wrap">
+<el-table :data="list" border stripe v-loading="loading" empty-text="暂无数据" style="width:100%" height="100%">
         <el-table-column prop="id" label="ID" width="60">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -76,6 +77,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <!-- 新增/编辑弹窗 -->

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

@@ -8,8 +8,8 @@
           <el-button type="primary" size="small" @click="handleCreate">+ 添加配置</el-button>
         </div>
       </div>
-      <div class="table-scroll-wrap">
-      <el-table :data="filteredData" stripe :max-height="tableHeight"
+      <div class="adm-table-wrap">
+      <el-table :data="filteredData" stripe height="100%"
         <el-table-column prop="id" label="ID" width="80">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -63,7 +63,6 @@
             </el-dropdown>
           </template>
         </el-table-column>
-      </el-table></div>
     </el-card>
 
     <el-dialog :visible.sync="dialogVisible" :title="isEdit ? '编辑八字配置' : '添加八字配置'" width="500px">
@@ -124,8 +123,10 @@
         <el-button @click="handleClose">关闭</el-button>
       </div>
     </el-dialog>
+  
   </div>
-</template>
+
+</div></template>
 
 <script>
 import SortMixin from '@/mixins/SortMixin'

+ 5 - 3
cfc-web/src/views/admin/BloodTypeConfigs.vue

@@ -8,7 +8,7 @@
           <el-button type="primary" size="small" @click="handleCreate">+ 添加配置</el-button>
         </div>
       </div>
-      <div class="table-scroll-wrap"><el-table :data="filteredData" stripe :max-height="tableHeight">
+      <div class="adm-table-wrap"><el-table :data="filteredData" stripe height="100%">
         <el-table-column prop="id" label="ID" width="80">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -47,7 +47,6 @@
             </el-dropdown>
           </template>
         </el-table-column>
-    </el-table></div>
   </el-card>
 
     <el-dialog :visible.sync="dialogVisible" :title="isEdit ? '编辑血型配置' : '添加血型配置'" width="500px">
@@ -95,8 +94,11 @@
         <el-button @click="handleClose">关闭</el-button>
       </div>
     </el-dialog>
+  
+  </el-table>
   </div>
-</template>
+
+</div></template>
 
 <script>
 import SortMixin from '@/mixins/SortMixin'

+ 3 - 1
cfc-web/src/views/admin/ButlerAssignments.vue

@@ -4,7 +4,8 @@
       <div slot="header">
         <span>管家分配管理</span>
       </div>
-      <el-table v-loading="loading" :data="records" border stripe>
+            <div class="adm-table-wrap">
+<el-table v-loading="loading" :data="records" border stripe height="100%">
         <el-table-column prop="id" label="ID" width="80">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
@@ -45,6 +46,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
       <el-pagination
         style="margin-top: 16px; text-align: right;"
         layout="total, prev, pager, next"

+ 6 - 2
cfc-web/src/views/admin/CfValueManage.vue

@@ -10,7 +10,8 @@
 
     <el-tabs v-model="activeTab">
       <el-tab-pane label="余额列表" name="balances">
-        <el-table :data="balanceList" v-loading="loadingBalance" border stripe>
+                <div class="adm-table-wrap">
+<el-table :data="balanceList" v-loading="loadingBalance" border stripe height="100%">
           <el-table-column prop="id" label="ID" width="70">
             <template slot="header">
               <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -55,6 +56,7 @@
             </template>
           </el-table-column>
         </el-table>
+        </div>
         <el-pagination v-if="balanceTotal > 0" :current-page="balancePage" :page-size="balanceSize" :total="balanceTotal" layout="prev, pager, next" @current-change="pageBalances" />
       </el-tab-pane>
 
@@ -63,7 +65,8 @@
           <el-input v-model="logUserId" placeholder="用户ID" style="width: 200px;" clearable @keyup.enter.native="searchLogs" />
           <el-button type="primary" @click="searchLogs">查询流水</el-button>
         </div>
-        <el-table :data="logList" v-loading="loadingLog" border stripe>
+                <div class="adm-table-wrap">
+<el-table :data="logList" v-loading="loadingLog" border stripe height="100%">
           <el-table-column prop="id" label="ID" width="70">
             <template slot="header">
               <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -108,6 +111,7 @@
             </template>
           </el-table-column>
         </el-table>
+        </div>
         <el-pagination v-if="logTotal > 0" :current-page="logPage" :page-size="logSize" :total="logTotal" layout="prev, pager, next" @current-change="pageLogs" />
       </el-tab-pane>
     </el-tabs>

+ 3 - 1
cfc-web/src/views/admin/ChallengeScoreConfig.vue

@@ -5,7 +5,8 @@
       <div class="header-tip">家庭挑战 发起/同意/拒绝 的方向性关系评分与能量增减(可正可负)</div>
     </div>
 
-    <el-table :data="matrix" v-loading="loading" border stripe>
+        <div class="adm-table-wrap">
+<el-table :data="matrix" v-loading="loading" border stripe height="100%">
       <el-table-column label="动作" width="120">
         <template slot-scope="{ row }">
           <el-tag size="mini" :type="row.action === 'reject' ? 'danger' : 'success'">{{ actionLabel(row.action) }}</el-tag>
@@ -37,6 +38,7 @@
         </template>
       </el-table-column>
     </el-table>
+    </div>
     <div class="footer-tip">注:reject 的沟通方向由代码固定(拒绝人→发起人 +、发起人→拒绝人 -、已同意成员→拒绝人 -),此处的值为增减幅度。信任/亲近为单向扣减(发起人→拒绝人)。</div>
   </div>
 </template>

+ 3 - 1
cfc-web/src/views/admin/CommentReview.vue

@@ -13,7 +13,8 @@
         <el-button type="primary" @click="loadList">查询</el-button>
       </div>
     </div>
-    <el-table :data="list" v-loading="loading" border stripe>
+        <div class="adm-table-wrap">
+<el-table :data="list" v-loading="loading" border stripe height="100%">
       <el-table-column prop="id" label="ID" width="60">
         <template slot="header">
           <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
@@ -53,6 +54,7 @@
         </template>
       </el-table-column>
     </el-table>
+    </div>
     <el-pagination
       v-if="total > 0"
       @current-change="handlePageChange"

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

@@ -10,8 +10,8 @@
         </div>
       </div>
 
-      <div class="table-scroll-wrap-sm">
-        <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe>
+      <div class="adm-table-wrap">
+        <el-table height="100%" :data="list" v-loading="loading" border stripe>
           <el-table-column prop="id" label="ID" width="80">
             <template slot="header">
               <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -53,7 +53,7 @@
               <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">发放时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
             </template>
           </el-table-column>
-        </el-table>
+      </el-table>
       </div>
 
       <el-pagination

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

@@ -20,8 +20,8 @@
         </div>
       </div>
 
-      <div class="table-scroll-wrap-sm">
-        <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe>
+      <div class="adm-table-wrap">
+        <el-table height="100%" :data="list" v-loading="loading" border stripe>
         <el-table-column prop="id" label="ID" width="70" />
         <el-table-column prop="name" label="名称" min-width="140" show-overflow-tooltip>
           <template slot="header">
@@ -91,7 +91,7 @@
             </el-dropdown>
           </template>
         </el-table-column>
-        </el-table>
+      </el-table>
       </div>
 
       <el-pagination

+ 3 - 1
cfc-web/src/views/admin/DanExecution.vue

@@ -14,7 +14,8 @@
         </div>
       </div>
 
-      <el-table :data="list" v-loading="loading" border stripe empty-text="暂无执行记录">
+            <div class="adm-table-wrap">
+<el-table :data="list" v-loading="loading" border stripe empty-text="暂无执行记录" height="100%">
         <el-table-column label="ID" prop="id" width="80" />
         <el-table-column label="订单ID" prop="assessmentOrderId" width="100" />
         <el-table-column label="家庭ID" prop="familyId" width="100" />
@@ -69,6 +70,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <el-pagination
         @size-change="handleSizeChange"

+ 3 - 1
cfc-web/src/views/admin/DanSettlement.vue

@@ -66,7 +66,8 @@
         </div>
       </div>
 
-      <el-table :data="list" v-loading="loading" border stripe empty-text="暂无结算记录">
+            <div class="adm-table-wrap">
+<el-table :data="list" v-loading="loading" border stripe empty-text="暂无结算记录" height="100%">
         <el-table-column label="ID" prop="id" width="80" />
         <el-table-column label="执行ID" prop="executionId" width="100" />
         <el-table-column label="订单ID" prop="assessmentOrderId" width="100" />
@@ -133,6 +134,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <el-pagination
         @size-change="handleSizeChange"

+ 3 - 1
cfc-web/src/views/admin/Dashboard.vue

@@ -56,7 +56,8 @@
       <div slot="header" class="card-header">
         <span><i class="el-icon-s-order"></i> 最近任务</span>
       </div>
-      <el-table :data="recentTasks" border stripe empty-text="暂无任务" size="small">
+            <div class="adm-table-wrap">
+<el-table :data="recentTasks" border stripe empty-text="暂无任务" size="small" height="100%">
         <el-table-column prop="id" label="ID" width="60" />
         <el-table-column prop="title" label="任务名称" min-width="160" show-overflow-tooltip />
         <el-table-column prop="type" label="类型" width="80">
@@ -74,6 +75,7 @@
           <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
   </div>
 </template>

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

@@ -8,8 +8,8 @@
         </div>
       </div>
 
-      <div class="table-scroll-wrap-sm">
-        <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe>
+      <div class="adm-table-wrap">
+        <el-table height="100%" :data="list" v-loading="loading" border stripe>
           <el-table-column prop="id" label="ID" width="70" />
           <el-table-column prop="name" label="规则名称" min-width="140" show-overflow-tooltip />
           <el-table-column label="类型" width="90">
@@ -59,7 +59,7 @@
               <el-button size="mini" type="danger" @click="handleDelete(row)">删除</el-button>
             </template>
           </el-table-column>
-        </el-table>
+      </el-table>
       </div>
 
       <el-pagination

+ 5 - 4
cfc-web/src/views/admin/EcomSupplierManage.vue

@@ -8,8 +8,8 @@
       </div>
     </div>
 
-    <div class="table-scroll-wrap">
-    <el-table :data="list" v-loading="loading" border stripe :max-height="tableHeight">
+    <div class="adm-table-wrap">
+    <el-table :data="list" v-loading="loading" border stripe height="100%">
       <el-table-column prop="id" label="ID" width="60">
         <template slot="header">
           <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -46,7 +46,6 @@
           <el-button size="mini" type="danger" @click="handleDelete(row)">删除</el-button>
         </template>
       </el-table-column>
-    </el-table></div>
 
     <el-pagination
       @size-change="onSizeChange"
@@ -81,8 +80,10 @@
         <el-button type="primary" @click="handleSave" :loading="saving">保存</el-button>
       </span>
     </el-dialog>
+  </el-table>
   </div>
-</template>
+
+</div></template>
 
 <script>
 import SortMixin from '@/mixins/SortMixin'

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

@@ -9,7 +9,8 @@
         <el-radio-button label="true">已处理</el-radio-button>
       </el-radio-group>
       </div>
-    <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe>
+        <div class="adm-table-wrap">
+<el-table height="100%" :data="list" v-loading="loading" border stripe>
       <el-table-column prop="id" label="ID" width="70">
         <template slot="header">
           <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -75,6 +76,7 @@
         </template>
       </el-table-column>
     </el-table>
+    </div>
 
     <el-pagination
       @size-change="onSizeChange"

+ 3 - 1
cfc-web/src/views/admin/EnergyBehaviorConfig.vue

@@ -12,7 +12,8 @@
       </div>
     </div>
 
-    <el-table :data="list" v-loading="loading" border stripe highlight-current-row>
+        <div class="adm-table-wrap">
+<el-table :data="list" v-loading="loading" border stripe highlight-current-row height="100%">
       <el-table-column prop="behaviorCode" label="行为编码" width="160">
         <template slot="header">
           <span class="sort-header" @click.stop="onSortClick('behaviorCode', $event)">行为编码<span v-if="sortIndex('behaviorCode') !== null" class="sort-index" :class="sortClass('behaviorCode') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('behaviorCode') }}</span>{{ sortIcon('behaviorCode') }}</span>
@@ -67,6 +68,7 @@
         </template>
       </el-table-column>
     </el-table>
+    </div>
 
     <el-dialog title="编辑能量行为配置" :visible.sync="dialogVisible" width="700px" :close-on-click-modal="false">
       <el-form :model="form" label-width="110px" size="small">

+ 3 - 1
cfc-web/src/views/admin/EnergyRuleManagement.vue

@@ -11,7 +11,8 @@
         <el-button type="primary" icon="el-icon-plus" @click="showDialog(null)">新增规则</el-button>
       </div>
       </div>
-    <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe>
+        <div class="adm-table-wrap">
+<el-table height="100%" :data="list" v-loading="loading" border stripe>
       <el-table-column prop="id" label="ID" width="60">
         <template slot="header">
           <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -71,6 +72,7 @@
         </template>
       </el-table-column>
     </el-table>
+    </div>
 
     <el-pagination
       @size-change="onSizeChange"

+ 3 - 1
cfc-web/src/views/admin/EnergySandbox.vue

@@ -41,7 +41,8 @@
         <span>家庭成员数据</span>
         <el-input v-model="memberKeyword" placeholder="搜索成员姓名..." prefix-icon="el-icon-search" size="small" clearable style="float: right; width: 200px;" />
       </div>
-      <el-table :data="filteredMembers" border style="width: 100%" :max-height="tableHeight">
+            <div class="adm-table-wrap">
+<el-table :data="filteredMembers" border style="width: 100%" height="100%">
         <el-table-column prop="memberType" label="成员类型" width="100"></el-table-column>
         <el-table-column prop="name" label="姓名" width="120"></el-table-column>
         <el-table-column prop="bodyScore" label="身" width="80" align="center">
@@ -78,6 +79,7 @@
           <span>暂无数据</span>
         </template>
       </el-table>
+      </div>
     </el-card>
   </div>
 </template>

+ 5 - 4
cfc-web/src/views/admin/FamilyEarnings.vue

@@ -8,8 +8,8 @@
       </div>
     </div>
 
-    <div class="table-scroll-wrap">
-      <el-table :data="list" v-loading="loading" border stripe :max-height="tableHeight" @row-click="showDetail">
+    <div class="adm-table-wrap">
+      <el-table :data="list" v-loading="loading" border stripe height="100%" @row-click="showDetail">
         <el-table-column prop="id" label="ID" width="70">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">@dblclick.stop="onSortToggle('id')">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -49,7 +49,7 @@
             <el-button size="mini" type="primary" @click.stop="showDetail(row)">详情</el-button>
           </template>
         </el-table-column>
-      </el-table>
+    </el-table>
     </div>
 
     <!-- 家庭收益详情弹窗 -->
@@ -92,7 +92,8 @@
               <span v-else>否</span>
             </template>
           </el-table-column>
-        </el-table>
+      
+      </el-table>
       </div>
     </el-dialog>
   </div>

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

@@ -14,8 +14,8 @@
       </div>
     </div>
 
-    <div class="table-scroll-wrap">
-      <el-table :data="list" v-loading="loading" border stripe :max-height="tableHeight">
+    <div class="adm-table-wrap">
+      <el-table :data="list" v-loading="loading" border stripe height="100%">
         <el-table-column prop="id" label="ID" width="70" />
         <el-table-column prop="familyId" label="家庭ID" width="90" />
         <el-table-column label="类型" width="100">
@@ -41,7 +41,7 @@
             <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">@dblclick.stop="onSortToggle('createdAt')">创建时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
           </template>
         </el-table-column>
-      </el-table>
+    </el-table>
     </div>
 
     <el-pagination

+ 3 - 1
cfc-web/src/views/admin/FileManage.vue

@@ -52,7 +52,8 @@
         <el-button type="primary" @click="loadData">查询</el-button>
       </div>
 
-      <el-table :data="tableData" border stripe v-loading="loading" style="margin-top:16px">
+            <div class="adm-table-wrap">
+<el-table :data="tableData" border stripe v-loading="loading" style="margin-top:16px" height="100%">
         <el-table-column prop="id" label="ID" width="70">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -115,6 +116,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="query.page" :page-size="query.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" :page-sizes="[10, 20, 50, 100]" style="margin-top:16px"></el-pagination>
     </el-card>

+ 4 - 2
cfc-web/src/views/admin/Foods.vue

@@ -42,14 +42,15 @@
         </div>
       </div>
 
-      <el-table
+            <div class="adm-table-wrap">
+<el-table
         :data="list"
         v-loading="loading"
         border
         stripe
         size="mini"
         style="width: 100%"
-        :max-height="tableHeight"
+        height="100%"
       >
         <el-table-column type="index" label="#" width="50" align="center" />
         <el-table-column prop="name" label="名称" min-width="120" fixed :class-name="sortClass('name')">
@@ -157,6 +158,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <div class="pagination-wrap">
         <el-pagination

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

@@ -20,7 +20,8 @@
           <el-button type="primary" size="mini" @click="openCreate">创建任务</el-button>
         </div>
       </div>
-      <el-table :max-height="tableHeight" :data="tasks" v-loading="loading" border stripe>
+            <div class="adm-table-wrap">
+<el-table height="100%" :data="tasks" v-loading="loading" border stripe>
         <el-table-column prop="id" label="ID" width="80">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">@dblclick.stop="onSortToggle('id')">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -83,6 +84,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
       <el-pagination
         v-if="total > 0"
         @size-change="handleSizeChange"

+ 3 - 1
cfc-web/src/views/admin/GuidePopupConfig.vue

@@ -19,7 +19,8 @@
           <el-button type="primary" @click="load">查询</el-button>
         </el-form-item>
       </el-form>
-      <el-table :data="list" v-loading="loading" border>
+            <div class="adm-table-wrap">
+<el-table :data="list" v-loading="loading" border height="100%">
         <el-table-column prop="id" label="ID" width="60" />
         <el-table-column prop="slug" label="标识" width="160" />
         <el-table-column prop="title" label="标题" min-width="160" show-overflow-tooltip />
@@ -42,6 +43,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <el-drawer :title="editing.id ? '编辑弹窗' : '新建弹窗'" :visible.sync="drawerVisible" size="60%">

+ 3 - 1
cfc-web/src/views/admin/GuidePopupStats.vue

@@ -12,11 +12,13 @@
         <el-col :span="6"><el-card shadow="never"><div class="stat-num">{{ stats.totalClicks || 0 }}</div><div class="stat-label">总点击</div></el-card></el-col>
         <el-col :span="6"><el-card shadow="never"><div class="stat-num">{{ stats.completionRate || '0.0%' }}</div><div class="stat-label">完成率</div></el-card></el-col>
       </el-row>
-      <el-table :data="trend" border style="margin-top:16px">
+            <div class="adm-table-wrap">
+<el-table :data="trend" border style="margin-top:16px" height="100%">
         <el-table-column prop="hour" label="时段" width="120" />
         <el-table-column prop="views" label="曝光" />
         <el-table-column prop="clicks" label="点击" />
       </el-table>
+      </div>
     </el-card>
   </div>
 </template>

+ 3 - 1
cfc-web/src/views/admin/HealthCheckinManage.vue

@@ -25,7 +25,8 @@
         </div>
       </div>
 
-      <el-table :data="list" v-loading="loading" border stripe empty-text="暂无打卡记录" :max-height="tableHeight">
+            <div class="adm-table-wrap">
+<el-table :data="list" v-loading="loading" border stripe empty-text="暂无打卡记录" height="100%">
         <el-table-column prop="id" label="ID" width="70" />
         <el-table-column label="孩子" width="100">
           <template slot-scope="{ row }">{{ row.childName || '-' }}</template>
@@ -50,6 +51,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
   </div>
 </template>

+ 5 - 2
cfc-web/src/views/admin/HealthPlanManage.vue

@@ -14,7 +14,8 @@
       </div>
     </div>
 
-    <el-table :data="plans" border stripe style="width:100%" v-loading="loading" :max-height="tableHeight">
+        <div class="adm-table-wrap">
+<el-table :data="plans" border stripe style="width:100%" v-loading="loading" height="100%">
       <el-table-column prop="id" label="方案ID" width="80" />
       <el-table-column prop="familyName" label="家庭" width="140">
         <template slot-scope="{ row }">{{ row.familyName || row.familyId }}</template>
@@ -61,6 +62,7 @@
         </template>
       </el-table-column>
     </el-table>
+    </div>
 
     <el-pagination
       style="margin-top:16px;text-align:right"
@@ -114,7 +116,8 @@
               <el-tag :type="taskStatusType(row.status)" size="mini">{{ taskStatusLabel(row.status) }}</el-tag>
             </template>
           </el-table-column>
-        </el-table>
+      
+      </el-table>
       </div>
       <div slot="footer">
         <el-button @click="detailVisible = false">关闭</el-button>

+ 12 - 5
cfc-web/src/views/admin/HealthReportEdit.vue

@@ -72,7 +72,8 @@
           <el-button size="mini" @click="addIndicator">添加指标</el-button>
         </div>
       </div>
-      <el-table :data="payload.indicators" border size="small" :max-height="400">
+            <div class="adm-table-wrap">
+<el-table :data="payload.indicators" border size="small" height="100%">
         <el-table-column type="index" label="#" width="50" />
         <el-table-column label="分类" width="140">
           <template slot-scope="{ row }">
@@ -122,6 +123,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <el-card v-loading="loading" class="edit-card" v-if="!loading && payload.gutFlora && payload.gutFlora.length">
@@ -131,7 +133,8 @@
           <el-button size="mini" @click="addGutFlora">添加菌群</el-button>
         </div>
       </div>
-      <el-table :data="payload.gutFlora" border size="small" :max-height="400">
+            <div class="adm-table-wrap">
+<el-table :data="payload.gutFlora" border size="small" height="100%">
         <el-table-column type="index" label="#" width="50" />
         <el-table-column label="菌种名称" min-width="160">
           <template slot-scope="{ row }">
@@ -168,7 +171,6 @@
             <el-button size="mini" type="text" @click="removeGutFlora($index)">删除</el-button>
           </template>
         </el-table-column>
-      </el-table>
     </el-card>
 
     <el-card v-loading="loading" class="edit-card" v-if="!loading && payload.diseaseRisks && payload.diseaseRisks.length">
@@ -178,7 +180,8 @@
           <el-button size="mini" @click="addDiseaseRisk">添加风险</el-button>
         </div>
       </div>
-      <el-table :data="payload.diseaseRisks" border size="small">
+            <div class="adm-table-wrap">
+<el-table :data="payload.diseaseRisks" border size="small" height="100%">
         <el-table-column type="index" label="#" width="50" />
         <el-table-column label="疾病名称" min-width="200">
           <template slot-scope="{ row }">
@@ -205,14 +208,18 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <div class="action-bar" v-if="!loading">
       <el-button @click="goBack">取消</el-button>
       <el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
     </div>
+  
+  </el-table>
   </div>
-</template>
+
+</div></template>
 
 <script>
 import { getHealthReportDetail, editHealthReport } from '@/api/admin'

+ 3 - 1
cfc-web/src/views/admin/HealthReportReassign.vue

@@ -14,7 +14,8 @@
       </el-form-item>
     </el-form>
 
-    <el-table :data="reports" v-loading="loading" border stripe>
+        <div class="adm-table-wrap">
+<el-table :data="reports" v-loading="loading" border stripe height="100%">
       <el-table-column prop="id" label="报告ID" width="100" />
       <el-table-column prop="reportType" label="报告类型" width="120">
         <template slot-scope="{ row }">
@@ -45,6 +46,7 @@
         </template>
       </el-table-column>
     </el-table>
+    </div>
 
     <!-- 重新分配弹窗 -->
     <el-dialog title="重新分配报告归属" :visible.sync="dialogVisible" width="480px" :close-on-click-modal="false">

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

@@ -23,8 +23,8 @@
         <el-button size="mini" @click="resetFilter">重置</el-button>
       </div>
 
-      <div class="table-scroll-wrap-sm">
-        <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe>
+      <div class="adm-table-wrap">
+        <el-table height="100%" :data="list" v-loading="loading" border stripe>
           <el-table-column prop="id" label="ID" width="60" />
           <el-table-column label="类型" width="80">
             <template slot-scope="{ row }">
@@ -83,7 +83,7 @@
               <el-button size="mini" type="danger" @click="handleDelete(row)">删除</el-button>
             </template>
           </el-table-column>
-        </el-table>
+      </el-table>
       </div>
 
       <div class="pagination-wrap" style="margin-top:16px;text-align:right;">

+ 3 - 1
cfc-web/src/views/admin/InnatePortraitConfig.vue

@@ -5,7 +5,8 @@
         <span>先天画像来源权重配置</span>
         <el-button style="float: right" type="primary" size="small" @click="load">刷新</el-button>
       </div>
-      <el-table :data="list" border stripe>
+            <div class="adm-table-wrap">
+<el-table :data="list" border stripe height="100%">
         <el-table-column prop="sourceName" label="来源" width="120" />
         <el-table-column label="权重" width="220">
           <template slot-scope="scope">
@@ -40,6 +41,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
       <div style="margin-top: 12px; color: #999; font-size: 12px">
         提示:权重按基点配置(2500=25%)。保存后自动清除所有成员先天分缓存并触发重算。启用来源权重之和不足100%时按比例归一化。
       </div>

+ 27 - 6
cfc-web/src/views/admin/InventoryManage.vue

@@ -22,7 +22,8 @@
             <el-date-picker v-model="txFilters.dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd" style="width:260px;margin-right:8px;" />
             <el-button type="primary" size="mini" @click="loadTransactions">查询</el-button>
           </div>
-          <el-table :data="txList" v-loading="txLoading" border stripe max-height="500">
+                    <div class="adm-table-wrap">
+<el-table :data="txList" v-loading="txLoading" border stripe height="100%">
             <el-table-column prop="id" label="ID" width="60" />
             <el-table-column label="商品" min-width="150">
               <template slot-scope="{ row }">{{ row.productName || 'ID:' + row.productId }}</template>
@@ -56,13 +57,17 @@
                 <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
               </template>
             </el-table-column>
+          
+          
           </el-table>
+          </div>
           <el-pagination @size-change="txPageSize=$event;loadTransactions()" @current-change="txPage=$event;loadTransactions()" :current-page="txPage" :page-sizes="[10,20,50]" :page-size="txPageSize" :total="txTotal" layout="total, sizes, prev, pager, next, jumper" class="pagination-wrap" />
         </el-tab-pane>
 
         <!-- 入库单 -->
         <el-tab-pane label="入库单" name="inbound">
-          <el-table :data="inboundList" v-loading="inboundLoading" border stripe max-height="500">
+                    <div class="adm-table-wrap">
+<el-table :data="inboundList" v-loading="inboundLoading" border stripe height="100%">
             <el-table-column prop="orderNo" label="入库单号" width="180">
               <template slot="header">
                 <span class="sort-header" @click.stop="onSortClick('orderNo', $event)">入库单号<span v-if="sortIndex('orderNo') !== null" class="sort-index" :class="sortClass('orderNo') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('orderNo') }}</span>{{ sortIcon('orderNo') }}</span>
@@ -104,14 +109,18 @@
                 <el-button v-if="row.status === 'pending'" size="mini" type="danger" @click="handleCancelInbound(row)">取消</el-button>
               </template>
             </el-table-column>
+          
+          
           </el-table>
+          </div>
           <el-pagination @size-change="inboundPageSize=$event;loadInboundOrders()" @current-change="inboundPage=$event;loadInboundOrders()" :current-page="inboundPage" :page-sizes="[10,20,50]" :page-size="inboundPageSize" :total="inboundTotal" layout="total, sizes, prev, pager, next, jumper" class="pagination-wrap" />
         </el-tab-pane>
 
         <!-- 库存预警 -->
         <el-tab-pane label="库存预警" name="alerts">
           <el-button type="primary" size="mini" @click="loadAlerts" style="margin-bottom:12px;">刷新</el-button>
-          <el-table :data="alertList" v-loading="alertLoading" border stripe max-height="500">
+                    <div class="adm-table-wrap">
+<el-table :data="alertList" v-loading="alertLoading" border stripe height="100%">
             <el-table-column prop="productName" label="商品名称" min-width="200" />
             <el-table-column prop="specs" label="SKU规格" width="150">
               <template slot-scope="{ row }">{{ row.specs || '-' }}</template>
@@ -120,13 +129,17 @@
               <template slot-scope="{ row }"><span style="color:#f56c6c;font-weight:bold;">{{ row.currentStock }}</span></template>
             </el-table-column>
             <el-table-column prop="minStockAlert" label="预警下限" width="80" />
+          
+          
           </el-table>
+          </div>
         </el-tab-pane>
 
         <!-- 出库单 -->
         <el-tab-pane label="出库单" name="outbound">
           <el-button type="primary" size="mini" @click="showCreateOutbound = true" style="margin-bottom:12px;">新建出库单</el-button>
-          <el-table :data="outboundList" v-loading="outboundLoading" border stripe max-height="500">
+                    <div class="adm-table-wrap">
+<el-table :data="outboundList" v-loading="outboundLoading" border stripe height="100%">
             <el-table-column prop="id" label="ID" width="60">
               <template slot="header">
                 <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -148,14 +161,18 @@
                 <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
               </template>
             </el-table-column>
+          
+          
           </el-table>
+          </div>
           <el-pagination @size-change="outboundPageSize=$event;loadOutboundList()" @current-change="outboundPage=$event;loadOutboundList()" :current-page="outboundPage" :page-sizes="[10,20,50]" :page-size="outboundPageSize" :total="outboundTotal" layout="total, sizes, prev, pager, next, jumper" class="pagination-wrap" />
         </el-tab-pane>
 
         <!-- 盘点记录 -->
         <el-tab-pane label="盘点记录" name="counting">
           <el-button type="primary" size="mini" @click="showCreateCounting = true" style="margin-bottom:12px;">新建盘点</el-button>
-          <el-table :data="countingList" v-loading="countingLoading" border stripe max-height="500">
+                    <div class="adm-table-wrap">
+<el-table :data="countingList" v-loading="countingLoading" border stripe height="100%">
             <el-table-column prop="id" label="ID" width="60">
               <template slot="header">
                 <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -192,7 +209,10 @@
                 <el-button v-if="row.status === 'pending'" size="mini" type="success" @click="handleConfirmCounting(row)">确认</el-button>
               </template>
             </el-table-column>
+          
+          
           </el-table>
+          </div>
           <el-pagination @size-change="countingPageSize=$event;loadCountingRecords()" @current-change="countingPage=$event;loadCountingRecords()" :current-page="countingPage" :page-sizes="[10,20,50]" :page-size="countingPageSize" :total="countingTotal" layout="total, sizes, prev, pager, next, jumper" class="pagination-wrap" />
         </el-tab-pane>
       </el-tabs>
@@ -210,7 +230,6 @@
         <el-table-column prop="productId" label="商品ID" width="70" />
         <el-table-column prop="skuId" label="SKU ID" width="70" />
         <el-table-column prop="quantity" label="数量" width="70" />
-      </el-table>
     </el-dialog>
 
     <!-- 新建出库弹窗 -->
@@ -268,6 +287,8 @@
         <el-button type="primary" @click="handleCreateCounting">提交</el-button>
       </span>
     </el-dialog>
+  
+  </el-table>
   </div>
 </template>
 

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

@@ -8,7 +8,8 @@
           <el-button type="primary" size="small" @click="handleCreate">+ 新增标签</el-button>
         </div>
       </div>
-      <el-table :max-height="tableHeight" :data="filteredTags" v-loading="loading" border stripe>
+            <div class="adm-table-wrap">
+<el-table height="100%" :data="filteredTags" v-loading="loading" border stripe>
         <el-table-column prop="id" label="ID" width="70">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
@@ -45,6 +46,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <el-dialog :visible.sync="dialogVisible" :title="isEdit ? '编辑标签' : '新增标签'" width="450px" :close-on-click-modal="false">

+ 12 - 4
cfc-web/src/views/admin/LangGraphAdmin.vue

@@ -44,7 +44,8 @@
       <el-tab-pane label="概览" name="overview">
         <div class="lg-panel">
           <div class="lg-panel-title">组件健康状态</div>
-          <el-table :data="componentList" border stripe size="small" class="lg-table">
+                    <div class="adm-table-wrap">
+<el-table :data="componentList" border stripe size="small" class="lg-table" height="100%">
             <el-table-column prop="name" label="组件" width="140" />
             <el-table-column prop="status" label="状态" width="100">
               <template slot-scope="{ row }">
@@ -55,6 +56,7 @@
             </el-table-column>
             <el-table-column prop="detail" label="详情" show-overflow-tooltip />
           </el-table>
+          </div>
         </div>
         <div class="lg-panel">
           <div class="lg-panel-title">服务配置</div>
@@ -80,7 +82,8 @@
           <el-input v-model="apiFilter" placeholder="筛选路径或说明..." prefix-icon="el-icon-search" size="small" clearable style="width:280px" />
           <el-button size="small" @click="fetchSpec">刷新</el-button>
         </div>
-        <el-table :data="filteredApiEndpoints" border stripe size="small" class="lg-table lg-table-full">
+                <div class="adm-table-wrap">
+<el-table :data="filteredApiEndpoints" border stripe size="small" class="lg-table lg-table-full" height="100%">
           <el-table-column prop="method" label="方法" width="80" fixed>
             <template slot-scope="{ row }">
               <el-tag :type="row.method === 'GET' ? 'info' : 'primary'" size="mini">{{ row.method }}</el-tag>
@@ -94,6 +97,7 @@
             </template>
           </el-table-column>
         </el-table>
+        </div>
       </el-tab-pane>
 
       <el-tab-pane label="请求历史" name="logs">
@@ -106,7 +110,8 @@
           <el-tag size="small" type="info">共 {{ totalLogs }} 条</el-tag>
           <el-button size="small" @click="fetchLogs" :loading="logLoading">刷新</el-button>
         </div>
-        <el-table :data="filteredLogs" border stripe size="small" class="lg-table lg-table-full">
+                <div class="adm-table-wrap">
+<el-table :data="filteredLogs" border stripe size="small" class="lg-table lg-table-full" height="100%">
           <el-table-column prop="ts" label="时间" width="70" fixed />
           <el-table-column prop="method" label="方法" width="60">
             <template slot-scope="{ row }">
@@ -131,7 +136,9 @@
             </template>
           </el-table-column>
         </el-table>
-        <el-table :data="logDetailRows" border stripe size="small" class="lg-table lg-table-full lg-detail-table" v-if="expandedLog">
+        </div>
+                <div class="adm-table-wrap">
+<el-table :data="logDetailRows" border stripe size="small" class="lg-table lg-table-full lg-detail-table" v-if="expandedLog" height="100%">
           <el-table-column label="时间" width="90"><template slot-scope="{ row }">{{ row.ts }}</template></el-table-column>
           <el-table-column label="方法" width="60"><template slot-scope="{ row }">{{ row.method }}</template></el-table-column>
           <el-table-column label="路径"><template slot-scope="{ row }">{{ row.path }}</template></el-table-column>
@@ -141,6 +148,7 @@
           <el-table-column label="请求体" min-width="200"><template slot-scope="{ row }"><pre class="detail-pre">{{ row.body || '(空)' }}</pre></template></el-table-column>
           <el-table-column label="响应体" min-width="200"><template slot-scope="{ row }"><pre class="detail-pre">{{ (row.response || '').substring(0, 2000) }}</pre></template></el-table-column>
         </el-table>
+        </div>
       </el-tab-pane>
 
     </el-tabs>

+ 3 - 1
cfc-web/src/views/admin/MbtiProfiles.vue

@@ -12,7 +12,8 @@
         </div>
       </div>
 
-      <el-table :data="list" border stripe v-loading="loading" size="mini" style="width:100%">
+            <div class="adm-table-wrap">
+<el-table :data="list" border stripe v-loading="loading" size="mini" style="width:100%" height="100%">
         <el-table-column prop="typeCode" label="类型代码" width="100" align="center" />
         <el-table-column prop="name" label="中文名称" width="120" align="center" />
         <el-table-column prop="description" label="核心特质" show-overflow-tooltip />
@@ -32,6 +33,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <el-dialog :title="form.id ? '编辑画像' : '新增画像'" :visible.sync="dialogVisible" width="680px" :close-on-click-modal="false">

+ 10 - 9
cfc-web/src/views/admin/MembershipCenter.vue

@@ -26,8 +26,8 @@
         
         <!-- Tab 1: 会员等级配置 -->
         <el-tab-pane label="会员等级配置" name="levels">
-          <div class="table-scroll-wrap-lg">
-            <el-table :data="safeLevels" v-loading="levelsLoading" border stripe size="small" empty-text="暂无等级配置">
+          <div class="adm-table-wrap">
+            <el-table :data="safeLevels" v-loading="levelsLoading" border stripe size="small" empty-text="暂无等级配置" height="100%">
               <el-table-column prop="levelCode" label="等级编码" width="110">
                 <template slot-scope="{ row }">
                   <el-tag :type="levelTagType(row.levelCode)" size="small">{{ row.levelCode }}</el-tag>
@@ -75,14 +75,14 @@
                   <el-button size="mini" type="info" plain @click="handleViewFeatures(row)">权限</el-button>
                 </template>
               </el-table-column>
-            </el-table>
+          </el-table>
           </div>
         </el-tab-pane>
 
         <!-- Tab 2: 升级记录 -->
         <el-tab-pane label="升级记录" name="records">
-          <div class="table-scroll-wrap-lg">
-            <el-table :data="upgradeRecords" v-loading="recordsLoading" border stripe size="small" empty-text="暂无升级记录">
+          <div class="adm-table-wrap">
+            <el-table :data="upgradeRecords" v-loading="recordsLoading" border stripe size="small" empty-text="暂无升级记录" height="100%">
               <el-table-column prop="id" label="ID" width="60">
                 <template slot="header">
                   <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -122,7 +122,7 @@
                   <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">创建时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
                 </template>
               </el-table-column>
-            </el-table>
+          </el-table>
           </div>
           <div class="pagination-wrap" v-if="recordsTotal > 0">
             <el-pagination
@@ -150,8 +150,8 @@
             <el-button type="primary" size="small" icon="el-icon-search" @click="handleMaintainSearch" :loading="maintainSearching">搜索</el-button>
             <span v-if="safeMaintainUsers.length" class="search-result-count">找到 {{ safeMaintainUsers.length }} 个用户</span>
           </div>
-          <div v-if="safeMaintainUsers.length" class="table-scroll-wrap-sm">
-            <el-table :data="safeMaintainUsers" border stripe size="small">
+          <div v-if="safeMaintainUsers.length" class="adm-table-wrap">
+            <el-table :data="safeMaintainUsers" border stripe size="small" height="100%">
               <el-table-column prop="id" label="ID" width="70" />
               <el-table-column prop="nickname" label="昵称" width="130" />
               <el-table-column prop="phone" label="手机号" width="130" />
@@ -168,7 +168,8 @@
                   <el-button size="mini" type="warning" @click="openMaintainDialog(row)">维护</el-button>
                 </template>
               </el-table-column>
-            </el-table>
+          
+          </el-table>
           </div>
           <div v-else-if="maintainSearched" class="empty-state">
             <i class="el-icon-user-solid"></i>

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

@@ -19,14 +19,14 @@
         </div>
       </div>
 
-      <div class="table-scroll-wrap">
+      <div class="adm-table-wrap">
         <el-table
           :data="filteredMenuTree"
           row-key="id"
           :tree-props="{ children: 'children' }"
           :default-expand-all="!searchKeyword"
           stripe
-          :max-height="tableHeight"
+          height="100%"
           style="min-width: 1200px; width: 100%"
         >
           <el-table-column prop="displayName" label="菜单名称" min-width="220" show-overflow-tooltip>
@@ -61,10 +61,10 @@
               <el-button size="mini" type="success" @click="handleCreate(row.id)">+ 子菜单</el-button>
             </template>
           </el-table-column>
-        </el-table>
         <div v-if="!filteredMenuTree.length" class="empty-state">
           <el-empty description="暂无菜单数据"></el-empty>
         </div>
+      </el-table>
       </div>
     </el-card>
 

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

@@ -6,8 +6,8 @@
         <el-button type="primary" size="small" @click="openCreate">+ 新建公告</el-button>
       </div>
 
-      <div class="table-scroll-wrap-lg">
-        <el-table :data="list" v-loading="loading" border stripe :max-height="tableHeight">
+      <div class="adm-table-wrap">
+        <el-table :data="list" v-loading="loading" border stripe height="100%">
           <el-table-column prop="id" label="ID" width="70" />
           <el-table-column label="类型" width="90">
             <template slot-scope="{ row }">
@@ -39,7 +39,7 @@
               <el-button size="mini" type="danger" @click="handleDelete(row)">删除</el-button>
             </template>
           </el-table-column>
-        </el-table>
+      </el-table>
       </div>
     </el-card>
 

+ 3 - 1
cfc-web/src/views/admin/NotificationTemplateManage.vue

@@ -7,7 +7,8 @@
       </div>
 
       <!-- 模板列表 -->
-      <el-table :data="list" border v-loading="loading">
+            <div class="adm-table-wrap">
+<el-table :data="list" border v-loading="loading" height="100%">
         <el-table-column prop="type" label="类型" width="160">
           <template slot-scope="{ row }">
             <el-tag size="small">{{ row.type }}</el-tag>
@@ -58,6 +59,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <!-- 编辑弹窗 -->

+ 3 - 1
cfc-web/src/views/admin/NumSoulDetailConfig.vue

@@ -13,7 +13,8 @@
           <el-button type="primary" size="small" style="margin-left: 8px" @click="showCreate">新增</el-button>
         </div>
       </div>
-      <el-table :data="list" border stripe>
+            <div class="adm-table-wrap">
+<el-table :data="list" border stripe height="100%">
         <el-table-column prop="numberType" label="类型" width="110">
           <template slot-scope="scope">{{ typeLabel(scope.row.numberType) }}</template>
         </el-table-column>
@@ -34,6 +35,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <el-dialog :title="form.id ? '编辑' : '新增'" :visible.sync="dialogVisible" width="520px">
         <el-form :model="form" label-width="100px">

+ 8 - 4
cfc-web/src/views/admin/NutritionProducts.vue

@@ -42,14 +42,15 @@
         </div>
       </div>
 
-      <el-table
+            <div class="adm-table-wrap">
+<el-table
         :data="list"
         v-loading="loading"
         border
         stripe
         size="mini"
         style="width: 100%"
-        :max-height="tableHeight"
+        height="100%"
       >
         <el-table-column type="index" label="#" width="50" align="center" />
         <el-table-column label="图片" width="100" align="center">
@@ -132,6 +133,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <!-- 分页 -->
       <div class="pagination-wrapper">
@@ -210,7 +212,6 @@
             <template slot-scope="{ row }">{{ row.amountPerServing }} {{ row.unit }}</template>
           </el-table-column>
           <el-table-column prop="purpose" label="功效" min-width="150" />
-        </el-table>
         <div v-else class="no-data">暂无成份数据</div>
 
         <el-divider content-position="left">平台价格</el-divider>
@@ -232,7 +233,6 @@
               <span v-else class="no-data">-</span>
             </template>
           </el-table-column>
-        </el-table>
         <div v-else class="no-data">暂无平台价格数据</div>
 
         <el-divider content-position="left">本平台关联商品</el-divider>
@@ -242,6 +242,10 @@
           <p>价格: ¥{{ (detailData.localProduct.price / 100).toFixed(2) }}</p>
         </div>
         <div v-else class="no-data">未关联本平台商品</div>
+      
+      </el-table>
+      
+      </el-table>
       </div>
       <div v-else-if="detailLoading" style="text-align: center; padding: 40px;">
         <i class="el-icon-loading" style="font-size: 24px;"></i>

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

@@ -91,7 +91,7 @@
           </el-dropdown>
         </template>
       </el-table-column>
-      </el-table>
+    </el-table>
     </div>
 
 <el-pagination

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

@@ -27,8 +27,8 @@
       />
     </div>
 
-    <div class="table-scroll-wrap">
-      <el-table :data="list" v-loading="loading" border stripe :max-height="tableHeight">
+    <div class="adm-table-wrap">
+      <el-table :data="list" v-loading="loading" border stripe height="100%">
         <el-table-column prop="id" label="ID" width="70">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
@@ -101,7 +101,7 @@
             </el-button>
           </template>
         </el-table-column>
-      </el-table>
+    </el-table>
     </div>
 
     <div class="pagination-wrap">

+ 3 - 1
cfc-web/src/views/admin/PeriodicServiceConfig.vue

@@ -5,7 +5,8 @@
       <el-button type="primary" icon="el-icon-plus" @click="openDialog()">新增配置</el-button>
     </div>
 
-    <el-table :data="list" v-loading="loading" border stripe style="width: 100%">
+        <div class="adm-table-wrap">
+<el-table :data="list" v-loading="loading" border stripe style="width: 100%" height="100%">
       <el-table-column prop="id" label="ID" width="60">
         <template slot="header">
           <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
@@ -52,6 +53,7 @@
         </template>
       </el-table-column>
     </el-table>
+    </div>
 
     <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px" @closed="resetForm">
       <el-form :model="form" label-width="120px" size="small">

+ 3 - 1
cfc-web/src/views/admin/PlanManagement.vue

@@ -16,7 +16,8 @@
       </div>
     </div>
 
-    <el-table :data="plans" border stripe style="width:100%" :max-height="tableHeight">
+        <div class="adm-table-wrap">
+<el-table :data="plans" border stripe style="width:100%" height="100%">
       <el-table-column prop="id" label="方案ID" width="80">
         <template slot="header">
           <span class="sort-header" @click.stop="onSortClick('id', $event)">方案ID{{ sortIcon('id') }}</span>
@@ -66,6 +67,7 @@
         </template>
       </el-table-column>
     </el-table>
+    </div>
 
     <el-dialog title="方案详情" :visible.sync="detailVisible" width="600px">
       <el-form label-width="100px" v-if="currentPlan">

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

@@ -20,8 +20,8 @@
           <el-input v-model="filter.childKeyword" placeholder="搜索孩子 ID..." prefix-icon="el-icon-search" size="mini" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         </div>
       </div>
-      <div class="table-scroll-wrap-sm">
-      <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe>
+      <div class="adm-table-wrap">
+      <el-table height="100%" :data="list" v-loading="loading" border stripe>
         <el-table-column prop="createdAt" label="时间" width="160">
           <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
           <template slot="header">
@@ -79,7 +79,7 @@
             </el-dropdown>
           </template>
         </el-table-column>
-      </el-table>
+    </el-table>
     </div>
 
     <el-pagination

+ 3 - 1
cfc-web/src/views/admin/ProblemDomainManage.vue

@@ -6,7 +6,8 @@
         <el-button type="primary" size="mini" icon="el-icon-plus" @click="openCreate">新增问题域</el-button>
       </div>
 
-      <el-table :data="list" v-loading="loading" border stripe>
+            <div class="adm-table-wrap">
+<el-table :data="list" v-loading="loading" border stripe height="100%">
         <el-table-column prop="id" label="ID" width="60">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -60,6 +61,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <el-dialog :title="form.id ? '编辑问题域' : '新增问题域'" :visible.sync="dialogVisible" width="700px" top="5vh">

+ 7 - 3
cfc-web/src/views/admin/ProductManage.vue

@@ -81,7 +81,8 @@
 
       <!-- 表格区:数值右对齐等宽、状态标准化标签、操作列降为文本按钮 -->
       <div class="pm-table-wrap">
-        <el-table
+                <div class="adm-table-wrap">
+<el-table
           ref="table"
           height="100%"
           :data="list"
@@ -210,6 +211,7 @@
             </template>
           </el-table-column>
         </el-table>
+        </div>
       </div>
 
       <!-- 分页:右侧对齐,总数前置 -->
@@ -274,7 +276,8 @@
             <el-table-column prop="specs" label="规格" min-width="180" show-overflow-tooltip />
             <el-table-column prop="stock" label="库存" width="88" align="right" class-name="pm-num" />
             <el-table-column prop="minStockAlert" label="预警下限" width="96" align="right" class-name="pm-num" />
-          </el-table>
+        
+        </el-table>
         </div>
 
         <div class="pm-section">
@@ -300,7 +303,8 @@
             <el-table-column label="时间" width="160">
               <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
             </el-table-column>
-          </el-table>
+        
+        </el-table>
         </div>
       </div>
       <span slot="footer">

+ 3 - 1
cfc-web/src/views/admin/ProductPpointManage.vue

@@ -18,7 +18,8 @@
         </el-tag>
       </div>
     </div>
-    <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe>
+        <div class="adm-table-wrap">
+<el-table height="100%" :data="list" v-loading="loading" border stripe>
       <el-table-column prop="id" label="ID" width="70" />
       <el-table-column label="商品图片" width="70">
         <template slot-scope="{ row }">
@@ -87,6 +88,7 @@
         </template>
       </el-table-column>
     </el-table>
+    </div>
 
     <el-pagination
       @size-change="onSizeChange"

+ 3 - 1
cfc-web/src/views/admin/ProductProfitRate.vue

@@ -13,7 +13,8 @@
         <el-button type="primary" @click="loadList">查询</el-button>
       </div>
       </div>
-    <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe>
+        <div class="adm-table-wrap">
+<el-table height="100%" :data="list" v-loading="loading" border stripe>
       <el-table-column prop="id" label="ID" width="70">
         <template slot="header">
           <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
@@ -75,6 +76,7 @@
         </template>
       </el-table-column>
     </el-table>
+    </div>
 
     <el-pagination
       @size-change="onSizeChange"

+ 3 - 1
cfc-web/src/views/admin/ProfileManagement.vue

@@ -12,7 +12,8 @@
         <el-button size="mini" @click="resetSearch">重置</el-button>
       </div>
 
-      <el-table :data="list" v-loading="loading" border stripe style="min-width:800px;">
+            <div class="adm-table-wrap">
+<el-table :data="list" v-loading="loading" border stripe style="min-width:800px;" height="100%">
         <el-table-column prop="id" label="ID" width="70" />
         <el-table-column prop="name" label="姓名" width="100" />
         <el-table-column prop="age" label="年龄" width="70" />
@@ -46,6 +47,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <el-pagination
         style="margin-top:16px;text-align:right;"

+ 8 - 4
cfc-web/src/views/admin/PromotionManagement.vue

@@ -6,7 +6,8 @@
           <div style="margin-bottom: 16px">
             <el-input v-model="milestoneKeyword" placeholder="搜索里程碑..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleMilestoneSearch" @clear="handleMilestoneSearch" />
     </div>
-    <el-table :data="filteredMilestones" v-loading="milestoneLoading" border stripe :max-height="tableHeight">
+        <div class="adm-table-wrap">
+<el-table :data="filteredMilestones" v-loading="milestoneLoading" border stripe height="100%">
             <el-table-column prop="milestone" label="里程碑" min-width="180" show-overflow-tooltip>
               <template slot="header">
                 <span class="sort-header" @click.stop="onSortClick('milestone', $event)">里程碑<span v-if="sortIndex('milestone') !== null" class="sort-index" :class="sortClass('milestone') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('milestone') }}</span>{{ sortIcon('milestone') }}</span>
@@ -30,6 +31,7 @@
               </template>
             </el-table-column>
     </el-table>
+    </div>
     </el-tab-pane>
         <el-tab-pane label="排行榜" name="leaderboard">
           <div style="margin-bottom: 16px; display: flex; gap: 16px; align-items: center; flex-wrap: wrap">
@@ -51,7 +53,8 @@
             <el-button type="primary" @click="handleSearch">搜索</el-button>
             <el-button @click="resetLeaderboard">重置</el-button>
     </div>
-    <el-table :data="leaderboardList" v-loading="leaderboardLoading" border stripe :max-height="tableHeight">
+        <div class="adm-table-wrap">
+<el-table :data="leaderboardList" v-loading="leaderboardLoading" border stripe height="100%">
             <el-table-column label="排名" width="80" type="index" :index="leaderboardIndex" />
             <el-table-column label="头像" width="80">
               <template slot-scope="{ row }">
@@ -68,7 +71,6 @@
                 <span class="sort-header" @click.stop="onSortClick('referralCount', $event)">邀请数<span v-if="sortIndex('referralCount') !== null" class="sort-index" :class="sortClass('referralCount') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('referralCount') }}</span>{{ sortIcon('referralCount') }}</span>
               </template>
             </el-table-column>
-    </el-table>
     <el-pagination
             v-if="leaderboardTotal > leaderboardSize"
             @current-change="onLeaderboardPageChange"
@@ -99,8 +101,10 @@
         <el-button type="primary" @click="handleMilestoneSubmit" :loading="milestoneSubmitting">保存</el-button>
       </div>
     </el-dialog>
+  </el-table>
   </div>
-</template>
+
+</div></template>
 
 <script>
 import SortMixin from '@/mixins/SortMixin'

+ 3 - 1
cfc-web/src/views/admin/PurchaseManagement.vue

@@ -52,7 +52,8 @@
       </el-form>
 
       <!-- 列表 -->
-      <el-table :data="list" v-loading="loading" border stripe style="width: 100%;">
+            <div class="adm-table-wrap">
+<el-table :data="list" v-loading="loading" border stripe style="width: 100%;" height="100%">
         <el-table-column prop="orderNo" label="订单号" width="180">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('orderNo', $event)">订单号<span v-if="sortIndex('orderNo') !== null" class="sort-index" :class="sortClass('orderNo') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('orderNo') }}</span>{{ sortIcon('orderNo') }}</span>
@@ -114,6 +115,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <!-- 分页 -->
       <el-pagination

+ 5 - 4
cfc-web/src/views/admin/Recipes.vue

@@ -31,8 +31,8 @@
           <el-button type="primary" size="mini" @click="openCreate">新建食谱</el-button>
         </div>
       </div>
-      <div class="table-scroll-wrap-sm">
-      <el-table :data="list" v-loading="loading" border stripe :max-height="tableHeight">
+      <div class="adm-table-wrap">
+      <el-table :data="list" v-loading="loading" border stripe height="100%">
         <el-table-column prop="id" label="ID" width="80" />
         <el-table-column prop="name" label="名称">
           <template slot="header">
@@ -69,7 +69,6 @@
           </template>
         </el-table-column>
 
-    </el-table></div>
     </el-card>
     <el-dialog :title="editId ? '编辑食谱' : '新建食谱'" :visible.sync="dialogVisible" width="700px">
       <el-form label-width="120px">
@@ -120,8 +119,10 @@
         <el-button type="primary" :loading="submitting" @click="saveItem">保存</el-button>
       </div>
     </el-dialog>
+  </el-table>
   </div>
-</template>
+
+</div></template>
 
 <script>
 import SortMixin from '@/mixins/SortMixin'

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

@@ -8,8 +8,8 @@
         </div>
       </div>
 
-      <div class="table-scroll-wrap">
-        <el-table :data="list" v-loading="loading" border stripe>
+      <div class="adm-table-wrap">
+        <el-table :data="list" v-loading="loading" border stripe height="100%">
           <el-table-column prop="ruleName" label="规则名称" min-width="140" show-overflow-tooltip />
           <el-table-column label="类型" width="100">
             <template slot-scope="{ row }">
@@ -82,7 +82,7 @@
               <el-button size="mini" type="danger" @click="handleDelete(row)">删除</el-button>
             </template>
           </el-table-column>
-        </el-table>
+      </el-table>
       </div>
 
       <el-pagination

+ 3 - 1
cfc-web/src/views/admin/ReportAutoLearn.vue

@@ -10,7 +10,8 @@
 
       <el-alert title='自学习机制:当同一类型的未知报告积累到3份后,系统自动标记为"可生成"状态,管理员可在此生成报告类型和指纹规则。' type='info' :closable='false' show-icon style='margin-bottom: 16px;' />
 
-      <el-table :data="clusters" v-loading="loading" border stripe>
+            <div class="adm-table-wrap">
+<el-table :data="clusters" v-loading="loading" border stripe height="100%">
         <el-table-column prop="id" label="ID" width="60">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
@@ -51,6 +52,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <el-dialog :visible.sync="detailVisible" title="聚类详情" width="750px">

+ 8 - 4
cfc-web/src/views/admin/ReportCollectorTrainer.vue

@@ -13,7 +13,8 @@
               <el-option label="已生成" value="generated" />
             </el-select>
           </div>
-          <el-table :data="clusters" v-loading="loading" border stripe :max-height="tableHeight" @row-click="selectCluster">
+                    <div class="adm-table-wrap">
+<el-table :data="clusters" v-loading="loading" border stripe height="100%" @row-click="selectCluster">
             <el-table-column prop="id" label="ID" width="50">
               <template slot="header">
                 <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
@@ -40,6 +41,7 @@
               </template>
             </el-table-column>
           </el-table>
+          </div>
         </el-card>
       </el-col>
 
@@ -51,7 +53,8 @@
             <el-tag :type="clusterStatusType(currentCluster.status)" size="small" style="margin-left:8px">{{ clusterStatusText(currentCluster.status) }}</el-tag>
           </div>
 
-          <el-table :data="uploads" v-loading="uploadLoading" border stripe :max-height="300" @row-click="selectUpload">
+                    <div class="adm-table-wrap">
+<el-table :data="uploads" v-loading="uploadLoading" border stripe height="100%" @row-click="selectUpload">
             <el-table-column prop="id" label="ID" width="50" />
             <el-table-column label="文件" min-width="200">
               <template slot-scope="{ row }">
@@ -67,7 +70,6 @@
                 <el-tag v-else size="small" type="info">未标注</el-tag>
               </template>
             </el-table-column>
-          </el-table>
         </el-card>
         <el-card v-else>
           <div style="text-align:center;padding:80px;color:#bbb">
@@ -125,8 +127,10 @@
         <el-button type="primary" :loading="savingAnnotation" @click="saveAnnotation">保存标注</el-button>
       </div>
     </el-dialog>
+  </el-table>
   </div>
-</template>
+
+</div></template>
 
 <script>
 import { getUnknownClusters, getClusterUploads, generateTypeFromCluster } from '@/api/reportParser'

+ 3 - 1
cfc-web/src/views/admin/ReportManagement.vue

@@ -23,7 +23,8 @@
         <el-button @click="doSync">同步数据</el-button>
       </div>
 
-      <el-table :data="tableData" border stripe v-loading="loading" style="margin-top:16px">
+            <div class="adm-table-wrap">
+<el-table :data="tableData" border stripe v-loading="loading" style="margin-top:16px" height="100%">
         <el-table-column prop="id" label="ID" width="80">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
@@ -74,6 +75,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
 
       <el-pagination
         @size-change="handleSizeChange"

+ 5 - 2
cfc-web/src/views/admin/ReportParserImport.vue

@@ -8,7 +8,8 @@
         </div>
       </div>
 
-      <el-table :data="imports" v-loading="loading" border stripe>
+            <div class="adm-table-wrap">
+<el-table :data="imports" v-loading="loading" border stripe height="100%">
         <el-table-column prop="id" label="ID" width="60">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
@@ -44,6 +45,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <!-- 上传导入包对话框 -->
@@ -83,9 +85,10 @@
               <el-tag :type="row.isExclusion ? 'danger' : 'success'" size="mini">{{ row.isExclusion ? '排除' : '正向' }}</el-tag>
             </template>
           </el-table-column>
-        </el-table>
         <h4>解析脚本</h4>
         <pre class="script-preview">{{ previewData.parserScript }}</pre>
+      
+      </el-table>
       </div>
     </el-dialog>
   </div>

+ 3 - 1
cfc-web/src/views/admin/ReportTemplateManage.vue

@@ -6,7 +6,8 @@
         <span>报告模板管理</span>
         <el-button size="small" type="primary" @click="openEditor(null)">+ 新增模板</el-button>
       </div>
-      <el-table :data="templates" v-loading="loading" border stripe :max-height="tableHeight">
+            <div class="adm-table-wrap">
+<el-table :data="templates" v-loading="loading" border stripe height="100%">
         <el-table-column prop="id" label="ID" width="60">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
@@ -40,6 +41,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <!-- Tab 2: 模板编辑器 -->

+ 5 - 2
cfc-web/src/views/admin/ReportTypeManagement.vue

@@ -9,7 +9,8 @@
         </div>
       </div>
 
-      <el-table :data="filteredTypes" v-loading="loading" border stripe>
+            <div class="adm-table-wrap">
+<el-table :data="filteredTypes" v-loading="loading" border stripe height="100%">
         <el-table-column prop="id" label="ID" width="60">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
@@ -78,6 +79,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <!-- 编辑/新增对话框 -->
@@ -131,7 +133,6 @@
             <el-button size="mini" type="danger" @click="handleDeleteFp(row)">删除</el-button>
           </template>
         </el-table-column>
-      </el-table>
 
       <el-dialog :visible.sync="fpFormVisible" title="新增指纹规则" width="450px" :append-to-body="true">
         <el-form :model="fpForm" label-width="80px" size="small">
@@ -155,6 +156,8 @@
         </div>
       </el-dialog>
     </el-dialog>
+  
+  </el-table>
   </div>
 </template>
 

+ 5 - 2
cfc-web/src/views/admin/ReportUnknownCluster.vue

@@ -13,7 +13,8 @@
         </div>
       </div>
 
-      <el-table :data="clusters" v-loading="loading" border stripe>
+            <div class="adm-table-wrap">
+<el-table :data="clusters" v-loading="loading" border stripe height="100%">
         <el-table-column prop="id" label="ID" width="60">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID{{ sortIcon('id') }}</span>
@@ -51,6 +52,7 @@
           </template>
         </el-table-column>
       </el-table>
+      </div>
     </el-card>
 
     <!-- 详情对话框 -->
@@ -74,9 +76,10 @@
           <el-table-column prop="id" label="ID" width="60" />
           <el-table-column prop="fileUrl" label="文件" min-width="200" />
           <el-table-column prop="uploadedAt" label="上传时间" width="180" />
-        </el-table>
       </template>
     </el-dialog>
+  
+  </el-table>
   </div>
 </template>
 

Неке датотеке нису приказане због велике количине промена