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

Merge branch 'test_dev' into lisijia

李思佳 2 недель назад
Родитель
Сommit
7558410332
33 измененных файлов с 1134 добавлено и 362 удалено
  1. 1 1
      LiangZhiYUMao/pages/plaza/report.vue
  2. 0 1
      gateway/src/main/resources/application.yml
  3. 1 1
      marriageAdmin-vue/index.html
  4. 6 6
      marriageAdmin-vue/src/assets/data-display.css
  5. 8 82
      marriageAdmin-vue/src/assets/list-common.css
  6. 7 22
      marriageAdmin-vue/src/assets/main.css
  7. 2 0
      marriageAdmin-vue/src/config/api.js
  8. 12 1
      marriageAdmin-vue/src/layouts/MainLayout.vue
  9. 32 16
      marriageAdmin-vue/src/views/activity/ActivityList.vue
  10. 19 8
      marriageAdmin-vue/src/views/admin/AdminUserList.vue
  11. 2 0
      marriageAdmin-vue/src/views/banner/BannerList.vue
  12. 27 5
      marriageAdmin-vue/src/views/course/CourseList.vue
  13. 21 11
      marriageAdmin-vue/src/views/dynamic/DynamicList.vue
  14. 63 47
      marriageAdmin-vue/src/views/matchmaker/CaseAudit.vue
  15. 52 36
      marriageAdmin-vue/src/views/matchmaker/MatchmakerAudit.vue
  16. 55 3
      marriageAdmin-vue/src/views/matchmaker/MatchmakerForm.vue
  17. 69 20
      marriageAdmin-vue/src/views/matchmaker/MatchmakerList.vue
  18. 28 11
      marriageAdmin-vue/src/views/matchmaker/PointsOrderList.vue
  19. 36 20
      marriageAdmin-vue/src/views/matchmaker/ResourceList.vue
  20. 4 0
      marriageAdmin-vue/src/views/points-product/PointsProductList.vue
  21. 40 16
      marriageAdmin-vue/src/views/report/ReportList.vue
  22. 29 6
      marriageAdmin-vue/src/views/success-case/SuccessCaseList.vue
  23. 88 37
      marriageAdmin-vue/src/views/user/UserList.vue
  24. 21 5
      service/admin/src/main/java/com/zhentao/controller/DashboardController.java
  25. 91 0
      service/admin/src/main/java/com/zhentao/controller/MatchmakerController.java
  26. 71 3
      service/admin/src/main/java/com/zhentao/controller/UserController.java
  27. 112 0
      service/admin/src/main/java/com/zhentao/vo/MatchmakerExcelVO.java
  28. 108 0
      service/admin/src/main/java/com/zhentao/vo/UserExcelVO.java
  29. 1 1
      service/dynamic/src/main/java/com/zhentao/controller/DynamicController.java
  30. 9 0
      service/dynamic/src/main/java/com/zhentao/service/impl/ReportServiceImpl.java
  31. 75 0
      service/homePage/src/main/java/com/zhentao/constant/RedisKeyConstants.java
  32. 26 2
      service/homePage/src/main/java/com/zhentao/service/impl/ActivityServiceImpl.java
  33. 18 1
      service/homePage/src/main/resources/mapper/MatchmakerMapper.xml

+ 1 - 1
LiangZhiYUMao/pages/plaza/report.vue

