Przeglądaj źródła

Merge branch 'cjp' into test_dev

caojp 2 tygodni temu
rodzic
commit
742ed4a731
25 zmienionych plików z 362 dodań i 113 usunięć
  1. 1 1
      common/src/main/java/com/zhentao/constant/RedisKeyConstants.java
  2. 2 1
      marriageAdmin-vue/src/config/api.js
  3. 15 1
      marriageAdmin-vue/src/layouts/MainLayout.vue
  4. 4 3
      marriageAdmin-vue/src/utils/request.js
  5. 7 1
      marriageAdmin-vue/src/views/Login.vue
  6. 56 20
      marriageAdmin-vue/src/views/activity/ActivityForm.vue
  7. 1 39
      marriageAdmin-vue/src/views/activity/ActivityRegistrations.vue
  8. 24 4
      marriageAdmin-vue/src/views/admin/AdminUserList.vue
  9. 41 2
      marriageAdmin-vue/src/views/matchmaker/MatchmakerAudit.vue
  10. 56 9
      marriageAdmin-vue/src/views/matchmaker/MatchmakerForm.vue
  11. 40 3
      marriageAdmin-vue/src/views/matchmaker/MatchmakerList.vue
  12. 1 1
      marriageAdmin-vue/src/views/matchmaker/PointsOrderList.vue
  13. 23 1
      marriageAdmin-vue/src/views/matchmaker/ResourceList.vue
  14. 7 1
      marriageAdmin-vue/src/views/success-case/SuccessCaseForm.vue
  15. 0 1
      marriageAdmin-vue/src/views/success-case/SuccessCaseList.vue
  16. 58 0
      service/admin/src/main/java/com/zhentao/controller/ActivityController.java
  17. 10 0
      service/admin/src/main/java/com/zhentao/controller/AdminUserController.java
  18. 3 2
      service/admin/src/main/java/com/zhentao/controller/MarrApplyControllor.java
  19. 2 1
      service/admin/src/main/java/com/zhentao/mapper/MarrApplyMapper.java
  20. 2 0
      service/admin/src/main/java/com/zhentao/security/SecurityConfig.java
  21. 1 1
      service/admin/src/main/java/com/zhentao/service/MarrApplyService.java
  22. 2 2
      service/admin/src/main/java/com/zhentao/service/impl/MarrApplyServiceImpl.java
  23. 3 0
      service/admin/src/main/resources/com/zhentao/mapper/MarrApplyMapper.xml
  24. 1 1
      service/homePage/src/main/java/com/zhentao/constant/RedisKeyConstants.java
  25. 2 18
      service/homePage/src/main/resources/mapper/MatchmakerMapper.xml

+ 1 - 1
common/src/main/java/com/zhentao/constant/RedisKeyConstants.java