@@ -288,7 +288,7 @@ export default {
 				
 				// 提交成功
 				uni.showToast({
-					title: '举报已提交,我们将尽快处理',
+					title: '举报已提交',
 					icon: 'success',
 					duration: 2000
 				})

+ 0 - 1
gateway/src/main/resources/application.yml

@@ -187,7 +187,6 @@ spring:
             - Path=/api/recommend/**
           filters:
             - StripPrefix=0
-            - StripPrefix=0
 
         # 课程订单服务路由(Essential服务-微信支付)
         - id: course-order-route

+ 1 - 1
marriageAdmin-vue/index.html

@@ -2,7 +2,7 @@
 <html lang="">
   <head>
     <meta charset="UTF-8">
-    <link rel="icon" href="/favicon.ico">
+    <link rel="icon" type="image/png" href="/src/assets/qingluan.png">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Vite App</title>
   </head>

+ 6 - 6
marriageAdmin-vue/src/assets/data-display.css

@@ -204,7 +204,7 @@
 
 /* 状态标签颜色方案 */
 
-/* 成功状态 - 绿色 */
+/* 成功状态 - 绿色,保持为铂金等级色 */
 .el-tag--success,
 .status-success {
   background-color: #f0fdf4 !important;
@@ -274,12 +274,12 @@
   border: 1px solid #a5f3fc !important;
 }
 
-/* 已禁用状态 */
+/* 已禁用状态,改为钻石等级色 */
 .status-disabled {
-  background-color: #f3f4f6 !important;
-  color: #9ca3af !important;
-  border: 1px solid #d1d5db !important;
-  opacity: 0.7;
+  background-color: #fef2f2 !important;
+  color: #dc2626 !important;
+  border: 1px solid #fecaca !important;
+  opacity: 1 !important;
 }
 
 /* 状态标签悬停效果 */

+ 8 - 82
marriageAdmin-vue/src/assets/list-common.css

@@ -8,100 +8,26 @@
 
 /* 页面标题 */
 .page-title {
-  font-size: var(--font-3xl);
-  font-weight: var(--font-bold);
-  color: var(--text-primary);
-  margin: 0 0 var(--spacing-xl) 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+  margin: 0 0 20px 0;
   position: relative;
-  padding-bottom: var(--spacing-md);
+  padding-bottom: 0;
   animation: fadeIn 0.4s ease-out;
-  /* 使用 fadeIn 代替 slideInLeft,避免动画冲突 */
 }
 
 .page-title::after {
-  content: '';
-  position: absolute;
-  left: 0;
-  bottom: 0;
-  width: 60px;
-  height: 4px;
-  background: linear-gradient(90deg, var(--primary-color), var(--primary-light));
-  border-radius: 2px;
+  display: none;
 }
 
 /* ==================== 工具栏卡片 ==================== */
 .toolbar-card {
-  margin-bottom: var(--spacing-lg);
-  background-color: var(--bg-primary);
-  border: 1px solid var(--border-color);
-  transition: all var(--transition-base);
-  animation: fadeIn 0.5s ease-out;
-  /* 移除 slideInDown 动画,避免遮挡页面标题 */
-}
-
-.toolbar-card:hover {
-  box-shadow: var(--shadow-md);
+  margin-bottom: 20px;
 }
 
 .toolbar-card .el-card__body {
-  padding: var(--spacing-lg);
-}
-
-/* 搜索输入框样式 */
-.toolbar-card .el-input {
-  transition: all var(--transition-base);
-}
-
-.toolbar-card .el-input:hover .el-input__wrapper {
-  border-color: var(--primary-light);
-}
-
-.toolbar-card .el-input .el-input__wrapper.is-focus {
-  box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
-}
-
-/* 按钮样式增强 */
-.toolbar-card .el-button {
-  transition: all var(--transition-base);
-  font-weight: var(--font-medium);
-  white-space: nowrap; /* 防止文字换行 */
-  padding: 8px 15px; /* 确保按钮有足够的padding */
-}
-
-.toolbar-card .el-button--primary {
-  background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
-  border: none;
-  box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
-  color: #ffffff !important;  /* 确保按钮文字为白色 */
-}
-
-.toolbar-card .el-button--primary,
-.toolbar-card .el-button--primary * {
-  color: #ffffff !important;  /* 确保按钮所有文字为白色 */
-}
-
-.toolbar-card .el-button--primary span {
-  display: inline !important; /* 确保span元素正常显示 */
-  visibility: visible !important; /* 确保文字可见 */
-  opacity: 1 !important; /* 确保文字不透明 */
-}
-
-.toolbar-card .el-button--primary .el-icon {
-  color: #ffffff !important;  /* 确保按钮图标为白色 */
-}
-
-.toolbar-card .el-button--primary:hover {
-  transform: translateY(-2px);
-  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
-}
-
-.toolbar-card .el-button--primary:active {
-  transform: translateY(0);
-}
-
-/* 下拉选择器 */
-.toolbar-card .el-select {
-  transition: all var(--transition-base);
+  padding: 20px;
 }
 
 /* ==================== 表格卡片 ==================== */

+ 7 - 22
marriageAdmin-vue/src/assets/main.css

@@ -68,35 +68,20 @@
 
 /* 按钮样式增强 */
 .el-button {
-  border-radius: var(--radius-base);
-  font-weight: var(--font-medium);
-  transition: all var(--transition-base);
-  border: 1px solid transparent;
+  border-radius: 8px;
+  font-weight: 500;
+  transition: all 0.2s;
 }
 
 .el-button--primary {
-  background-color: var(--primary-color);
-  border-color: var(--primary-color);
-  color: #ffffff !important;  /* 确保按钮文字为白色 */
-}
-
-.el-button--primary span {
-  color: #ffffff !important;  /* 确保按钮内部文字为白色 */
-}
-
-.el-button--primary .el-icon {
-  color: #ffffff !important;  /* 确保按钮图标为白色 */
+  background-color: #409eff;
+  border-color: #409eff;
 }
 
 .el-button--primary:hover,
 .el-button--primary:focus {
-  background-color: var(--primary-light);
-  border-color: var(--primary-light);
-}
-
-.el-button--primary:active {
-  background-color: var(--primary-dark);
-  border-color: var(--primary-dark);
+  background-color: #66b1ff;
+  border-color: #66b1ff;
 }
 
 .el-button--success {

+ 2 - 0
marriageAdmin-vue/src/config/api.js

@@ -52,6 +52,7 @@ export const API_ENDPOINTS = {
   MATCHMAKER_UPDATE: '/api/matchmaker/update',
   MATCHMAKER_DELETE: '/api/matchmaker/delete',
   MATCHMAKER_STATS: '/api/matchmaker/stats',
+  MATCHMAKER_EXPORT: '/admin/matchmaker/export',
   MATCHMAKER_AUDIT_LIST: '/admin/marr-apply/list',
   MATCHMAKER_AUDIT_APPROVE: '/admin/marr-apply/approve',
   MATCHMAKER_AUDIT_BATCH_APPROVE: '/admin/marr-apply/batch-approve',
@@ -93,6 +94,7 @@ export const API_ENDPOINTS = {
   USER_UPDATE: '/admin/user/update',
   USER_STATS: '/admin/user/stats',
   USER_VIP_LIST: '/admin/user/vip/list',
+  USER_EXPORT: '/admin/user/export',
   // VIP 与系统消息
   USER_VIP_REMIND: '/admin/user/vip/remind',
   

+ 12 - 1
marriageAdmin-vue/src/layouts/MainLayout.vue

@@ -221,7 +221,7 @@ const handleCommand = async (command) => {
 
 /* ==================== 侧边栏样式 ==================== */
 .layout-aside {
-  background: linear-gradient(180deg, #1e293b 0%, #0f172a 100%);
+  background: linear-gradient(180deg, #374151 0%, #1f2937 100%);
   box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
   transition: width var(--transition-slow);
   position: relative;
@@ -334,6 +334,17 @@ const handleCommand = async (command) => {
   margin: 2px 8px;
   height: 42px;
   line-height: 42px;
+  color: #ffffff !important;
+}
+
+.aside-menu :deep(.el-menu--inline .el-menu-item:hover) {
+  color: #ffffff !important;
+  background-color: var(--sidebar-hover-bg) !important;
+}
+
+.aside-menu :deep(.el-menu--inline .el-menu-item.is-active) {
+  color: #ffffff !important;
+  background: linear-gradient(90deg, var(--primary-color) 0%, var(--primary-light) 100%) !important;
 }
 
 /* 菜单图标 */

+ 32 - 16
marriageAdmin-vue/src/views/activity/ActivityList.vue

@@ -5,14 +5,16 @@
     <!-- 操作栏 -->
     <el-card shadow="never" class="toolbar-card">
       <el-row :gutter="20">
-        <el-col :span="6">
-          <el-button type="primary" icon="Plus" @click="$router.push('/activity/create')">
-            新增活动
-          </el-button>
-          <el-button icon="Refresh" @click="loadActivityList">刷新</el-button>
+        <el-col :span="8">
+          <el-space>
+            <el-button type="primary" icon="Plus" @click="$router.push('/activity/create')">
+              新增活动
+            </el-button>
+            <el-button icon="Refresh" @click="loadActivityList">刷新</el-button>
+          </el-space>
         </el-col>
-        <el-col :span="18">
-          <el-space wrap>
+        <el-col :span="16">
+          <el-space style="justify-content: flex-end; width: 100%;">
             <el-select v-model="filters.type" placeholder="活动类型" clearable style="width: 120px" @change="loadActivityList">
               <el-option label="全部" :value="undefined" />
               <el-option label="线上" :value="1" />
@@ -67,6 +69,8 @@
               fit="cover"
               class="cover-image"
               :preview-src-list="getCoverPreviewList(row)"
+              preview-teleported
+              hide-on-click-modal
             >
               <template #error>
                 <div class="image-slot">
@@ -122,11 +126,7 @@
         <el-table-column prop="status" label="状态" width="100" align="center">
           <template #default="{ row }">
             <el-tag 
-              :class="{
-                'status-pending': row.status === 1,
-                'status-processing': row.status === 2,
-                'status-disabled': row.status === 3
-              }"
+              :type="row.status === 1 ? 'info' : row.status === 2 ? 'success' : 'danger'"
               size="small"
               effect="light"
             >
@@ -291,7 +291,10 @@ const loadActivityList = async () => {
           item.registrationEndTime ||
           item.registration_end_time ||
           item.signup_end_time ||
-          ''
+          '',
+        // 兼容报名人数字段
+        actualParticipants: item.actualParticipants ?? item.actual_participants ?? 0,
+        maxParticipants: item.maxParticipants ?? item.max_participants
       }))
       total.value = response.data.total || activityList.value.length
     }
@@ -412,10 +415,23 @@ onMounted(() => {
 </script>
 
 <style scoped>
-@import '@/assets/list-common.css';
-
 .activity-list-container {
-  padding: 0;
+  padding: 20px;
+}
+
+.page-title {
+  margin: 0 0 20px 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.toolbar-card {
+  margin-bottom: 20px;
+}
+
+.table-card {
+  margin-bottom: 20px;
 }
 
 /* 活动封面图片 */

+ 19 - 8
marriageAdmin-vue/src/views/admin/AdminUserList.vue

@@ -195,15 +195,14 @@
           />
         </el-form-item>
         
-        <el-form-item label="角色" prop="roleIds">
+        <el-form-item label="角色" prop="roleId">
           <el-select
-            v-model="formData.roleIds"
-            multiple
+            v-model="formData.roleId"
             placeholder="请选择角色"
             style="width: 100%"
           >
             <el-option
-              v-for="role in roleList"
+              v-for="role in filteredRoleList"
               :key="role.id"
               :label="role.roleName"
               :value="role.id"
@@ -247,6 +246,13 @@ const pageSize = ref(10)
 const total = ref(0)
 const roleList = ref([])
 
+// 筛选角色列表,只显示普通管理员和超级管理员
+const filteredRoleList = computed(() => {
+  return roleList.value.filter(role => 
+    role.roleCode === 'ADMIN' || role.roleCode === 'SUPER_ADMIN'
+  )
+})
+
 // 对话框
 const dialogVisible = ref(false)
 const dialogTitle = ref('注册管理员')
@@ -260,7 +266,7 @@ const formData = ref({
   realName: '',
   phone: '',
   email: '',
-  roleIds: [],
+  roleId: null,
   status: 1
 })
 
@@ -283,7 +289,7 @@ const formRules = {
   email: [
     { type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
   ],
-  roleIds: [
+  roleId: [
     { required: true, message: '请选择角色', trigger: 'change' }
   ]
 }
@@ -360,7 +366,7 @@ const handleEdit = (row) => {
     realName: row.realName || '',
     phone: row.phone || '',
     email: row.email || '',
-    roleIds: row.roles ? row.roles.map(r => r.id) : [],
+    roleId: row.roles && row.roles.length > 0 ? row.roles[0].id : null,
     status: row.status
   }
   dialogVisible.value = true
@@ -421,6 +427,11 @@ const handleSubmit = async () => {
       const method = isEdit.value ? 'PUT' : 'POST'
       
       const body = { ...formData.value }
+      // 将单个roleId转换为roleIds数组
+      if (body.roleId) {
+        body.roleIds = [body.roleId]
+      }
+      delete body.roleId
       if (isEdit.value && !body.password) {
         delete body.password
       }
@@ -460,7 +471,7 @@ const resetForm = () => {
     realName: '',
     phone: '',
     email: '',
-    roleIds: [],
+    roleId: null,
     status: 1
   }
   if (formRef.value) {

+ 2 - 0
marriageAdmin-vue/src/views/banner/BannerList.vue

@@ -54,6 +54,8 @@
               fit="cover"
               class="banner-image"
               :preview-src-list="[row.imageUrl]"
+              preview-teleported
+              hide-on-click-modal
             />
           </template>
         </el-table-column>

+ 27 - 5
marriageAdmin-vue/src/views/course/CourseList.vue

@@ -3,8 +3,17 @@
     <h2 class="page-title">课程管理</h2>
     
     <el-card shadow="never" class="toolbar-card">
-      <el-button type="primary" icon="Plus" @click="$router.push('/course/create')">新增课程</el-button>
-      <el-button icon="Refresh" @click="loadList">刷新</el-button>
+      <el-row :gutter="20">
+        <el-col :span="18">
+          <el-space wrap>
+            <el-button type="primary" icon="Plus" @click="$router.push('/course/create')">新增课程</el-button>
+            <el-button icon="Refresh" @click="loadList">刷新</el-button>
+          </el-space>
+        </el-col>
+        <el-col :span="6">
+          <!-- 预留搜索区域 -->
+        </el-col>
+      </el-row>
     </el-card>
     
     <el-card shadow="never" class="table-card">
@@ -198,10 +207,23 @@ onMounted(() => loadList())
 </script>
 
 <style scoped>
-@import '@/assets/list-common.css';
-
 .course-list-container {
-  padding: 0;
+  padding: 20px;
+}
+
+.page-title {
+  margin: 0 0 20px 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.toolbar-card {
+  margin-bottom: 20px;
+}
+
+.table-card {
+  margin-bottom: 20px;
 }
 
 .cover-wrapper {

+ 21 - 11
marriageAdmin-vue/src/views/dynamic/DynamicList.vue

@@ -3,15 +3,23 @@
     <h2 class="page-title">动态管理</h2>
     
     <el-card shadow="never" class="toolbar-card">
-      <el-space wrap>
-        <el-select v-model="filters.auditStatus" placeholder="审核状态" clearable style="width: 120px" @change="loadList">
-          <el-option label="全部" :value="undefined" />
-          <el-option label="待审核" :value="0" />
-          <el-option label="已通过" :value="1" />
-          <el-option label="未通过" :value="2" />
-        </el-select>
-        <el-button icon="Refresh" @click="loadList">刷新</el-button>
-      </el-space>
+      <el-row :gutter="20">
+        <el-col :span="8">
+          <el-space>
+            <el-button icon="Refresh" @click="loadList">刷新</el-button>
+          </el-space>
+        </el-col>
+        <el-col :span="16">
+          <el-space style="justify-content: flex-end; width: 100%;">
+            <el-select v-model="filters.auditStatus" placeholder="审核状态" clearable style="width: 120px" @change="loadList">
+              <el-option label="全部" :value="undefined" />
+              <el-option label="待审核" :value="0" />
+              <el-option label="已通过" :value="1" />
+              <el-option label="未通过" :value="2" />
+            </el-select>
+          </el-space>
+        </el-col>
+      </el-row>
     </el-card>
     
     <el-card shadow="never" class="table-card">
@@ -25,6 +33,8 @@
               :preview-src-list="[formatCover(row.coverUrl)]"
               fit="cover"
               style="width:80px;height:80px;border-radius:6px;"
+              preview-teleported
+              hide-on-click-modal
             />
             <div v-else class="cover-placeholder">无图</div>
           </template>
@@ -202,8 +212,8 @@ onMounted(() => loadList())
 </script>
 
 <style scoped>
-.dynamic-list-container { padding: 0; }
-.page-title { font-size: 24px; font-weight: bold; color: #333; margin: 0 0 20px 0; }
+.dynamic-list-container { padding: 20px; }
+.page-title { font-size: 24px; font-weight: 600; color: #303133; margin: 0 0 20px 0; }
 .toolbar-card { margin-bottom: 20px; }
 .table-card { margin-top: 20px; }
 .pagination-container { display: flex; justify-content: flex-end; margin-top: 20px; }

+ 63 - 47
marriageAdmin-vue/src/views/matchmaker/CaseAudit.vue

@@ -3,50 +3,53 @@
     <h2 class="page-title">案例审核</h2>
 
     <el-card shadow="never" class="toolbar-card">
-      <el-space wrap>
-        <el-input
-          v-model="filters.maleRealName"
-          placeholder="按男方姓名模糊搜索"
-          clearable
-          style="width: 200px"
-          @keyup.enter="loadList"
-        >
-          <template #append>
-            <el-button icon="Search" @click="loadList" />
-          </template>
-        </el-input>
-        <el-input
-          v-model="filters.femaleRealName"
-          placeholder="按女方姓名模糊搜索"
-          clearable
-          style="width: 200px"
-          @keyup.enter="loadList"
-        >
-          <template #append>
-            <el-button icon="Search" @click="loadList" />
-          </template>
-        </el-input>
-        <el-select
-          v-model="filters.auditStatus"
-          placeholder="审核状态"
-          clearable
-          style="width: 150px"
-          @change="loadList"
-        >
-          <el-option label="待审核" :value="0" />
-          <el-option label="审核通过" :value="1" />
-          <el-option label="审核失败" :value="2" />
-        </el-select>
-        <el-button 
-          type="primary" 
-          :disabled="selectedRows.length === 0"
-          @click="handleBatchAudit"
-          style="margin-left: 10px"
-        >
-          批量审核{{ selectedRows.length > 0 ? ` (${selectedRows.length})` : '' }}
-        </el-button>
-        <el-button icon="Refresh" @click="resetFilters">重置</el-button>
-      </el-space>
+      <el-row :gutter="20">
+        <el-col :span="8">
+          <el-space>
+            <el-button 
+              type="primary" 
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchAudit"
+            >
+              批量审核{{ selectedRows.length > 0 ? ` (${selectedRows.length})` : '' }}
+            </el-button>
+            <el-button icon="Refresh" @click="resetFilters">重置</el-button>
+          </el-space>
+        </el-col>
+        <el-col :span="16">
+          <el-space style="justify-content: flex-end; width: 100%;">
+            <el-select
+              v-model="filters.auditStatus"
+              placeholder="审核状态"
+              clearable
+              style="width: 120px"
+              @change="loadList"
+            >
+              <el-option label="待审核" :value="0" />
+              <el-option label="审核通过" :value="1" />
+              <el-option label="审核失败" :value="2" />
+            </el-select>
+            <el-input
+              v-model="filters.maleRealName"
+              placeholder="男方姓名"
+              clearable
+              style="width: 120px"
+              @keyup.enter="loadList"
+            />
+            <el-input
+              v-model="filters.femaleRealName"
+              placeholder="女方姓名"
+              clearable
+              style="width: 120px"
+              @keyup.enter="loadList"
+            >
+              <template #append>
+                <el-button icon="Search" @click="loadList" />
+              </template>
+            </el-input>
+          </el-space>
+        </el-col>
+      </el-row>
     </el-card>
 
     <el-card shadow="never" class="table-card">
@@ -677,10 +680,23 @@ onMounted(loadList)
 </script>
 
 <style scoped>
-@import '@/assets/list-common.css';
-
 .case-audit-container {
-  padding: 0;
+  padding: 20px;
+}
+
+.page-title {
+  margin: 0 0 20px 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.toolbar-card {
+  margin-bottom: 20px;
+}
+
+.table-card {
+  margin-bottom: 20px;
 }
 
 .proof-images {

+ 52 - 36
marriageAdmin-vue/src/views/matchmaker/MatchmakerAudit.vue

@@ -3,39 +3,42 @@
     <h2 class="page-title">红娘审核</h2>
 
     <el-card shadow="never" class="toolbar-card">
-      <el-space wrap>
-        <el-input
-          v-model="filters.name"
-          placeholder="按姓名模糊搜索"
-          clearable
-          style="width: 200px"
-          @keyup.enter="loadList"
-        >
-          <template #append>
-            <el-button icon="Search" @click="loadList" />
-          </template>
-        </el-input>
-        <el-input
-          v-model="filters.phone"
-          placeholder="按手机号模糊搜索"
-          clearable
-          style="width: 200px"
-          @keyup.enter="loadList"
-        >
-          <template #append>
-            <el-button icon="Search" @click="loadList" />
-          </template>
-        </el-input>
-        <el-button 
-          type="primary" 
-          :disabled="selectedRows.length === 0"
-          @click="handleBatchAudit"
-          style="margin-left: 10px"
-        >
-          批量审核{{ selectedRows.length > 0 ? ` (${selectedRows.length})` : '' }}
-        </el-button>
-        <el-button icon="Refresh" @click="resetFilters">重置</el-button>
-      </el-space>
+      <el-row :gutter="20">
+        <el-col :span="10">
+          <el-space>
+            <el-button 
+              type="primary" 
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchAudit"
+            >
+              批量审核{{ selectedRows.length > 0 ? ` (${selectedRows.length})` : '' }}
+            </el-button>
+            <el-button icon="Refresh" @click="resetFilters">重置</el-button>
+          </el-space>
+        </el-col>
+        <el-col :span="14">
+          <el-space style="justify-content: flex-end; width: 100%;">
+            <el-input
+              v-model="filters.name"
+              placeholder="搜索姓名"
+              clearable
+              style="width: 140px"
+              @keyup.enter="loadList"
+            />
+            <el-input
+              v-model="filters.phone"
+              placeholder="搜索手机号"
+              clearable
+              style="width: 140px"
+              @keyup.enter="loadList"
+            >
+              <template #append>
+                <el-button icon="Search" @click="loadList" />
+              </template>
+            </el-input>
+          </el-space>
+        </el-col>
+      </el-row>
     </el-card>
 
     <el-card shadow="never" class="table-card">
@@ -679,10 +682,23 @@ onMounted(loadList)
 </script>
 
 <style scoped>
-@import '@/assets/list-common.css';
-
 .matchmaker-audit-container {
-  padding: 0;
+  padding: 20px;
+}
+
+.page-title {
+  margin: 0 0 20px 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.toolbar-card {
+  margin-bottom: 20px;
+}
+
+.table-card {
+  margin-bottom: 20px;
 }
 
 .batch-audit-progress {

+ 55 - 3
marriageAdmin-vue/src/views/matchmaker/MatchmakerForm.vue

@@ -24,9 +24,23 @@
               <el-input v-model="form.realName" placeholder="请输入真实姓名" />
             </el-form-item>
           </el-col>
+        </el-row>
+        
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item :label="form.phoneType === 'mobile' ? '手机号' : '座机号'" prop="phone">
+              <el-input 
+                v-model="form.phone" 
+                :placeholder="form.phoneType === 'mobile' ? '请输入11位手机号' : '请输入7/8/11位座机号'" 
+              />
+            </el-form-item>
+          </el-col>
           <el-col :span="12">
-            <el-form-item label="手机号" prop="phone">
-              <el-input v-model="form.phone" placeholder="请输入手机号" />
+            <el-form-item label="电话类型" prop="phoneType">
+              <el-radio-group v-model="form.phoneType" @change="handlePhoneTypeChange">
+                <el-radio label="mobile">手机</el-radio>
+                <el-radio label="landline">座机</el-radio>
+              </el-radio-group>
             </el-form-item>
           </el-col>
         </el-row>
@@ -126,6 +140,7 @@ const form = reactive({
   matchmakerId: null,
   avatarUrl: '',
   realName: '',
+  phoneType: 'mobile',
   phone: '',
   gender: 1,
   birthDate: null,
@@ -136,9 +151,46 @@ const form = reactive({
   cityId: null
 })
 
+// 电话号码校验函数
+const validatePhone = (rule, value, callback) => {
+  if (!value) {
+    callback(new Error(form.phoneType === 'mobile' ? '请输入手机号' : '请输入座机号'))
+    return
+  }
+  
+  // 移除非数字字符(如空格、横线等)
+  const cleanNumber = value.replace(/[^0-9]/g, '')
+  
+  if (form.phoneType === 'mobile') {
+    // 手机号校验:11位数字,以1开头
+    if (!/^1[3-9]\d{9}$/.test(cleanNumber)) {
+      callback(new Error('请输入正确的11位手机号'))
+      return
+    }
+  } else {
+    // 座机号校验:7位、8位或11位数字
+    if (![7, 8, 11].includes(cleanNumber.length)) {
+      callback(new Error('座机号应为7位、8位或11位数字'))
+      return
+    }
+  }
+  
+  callback()
+}
+
+// 电话类型切换时清空电话号并重新校验
+const handlePhoneTypeChange = () => {
+  form.phone = ''
+  // 清除电话字段的校验状态
+  if (formRef.value) {
+    formRef.value.clearValidate('phone')
+  }
+}
+
 const rules = {
   realName: [{ required: true, message: '请输入真实姓名', trigger: 'blur' }],
-  phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
+  phoneType: [{ required: true, message: '请选择电话类型', trigger: 'change' }],
+  phone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
   gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
   matchmakerType: [{ required: true, message: '请选择红娘类型', trigger: 'change' }],
   level: [{ required: true, message: '请选择等级', trigger: 'change' }]

+ 69 - 20
marriageAdmin-vue/src/views/matchmaker/MatchmakerList.vue

@@ -5,14 +5,17 @@
     <!-- 工具栏 -->
     <el-card shadow="never" class="toolbar-card">
       <el-row :gutter="20">
-        <el-col :span="6">
-          <el-button type="primary" icon="Plus" @click="$router.push('/matchmaker/create')">
-            添加红娘
-          </el-button>
-          <el-button icon="Refresh" @click="loadList">刷新</el-button>
+        <el-col :span="10">
+          <el-space>
+            <el-button type="primary" icon="Plus" @click="$router.push('/matchmaker/create')">
+              添加红娘
+            </el-button>
+            <el-button icon="Refresh" @click="loadList">刷新</el-button>
+            <el-button type="success" icon="Download" @click="handleExport">导出数据</el-button>
+          </el-space>
         </el-col>
-        <el-col :span="18">
-          <el-space wrap>
+        <el-col :span="14">
+          <el-space style="justify-content: flex-end; width: 100%;">
             <el-select v-model="filters.matchmakerType" placeholder="红娘类型" clearable style="width: 120px" @change="loadList">
               <el-option label="兼职" :value="1" />
               <el-option label="全职" :value="2" />
@@ -81,11 +84,7 @@
         <el-table-column prop="status" label="状态" width="90" align="center">
           <template #default="{ row }">
             <el-tag 
-              :class="{
-                'status-success': row.status === 1,
-                'status-danger': row.status === 0,
-                'status-disabled': row.status === 2
-              }"
+              :type="row.status === 0 ? 'danger' : row.status === 1 ? '' : 'info'"
               size="small"
               effect="light"
             >
@@ -198,11 +197,7 @@
               </el-descriptions-item>
               <el-descriptions-item label="状态">
                 <el-tag 
-                  :class="{
-                    'status-success': detailData.status === 1,
-                    'status-danger': detailData.status === 0,
-                    'status-disabled': detailData.status === 2
-                  }"
+                  :type="detailData.status === 0 ? 'danger' : detailData.status === 1 ? '' : 'info'"
                   size="small"
                 >
                   {{ detailData.status === 1 ? '正常' : detailData.status === 0 ? '禁用' : '离职' }}
@@ -429,14 +424,68 @@ const handleDelete = async (row) => {
   }
 }
 
+// 导出数据
+const handleExport = async () => {
+  try {
+    loading.value = true
+    const params = {}
+    
+    if (filters.matchmakerType !== null && filters.matchmakerType !== undefined) {
+      params.matchmakerType = filters.matchmakerType
+    }
+    if (filters.level !== null && filters.level !== undefined) {
+      params.level = filters.level
+    }
+    if (filters.keyword && filters.keyword.trim()) {
+      params.keyword = filters.keyword.trim()
+    }
+    
+    const response = await request.get(API_ENDPOINTS.MATCHMAKER_EXPORT, {
+      params: params,
+      responseType: 'blob'
+    })
+    
+    // 创建下载链接
+    const blob = response instanceof Blob ? response : new Blob([response])
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = `红娘列表_${new Date().getTime()}.xlsx`
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+    window.URL.revokeObjectURL(url)
+    
+    ElMessage.success('导出成功')
+  } catch (error) {
+    console.error('导出失败:', error)
+    ElMessage.error('导出失败: ' + (error.message || '未知错误'))
+  } finally {
+    loading.value = false
+  }
+}
+
 onMounted(() => loadList())
 </script>
 
 <style scoped>
-@import '@/assets/list-common.css';
-
 .matchmaker-list-container {
-  padding: 0;
+  padding: 20px;
+}
+
+.page-title {
+  margin: 0 0 20px 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.toolbar-card {
+  margin-bottom: 20px;
+}
+
+.table-card {
+  margin-bottom: 20px;
 }
 
 .detail-content {

+ 28 - 11
marriageAdmin-vue/src/views/matchmaker/PointsOrderList.vue

@@ -5,27 +5,33 @@
     <!-- 工具栏 -->
     <el-card shadow="never" class="toolbar-card">
       <el-row :gutter="20">
-        <el-col :span="24">
-          <el-space wrap>
+        <el-col :span="6">
+          <el-space>
+            <el-button type="primary" icon="Search" @click="loadList">查询</el-button>
+            <el-button icon="Refresh" @click="resetFilters">重置</el-button>
+          </el-space>
+        </el-col>
+        <el-col :span="18">
+          <el-space style="justify-content: flex-end; width: 100%;">
             <el-input 
               v-model="filters.orderNo" 
               placeholder="订单号" 
               clearable 
-              style="width: 200px" 
+              style="width: 180px" 
               @keyup.enter="loadList"
             />
             <el-input 
               v-model="filters.productName" 
               placeholder="产品名称" 
               clearable 
-              style="width: 200px" 
+              style="width: 180px" 
               @keyup.enter="loadList"
             />
             <el-select 
               v-model="filters.review" 
               placeholder="审核状态" 
               clearable 
-              style="width: 150px" 
+              style="width: 130px" 
               @change="loadList"
             >
               <el-option label="待审核" :value="0" />
@@ -36,7 +42,7 @@
               v-model="filters.status" 
               placeholder="发货状态" 
               clearable 
-              style="width: 150px" 
+              style="width: 130px" 
               @change="loadList"
             >
               <el-option label="待发货" :value="0" />
@@ -44,8 +50,6 @@
               <el-option label="已完成" :value="2" />
               <el-option label="已取消" :value="3" />
             </el-select>
-            <el-button type="primary" icon="Search" @click="loadList">查询</el-button>
-            <el-button icon="Refresh" @click="resetFilters">重置</el-button>
           </el-space>
         </el-col>
       </el-row>
@@ -480,10 +484,23 @@ onMounted(() => loadList())
 </script>
 
 <style scoped>
-@import '@/assets/list-common.css';
-
 .points-order-list-container {
-  padding: 0;
+  padding: 20px;
+}
+
+.page-title {
+  margin: 0 0 20px 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.toolbar-card {
+  margin-bottom: 20px;
+}
+
+.table-card {
+  margin-bottom: 20px;
 }
 
 .loading-container {

+ 36 - 20
marriageAdmin-vue/src/views/matchmaker/ResourceList.vue

@@ -5,27 +5,40 @@
     <!-- 工具栏 -->
     <el-card shadow="never" class="toolbar-card">
       <el-row :gutter="20">
-        <el-col :span="24">
-          <el-space wrap>
+        <el-col :span="8">
+          <el-space>
+            <el-button 
+              type="primary" 
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchAudit"
+            >
+              批量审核{{ selectedRows.length > 0 ? ` (${selectedRows.length})` : '' }}
+            </el-button>
+            <el-button type="primary" icon="Search" @click="loadList">查询</el-button>
+            <el-button icon="Refresh" @click="resetFilters">重置</el-button>
+          </el-space>
+        </el-col>
+        <el-col :span="16">
+          <el-space style="justify-content: flex-end; width: 100%;">
             <el-input 
               v-model="filters.name" 
               placeholder="名称" 
               clearable 
-              style="width: 200px" 
+              style="width: 150px" 
               @keyup.enter="loadList"
             />
             <el-input 
               v-model="filters.phone" 
               placeholder="手机号" 
               clearable 
-              style="width: 200px" 
+              style="width: 150px" 
               @keyup.enter="loadList"
             />
             <el-select 
               v-model="filters.status" 
               placeholder="审核状态" 
               clearable 
-              style="width: 150px" 
+              style="width: 120px" 
               @change="loadList"
             >
               <el-option label="待审核" :value="0" />
@@ -36,7 +49,7 @@
               v-model="filters.diploma" 
               placeholder="学历" 
               clearable 
-              style="width: 150px" 
+              style="width: 100px" 
               @change="loadList"
             >
               <el-option label="高中" value="高中" />
@@ -50,23 +63,13 @@
               v-model="filters.marrStatus" 
               placeholder="婚姻状况" 
               clearable 
-              style="width: 150px" 
+              style="width: 120px" 
               @change="loadList"
             >
               <el-option label="未婚" :value="0" />
               <el-option label="离异" :value="1" />
               <el-option label="丧偶" :value="2" />
             </el-select>
-            <el-button 
-              type="primary" 
-              :disabled="selectedRows.length === 0"
-              @click="handleBatchAudit"
-              style="margin-left: 10px"
-            >
-              批量审核{{ selectedRows.length > 0 ? ` (${selectedRows.length})` : '' }}
-            </el-button>
-            <el-button type="primary" icon="Search" @click="loadList">查询</el-button>
-            <el-button icon="Refresh" @click="resetFilters">重置</el-button>
           </el-space>
         </el-col>
       </el-row>
@@ -632,10 +635,23 @@ onMounted(() => loadList())
 </script>
 
 <style scoped>
-@import '@/assets/list-common.css';
-
 .resource-list-container {
-  padding: 0;
+  padding: 20px;
+}
+
+.page-title {
+  margin: 0 0 20px 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.toolbar-card {
+  margin-bottom: 20px;
+}
+
+.table-card {
+  margin-bottom: 20px;
 }
 
 .batch-audit-progress {

+ 4 - 0
marriageAdmin-vue/src/views/points-product/PointsProductList.vue

@@ -55,6 +55,8 @@
               fit="cover"
               style="width: 80px; height: 80px; border-radius: 6px; cursor: pointer;"
               :preview-src-list="[row.imageUrl]"
+              preview-teleported
+              hide-on-click-modal
             />
             <span v-else style="color: #999;">暂无图片</span>
           </template>
@@ -281,6 +283,8 @@
                 fit="cover"
                 style="width: 200px; height: 200px; border-radius: 6px;"
                 :preview-src-list="[detailData.imageUrl]"
+                preview-teleported
+                hide-on-click-modal
               />
               <span v-else style="color: #999;">暂无图片</span>
             </el-descriptions-item>

+ 40 - 16
marriageAdmin-vue/src/views/report/ReportList.vue

@@ -3,18 +3,30 @@
     <h2 class="page-title">举报管理</h2>
 
     <el-card shadow="never" class="toolbar-card">
-      <el-space wrap>
-        <el-select v-model="filters.status" placeholder="举报状态" clearable style="width: 140px" @change="loadList">
-          <el-option label="全部" :value="undefined" />
-          <el-option label="待处理" :value="0" />
-          <el-option label="处理中" :value="1" />
-          <el-option label="已处理" :value="2" />
-          <el-option label="已驳回" :value="3" />
-        </el-select>
-        <el-input v-model="filters.dynamicId" placeholder="动态ID" clearable style="width: 160px" @keyup.enter.native="loadList" />
-        <el-button icon="Search" type="primary" @click="loadList">查询</el-button>
-        <el-button icon="Refresh" @click="resetAndReload">重置</el-button>
-      </el-space>
+      <el-row :gutter="20">
+        <el-col :span="8">
+          <el-space>
+            <el-button icon="Search" type="primary" @click="loadList">查询</el-button>
+            <el-button icon="Refresh" @click="resetAndReload">重置</el-button>
+          </el-space>
+        </el-col>
+        <el-col :span="16">
+          <el-space style="justify-content: flex-end; width: 100%;">
+            <el-select v-model="filters.status" placeholder="举报状态" clearable style="width: 140px" @change="loadList">
+              <el-option label="全部" :value="undefined" />
+              <el-option label="待处理" :value="0" />
+              <el-option label="处理中" :value="1" />
+              <el-option label="已处理" :value="2" />
+              <el-option label="已驳回" :value="3" />
+            </el-select>
+            <el-input v-model="filters.dynamicId" placeholder="动态ID" clearable style="width: 160px" @keyup.enter.native="loadList">
+              <template #append>
+                <el-button icon="Search" @click="loadList" />
+              </template>
+            </el-input>
+          </el-space>
+        </el-col>
+      </el-row>
     </el-card>
 
     <el-card shadow="never" class="table-card">
@@ -47,7 +59,8 @@
                 fit="cover" 
                 style="width:64px;height:64px;border-radius:6px;cursor:pointer;"
                 :lazy="true"
-                :hide-on-click-modal="true"
+                preview-teleported
+                hide-on-click-modal
                 :data-report-id="row.reportId"
                 @error="(e) => handleImageError(e, row)"
               >
@@ -500,7 +513,8 @@ onMounted(loadList)
 const moderateDialog = reactive({ 
   visible: false, 
   loading: false, 
-  action: 2, 
+  action: 2,
+  reportId: null,
   form: { 
     dynamicId: null, 
     userId: null,
@@ -517,6 +531,7 @@ const moderateDialog = reactive({
 const openModerate = async (row, action) => {
   moderateDialog.visible = true
   moderateDialog.action = action
+  moderateDialog.reportId = row.reportId
   moderateDialog.loading = true
   moderateDialog.form.reason = ''
   // 重置其他字段
@@ -585,6 +600,14 @@ const submitModerate = async () => {
       params: { dynamicId: moderateDialog.form.dynamicId, action: moderateDialog.action, reason: moderateDialog.form.reason }
     })
     if (res.code === 200) {
+      // 更新举报状态:封禁设为2,取消封禁设为1
+      if (moderateDialog.reportId && (moderateDialog.action === 2 || moderateDialog.action === 1)) {
+        const newStatus = moderateDialog.action === 2 ? 2 : 1
+        await request.post(`${API_ENDPOINTS.REPORT_HANDLE}/${moderateDialog.reportId}`, {
+          status: newStatus,
+          handleResult: moderateDialog.form.reason
+        })
+      }
       ElMessage.success('操作成功')
       moderateDialog.visible = false
       loadList()
@@ -596,6 +619,7 @@ const submitModerate = async () => {
 const openUnban = async (row) => {
   moderateDialog.visible = true
   moderateDialog.action = 1
+  moderateDialog.reportId = row.reportId
   moderateDialog.loading = true
   moderateDialog.form.dynamicId = row.dynamicId
   moderateDialog.form.reason = ''
@@ -632,8 +656,8 @@ const openUnban = async (row) => {
 </script>
 
 <style scoped>
-.report-page { padding: 0; }
-.page-title { font-size: 24px; font-weight: bold; color: #333; margin: 0 0 20px 0; }
+.report-page { padding: 20px; }
+.page-title { font-size: 24px; font-weight: 600; color: #303133; margin: 0 0 20px 0; }
 .toolbar-card { margin-bottom: 20px; }
 .table-card { margin-top: 20px; }
 .pagination-container { display: flex; justify-content: flex-end; margin-top: 20px; }

+ 29 - 6
marriageAdmin-vue/src/views/success-case/SuccessCaseList.vue

@@ -3,8 +3,17 @@
     <h2 class="page-title">成功案例管理</h2>
     
     <el-card shadow="never" class="toolbar-card">
-      <el-button type="primary" icon="Plus" @click="$router.push('/success-case/create')">新增案例</el-button>
-      <el-button icon="Refresh" @click="loadList">刷新</el-button>
+      <el-row :gutter="20">
+        <el-col :span="18">
+          <el-space wrap>
+            <el-button type="primary" icon="Plus" @click="$router.push('/success-case/create')">新增案例</el-button>
+            <el-button icon="Refresh" @click="loadList">刷新</el-button>
+          </el-space>
+        </el-col>
+        <el-col :span="6">
+          <!-- 预留搜索区域 -->
+        </el-col>
+      </el-row>
     </el-card>
     
     <el-card shadow="never" class="table-card">
@@ -306,10 +315,24 @@ onMounted(() => {
 </script>
 
 <style scoped>
-.success-case-list-container { padding: 0; }
-.page-title { font-size: 24px; font-weight: bold; color: #333; margin: 0 0 20px 0; }
-.toolbar-card { margin-bottom: 20px; }
-.table-card { margin-top: 20px; }
+.success-case-list-container {
+  padding: 20px;
+}
+
+.page-title {
+  margin: 0 0 20px 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.toolbar-card {
+  margin-bottom: 20px;
+}
+
+.table-card {
+  margin-bottom: 20px;
+}
 .pagination-container { display: flex; justify-content: flex-end; margin-top: 20px; }
 
 /* 详情对话框样式 */

+ 88 - 37
marriageAdmin-vue/src/views/user/UserList.vue

@@ -4,8 +4,14 @@
     
     <el-card shadow="never" class="toolbar-card">
       <el-row :gutter="20">
-        <el-col :span="24">
-          <el-space wrap>
+        <el-col :span="8">
+          <el-space>
+            <el-button icon="Refresh" @click="loadList">刷新</el-button>
+            <el-button type="success" icon="Download" @click="handleExport">导出数据</el-button>
+          </el-space>
+        </el-col>
+        <el-col :span="16">
+          <el-space style="justify-content: flex-end; width: 100%;">
             <el-select v-model="filters.gender" placeholder="性别" clearable style="width: 100px" @change="loadList">
               <el-option label="全部" :value="undefined" />
               <el-option label="男" :value="1" />
@@ -24,8 +30,6 @@
                 <el-button icon="Search" @click="loadList" />
               </template>
             </el-input>
-            
-            <el-button icon="Refresh" @click="loadList">刷新</el-button>
           </el-space>
         </el-col>
       </el-row>
@@ -46,7 +50,34 @@
           </div>
         </template>
         <el-table-column type="index" label="序号" width="60" />
-        <el-table-column prop="userId" label="用户ID" width="80" />
+        <el-table-column prop="status" label="状态" width="80" align="center">
+          <template #default="{ row }">
+            <el-tag 
+              v-if="row.status === 1" 
+              type="success" 
+              size="small"
+              effect="light"
+            >
+              正常
+            </el-tag>
+            <el-tag 
+              v-else-if="row.status === 0" 
+              type="danger" 
+              size="small"
+              effect="light"
+            >
+              禁用
+            </el-tag>
+            <el-tag 
+              v-else 
+              type="info" 
+              size="small"
+              effect="light"
+            >
+              注销
+            </el-tag>
+          </template>
+        </el-table-column>
         <el-table-column prop="avatarUrl" label="头像" width="80">
           <template #default="{ row }">
             <el-avatar :src="row.avatarUrl || 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'" />
@@ -93,7 +124,7 @@
         <el-table-column prop="isProfileComplete" label="资料完整" width="100" align="center">
           <template #default="{ row }">
             <el-tag 
-              :class="row.isProfileComplete === 1 ? 'status-success' : 'status-warning'" 
+              :type="row.isProfileComplete === 1 ? 'success' : 'danger'" 
               size="small"
               effect="light"
             >
@@ -113,34 +144,6 @@
             {{ row.lastLoginAt || '-' }}
           </template>
         </el-table-column>
-        <el-table-column prop="status" label="状态" width="90" align="center">
-          <template #default="{ row }">
-            <el-tag 
-              v-if="row.status === 1" 
-              class="status-success" 
-              size="small"
-              effect="light"
-            >
-              正常
-            </el-tag>
-            <el-tag 
-              v-else-if="row.status === 0" 
-              class="status-danger" 
-              size="small"
-              effect="light"
-            >
-              禁用
-            </el-tag>
-            <el-tag 
-              v-else 
-              class="status-disabled" 
-              size="small"
-              effect="light"
-            >
-              注销
-            </el-tag>
-          </template>
-        </el-table-column>
         <el-table-column label="操作" width="200" fixed="right">
           <template #default="{ row }">
             <el-button type="primary" size="small" link @click="handleViewDetail(row)">详情</el-button>
@@ -318,14 +321,62 @@ const handleToggleStatus = async (row) => {
   }
 }
 
+// 导出数据
+const handleExport = async () => {
+  try {
+    loading.value = true
+    const params = {
+      gender: filters.gender,
+      status: filters.status,
+      keyword: filters.keyword || undefined
+    }
+    
+    const response = await request.get(API_ENDPOINTS.USER_EXPORT, {
+      params: params,
+      responseType: 'blob'
+    })
+    
+    // 创建下载链接
+    const blob = response instanceof Blob ? response : new Blob([response])
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = `用户列表_${new Date().getTime()}.xlsx`
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+    window.URL.revokeObjectURL(url)
+    
+    ElMessage.success('导出成功')
+  } catch (error) {
+    console.error('导出失败:', error)
+    ElMessage.error('导出失败: ' + (error.message || '未知错误'))
+  } finally {
+    loading.value = false
+  }
+}
+
 onMounted(() => loadList())
 </script>
 
 <style scoped>
-@import '@/assets/list-common.css';
-
 .user-list-container {
-  padding: 0;
+  padding: 20px;
+}
+
+.page-title {
+  margin: 0 0 20px 0;
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.toolbar-card {
+  margin-bottom: 20px;
+}
+
+.table-card {
+  margin-bottom: 20px;
 }
 
 /* 额外的用户列表特定样式 */

+ 21 - 5
service/admin/src/main/java/com/zhentao/controller/DashboardController.java

@@ -14,6 +14,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.web.bind.annotation.*;
 
+import org.springframework.jdbc.core.JdbcTemplate;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -33,6 +35,7 @@ public class DashboardController {
     private final UsersMapper usersMapper;
     private final ActivityMapper activityMapper;
     private final RedisTemplate<String, Object> redisTemplate;
+    private final JdbcTemplate jdbcTemplate;
     
     @Autowired
     private AuthService authService;
@@ -46,10 +49,11 @@ public class DashboardController {
     private static final int CACHE_EXPIRE_MINUTES = 5; // 缓存5分钟
     
     public DashboardController(UsersMapper usersMapper, ActivityMapper activityMapper, 
-                              RedisTemplate<String, Object> redisTemplate) {
+                              RedisTemplate<String, Object> redisTemplate, JdbcTemplate jdbcTemplate) {
         this.usersMapper = usersMapper;
         this.activityMapper = activityMapper;
         this.redisTemplate = redisTemplate;
+        this.jdbcTemplate = jdbcTemplate;
     }
     
     /**
@@ -107,11 +111,23 @@ public class DashboardController {
             }
             stats.put("vipUsers", vipUsers != null ? vipUsers : 0);
             
-            // 成功案例数(模拟数据)
-            stats.put("totalCases", 89);
+            // 成功案例数(从数据库查询)
+            Long totalCases = 0L;
+            try {
+                totalCases = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM success_case", Long.class);
+            } catch (Exception e) {
+                log.warn("成功案例数查询失败: {}", e.getMessage());
+            }
+            stats.put("totalCases", totalCases != null ? totalCases : 0);
             
-            // 课程数量(模拟数据)
-            stats.put("totalCourses", 34);
+            // 课程数量(从数据库查询)
+            Long totalCourses = 0L;
+            try {
+                totalCourses = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM courses", Long.class);
+            } catch (Exception e) {
+                log.warn("课程数量查询失败: {}", e.getMessage());
+            }
+            stats.put("totalCourses", totalCourses != null ? totalCourses : 0);
             
             // 3. 存入Redis缓存(5分钟过期)
             try {

+ 91 - 0
service/admin/src/main/java/com/zhentao/controller/MatchmakerController.java

@@ -0,0 +1,91 @@
+package com.zhentao.controller;
+
+import com.alibaba.excel.EasyExcel;
+import com.zhentao.vo.MatchmakerExcelVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 红娘管理控制器(管理端)
+ */
+@RestController
+@RequestMapping("/admin/matchmaker")
+@CrossOrigin(origins = "*")
+public class MatchmakerController {
+    
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
+    
+    /**
+     * 导出红娘数据为Excel
+     */
+    @GetMapping("/export")
+    public void exportMatchmakerList(
+            HttpServletResponse response,
+            @RequestParam(value = "matchmakerType", required = false) Integer matchmakerType,
+            @RequestParam(value = "level", required = false) Integer level,
+            @RequestParam(value = "keyword", required = false) String keyword) throws IOException {
+        try {
+            // 构建SQL查询
+            StringBuilder sql = new StringBuilder();
+            sql.append("SELECT m.matchmaker_id, m.real_name, m.phone, m.matchmaker_type, ");
+            sql.append("m.level, m.success_couples, m.status, m.created_at, ");
+            sql.append("c.name as city_name ");
+            sql.append("FROM matchmakers m ");
+            sql.append("LEFT JOIN area c ON m.city_id = c.id ");
+            sql.append("WHERE 1=1 ");
+            
+            // 类型筛选
+            if (matchmakerType != null) {
+                sql.append("AND m.matchmaker_type = ").append(matchmakerType).append(" ");
+            }
+            
+            // 等级筛选
+            if (level != null) {
+                sql.append("AND m.level = ").append(level).append(" ");
+            }
+            
+            // 关键词搜索
+            if (keyword != null && !keyword.trim().isEmpty()) {
+                String kw = keyword.trim().replace("'", "''");
+                sql.append("AND (m.real_name LIKE '%").append(kw).append("%' ");
+                sql.append("OR m.phone LIKE '%").append(kw).append("%') ");
+            }
+            
+            sql.append("ORDER BY m.created_at DESC");
+            
+            // 执行查询
+            List<Map<String, Object>> results = jdbcTemplate.queryForList(sql.toString());
+            
+            // 转换为Excel VO
+            List<MatchmakerExcelVO> excelData = results.stream()
+                    .map(MatchmakerExcelVO::fromMap)
+                    .collect(Collectors.toList());
+            
+            // 设置响应头
+            String fileName = "红娘列表_" + System.currentTimeMillis() + ".xlsx";
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + URLEncoder.encode(fileName, "UTF-8"));
+            
+            // 写入Excel
+            EasyExcel.write(response.getOutputStream(), MatchmakerExcelVO.class)
+                    .sheet("红娘列表")
+                    .doWrite(excelData);
+            
+        } catch (Exception e) {
+            e.printStackTrace();
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            response.setContentType("application/json;charset=UTF-8");
+            response.getWriter().write("{\"code\":500,\"msg\":\"导出失败:" + e.getMessage() + "\"}");
+        }
+    }
+}

+ 71 - 3
service/admin/src/main/java/com/zhentao/controller/UserController.java

@@ -1,16 +1,21 @@
 package com.zhentao.controller;
 
+import com.alibaba.excel.EasyExcel;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zhentao.common.Result;
 import com.zhentao.entity.*;
 import com.zhentao.mapper.*;
+import com.zhentao.vo.UserExcelVO;
 import com.zhentao.vo.UserVO;
 import com.zhentao.vo.UserVipListVO;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URLEncoder;
 import java.time.Duration;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
@@ -173,9 +178,9 @@ public class UserController {
                 vo.setVipStartTime(userVip.getStartTime());
                 vo.setVipEndTime(userVip.getEndTime());
 
-                // 计算剩余天数:到期时间 - 开通时间
-                if (userVip.getStartTime() != null && userVip.getEndTime() != null) {
-                    long days = Duration.between(userVip.getStartTime(), userVip.getEndTime()).toDays();
+                // 计算剩余天数:到期时间 - 当前时间
+                if (userVip.getEndTime() != null) {
+                    long days = Duration.between(LocalDateTime.now(), userVip.getEndTime()).toDays();
                     vo.setVipRemainingDays((int) Math.max(days, 0));
                 } else {
                     vo.setVipRemainingDays(0);
@@ -410,5 +415,68 @@ public class UserController {
             return Result.error("统计失败:" + e.getMessage());
         }
     }
+    
+    /**
+     * 导出用户数据为Excel
+     */
+    @GetMapping("/export")
+    public void exportUserList(
+            HttpServletResponse response,
+            @RequestParam(value = "gender", required = false) Integer gender,
+            @RequestParam(value = "status", required = false) Integer status,
+            @RequestParam(value = "keyword", required = false) String keyword) throws IOException {
+        try {
+            QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
+            
+            // 性别筛选
+            if (gender != null) {
+                queryWrapper.eq("gender", gender);
+            }
+            
+            // 状态筛选
+            if (status != null) {
+                queryWrapper.eq("status", status);
+            }
+            
+            // 关键词搜索
+            if (keyword != null && !keyword.isEmpty()) {
+                queryWrapper.and(wrapper -> wrapper
+                        .like("nickname", keyword)
+                        .or()
+                        .like("phone", keyword)
+                        .or()
+                        .like("email", keyword));
+            }
+            
+            // 按创建时间倒序
+            queryWrapper.orderByDesc("created_at");
+            
+            // 查询所有符合条件的用户
+            List<Users> users = usersMapper.selectList(queryWrapper);
+            
+            // 转换为UserVO并填充VIP信息,然后转换为Excel VO
+            List<UserExcelVO> excelData = users.stream()
+                    .map(this::convertToUserVO)
+                    .map(UserExcelVO::fromUserVO)
+                    .collect(Collectors.toList());
+            
+            // 设置响应头
+            String fileName = "用户列表_" + System.currentTimeMillis() + ".xlsx";
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + URLEncoder.encode(fileName, "UTF-8"));
+            
+            // 写入Excel
+            EasyExcel.write(response.getOutputStream(), UserExcelVO.class)
+                    .sheet("用户列表")
+                    .doWrite(excelData);
+            
+        } catch (Exception e) {
+            e.printStackTrace();
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            response.setContentType("application/json;charset=UTF-8");
+            response.getWriter().write("{\"code\":500,\"msg\":\"导出失败:" + e.getMessage() + "\"}");
+        }
+    }
 }
 

+ 112 - 0
service/admin/src/main/java/com/zhentao/vo/MatchmakerExcelVO.java

@@ -0,0 +1,112 @@
+package com.zhentao.vo;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.write.style.ColumnWidth;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * 红娘Excel导出VO
+ */
+@Data
+@ColumnWidth(20)
+public class MatchmakerExcelVO implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    
+    @ExcelProperty(value = "红娘ID", index = 0)
+    private Integer matchmakerId;
+    
+    @ExcelProperty(value = "姓名", index = 1)
+    private String realName;
+    
+    @ExcelProperty(value = "手机号", index = 2)
+    private String phone;
+    
+    @ExcelProperty(value = "类型", index = 3)
+    private String matchmakerType;
+    
+    @ExcelProperty(value = "等级", index = 4)
+    private String level;
+    
+    @ExcelProperty(value = "成功对数", index = 5)
+    private Integer successCouples;
+    
+    @ExcelProperty(value = "城市", index = 6)
+    private String cityName;
+    
+    @ExcelProperty(value = "状态", index = 7)
+    private String status;
+    
+    @ExcelProperty(value = "创建时间", index = 8)
+    private String createdAt;
+    
+    /**
+     * 从Map转换
+     */
+    public static MatchmakerExcelVO fromMap(java.util.Map<String, Object> map) {
+        MatchmakerExcelVO vo = new MatchmakerExcelVO();
+        
+        vo.setMatchmakerId(getIntValue(map, "matchmaker_id"));
+        vo.setRealName(getStringValue(map, "real_name"));
+        vo.setPhone(getStringValue(map, "phone"));
+        
+        // 类型
+        Integer type = getIntValue(map, "matchmaker_type");
+        vo.setMatchmakerType(type != null && type == 2 ? "全职" : "兼职");
+        
+        // 等级
+        Integer levelVal = getIntValue(map, "level");
+        if (levelVal != null) {
+            switch (levelVal) {
+                case 1: vo.setLevel("青铜"); break;
+                case 2: vo.setLevel("白银"); break;
+                case 3: vo.setLevel("黄金"); break;
+                case 4: vo.setLevel("铂金"); break;
+                case 5: vo.setLevel("钻石"); break;
+                default: vo.setLevel("未知");
+            }
+        }
+        
+        vo.setSuccessCouples(getIntValue(map, "success_couples"));
+        vo.setCityName(getStringValue(map, "city_name"));
+        
+        // 状态
+        Integer statusVal = getIntValue(map, "status");
+        if (statusVal != null) {
+            switch (statusVal) {
+                case 1: vo.setStatus("正常"); break;
+                case 0: vo.setStatus("禁用"); break;
+                case 2: vo.setStatus("离职"); break;
+                default: vo.setStatus("未知");
+            }
+        }
+        
+        // 时间
+        Object createdAt = map.get("created_at");
+        if (createdAt instanceof LocalDateTime) {
+            vo.setCreatedAt(((LocalDateTime) createdAt).format(FORMATTER));
+        } else if (createdAt != null) {
+            vo.setCreatedAt(createdAt.toString());
+        }
+        
+        return vo;
+    }
+    
+    private static String getStringValue(java.util.Map<String, Object> map, String key) {
+        Object value = map.get(key);
+        return value != null ? value.toString() : null;
+    }
+    
+    private static Integer getIntValue(java.util.Map<String, Object> map, String key) {
+        Object value = map.get(key);
+        if (value instanceof Number) {
+            return ((Number) value).intValue();
+        }
+        return null;
+    }
+}

+ 108 - 0
service/admin/src/main/java/com/zhentao/vo/UserExcelVO.java

@@ -0,0 +1,108 @@
+package com.zhentao.vo;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.write.style.ColumnWidth;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * 用户Excel导出VO
+ */
+@Data
+@ColumnWidth(20)
+public class UserExcelVO implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    
+    @ExcelProperty(value = "用户ID", index = 0)
+    private Integer userId;
+    
+    @ExcelProperty(value = "昵称", index = 1)
+    private String nickname;
+    
+    @ExcelProperty(value = "手机号", index = 2)
+    private String phone;
+    
+    @ExcelProperty(value = "邮箱", index = 3)
+    private String email;
+    
+    @ExcelProperty(value = "性别", index = 4)
+    private String gender;
+    
+    @ExcelProperty(value = "年龄", index = 5)
+    private Integer age;
+    
+    @ExcelProperty(value = "VIP等级", index = 6)
+    private String vipLevel;
+    
+    @ExcelProperty(value = "VIP剩余天数", index = 7)
+    private Integer vipRemainingDays;
+    
+    @ExcelProperty(value = "资料完整", index = 8)
+    private String isProfileComplete;
+    
+    @ExcelProperty(value = "状态", index = 9)
+    private String status;
+    
+    @ExcelProperty(value = "注册时间", index = 10)
+    private String createdAt;
+    
+    @ExcelProperty(value = "最后登录", index = 11)
+    private String lastLoginAt;
+    
+    /**
+     * 从UserVO转换
+     */
+    public static UserExcelVO fromUserVO(UserVO user) {
+        UserExcelVO vo = new UserExcelVO();
+        vo.setUserId(user.getUserId());
+        vo.setNickname(user.getNickname());
+        vo.setPhone(user.getPhone());
+        vo.setEmail(user.getEmail());
+        
+        // 性别
+        if (user.getGender() != null) {
+            vo.setGender(user.getGender() == 1 ? "男" : user.getGender() == 2 ? "女" : "未知");
+        } else {
+            vo.setGender("未知");
+        }
+        
+        vo.setAge(user.getAge());
+        vo.setVipLevel(user.getVipLevel() != null ? user.getVipLevel() : "普通用户");
+        vo.setVipRemainingDays(user.getVipRemainingDays());
+        
+        // 资料完整
+        vo.setIsProfileComplete(user.getIsProfileComplete() != null && user.getIsProfileComplete() == 1 ? "已完成" : "未完成");
+        
+        // 状态
+        if (user.getStatus() != null) {
+            switch (user.getStatus()) {
+                case 1:
+                    vo.setStatus("正常");
+                    break;
+                case 0:
+                    vo.setStatus("禁用");
+                    break;
+                case 2:
+                    vo.setStatus("注销");
+                    break;
+                default:
+                    vo.setStatus("未知");
+            }
+        }
+        
+        // 时间格式化
+        if (user.getCreatedAt() != null) {
+            vo.setCreatedAt(user.getCreatedAt().format(FORMATTER));
+        }
+        if (user.getLastLoginAt() != null) {
+            vo.setLastLoginAt(user.getLastLoginAt().format(FORMATTER));
+        }
+        
+        return vo;
+    }
+}

+ 1 - 1
service/dynamic/src/main/java/com/zhentao/controller/DynamicController.java

@@ -76,7 +76,7 @@ public class DynamicController {
 
     /**
      * 管理端:封禁/删除动态
-     * action: 2=封禁 3=删除,reason为必填
+     * action: 2=封禁 1=正常,reason为必填
      */
     @PostMapping("/admin/moderate")
     public Result<String> adminModerate(@RequestParam Integer dynamicId,

+ 9 - 0
service/dynamic/src/main/java/com/zhentao/service/impl/ReportServiceImpl.java

@@ -2,7 +2,9 @@ package com.zhentao.service.impl;
 
 import com.zhentao.dto.ReportCreateDTO;
 import com.zhentao.entity.DynamicReports;
+import com.zhentao.entity.UserDynamics;
 import com.zhentao.mapper.DynamicReportsMapper;
+import com.zhentao.mapper.UserDynamicsMapper;
 import com.zhentao.service.ReportService;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
@@ -18,6 +20,7 @@ import java.time.LocalDateTime;
 public class ReportServiceImpl implements ReportService {
     
     private final DynamicReportsMapper reportsMapper;
+    private final UserDynamicsMapper userDynamicsMapper;
     
     @Override
     public boolean submitReport(ReportCreateDTO dto) {
@@ -46,6 +49,12 @@ public class ReportServiceImpl implements ReportService {
         List<String> screenshots = dto.getScreenshots();
         if (screenshots != null && !screenshots.isEmpty()) {
             report.setScreenshots(String.join(",", screenshots));
+        } else {
+            // 如果没有传入截图,则从动态中获取media_urls作为截图
+            UserDynamics dynamic = userDynamicsMapper.selectById(dto.getDynamicId());
+            if (dynamic != null && dynamic.getMediaUrls() != null && !dynamic.getMediaUrls().isEmpty()) {
+                report.setScreenshots(dynamic.getMediaUrls());
+            }
         }
         
         report.setStatus(0); // 待处理

+ 75 - 0
service/homePage/src/main/java/com/zhentao/constant/RedisKeyConstants.java

@@ -0,0 +1,75 @@
+package com.zhentao.constant;
+
+/**
+ * Redis缓存Key常量
+ */
+public class RedisKeyConstants {
+    
+    /**
+     * 红娘详情缓存Key前缀
+     * 格式:matchmaker:detail:{matchmakerId}
+     */
+    public static final String MATCHMAKER_DETAIL = "matchmaker:detail:";
+    
+    /**
+     * 红娘列表缓存Key前缀
+     * 格式:matchmaker:list:{type}:{level}:{provinceId}:{cityId}:{pageNum}:{pageSize}
+     */
+    public static final String MATCHMAKER_LIST = "matchmaker:list:";
+    
+    /**
+     * 全职红娘列表缓存Key前缀
+     * 格式:matchmaker:formal:{pageNum}:{pageSize}
+     */
+    public static final String MATCHMAKER_FORMAL_LIST = "matchmaker:formal:";
+    
+    /**
+     * 热门红娘列表缓存Key
+     */
+    public static final String MATCHMAKER_HOT_LIST = "matchmaker:hot:list";
+    
+    /**
+     * 红娘详情缓存过期时间(秒)- 1小时
+     */
+    public static final long MATCHMAKER_DETAIL_EXPIRE = 3600;
+    
+    /**
+     * 红娘列表缓存过期时间(秒)- 30分钟
+     */
+    public static final long MATCHMAKER_LIST_EXPIRE = 1800;
+    
+    /**
+     * 热门红娘列表缓存过期时间(秒)- 30分钟
+     */
+    public static final long MATCHMAKER_HOT_EXPIRE = 1800;
+    
+    /**
+     * 构建红娘详情缓存Key
+     */
+    public static String buildMatchmakerDetailKey(Integer matchmakerId) {
+        return MATCHMAKER_DETAIL + matchmakerId;
+    }
+    
+    /**
+     * 构建红娘列表缓存Key
+     */
+    public static String buildMatchmakerListKey(Integer type, Integer level, Integer provinceId, 
+                                                 Integer cityId, String keyword, Integer pageNum, Integer pageSize) {
+        // 将keyword转换为缓存键的一部分,如果为空则使用"all"
+        String keywordPart = (keyword != null && !keyword.trim().isEmpty()) ? keyword.trim() : "all";
+        return MATCHMAKER_LIST + 
+               (type != null ? type : "all") + ":" +
+               (level != null ? level : "all") + ":" +
+               (provinceId != null ? provinceId : "all") + ":" +
+               (cityId != null ? cityId : "all") + ":" +
+               keywordPart + ":" +
+               pageNum + ":" + pageSize;
+    }
+    
+    /**
+     * 构建全职红娘列表缓存Key
+     */
+    public static String buildFormalListKey(Integer pageNum, Integer pageSize) {
+        return MATCHMAKER_FORMAL_LIST + pageNum + ":" + pageSize;
+    }
+}

+ 26 - 2
service/homePage/src/main/java/com/zhentao/service/impl/ActivityServiceImpl.java

@@ -60,7 +60,19 @@ public class ActivityServiceImpl implements ActivityService {
             queryWrapper.last("LIMIT " + limit);
         }
         
-        return activityMapper.selectList(queryWrapper);
+        List<Activity> activities = activityMapper.selectList(queryWrapper);
+        
+        // 为每个活动计算实际报名人数(从报名表统计)
+        for (Activity activity : activities) {
+            QueryWrapper<ActivityRegistration> regQuery = new QueryWrapper<>();
+            regQuery.eq("activity_id", activity.getId());
+            // 只统计有效报名(排除已取消状态=2)
+            regQuery.ne("status", 2);
+            Long registrationCount = activityRegistrationMapper.selectCount(regQuery);
+            activity.setActualParticipants(registrationCount != null ? registrationCount.intValue() : 0);
+        }
+        
+        return activities;
     }
     
     /**
@@ -92,7 +104,19 @@ public class ActivityServiceImpl implements ActivityService {
         // 按开始时间降序排序
         queryWrapper.orderByDesc("start_time");
         
-        return activityMapper.selectPage(page, queryWrapper);
+        Page<Activity> result = activityMapper.selectPage(page, queryWrapper);
+        
+        // 为每个活动计算实际报名人数(从报名表统计)
+        for (Activity activity : result.getRecords()) {
+            QueryWrapper<ActivityRegistration> regQuery = new QueryWrapper<>();
+            regQuery.eq("activity_id", activity.getId());
+            // 只统计有效报名(排除已取消状态=2)
+            regQuery.ne("status", 2);
+            Long registrationCount = activityRegistrationMapper.selectCount(regQuery);
+            activity.setActualParticipants(registrationCount != null ? registrationCount.intValue() : 0);
+        }
+        
+        return result;
     }
     
     /**

+ 18 - 1
service/homePage/src/main/resources/mapper/MatchmakerMapper.xml

@@ -64,7 +64,24 @@
             AND m.matchmaker_type = #{matchmakerType}
         </if>
         <if test="level != null">
-            AND m.level = #{level}
+            <!-- 根据积分区间筛选等级:青铜(1):0-500, 白银(2):501-1000, 黄金(3):1001-1500, 铂金(4):1501-2000, 钻石(5):2001+ -->
+            <choose>
+                <when test="level == 1">
+                    AND (m.points IS NULL OR m.points &lt;= 500)
+                </when>
+                <when test="level == 2">
+                    AND m.points &gt; 500 AND m.points &lt;= 1000
+                </when>
+                <when test="level == 3">
+                    AND m.points &gt; 1000 AND m.points &lt;= 1500
+                </when>
+                <when test="level == 4">
+                    AND m.points &gt; 1500 AND m.points &lt;= 2000
+                </when>
+                <when test="level == 5">
+                    AND m.points &gt; 2000
+                </when>
+            </choose>
         </if>
         <if test="provinceId != null">
             AND m.province_id = #{provinceId}