@@ -15,7 +15,7 @@ public class RedisKeyConstants {
      * 红娘列表缓存Key前缀
      * 红娘列表缓存Key前缀
      * 格式:matchmaker:list:{type}:{level}:{provinceId}:{cityId}:{pageNum}:{pageSize}
      * 格式:matchmaker:list:{type}:{level}:{provinceId}:{cityId}:{pageNum}:{pageSize}
      */
      */
-    public static final String MATCHMAKER_LIST = "matchmaker:list:";
+    public static final String MATCHMAKER_LIST = "matchmaker:list:v2:";
     
     
     /**
     /**
      * 全职红娘列表缓存Key前缀
      * 全职红娘列表缓存Key前缀

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

@@ -121,7 +121,8 @@ export const API_ENDPOINTS = {
   UPLOAD_FILE: '/admin/upload/file',
   UPLOAD_FILE: '/admin/upload/file',
   
   
   // 城市管理
   // 城市管理
-  CITY_LIST: '/api/recommend/area/cities'
+  CITY_LIST: '/api/recommend/area/cities',
+  PROVINCE_LIST: '/api/recommend/area/provinces'
 }
 }
 
 
 // Redis 缓存键前缀
 // Redis 缓存键前缀

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

@@ -321,10 +321,24 @@ const handleCommand = async (command) => {
 }
 }
 
 
 .aside-menu :deep(.el-sub-menu.is-active > .el-sub-menu__title) {
 .aside-menu :deep(.el-sub-menu.is-active > .el-sub-menu__title) {
-  color: var(--primary-light) !important;
+  color: #ffffff !important;
   font-weight: var(--font-medium);
   font-weight: var(--font-medium);
 }
 }
 
 
+.aside-menu :deep(.el-sub-menu.is-opened > .el-sub-menu__title) {
+  color: #ffffff !important;
+}
+
+.aside-menu :deep(.el-sub-menu.is-active > .el-sub-menu__title .el-icon),
+.aside-menu :deep(.el-sub-menu.is-opened > .el-sub-menu__title .el-icon) {
+  color: #ffffff !important;
+}
+
+.aside-menu :deep(.el-sub-menu.is-active > .el-sub-menu__title .el-sub-menu__icon-arrow),
+.aside-menu :deep(.el-sub-menu.is-opened > .el-sub-menu__title .el-sub-menu__icon-arrow) {
+  color: #ffffff !important;
+}
+
 .aside-menu :deep(.el-menu--inline) {
 .aside-menu :deep(.el-menu--inline) {
   background-color: rgba(0, 0, 0, 0.15);
   background-color: rgba(0, 0, 0, 0.15);
 }
 }

+ 4 - 3
marriageAdmin-vue/src/utils/request.js

@@ -74,8 +74,8 @@ request.interceptors.response.use(
       clearTokenAndRedirect(res.msg || '未授权,请重新登录')
       clearTokenAndRedirect(res.msg || '未授权,请重新登录')
       return Promise.reject(new Error(res.msg || '未授权'))
       return Promise.reject(new Error(res.msg || '未授权'))
     } else if (res.code === 403) {
     } else if (res.code === 403) {
-      // 拒绝访问,清除token并跳转登录
-      clearTokenAndRedirect(res.msg || '拒绝访问,请重新登录')
+      // 拒绝访问:通常是权限不足,不应清token或强制跳转登录
+      showErrorMessage(res.msg || '拒绝访问')
       return Promise.reject(new Error(res.msg || '拒绝访问'))
       return Promise.reject(new Error(res.msg || '拒绝访问'))
     } else {
     } else {
       // 其他错误
       // 其他错误
@@ -103,7 +103,8 @@ request.interceptors.response.use(
           clearTokenAndRedirect('未授权,请重新登录')
           clearTokenAndRedirect('未授权,请重新登录')
           break
           break
         case 403:
         case 403:
-          clearTokenAndRedirect('拒绝访问,请重新登录')
+          // 权限不足:仅提示,不跳转登录
+          showErrorMessage(error.response.data?.msg || '拒绝访问')
           break
           break
         case 404:
         case 404:
           // 只对API请求显示404提示
           // 只对API请求显示404提示

+ 7 - 1
marriageAdmin-vue/src/views/Login.vue

@@ -337,7 +337,13 @@ const handleLogin = async () => {
 
 
 /* 表单项增强 */
 /* 表单项增强 */
 .login-form :deep(.el-form-item) {
 .login-form :deep(.el-form-item) {
-  margin-bottom: var(--spacing-xl);
+  margin-bottom: 12px;
+}
+
+/* 登录按钮容器增加上间距 */
+.login-form :deep(.el-form-item:last-child) {
+  margin-top: 28px;
+  margin-bottom: 0;
 }
 }
 
 
 .login-form :deep(.el-input__wrapper) {
 .login-form :deep(.el-input__wrapper) {

+ 56 - 20
marriageAdmin-vue/src/views/activity/ActivityForm.vue

@@ -193,35 +193,71 @@ const activityForm = reactive({
 
 
 // 自定义时间范围验证
 // 自定义时间范围验证
 const validateTimeRange = (rule, value, callback) => {
 const validateTimeRange = (rule, value, callback) => {
-  console.log('验证时间范围:', {
-    value,
-    startTime: activityForm.startTime,
-    endTime: activityForm.endTime,
-    isEdit: isEdit.value
-  })
+  const now = new Date()
+  let startTime = activityForm.startTime
+  let endTime = activityForm.endTime
   
   
-  // 优先检查 activityForm 中的时间数据(不管是编辑还是创建模式)
-  if (activityForm.startTime && activityForm.endTime) {
-    console.log('✅ 时间验证通过(使用 activityForm 中的数据)')
-    callback()
-    return
+  // 优先使用 timeRange 数据
+  if (value && Array.isArray(value) && value.length === 2 && value[0] && value[1]) {
+    startTime = value[0]
+    endTime = value[1]
   }
   }
   
   
-  // 如果 activityForm 中没有,检查 timeRange
-  if (!value || !Array.isArray(value) || value.length !== 2) {
-    console.log('❌ 时间验证失败:timeRange 不是有效数组')
+  // 检查是否有完整的时间数据
+  if (!startTime || !endTime) {
     callback(new Error('请选择完整的活动时间范围'))
     callback(new Error('请选择完整的活动时间范围'))
     return
     return
   }
   }
   
   
-  // 检查是否两个时间都已选择
-  if (!value[0] || !value[1]) {
-    console.log('❌ 时间验证失败:开始或结束时间为空')
-    callback(new Error('请选择完整的活动时间范围'))
+  const start = new Date(startTime)
+  const end = new Date(endTime)
+  
+  // 验证开始时间必须大于当前时间(编辑模式下不验证,因为可能是已创建的活动)
+  if (!isEdit.value && start <= now) {
+    callback(new Error('开始时间必须大于当前时间'))
+    return
+  }
+  
+  // 验证结束时间必须大于开始时间
+  if (end <= start) {
+    callback(new Error('结束时间必须大于开始时间'))
+    return
+  }
+  
+  callback()
+}
+
+// 自定义报名截止时间验证
+const validateRegistrationEndTime = (rule, value, callback) => {
+  if (!value) {
+    callback(new Error('请选择报名截止时间'))
+    return
+  }
+  
+  const registrationEnd = new Date(value)
+  const startTime = activityForm.startTime || (timeRange.value && timeRange.value[0])
+  const endTime = activityForm.endTime || (timeRange.value && timeRange.value[1])
+  
+  if (!startTime || !endTime) {
+    callback(new Error('请先选择活动时间'))
+    return
+  }
+  
+  const start = new Date(startTime)
+  const end = new Date(endTime)
+  
+  // 报名截止时间必须大于开始时间
+  if (registrationEnd <= start) {
+    callback(new Error('报名截止时间必须大于活动开始时间'))
+    return
+  }
+  
+  // 报名截止时间必须小于结束时间
+  if (registrationEnd >= end) {
+    callback(new Error('报名截止时间必须小于活动结束时间'))
     return
     return
   }
   }
   
   
-  console.log('✅ 时间验证通过(使用 timeRange)')
   callback()
   callback()
 }
 }
 
 
@@ -233,7 +269,7 @@ const activityRules = {
   location: [{ required: true, message: '请输入活动地点', trigger: 'blur' }],
   location: [{ required: true, message: '请输入活动地点', trigger: 'blur' }],
   coverImage: [{ required: true, message: '请上传活动封面', trigger: 'change' }],
   coverImage: [{ required: true, message: '请上传活动封面', trigger: 'change' }],
   timeRange: [{ validator: validateTimeRange, trigger: 'change' }],
   timeRange: [{ validator: validateTimeRange, trigger: 'change' }],
-  registrationEndTime: [{ required: true, message: '请选择报名截止时间', trigger: 'change' }],
+  registrationEndTime: [{ validator: validateRegistrationEndTime, trigger: 'change' }],
   description: [{ required: true, message: '请输入活动描述', trigger: 'blur' }]
   description: [{ required: true, message: '请输入活动描述', trigger: 'blur' }]
 }
 }
 
 

+ 1 - 39
marriageAdmin-vue/src/views/activity/ActivityRegistrations.vue

@@ -128,15 +128,6 @@
             >
             >
               签到
               签到
             </el-button>
             </el-button>
-            <el-button
-              v-if="row.status !== 2"
-              type="danger"
-              size="small"
-              link
-              @click="handleCancelRegistration(row)"
-            >
-              取消报名
-            </el-button>
           </template>
           </template>
         </el-table-column>
         </el-table-column>
       </el-table>
       </el-table>
@@ -160,7 +151,7 @@
 <script setup>
 <script setup>
 import { ref, reactive, onMounted } from 'vue'
 import { ref, reactive, onMounted } from 'vue'
 import { useRoute } from 'vue-router'
 import { useRoute } from 'vue-router'
-import { ElMessage, ElMessageBox } from 'element-plus'
+import { ElMessage } from 'element-plus'
 import request from '@/utils/request'
 import request from '@/utils/request'
 import { API_BASE_URL, API_ENDPOINTS } from '@/config/api'
 import { API_BASE_URL, API_ENDPOINTS } from '@/config/api'
 
 
@@ -266,35 +257,6 @@ const handleCheckIn = async (row) => {
   }
   }
 }
 }
 
 
-// 取消报名
-const handleCancelRegistration = async (row) => {
-  try {
-    await ElMessageBox.confirm('确定要取消此用户的报名吗?', '提示', {
-      confirmButtonText: '确定',
-      cancelButtonText: '取消',
-      type: 'warning'
-    })
-    
-    const response = await request.delete(`/admin/activity/cancel-registration`, {
-      params: {
-        activityId: activityId.value,
-        userId: row.userId
-      }
-    })
-    
-    if (response.code === 200) {
-      ElMessage.success('取消报名成功')
-      // 重新加载列表和统计数据
-      await loadRegistrationList()
-      await loadStats()
-    }
-  } catch (error) {
-    if (error !== 'cancel') {
-      console.error('取消报名失败:', error)
-    }
-  }
-}
-
 // 导出数据
 // 导出数据
 const handleExport = async () => {
 const handleExport = async () => {
   try {
   try {

+ 24 - 4
marriageAdmin-vue/src/views/admin/AdminUserList.vue

@@ -93,6 +93,7 @@
               type="primary"
               type="primary"
               link
               link
               size="small"
               size="small"
+              :disabled="!canOperate(row)"
               @click="handleEdit(row)"
               @click="handleEdit(row)"
             >
             >
               编辑
               编辑
@@ -102,7 +103,7 @@
               type="warning"
               type="warning"
               link
               link
               size="small"
               size="small"
-              :disabled="isSuperAdmin(row) || isCurrentUser(row)"
+              :disabled="!canOperate(row) || isCurrentUser(row)"
               @click="handleDisable(row)"
               @click="handleDisable(row)"
             >
             >
               禁用
               禁用
@@ -112,7 +113,7 @@
               type="success"
               type="success"
               link
               link
               size="small"
               size="small"
-              :disabled="isSuperAdmin(row)"
+              :disabled="!canOperate(row)"
               @click="handleEnable(row)"
               @click="handleEnable(row)"
             >
             >
               启用
               启用
@@ -121,7 +122,7 @@
               type="danger"
               type="danger"
               link
               link
               size="small"
               size="small"
-              :disabled="isSuperAdmin(row)"
+              :disabled="!canOperate(row) || isCurrentUser(row)"
               @click="handleDelete(row)"
               @click="handleDelete(row)"
             >
             >
               删除
               删除
@@ -248,9 +249,11 @@ const roleList = ref([])
 
 
 // 筛选角色列表,只显示普通管理员和超级管理员
 // 筛选角色列表,只显示普通管理员和超级管理员
 const filteredRoleList = computed(() => {
 const filteredRoleList = computed(() => {
-  return roleList.value.filter(role => 
+  const base = roleList.value.filter(role => 
     role.roleCode === 'ADMIN' || role.roleCode === 'SUPER_ADMIN'
     role.roleCode === 'ADMIN' || role.roleCode === 'SUPER_ADMIN'
   )
   )
+  if (userStore.isSuperAdmin) return base
+  return base.filter(role => role.roleCode !== 'SUPER_ADMIN')
 })
 })
 
 
 // 对话框
 // 对话框
@@ -489,6 +492,23 @@ const isCurrentUser = (row) => {
   return row.id === userStore.userInfo?.id
   return row.id === userStore.userInfo?.id
 }
 }
 
 
+// 是否允许对该行用户进行操作
+const canOperate = (row) => {
+  if (!row) return false
+  if (isCurrentUser(row)) return true
+
+  const targetIsSuperAdmin = isSuperAdmin(row)
+  if (targetIsSuperAdmin) {
+    return false
+  }
+
+  if (userStore.isSuperAdmin) {
+    return true
+  }
+
+  return false
+}
+
 // 禁用管理员
 // 禁用管理员
 const handleDisable = async (row) => {
 const handleDisable = async (row) => {
   try {
   try {

+ 41 - 2
marriageAdmin-vue/src/views/matchmaker/MatchmakerAudit.vue

@@ -18,6 +18,17 @@
         </el-col>
         </el-col>
         <el-col :span="14">
         <el-col :span="14">
           <el-space style="justify-content: flex-end; width: 100%;">
           <el-space style="justify-content: flex-end; width: 100%;">
+            <el-select
+              v-model="filters.status"
+              placeholder="审核状态"
+              clearable
+              style="width: 140px"
+              @change="handleStatusChange"
+            >
+              <el-option label="待审核" :value="2" />
+              <el-option label="审核通过" :value="0" />
+              <el-option label="审核未通过" :value="1" />
+            </el-select>
             <el-input
             <el-input
               v-model="filters.name"
               v-model="filters.name"
               placeholder="搜索姓名"
               placeholder="搜索姓名"
@@ -77,7 +88,7 @@
         <el-table-column prop="introduction" label="简介" min-width="160" show-overflow-tooltip />
         <el-table-column prop="introduction" label="简介" min-width="160" show-overflow-tooltip />
         <el-table-column prop="createTime" label="申请时间" width="180">
         <el-table-column prop="createTime" label="申请时间" width="180">
           <template #default="{ row }">
           <template #default="{ row }">
-            <span class="text-muted">{{ row.createTime || row.create_time || '-' }}</span>
+            <span class="text-muted">{{ formatDateTime(row.createTime || row.create_time) }}</span>
           </template>
           </template>
         </el-table-column>
         </el-table-column>
         <el-table-column label="审核原因" min-width="150" show-overflow-tooltip>
         <el-table-column label="审核原因" min-width="150" show-overflow-tooltip>
@@ -328,6 +339,24 @@ import { User, Star, Document } from '@element-plus/icons-vue'
 import request from '@/utils/request'
 import request from '@/utils/request'
 import { API_ENDPOINTS } from '@/config/api'
 import { API_ENDPOINTS } from '@/config/api'
 
 
+// 格式化日期时间
+const formatDateTime = (dateTime) => {
+  if (!dateTime) return '-'
+  try {
+    const date = new Date(dateTime)
+    if (isNaN(date.getTime())) return dateTime
+    const year = date.getFullYear()
+    const month = String(date.getMonth() + 1).padStart(2, '0')
+    const day = String(date.getDate()).padStart(2, '0')
+    const hours = String(date.getHours()).padStart(2, '0')
+    const minutes = String(date.getMinutes()).padStart(2, '0')
+    const seconds = String(date.getSeconds()).padStart(2, '0')
+    return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`
+  } catch {
+    return dateTime
+  }
+}
+
 const loading = ref(false)
 const loading = ref(false)
 const list = ref([])
 const list = ref([])
 const total = ref(0)
 const total = ref(0)
@@ -352,9 +381,15 @@ const detailData = ref(null)
 
 
 const filters = reactive({
 const filters = reactive({
   name: '',
   name: '',
-  phone: ''
+  phone: '',
+  status: undefined
 })
 })
 
 
+const handleStatusChange = () => {
+  currentPage.value = 1
+  loadList()
+}
+
 const auditForm = reactive({
 const auditForm = reactive({
   approved: true,
   approved: true,
   reason: ''
   reason: ''
@@ -377,6 +412,9 @@ const loadList = async () => {
       page: currentPage.value,
       page: currentPage.value,
       pageSize: pageSize.value
       pageSize: pageSize.value
     }
     }
+    if (filters.status !== undefined && filters.status !== null) {
+      params.status = filters.status
+    }
     if (filters.name && filters.name.trim()) {
     if (filters.name && filters.name.trim()) {
       params.name = filters.name.trim()
       params.name = filters.name.trim()
     }
     }
@@ -400,6 +438,7 @@ const loadList = async () => {
 const resetFilters = () => {
 const resetFilters = () => {
   filters.name = ''
   filters.name = ''
   filters.phone = ''
   filters.phone = ''
+  filters.status = undefined
   currentPage.value = 1
   currentPage.value = 1
   loadList()
   loadList()
 }
 }

+ 56 - 9
marriageAdmin-vue/src/views/matchmaker/MatchmakerForm.vue

@@ -84,9 +84,21 @@
         </el-row>
         </el-row>
         
         
         <el-row :gutter="20">
         <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="省份" prop="provinceId">
+              <el-select v-model="form.provinceId" placeholder="请选择省份" filterable style="width: 100%" @change="handleProvinceChange">
+                <el-option
+                  v-for="province in provinceList"
+                  :key="province.id"
+                  :label="province.name"
+                  :value="province.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
           <el-col :span="12">
           <el-col :span="12">
             <el-form-item label="城市" prop="cityId">
             <el-form-item label="城市" prop="cityId">
-              <el-select v-model="form.cityId" placeholder="请选择城市" filterable style="width: 100%">
+              <el-select v-model="form.cityId" placeholder="请选择城市" filterable style="width: 100%" :disabled="!form.provinceId">
                 <el-option
                 <el-option
                   v-for="city in cityList"
                   v-for="city in cityList"
                   :key="city.id"
                   :key="city.id"
@@ -134,6 +146,7 @@ const router = useRouter()
 const isEdit = ref(false)
 const isEdit = ref(false)
 const submitLoading = ref(false)
 const submitLoading = ref(false)
 const formRef = ref(null)
 const formRef = ref(null)
+const provinceList = ref([])
 const cityList = ref([])
 const cityList = ref([])
 
 
 const form = reactive({
 const form = reactive({
@@ -148,6 +161,7 @@ const form = reactive({
   level: 1,
   level: 1,
   profile: '',
   profile: '',
   status: 1,
   status: 1,
+  provinceId: null,
   cityId: null
   cityId: null
 })
 })
 
 
@@ -193,13 +207,28 @@ const rules = {
   phone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
   phone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
   gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
   gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
   matchmakerType: [{ required: true, message: '请选择红娘类型', trigger: 'change' }],
   matchmakerType: [{ required: true, message: '请选择红娘类型', trigger: 'change' }],
-  level: [{ required: true, message: '请选择等级', trigger: 'change' }]
+  level: [{ required: true, message: '请选择等级', trigger: 'change' }],
+  provinceId: [{ required: true, message: '请选择省份', trigger: 'change' }],
+  cityId: [{ required: true, message: '请选择城市', trigger: 'change' }]
+}
+
+const loadProvinceList = async () => {
+  try {
+    const response = await request.get(API_ENDPOINTS.PROVINCE_LIST)
+    if (response.code === 200) {
+      provinceList.value = response.data || []
+    }
+  } catch (error) {
+    console.error('加载省份列表失败:', error)
+    ElMessage.error('加载省份列表失败')
+  }
 }
 }
 
 
 // 加载城市列表
 // 加载城市列表
-const loadCityList = async () => {
+const loadCityList = async (provinceId = null) => {
   try {
   try {
-    const response = await request.get(API_ENDPOINTS.CITY_LIST)
+    const url = provinceId ? `${API_ENDPOINTS.CITY_LIST}?provinceId=${provinceId}` : API_ENDPOINTS.CITY_LIST
+    const response = await request.get(url)
     if (response.code === 200) {
     if (response.code === 200) {
       cityList.value = response.data || []
       cityList.value = response.data || []
     }
     }
@@ -209,6 +238,17 @@ const loadCityList = async () => {
   }
   }
 }
 }
 
 
+const handleProvinceChange = async () => {
+  form.cityId = null
+  cityList.value = []
+  if (formRef.value) {
+    formRef.value.clearValidate('cityId')
+  }
+  if (form.provinceId) {
+    await loadCityList(form.provinceId)
+  }
+}
+
 const loadDetail = async (id) => {
 const loadDetail = async (id) => {
   try {
   try {
     const response = await request.get(`${API_ENDPOINTS.MATCHMAKER_DETAIL}/${id}`)
     const response = await request.get(`${API_ENDPOINTS.MATCHMAKER_DETAIL}/${id}`)
@@ -225,7 +265,14 @@ const loadDetail = async (id) => {
       form.level = data.level !== undefined ? data.level : 1
       form.level = data.level !== undefined ? data.level : 1
       form.profile = data.profile || ''
       form.profile = data.profile || ''
       form.status = data.status !== undefined ? data.status : 1
       form.status = data.status !== undefined ? data.status : 1
+      form.provinceId = data.province_id || data.provinceId || null
       form.cityId = data.city_id || data.cityId || null
       form.cityId = data.city_id || data.cityId || null
+
+      if (form.provinceId) {
+        await loadCityList(form.provinceId)
+      } else if (form.cityId) {
+        await loadCityList()
+      }
     }
     }
   } catch (error) {
   } catch (error) {
     console.error('加载详情失败:', error)
     console.error('加载详情失败:', error)
@@ -323,6 +370,7 @@ const handleSubmit = async () => {
       level: form.level,
       level: form.level,
       profile: form.profile,
       profile: form.profile,
       status: form.status,
       status: form.status,
+      province_id: form.provinceId,
       city_id: form.cityId
       city_id: form.cityId
     }
     }
     
     
@@ -341,13 +389,12 @@ const handleSubmit = async () => {
   }
   }
 }
 }
 
 
-onMounted(() => {
-  // 加载城市列表
-  loadCityList()
-  
+onMounted(async () => {
+  await loadProvinceList()
+
   if (route.params.id) {
   if (route.params.id) {
     isEdit.value = true
     isEdit.value = true
-    loadDetail(route.params.id)
+    await loadDetail(route.params.id)
   }
   }
 })
 })
 </script>
 </script>

+ 40 - 3
marriageAdmin-vue/src/views/matchmaker/MatchmakerList.vue

@@ -16,12 +16,12 @@
         </el-col>
         </el-col>
         <el-col :span="14">
         <el-col :span="14">
           <el-space style="justify-content: flex-end; width: 100%;">
           <el-space style="justify-content: flex-end; width: 100%;">
-            <el-select v-model="filters.matchmakerType" placeholder="红娘类型" clearable style="width: 120px" @change="loadList">
+            <el-select v-model="filters.matchmakerType" placeholder="红娘类型" clearable style="width: 120px" @change="handleFilterChange">
               <el-option label="兼职" :value="1" />
               <el-option label="兼职" :value="1" />
               <el-option label="全职" :value="2" />
               <el-option label="全职" :value="2" />
             </el-select>
             </el-select>
             
             
-            <el-select v-model="filters.level" placeholder="等级" clearable style="width: 120px" @change="loadList">
+            <el-select v-model="filters.level" placeholder="等级" clearable style="width: 120px" @change="handleFilterChange">
               <el-option label="青铜" :value="1" />
               <el-option label="青铜" :value="1" />
               <el-option label="白银" :value="2" />
               <el-option label="白银" :value="2" />
               <el-option label="黄金" :value="3" />
               <el-option label="黄金" :value="3" />
@@ -92,6 +92,11 @@
             </el-tag>
             </el-tag>
           </template>
           </template>
         </el-table-column>
         </el-table-column>
+        <el-table-column prop="created_at" label="注册时间" width="180">
+          <template #default="{ row }">
+            <span>{{ formatDateTime(row.created_at || row.createTime || row.createdAt) }}</span>
+          </template>
+        </el-table-column>
         <el-table-column label="操作" width="200" fixed="right">
         <el-table-column label="操作" width="200" fixed="right">
           <template #default="{ row }">
           <template #default="{ row }">
             <el-button type="info" size="small" link @click="handleViewDetail(row)">
             <el-button type="info" size="small" link @click="handleViewDetail(row)">
@@ -252,6 +257,24 @@ import { User, Star, Location, Document } from '@element-plus/icons-vue'
 import request from '@/utils/request'
 import request from '@/utils/request'
 import { API_ENDPOINTS } from '@/config/api'
 import { API_ENDPOINTS } from '@/config/api'
 
 
+// 格式化日期时间
+const formatDateTime = (dateTime) => {
+  if (!dateTime) return '-'
+  try {
+    const date = new Date(dateTime)
+    if (isNaN(date.getTime())) return dateTime
+    const year = date.getFullYear()
+    const month = String(date.getMonth() + 1).padStart(2, '0')
+    const day = String(date.getDate()).padStart(2, '0')
+    const hours = String(date.getHours()).padStart(2, '0')
+    const minutes = String(date.getMinutes()).padStart(2, '0')
+    const seconds = String(date.getSeconds()).padStart(2, '0')
+    return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`
+  } catch {
+    return dateTime
+  }
+}
+
 const loading = ref(false)
 const loading = ref(false)
 const currentPage = ref(1)
 const currentPage = ref(1)
 const pageSize = ref(10)
 const pageSize = ref(10)
@@ -269,6 +292,11 @@ const filters = reactive({
   keyword: ''
   keyword: ''
 })
 })
 
 
+const handleFilterChange = () => {
+  currentPage.value = 1
+  loadList()
+}
+
 const loadList = async () => {
 const loadList = async () => {
   loading.value = true
   loading.value = true
   try {
   try {
@@ -303,7 +331,16 @@ const loadList = async () => {
       console.log('📄 分页数据:', pageData)
       console.log('📄 分页数据:', pageData)
       
       
       // 读取列表数据
       // 读取列表数据
-      list.value = pageData?.records || pageData?.list || (Array.isArray(pageData) ? pageData : []) || []
+      let records = pageData?.records || pageData?.list || (Array.isArray(pageData) ? pageData : []) || []
+      
+      // 按注册时间降序排序
+      records.sort((a, b) => {
+        const timeA = new Date(a.created_at || a.createTime || a.createdAt || 0).getTime()
+        const timeB = new Date(b.created_at || b.createTime || b.createdAt || 0).getTime()
+        return timeB - timeA
+      })
+      
+      list.value = records
       
       
       // 读取总数 - 优先从total字段读取,支持多种可能的字段名
       // 读取总数 - 优先从total字段读取,支持多种可能的字段名
       let totalValue = pageData?.total
       let totalValue = pageData?.total

+ 1 - 1
marriageAdmin-vue/src/views/matchmaker/PointsOrderList.vue

@@ -65,7 +65,7 @@
           </div>
           </div>
         </template>
         </template>
         <el-table-column type="index" label="序号" width="60" />
         <el-table-column type="index" label="序号" width="60" />
-        <el-table-column prop="orderNo" label="订单号" width="180" />
+        <el-table-column prop="orderNo" label="订单号" width="220" />
         <el-table-column prop="productName" label="产品名称" width="200" />
         <el-table-column prop="productName" label="产品名称" width="200" />
         <el-table-column prop="productImage" label="产品图片" width="100">
         <el-table-column prop="productImage" label="产品图片" width="100">
           <template #default="{ row }">
           <template #default="{ row }">

+ 23 - 1
marriageAdmin-vue/src/views/matchmaker/ResourceList.vue

@@ -120,7 +120,11 @@
             </el-tag>
             </el-tag>
           </template>
           </template>
         </el-table-column>
         </el-table-column>
-        <el-table-column prop="createTime" label="创建时间" width="180" />
+        <el-table-column prop="createTime" label="创建时间" width="180">
+          <template #default="{ row }">
+            <span>{{ formatDateTime(row.createTime) }}</span>
+          </template>
+        </el-table-column>
         <el-table-column label="操作" width="150" fixed="right">
         <el-table-column label="操作" width="150" fixed="right">
           <template #default="{ row }">
           <template #default="{ row }">
             <el-button 
             <el-button 
@@ -301,6 +305,24 @@ import { Document } from '@element-plus/icons-vue'
 import request from '@/utils/request'
 import request from '@/utils/request'
 import { API_ENDPOINTS } from '@/config/api'
 import { API_ENDPOINTS } from '@/config/api'
 
 
+// 格式化日期时间
+const formatDateTime = (dateTime) => {
+  if (!dateTime) return '-'
+  try {
+    const date = new Date(dateTime)
+    if (isNaN(date.getTime())) return dateTime
+    const year = date.getFullYear()
+    const month = String(date.getMonth() + 1).padStart(2, '0')
+    const day = String(date.getDate()).padStart(2, '0')
+    const hours = String(date.getHours()).padStart(2, '0')
+    const minutes = String(date.getMinutes()).padStart(2, '0')
+    const seconds = String(date.getSeconds()).padStart(2, '0')
+    return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`
+  } catch {
+    return dateTime
+  }
+}
+
 const loading = ref(false)
 const loading = ref(false)
 const currentPage = ref(1)
 const currentPage = ref(1)
 const pageSize = ref(10)
 const pageSize = ref(10)

+ 7 - 1
marriageAdmin-vue/src/views/success-case/SuccessCaseForm.vue

@@ -38,7 +38,7 @@
         <el-row :gutter="20">
         <el-row :gutter="20">
           <el-col :span="12">
           <el-col :span="12">
             <el-form-item label="结婚日期" prop="marriageDate">
             <el-form-item label="结婚日期" prop="marriageDate">
-              <el-date-picker v-model="form.marriageDate" type="date" placeholder="选择结婚日期" value-format="YYYY-MM-DD" style="width: 100%" />
+              <el-date-picker v-model="form.marriageDate" type="date" placeholder="选择结婚日期" value-format="YYYY-MM-DD" style="width: 100%" :disabled-date="disableFutureMarriageDate" />
             </el-form-item>
             </el-form-item>
           </el-col>
           </el-col>
           <el-col :span="12">
           <el-col :span="12">
@@ -110,6 +110,12 @@ const rules = {
   ]
   ]
 }
 }
 
 
+const disableFutureMarriageDate = (time) => {
+  const endOfToday = new Date()
+  endOfToday.setHours(23, 59, 59, 999)
+  return time.getTime() > endOfToday.getTime()
+}
+
 const loadDetail = async (id) => {
 const loadDetail = async (id) => {
   try {
   try {
     const response = await request.get(`${API_ENDPOINTS.CASE_DETAIL}/${id}`)
     const response = await request.get(`${API_ENDPOINTS.CASE_DETAIL}/${id}`)

+ 0 - 1
marriageAdmin-vue/src/views/success-case/SuccessCaseList.vue

@@ -34,7 +34,6 @@
             <el-switch v-model="row.caseStatus" :active-value="1" :inactive-value="0" @change="handleStatusChange(row)" />
             <el-switch v-model="row.caseStatus" :active-value="1" :inactive-value="0" @change="handleStatusChange(row)" />
           </template>
           </template>
         </el-table-column>
         </el-table-column>
-        <el-table-column prop="createdAt" label="创建时间" width="180" />
         <el-table-column label="操作" width="200" fixed="right">
         <el-table-column label="操作" width="200" fixed="right">
           <template #default="{ row }">
           <template #default="{ row }">
             <el-button type="info" size="small" link @click="handleViewDetail(row)">查看</el-button>
             <el-button type="info" size="small" link @click="handleViewDetail(row)">查看</el-button>

+ 58 - 0
service/admin/src/main/java/com/zhentao/controller/ActivityController.java

@@ -1,8 +1,12 @@
 package com.zhentao.controller;
 package com.zhentao.controller;
 
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.zhentao.common.Result;
 import com.zhentao.common.Result;
 import com.zhentao.entity.Activity;
 import com.zhentao.entity.Activity;
+import com.zhentao.entity.ActivityRegistration;
 import com.zhentao.mapper.ActivityMapper;
 import com.zhentao.mapper.ActivityMapper;
+import com.zhentao.service.ActivityRegistrationService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
@@ -19,6 +23,9 @@ public class ActivityController {
     
     
     @Autowired
     @Autowired
     private ActivityMapper activityMapper;
     private ActivityMapper activityMapper;
+
+    @Autowired
+    private ActivityRegistrationService activityRegistrationService;
     
     
     private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
     private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
     
     
@@ -111,6 +118,57 @@ public class ActivityController {
             return Result.error("删除活动失败:" + e.getMessage());
             return Result.error("删除活动失败:" + e.getMessage());
         }
         }
     }
     }
+
+
+    /**
+     * 取消报名
+     */
+    @DeleteMapping("/cancel-registration")
+    public Result<String> cancelRegistration(
+            @RequestParam("activityId") Integer activityId,
+            @RequestParam("userId") Integer userId
+    ) {
+        try {
+            if (activityId == null || userId == null) {
+                return Result.error("activityId和userId不能为空");
+            }
+
+            LambdaQueryWrapper<ActivityRegistration> queryWrapper = new LambdaQueryWrapper<ActivityRegistration>()
+                    .eq(ActivityRegistration::getActivityId, activityId)
+                    .eq(ActivityRegistration::getUserId, userId)
+                    .orderByDesc(ActivityRegistration::getRegistrationTime)
+                    .last("LIMIT 1");
+
+            ActivityRegistration registration = activityRegistrationService.getOne(queryWrapper, false);
+            if (registration == null) {
+                return Result.error("报名记录不存在");
+            }
+            if (registration.getStatus() != null && registration.getStatus() == 2) {
+                return Result.success("取消报名成功");
+            }
+            if (registration.getStatus() != null && registration.getStatus() == 3) {
+                return Result.error("已签到,无法取消报名");
+            }
+
+            registration.setStatus(2);
+            registration.setUpdatedTime(LocalDateTime.now());
+            boolean updated = activityRegistrationService.updateById(registration);
+            if (!updated) {
+                return Result.error("取消报名失败");
+            }
+
+            UpdateWrapper<Activity> updateWrapper = new UpdateWrapper<>();
+            updateWrapper.eq("id", activityId);
+            updateWrapper.gt("actual_participants", 0);
+            updateWrapper.setSql("actual_participants = actual_participants - 1");
+            activityMapper.update(null, updateWrapper);
+
+            return Result.success("取消报名成功");
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("取消报名失败:" + e.getMessage());
+        }
+    }
     
     
     
     
     /**
     /**

+ 10 - 0
service/admin/src/main/java/com/zhentao/controller/AdminUserController.java

@@ -200,6 +200,11 @@ public class AdminUserController {
             if (!userService.isSuperAdmin(currentUser.getId())) {
             if (!userService.isSuperAdmin(currentUser.getId())) {
                 return Result.error(403, "只有超级管理员可以修改管理员信息");
                 return Result.error(403, "只有超级管理员可以修改管理员信息");
             }
             }
+
+            // 超级管理员不能操作其他超级管理员(允许操作自己)
+            if (userService.isSuperAdmin(userId) && !userId.equals(currentUser.getId())) {
+                return Result.error(403, "不能操作其他超级管理员");
+            }
             
             
             AdminUser user = userService.getUserById(userId);
             AdminUser user = userService.getUserById(userId);
             if (user == null) {
             if (user == null) {
@@ -360,6 +365,11 @@ public class AdminUserController {
             if (!userService.isSuperAdmin(currentUser.getId())) {
             if (!userService.isSuperAdmin(currentUser.getId())) {
                 return Result.error(403, "只有超级管理员可以启用管理员");
                 return Result.error(403, "只有超级管理员可以启用管理员");
             }
             }
+
+            // 超级管理员不能操作其他超级管理员(允许操作自己)
+            if (userService.isSuperAdmin(userId) && !userId.equals(currentUser.getId())) {
+                return Result.error(403, "不能操作其他超级管理员");
+            }
             
             
             AdminUser user = userService.getUserById(userId);
             AdminUser user = userService.getUserById(userId);
             if (user == null) {
             if (user == null) {

+ 3 - 2
service/admin/src/main/java/com/zhentao/controller/MarrApplyControllor.java

@@ -30,9 +30,10 @@ public class MarrApplyControllor {
     public Result<Map<String, Object>> list(@RequestParam(value = "page", defaultValue = "1") Integer page,
     public Result<Map<String, Object>> list(@RequestParam(value = "page", defaultValue = "1") Integer page,
                                             @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
                                             @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
                                             @RequestParam(value = "name", required = false) String name,
                                             @RequestParam(value = "name", required = false) String name,
-                                            @RequestParam(value = "phone", required = false) String phone) {
+                                            @RequestParam(value = "phone", required = false) String phone,
+                                            @RequestParam(value = "status", required = false) Integer status) {
         try {
         try {
-            Page<MarrApply> pageData = marrApplyService.pageQuery(page, pageSize, name, phone);
+            Page<MarrApply> pageData = marrApplyService.pageQuery(page, pageSize, name, phone, status);
             Map<String, Object> data = new HashMap<>();
             Map<String, Object> data = new HashMap<>();
             data.put("list", pageData.getRecords());
             data.put("list", pageData.getRecords());
             data.put("total", pageData.getTotal());
             data.put("total", pageData.getTotal());

+ 2 - 1
service/admin/src/main/java/com/zhentao/mapper/MarrApplyMapper.java

@@ -19,5 +19,6 @@ public interface MarrApplyMapper extends BaseMapper<MarrApply> {
      */
      */
     Page<MarrApply> selectPageByCondition(Page<MarrApply> page,
     Page<MarrApply> selectPageByCondition(Page<MarrApply> page,
                                           @Param("name") String name,
                                           @Param("name") String name,
-                                          @Param("phone") String phone);
+                                          @Param("phone") String phone,
+                                          @Param("status") Integer status);
 }
 }

+ 2 - 0
service/admin/src/main/java/com/zhentao/security/SecurityConfig.java

@@ -50,6 +50,8 @@ public class SecurityConfig {
                 .antMatchers("/admin/auth/login", "/admin/auth/logout").permitAll()
                 .antMatchers("/admin/auth/login", "/admin/auth/logout").permitAll()
                 // 数据面板 - 仅超级管理员(检查是否有ROLE_SUPER_ADMIN权限或*权限)
                 // 数据面板 - 仅超级管理员(检查是否有ROLE_SUPER_ADMIN权限或*权限)
                 .antMatchers("/admin/dashboard/**").hasAnyAuthority("ROLE_SUPER_ADMIN", "*")
                 .antMatchers("/admin/dashboard/**").hasAnyAuthority("ROLE_SUPER_ADMIN", "*")
+                // 个人资料相关 - 任意已登录管理员可访问
+                .antMatchers("/admin/admin-user/current-profile", "/admin/admin-user/current-password").authenticated()
                 // 管理员管理 - 仅超级管理员
                 // 管理员管理 - 仅超级管理员
                 .antMatchers("/admin/admin-user/**").hasAnyAuthority("ROLE_SUPER_ADMIN", "*")
                 .antMatchers("/admin/admin-user/**").hasAnyAuthority("ROLE_SUPER_ADMIN", "*")
                 // VIP套餐管理 - 仅超级管理员
                 // VIP套餐管理 - 仅超级管理员

+ 1 - 1
service/admin/src/main/java/com/zhentao/service/MarrApplyService.java

@@ -16,7 +16,7 @@ public interface MarrApplyService extends IService<MarrApply> {
     /**
     /**
      * 分页查询红娘申请,支持姓名或手机号模糊搜索,按创建时间倒序
      * 分页查询红娘申请,支持姓名或手机号模糊搜索,按创建时间倒序
      */
      */
-    Page<MarrApply> pageQuery(Integer pageNum, Integer pageSize, String name, String phone);
+    Page<MarrApply> pageQuery(Integer pageNum, Integer pageSize, String name, String phone, Integer status);
     
     
     /**
     /**
      * 审核:更新用户isMatchmaker为1(同意)或保持原状(不同意)
      * 审核:更新用户isMatchmaker为1(同意)或保持原状(不同意)

+ 2 - 2
service/admin/src/main/java/com/zhentao/service/impl/MarrApplyServiceImpl.java

@@ -65,11 +65,11 @@ public class MarrApplyServiceImpl extends ServiceImpl<MarrApplyMapper, MarrApply
     private static final String IM_SERVICE_URL = "http://localhost:1004/api/im";
     private static final String IM_SERVICE_URL = "http://localhost:1004/api/im";
 
 
     @Override
     @Override
-    public Page<MarrApply> pageQuery(Integer pageNum, Integer pageSize, String name, String phone) {
+    public Page<MarrApply> pageQuery(Integer pageNum, Integer pageSize, String name, String phone, Integer status) {
         Page<MarrApply> page = new Page<>(pageNum == null ? 1 : pageNum, pageSize == null ? 10 : pageSize);
         Page<MarrApply> page = new Page<>(pageNum == null ? 1 : pageNum, pageSize == null ? 10 : pageSize);
         String nameKeyword = StringUtils.hasText(name) ? name.trim() : null;
         String nameKeyword = StringUtils.hasText(name) ? name.trim() : null;
         String phoneKeyword = StringUtils.hasText(phone) ? phone.trim() : null;
         String phoneKeyword = StringUtils.hasText(phone) ? phone.trim() : null;
-        this.baseMapper.selectPageByCondition(page, nameKeyword, phoneKeyword);
+        this.baseMapper.selectPageByCondition(page, nameKeyword, phoneKeyword, status);
         return page;
         return page;
     }
     }
 
 

+ 3 - 0
service/admin/src/main/resources/com/zhentao/mapper/MarrApplyMapper.xml

@@ -43,6 +43,9 @@
             <if test="phone != null and phone != ''">
             <if test="phone != null and phone != ''">
                 AND phone LIKE CONCAT('%', #{phone}, '%')
                 AND phone LIKE CONCAT('%', #{phone}, '%')
             </if>
             </if>
+            <if test="status != null">
+                AND status = #{status}
+            </if>
         </where>
         </where>
         ORDER BY create_time DESC
         ORDER BY create_time DESC
     </select>
     </select>

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

@@ -15,7 +15,7 @@ public class RedisKeyConstants {
      * 红娘列表缓存Key前缀
      * 红娘列表缓存Key前缀
      * 格式:matchmaker:list:{type}:{level}:{provinceId}:{cityId}:{pageNum}:{pageSize}
      * 格式:matchmaker:list:{type}:{level}:{provinceId}:{cityId}:{pageNum}:{pageSize}
      */
      */
-    public static final String MATCHMAKER_LIST = "matchmaker:list:";
+    public static final String MATCHMAKER_LIST = "matchmaker:list:v2:";
     
     
     /**
     /**
      * 全职红娘列表缓存Key前缀
      * 全职红娘列表缓存Key前缀

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

@@ -64,24 +64,8 @@
             AND m.matchmaker_type = #{matchmakerType}
             AND m.matchmaker_type = #{matchmakerType}
         </if>
         </if>
         <if test="level != null">
         <if test="level != null">
-            <!-- 根据积分区间筛选等级:青铜(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>
+            <!-- 根据 level 字段筛选等级:1-青铜 2-白银 3-黄金 4-铂金 5-钻石 -->
+            AND m.level = #{level}
         </if>
         </if>
         <if test="provinceId != null">
         <if test="provinceId != null">
             AND m.province_id = #{provinceId}
             AND m.province_id = #{provinceId}