Forráskód Böngészése

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

wangwenju 2 hónapja
szülő
commit
99dc8a2e61

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

@@ -80,7 +80,10 @@ export const API_ENDPOINTS = {
   
   // 文件上传
   UPLOAD_IMAGE: '/admin/upload/image',
-  UPLOAD_FILE: '/admin/upload/file'
+  UPLOAD_FILE: '/admin/upload/file',
+  
+  // 城市管理
+  CITY_LIST: '/api/recommend/area/cities'
 }
 
 // Redis 缓存键前缀

+ 6 - 0
marriageAdmin-vue/src/router/index.js

@@ -118,6 +118,12 @@ const router = createRouter({
               component: () => import('@/views/course/CourseForm.vue'),
               meta: { title: '创建课程' }
             },
+            {
+              path: 'detail/:id',
+              name: 'CourseDetail',
+              component: () => import('@/views/course/CourseDetail.vue'),
+              meta: { title: '课程详情' }
+            },
             {
               path: 'edit/:id',
               name: 'CourseEdit',

+ 35 - 2
marriageAdmin-vue/src/views/course/CourseForm.vue

@@ -192,7 +192,24 @@ const removeCover = () => {
 const loadDetail = async (id) => {
   try {
     const response = await request.get(`${API_ENDPOINTS.COURSE_DETAIL}/${id}`)
-    if (response.code === 200) Object.assign(form, response.data)
+    if (response.code === 200) {
+      const data = response.data
+      // 映射API返回的下划线字段到表单的驼峰字段
+      Object.assign(form, {
+        id: data.id,
+        name: data.name || '',
+        categoryName: data.categoryName || data.category_name || '',
+        instructor: data.instructor || '',
+        duration: data.duration || 0,
+        price: data.price || 0,
+        rating: data.rating || 4.5,
+        isRecommended: data.isRecommended !== undefined ? data.isRecommended : (data.is_recommended !== undefined ? data.is_recommended : 0),
+        coverImage: data.coverImage || data.cover_image || data.cover || '',
+        description: data.description || '',
+        content: data.content || '',
+        status: data.status !== undefined ? data.status : 1
+      })
+    }
   } catch (error) {
     console.error('加载详情失败:', error)
   }
@@ -209,7 +226,23 @@ const handleSubmit = async () => {
     const url = isEdit.value ? `${API_ENDPOINTS.COURSE_UPDATE}/${form.id}` : API_ENDPOINTS.COURSE_CREATE
     const method = isEdit.value ? 'put' : 'post'
     
-    const response = await request[method](url, form)
+    // 将前端的驼峰字段转换为后端期望的 snake_case 格式
+    const submitData = {
+      id: form.id,
+      name: form.name,
+      category_name: form.categoryName,  // 转换为下划线格式
+      instructor: form.instructor,
+      duration: form.duration,
+      price: form.price,
+      rating: form.rating,
+      is_recommended: form.isRecommended,
+      cover_image: form.coverImage,
+      description: form.description,
+      content: form.content,
+      status: form.status
+    }
+    
+    const response = await request[method](url, submitData)
     if (response.code === 200) {
       ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
       router.push('/course/list')

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

@@ -19,12 +19,12 @@
         <el-table-column type="index" label="序号" width="60" />
         <el-table-column label="封面" width="100">
           <template #default="{ row }">
-            <div v-if="row.coverImage || row.cover" class="cover-wrapper">
+            <div v-if="row.coverImage || row.cover_image || row.cover" class="cover-wrapper">
               <el-image
-                :src="row.coverImage || row.cover"
+                :src="row.coverImage || row.cover_image || row.cover"
                 fit="cover"
                 class="course-cover"
-                :preview-src-list="[row.coverImage || row.cover]"
+                :preview-src-list="[row.coverImage || row.cover_image || row.cover]"
                 preview-teleported
                 hide-on-click-modal
               >
@@ -47,7 +47,11 @@
             </span>
           </template>
         </el-table-column>
-        <el-table-column prop="categoryName" label="分类" width="120" />
+        <el-table-column prop="categoryName" label="分类" width="120">
+          <template #default="{ row }">
+            <span class="data-meta">{{ row.categoryName || row.category_name || '-' }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="instructor" label="讲师" width="120" />
         <el-table-column prop="price" label="价格" width="110" align="right">
           <template #default="{ row }">
@@ -95,8 +99,9 @@
             />
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="150" fixed="right">
+        <el-table-column label="操作" width="220" fixed="right">
           <template #default="{ row }">
+            <el-button type="primary" size="small" link @click="$router.push(`/course/detail/${row.id}`)">详情</el-button>
             <el-button type="primary" size="small" link @click="$router.push(`/course/edit/${row.id}`)">编辑</el-button>
             <el-button type="danger" size="small" link @click="handleDelete(row)">删除</el-button>
           </template>

+ 81 - 22
marriageAdmin-vue/src/views/matchmaker/MatchmakerForm.vue

@@ -3,7 +3,7 @@
     <h2 class="page-title">{{ isEdit ? '编辑红娘' : '添加红娘' }}</h2>
     
     <el-card shadow="never">
-      <el-form ref="formRef" :model="matchMaker" :rules="rules" label-width="120px">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
         <el-form-item label="红娘头像" prop="avatarUrl">
           <el-upload
             class="avatar-uploader"
@@ -12,7 +12,7 @@
             :before-upload="handleBeforeUpload"
             :http-request="handleUpload"
           >
-            <img v-if="matchMaker.avatarUrl" :src="matchMaker.avatar_url" class="avatar-preview" />
+            <img v-if="form.avatarUrl" :src="form.avatarUrl" class="avatar-preview" />
             <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
           </el-upload>
           <div class="upload-tip">建议尺寸:200x200px,支持jpg、png格式,不超过2MB</div>
@@ -21,12 +21,12 @@
         <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="真实姓名" prop="realName">
-              <el-input v-model="matchMaker.real_name" placeholder="请输入真实姓名" />
+              <el-input v-model="form.realName" placeholder="请输入真实姓名" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="手机号" prop="phone">
-              <el-input v-model="matchMaker.phone" placeholder="请输入手机号" />
+              <el-input v-model="form.phone" placeholder="请输入手机号" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -34,7 +34,7 @@
         <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="性别" prop="gender">
-              <el-radio-group v-model="matchMaker.gender">
+              <el-radio-group v-model="form.gender">
                 <el-radio :label="1">男</el-radio>
                 <el-radio :label="2">女</el-radio>
               </el-radio-group>
@@ -42,7 +42,7 @@
           </el-col>
           <el-col :span="12">
             <el-form-item label="出生日期" prop="birthDate">
-              <el-date-picker v-model="matchMaker.birth_date" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%" />
+              <el-date-picker v-model="form.birthDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -50,7 +50,7 @@
         <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="红娘类型" prop="matchmakerType">
-              <el-radio-group v-model="matchMaker.matchmakerType">
+              <el-radio-group v-model="form.matchmakerType">
                 <el-radio :label="1">兼职</el-radio>
                 <el-radio :label="2">全职</el-radio>
               </el-radio-group>
@@ -58,7 +58,7 @@
           </el-col>
           <el-col :span="12">
             <el-form-item label="等级" prop="level">
-              <el-select v-model="matchMaker.level" placeholder="请选择等级" style="width: 100%">
+              <el-select v-model="form.level" placeholder="请选择等级" style="width: 100%">
                 <el-option label="青铜" :value="1" />
                 <el-option label="白银" :value="2" />
                 <el-option label="黄金" :value="3" />
@@ -69,12 +69,27 @@
           </el-col>
         </el-row>
         
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="城市" prop="cityId">
+              <el-select v-model="form.cityId" placeholder="请选择城市" filterable style="width: 100%">
+                <el-option
+                  v-for="city in cityList"
+                  :key="city.id"
+                  :label="city.name"
+                  :value="city.id"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        
         <el-form-item label="个人简介" prop="profile">
-          <el-input v-model="matchMaker.profile" type="textarea" :rows="4" placeholder="请输入个人简介" />
+          <el-input v-model="form.profile" type="textarea" :rows="4" placeholder="请输入个人简介" />
         </el-form-item>
         
         <el-form-item label="状态" prop="status">
-          <el-radio-group v-model="matchMaker.status">
+          <el-radio-group v-model="form.status">
             <el-radio :label="1">正常</el-radio>
             <el-radio :label="0">禁用</el-radio>
             <el-radio :label="2">离职</el-radio>
@@ -105,18 +120,20 @@ const router = useRouter()
 const isEdit = ref(false)
 const submitLoading = ref(false)
 const formRef = ref(null)
+const cityList = ref([])
 
-const matchMaker = reactive({
+const form = reactive({
   matchmakerId: null,
-  avatar_url: '',
-  real_name: '',
+  avatarUrl: '',
+  realName: '',
   phone: '',
   gender: 1,
-  birth_date: null,
+  birthDate: null,
   matchmakerType: 2,
   level: 1,
   profile: '',
-  status: 1
+  status: 1,
+  cityId: null
 })
 
 const rules = {
@@ -127,11 +144,36 @@ const rules = {
   level: [{ required: true, message: '请选择等级', trigger: 'change' }]
 }
 
+// 加载城市列表
+const loadCityList = async () => {
+  try {
+    const response = await request.get(API_ENDPOINTS.CITY_LIST)
+    if (response.code === 200) {
+      cityList.value = response.data || []
+    }
+  } catch (error) {
+    console.error('加载城市列表失败:', error)
+    ElMessage.error('加载城市列表失败')
+  }
+}
+
 const loadDetail = async (id) => {
   try {
     const response = await request.get(`${API_ENDPOINTS.MATCHMAKER_DETAIL}/${id}`)
     if (response.code === 200) {
-      Object.assign(matchMaker, response.data)
+      const data = response.data
+      // 将后端返回的 snake_case 字段映射到前端的驼峰字段
+      form.matchmakerId = data.matchmaker_id || data.matchmakerId || null
+      form.avatarUrl = data.avatar_url || data.avatarUrl || ''
+      form.realName = data.real_name || data.realName || ''
+      form.phone = data.phone || ''
+      form.gender = data.gender !== undefined ? data.gender : 1
+      form.birthDate = data.birth_date || data.birthDate || null
+      form.matchmakerType = data.matchmaker_type !== undefined ? data.matchmaker_type : 2
+      form.level = data.level !== undefined ? data.level : 1
+      form.profile = data.profile || ''
+      form.status = data.status !== undefined ? data.status : 1
+      form.cityId = data.city_id || data.cityId || null
     }
   } catch (error) {
     console.error('加载详情失败:', error)
@@ -193,11 +235,11 @@ const handleUpload = async (options) => {
       }
       
       // 使用nextTick确保响应式更新
-      matchMaker.avatarUrl = imageUrl
+      form.avatarUrl = imageUrl
       
       // 强制触发视图更新
       setTimeout(() => {
-        console.log('当前form.avatarUrl值:', matchMaker.avatarUrl)
+        console.log('当前form.avatarUrl值:', form.avatarUrl)
       }, 100)
       
       ElMessage.success('头像上传成功')
@@ -217,11 +259,25 @@ const handleSubmit = async () => {
     await formRef.value.validate()
     submitLoading.value = true
     
-    const url = isEdit.value ? `${API_ENDPOINTS.MATCHMAKER_UPDATE}/${matchMaker.matchmakerId}` : API_ENDPOINTS.MATCHMAKER_CREATE
+    // 将前端的驼峰字段转换为后端期望的 snake_case 格式
+    const submitData = {
+      matchmaker_id: form.matchmakerId,
+      avatar_url: form.avatarUrl,
+      real_name: form.realName,
+      phone: form.phone,
+      gender: form.gender,
+      birth_date: form.birthDate,
+      matchmaker_type: form.matchmakerType,
+      level: form.level,
+      profile: form.profile,
+      status: form.status,
+      city_id: form.cityId
+    }
+    
+    const url = isEdit.value ? `${API_ENDPOINTS.MATCHMAKER_UPDATE}/${form.matchmakerId}` : API_ENDPOINTS.MATCHMAKER_CREATE
     const method = isEdit.value ? 'put' : 'post'
-
-    console.log(matchMaker)
-    const response = await request[method](url, matchMaker)
+    
+    const response = await request[method](url, submitData)
     if (response.code === 200) {
       ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
       router.push('/matchmaker/list')
@@ -234,6 +290,9 @@ const handleSubmit = async () => {
 }
 
 onMounted(() => {
+  // 加载城市列表
+  loadCityList()
+  
   if (route.params.id) {
     isEdit.value = true
     loadDetail(route.params.id)

+ 30 - 17
marriageAdmin-vue/src/views/matchmaker/MatchmakerList.vue

@@ -14,13 +14,11 @@
         <el-col :span="18">
           <el-space wrap>
             <el-select v-model="filters.matchmakerType" placeholder="红娘类型" clearable style="width: 120px" @change="loadList">
-              <el-option label="全部" :value="undefined" />
               <el-option label="兼职" :value="1" />
               <el-option label="全职" :value="2" />
             </el-select>
             
             <el-select v-model="filters.level" placeholder="等级" clearable style="width: 120px" @change="loadList">
-              <el-option label="全部" :value="undefined" />
               <el-option label="青铜" :value="1" />
               <el-option label="白银" :value="2" />
               <el-option label="黄金" :value="3" />
@@ -49,21 +47,21 @@
           </div>
         </template>
         <el-table-column type="index" label="序号" width="60" />
-        <el-table-column prop="avatarUrl" label="头像" width="80">
+        <el-table-column prop="avatar_url" label="头像" width="80">
           <template #default="{ row }">
             <el-avatar :src="row.avatar_url" />
           </template>
         </el-table-column>
-        <el-table-column prop="realName" label="姓名" width="100">
+        <el-table-column prop="real_name" label="姓名" width="100">
           <template #default="{ row }">
             <span class="data-highlight">{{ row.real_name }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="phone" label="手机号" width="130" />
-        <el-table-column prop="matchmakerType" label="类型" width="90" align="center">
+        <el-table-column prop="matchmaker_type" label="类型" width="90" align="center">
           <template #default="{ row }">
-            <el-tag :type="row.matchmakerType === 2 ? 'success' : 'info'" size="small" effect="light">
-              {{ row.matchmakerType === 2 ? '全职' : '兼职' }}
+            <el-tag :type="row.matchmaker_type === 2 ? 'success' : 'info'" size="small" effect="light">
+              {{ row.matchmaker_type === 2 ? '全职' : '兼职' }}
             </el-tag>
           </template>
         </el-table-column>
@@ -74,12 +72,12 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="successCouples" label="成功对数" width="100" align="center">
+        <el-table-column prop="success_couples" label="成功对数" width="100" align="center">
           <template #default="{ row }">
-            <span class="number-emphasis">{{ row.successCouples || 0 }}</span>
+            <span class="number-emphasis">{{ row.success_couples || 0 }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="cityName" label="城市" width="120" />
+        <el-table-column prop="city_name" label="城市" width="120" />
         <el-table-column prop="status" label="状态" width="90" align="center">
           <template #default="{ row }">
             <el-tag 
@@ -144,13 +142,28 @@ const filters = reactive({
 const loadList = async () => {
   loading.value = true
   try {
-    const response = await request.post(API_ENDPOINTS.MATCHMAKER_LIST, {
+    // 构建查询参数,只包含有值的筛选条件
+    const params = {
       pageNum: currentPage.value,
-      pageSize: pageSize.value,
-      matchmakerType: filters.matchmakerType,
-      level: filters.level,
-      keyword: filters.keyword
-    })
+      pageSize: pageSize.value
+    }
+    
+    // 只有当 matchmakerType 有值时才添加到参数中
+    if (filters.matchmakerType !== null && filters.matchmakerType !== undefined) {
+      params.matchmakerType = filters.matchmakerType
+    }
+    
+    // 只有当 level 有值时才添加到参数中
+    if (filters.level !== null && filters.level !== undefined) {
+      params.level = filters.level
+    }
+    
+    // 只有当 keyword 有值时才添加到参数中
+    if (filters.keyword && filters.keyword.trim()) {
+      params.keyword = filters.keyword.trim()
+    }
+    
+    const response = await request.post(API_ENDPOINTS.MATCHMAKER_LIST, params)
     if (response.code === 200) {
       list.value = response.data.records || response.data.list || response.data || []
       total.value = response.data.total || list.value.length
@@ -180,7 +193,7 @@ const handleDelete = async (row) => {
       cancelButtonText: '取消',
       type: 'warning'
     })
-    const response = await request.delete(`${API_ENDPOINTS.MATCHMAKER_DELETE}/${row.matchmakerId}`)
+    const response = await request.delete(`${API_ENDPOINTS.MATCHMAKER_DELETE}/${row.matchmaker_id}`)
     if (response.code === 200) {
       ElMessage.success('删除成功')
       loadList()

+ 69 - 12
marriageAdmin-vue/src/views/success-case/SuccessCaseList.vue

@@ -10,7 +10,11 @@
     <el-card shadow="never" class="table-card">
       <el-table v-loading="loading" :data="list" stripe>
         <el-table-column type="index" label="序号" width="60" :index="indexMethod" />
-        <el-table-column prop="caseNo" label="案例编号" width="140" />
+        <el-table-column label="案例编号" width="140">
+          <template #default="{ row }">
+            {{ row.caseNo || row.case_no || '-' }}
+          </template>
+        </el-table-column>
         <el-table-column prop="maleUserNickname" label="男方昵称" width="120" />
         <el-table-column prop="femaleUserNickname" label="女方昵称" width="120" />
         <el-table-column prop="quote" label="案例引言" min-width="200" show-overflow-tooltip />
@@ -25,7 +29,7 @@
         <el-table-column label="操作" width="200" fixed="right">
           <template #default="{ row }">
             <el-button type="info" size="small" link @click="handleViewDetail(row)">查看</el-button>
-            <el-button type="primary" size="small" link @click="$router.push(`/success-case/edit/${row.caseId}`)">编辑</el-button>
+            <el-button type="primary" size="small" link @click="$router.push(`/success-case/edit/${row.caseId || row.case_id}`)">编辑</el-button>
             <el-button type="danger" size="small" link @click="handleDelete(row)">删除</el-button>
           </template>
         </el-table-column>
@@ -164,7 +168,22 @@ const loadList = async () => {
     console.log('案例列表响应:', response)
     
     if (response.code === 200) {
-      list.value = response.data.list || response.data || []
+      let dataList = response.data.list || response.data || []
+      
+      // 兼容处理:如果后端返回的是下划线格式,转换为驼峰格式
+      list.value = dataList.map(item => ({
+        ...item,
+        caseId: item.caseId || item.case_id,
+        caseNo: item.caseNo || item.case_no,
+        caseStatus: item.caseStatus !== undefined ? item.caseStatus : item.case_status,
+        maleUserNickname: item.maleUserNickname || item.male_user_nickname,
+        femaleUserNickname: item.femaleUserNickname || item.female_user_nickname,
+        marriageDate: item.marriageDate || item.marriage_date,
+        matchmakerName: item.matchmakerName || item.matchmaker_name,
+        quote: item.quote,
+        createdAt: item.createdAt || item.created_at
+      }))
+      
       total.value = response.data.total || list.value.length
       
       console.log('案例列表数据:', list.value)
@@ -181,27 +200,34 @@ const loadList = async () => {
 }
 
 const handleStatusChange = async (row) => {
-  if (!row || !row.caseId) {
+  // 兼容处理:支持 caseId 和 case_id 两种格式
+  const caseId = row.caseId || row.case_id
+  if (!row || !caseId) {
     ElMessage.error('案例ID不存在,无法更新状态')
     return
   }
   
   try {
-    const response = await request.put(`${API_ENDPOINTS.CASE_UPDATE}/${row.caseId}`, { 
-      caseStatus: row.caseStatus 
+    // 获取当前状态值(兼容驼峰和下划线格式)
+    const currentStatus = row.caseStatus !== undefined ? row.caseStatus : row.case_status
+    
+    // 由于实体类添加了 @JsonProperty 注解,发送驼峰格式
+    const response = await request.put(`${API_ENDPOINTS.CASE_UPDATE}/${caseId}`, { 
+      caseStatus: currentStatus
     })
     
     if (response.code === 200) {
       ElMessage.success('状态更新成功')
     } else {
       // 回滚状态
-      row.caseStatus = row.caseStatus === 1 ? 0 : 1
+      row.caseStatus = currentStatus === 1 ? 0 : 1
       ElMessage.error(response.message || '状态更新失败')
     }
   } catch (error) {
     console.error('状态更新失败:', error)
     // 回滚状态
-    row.caseStatus = row.caseStatus === 1 ? 0 : 1
+    const currentStatus = row.caseStatus !== undefined ? row.caseStatus : row.case_status
+    row.caseStatus = currentStatus === 1 ? 0 : 1
     ElMessage.error('状态更新失败')
   }
 }
@@ -212,12 +238,34 @@ const handleViewDetail = async (row) => {
   detailData.value = {}
   
   try {
-    const response = await request.get(`${API_ENDPOINTS.CASE_DETAIL}/${row.caseId}`)
+    // 兼容处理:支持 caseId 和 case_id 两种格式
+    const caseId = row.caseId || row.case_id
+    if (!caseId) {
+      ElMessage.error('案例ID不存在')
+      detailVisible.value = false
+      return
+    }
+    
+    const response = await request.get(`${API_ENDPOINTS.CASE_DETAIL}/${caseId}`)
     
     console.log('案例详情响应:', response)
     
     if (response.code === 200) {
-      detailData.value = response.data || {}
+      const data = response.data || {}
+      // 兼容处理:如果后端返回的是下划线格式,转换为驼峰格式
+      detailData.value = {
+        ...data,
+        caseId: data.caseId || data.case_id,
+        caseNo: data.caseNo || data.case_no,
+        caseStatus: data.caseStatus !== undefined ? data.caseStatus : data.case_status,
+        maleUserNickname: data.maleUserNickname || data.male_user_nickname,
+        femaleUserNickname: data.femaleUserNickname || data.female_user_nickname,
+        marriageDate: data.marriageDate || data.marriage_date,
+        matchmakerName: data.matchmakerName || data.matchmaker_name,
+        quote: data.quote,
+        story: data.story,
+        createdAt: data.createdAt || data.created_at
+      }
       console.log('案例详情数据:', detailData.value)
     } else {
       ElMessage.error(response.message || '加载详情失败')
@@ -241,8 +289,10 @@ const handleEditFromDetail = () => {
 
 const handleDelete = async (row) => {
   try {
+    // 兼容处理:支持 caseNo 和 case_no 两种格式
+    const caseNo = row.caseNo || row.case_no || '该案例'
     await ElMessageBox.confirm(
-      `确定要删除案例"${row.caseNo}"吗?删除后将无法恢复!`, 
+      `确定要删除案例"${caseNo}"吗?删除后将无法恢复!`, 
       '删除确认', 
       { 
         type: 'warning',
@@ -251,7 +301,14 @@ const handleDelete = async (row) => {
       }
     )
     
-    const response = await request.delete(`${API_ENDPOINTS.CASE_DELETE}/${row.caseId}`)
+    // 兼容处理:支持 caseId 和 case_id 两种格式
+    const caseId = row.caseId || row.case_id
+    if (!caseId) {
+      ElMessage.error('案例ID不存在')
+      return
+    }
+    
+    const response = await request.delete(`${API_ENDPOINTS.CASE_DELETE}/${caseId}`)
     
     if (response.code === 200) {
       ElMessage.success('删除成功')

+ 27 - 6
marriageAdmin-vue/src/views/user/UserVipList.vue

@@ -8,15 +8,25 @@
         <el-table-column prop="nickname" label="昵称" width="120" />
         <el-table-column prop="vipLevel" label="VIP等级" width="100">
           <template #default="{ row }">
-            <el-tag type="warning" size="small">VIP {{ row.vipLevel }}</el-tag>
+            <el-tag type="warning" size="small">
+              {{ row.vipLevel || row.vip_level || 'VIP' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="vipStartTime" label="开通时间" width="180">
+          <template #default="{ row }">
+            {{ row.vipStartTime || row.vip_start_time || '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="vipEndTime" label="到期时间" width="180">
+          <template #default="{ row }">
+            {{ row.vipEndTime || row.vip_end_time || '-' }}
           </template>
         </el-table-column>
-        <el-table-column prop="vipStartTime" label="开通时间" width="180" />
-        <el-table-column prop="vipEndTime" label="到期时间" width="180" />
         <el-table-column prop="remainingDays" label="剩余天数" width="100">
           <template #default="{ row }">
-            <span :class="{ 'text-danger': row.remainingDays < 7 }">
-              {{ row.remainingDays }} 天
+            <span :class="{ 'text-danger': (row.remainingDays || row.remaining_days || 0) < 7 }">
+              {{ row.remainingDays !== undefined ? row.remainingDays : (row.remaining_days !== undefined ? row.remaining_days : 0) }} 天
             </span>
           </template>
         </el-table-column>
@@ -60,8 +70,19 @@ const loadList = async () => {
       params: { page: currentPage.value, pageSize: pageSize.value }
     })
     if (response.code === 200) {
-      list.value = response.data.list || response.data || []
+      const dataList = response.data.list || response.data || []
+      // 兼容处理:统一字段格式为驼峰
+      list.value = dataList.map(item => ({
+        ...item,
+        userId: item.userId || item.user_id,
+        nickname: item.nickname,
+        vipLevel: item.vipLevel || item.vip_level,
+        vipStartTime: item.vipStartTime || item.vip_start_time,
+        vipEndTime: item.vipEndTime || item.vip_end_time,
+        remainingDays: item.remainingDays !== undefined ? item.remainingDays : (item.remaining_days !== undefined ? item.remaining_days : 0)
+      }))
       total.value = response.data.total || list.value.length
+      console.log('VIP用户列表数据:', list.value)
     }
   } catch (error) {
     console.error('加载失败:', error)

+ 81 - 9
service/admin/src/main/java/com/zhentao/controller/UserController.java

@@ -6,6 +6,7 @@ import com.zhentao.common.Result;
 import com.zhentao.entity.*;
 import com.zhentao.mapper.*;
 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.*;
@@ -39,6 +40,9 @@ public class UserController {
     @Autowired
     private MatchmakerMapper matchmakerMapper;
     
+    @Autowired
+    private VipPackageMapper vipPackageMapper;
+    
     /**
      * 用户列表(分页)
      */
@@ -180,7 +184,7 @@ public class UserController {
     }
     
     /**
-     * 认证用户列表(资料完整的用户)
+     * VIP用户列表(查询有生效中VIP记录的用户)
      */
     @GetMapping("/vip/list")
     public Result<Map<String, Object>> vipList(
@@ -188,18 +192,86 @@ public class UserController {
             @RequestParam(defaultValue = "10") Integer pageSize) {
         
         try {
-            Page<Users> pageInfo = new Page<>(page, pageSize);
-            QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
+            // 先查询所有生效中的VIP记录
+            QueryWrapper<UserVip> vipWrapper = new QueryWrapper<>();
+            vipWrapper.eq("status", 1)  // 状态:生效中
+                    .gt("end_time", LocalDateTime.now())  // 未过期
+                    .orderByDesc("end_time");
             
-            // 只查询资料完整的用户
-            queryWrapper.eq("is_profile_complete", 1);
-            queryWrapper.orderByDesc("created_at");
+            List<UserVip> vipList = userVipMapper.selectList(vipWrapper);
             
-            Page<Users> result = usersMapper.selectPage(pageInfo, queryWrapper);
+            if (vipList == null || vipList.isEmpty()) {
+                Map<String, Object> data = new HashMap<>();
+                data.put("list", new ArrayList<>());
+                data.put("total", 0);
+                data.put("page", page);
+                data.put("pageSize", pageSize);
+                return Result.success(data);
+            }
+            
+            // 获取所有用户ID(去重)
+            List<Long> userIds = vipList.stream()
+                    .map(UserVip::getUserId)
+                    .distinct()
+                    .collect(Collectors.toList());
+            
+            // 计算总数
+            int total = userIds.size();
+            
+            // 分页处理
+            int start = (page - 1) * pageSize;
+            int end = Math.min(start + pageSize, userIds.size());
+            List<Long> pageUserIds = userIds.subList(start, end);
+            
+            // 查询用户信息
+            List<Users> users = new ArrayList<>();
+            if (!pageUserIds.isEmpty()) {
+                QueryWrapper<Users> userWrapper = new QueryWrapper<>();
+                userWrapper.in("user_id", pageUserIds);
+                users = usersMapper.selectList(userWrapper);
+            }
+            
+            // 转换为UserVipListVO
+            List<UserVipListVO> voList = new ArrayList<>();
+            for (Users user : users) {
+                // 查找该用户最新的VIP记录(按结束时间排序,取最新的)
+                UserVip userVip = vipList.stream()
+                        .filter(vip -> vip.getUserId().equals(Long.valueOf(user.getUserId())))
+                        .max((v1, v2) -> v1.getEndTime().compareTo(v2.getEndTime()))
+                        .orElse(null);
+                
+                if (userVip != null) {
+                    UserVipListVO vo = new UserVipListVO();
+                    vo.setUserId(user.getUserId());
+                    vo.setNickname(user.getNickname());
+                    
+                    // 查询VIP套餐信息
+                    VipPackage vipPackage = vipPackageMapper.selectById(userVip.getPackageId());
+                    if (vipPackage != null) {
+                        vo.setVipLevel(vipPackage.getPackageName());
+                    } else {
+                        vo.setVipLevel("VIP会员");
+                    }
+                    
+                    vo.setVipStartTime(userVip.getStartTime());
+                    vo.setVipEndTime(userVip.getEndTime());
+                    
+                    // 计算剩余天数
+                    LocalDateTime now = LocalDateTime.now();
+                    if (userVip.getEndTime().isAfter(now)) {
+                        long days = Duration.between(now, userVip.getEndTime()).toDays();
+                        vo.setRemainingDays((int) Math.max(0, days));
+                    } else {
+                        vo.setRemainingDays(0);
+                    }
+                    
+                    voList.add(vo);
+                }
+            }
             
             Map<String, Object> data = new HashMap<>();
-            data.put("list", result.getRecords());
-            data.put("total", result.getTotal());
+            data.put("list", voList);
+            data.put("total", total);
             data.put("page", page);
             data.put("pageSize", pageSize);
             

+ 18 - 1
service/homePage/src/main/java/com/zhentao/controller/CourseController.java

@@ -175,15 +175,32 @@ public class CourseController {
     @PutMapping("/admin/update/{id}")
     public Result<Course> updateCourse(@PathVariable Integer id, @RequestBody Course course) {
         try {
+            System.out.println("===== 更新课程 =====");
+            System.out.println("课程ID: " + id);
+            System.out.println("接收到的 categoryName: " + course.getCategoryName());
+            
             Course existCourse = courseService.getById(id);
             if (existCourse == null) {
                 return Result.error("课程不存在");
             }
             
+            // 确保 ID 正确设置
             course.setId(id);
+            
+            // 如果 categoryName 为 null 或空,保留原值
+            if (course.getCategoryName() == null || course.getCategoryName().trim().isEmpty()) {
+                course.setCategoryName(existCourse.getCategoryName());
+                System.out.println("categoryName 为空,保留原值: " + existCourse.getCategoryName());
+            } else {
+                System.out.println("准备更新 categoryName: " + course.getCategoryName());
+            }
+            
             boolean success = courseService.updateById(course);
             if (success) {
-                return Result.success(courseService.getById(id));
+                Course updatedCourse = courseService.getById(id);
+                System.out.println("更新成功,categoryName: " + updatedCourse.getCategoryName());
+                System.out.println("==================");
+                return Result.success(updatedCourse);
             }
             return Result.error("更新课程失败");
         } catch (Exception e) {

+ 7 - 0
service/homePage/src/main/java/com/zhentao/controller/MatchmakerController.java

@@ -28,6 +28,13 @@ public class MatchmakerController {
     @PostMapping("/list")
     public Result<Page<MatchmakerVO>> getMatchmakerList(@RequestBody MatchmakerQueryDTO queryDTO) {
         try {
+            // 调试日志:打印接收到的参数
+            System.out.println("📥 接收到的查询参数: matchmakerType=" + queryDTO.getMatchmakerType() 
+                    + ", level=" + queryDTO.getLevel() 
+                    + ", keyword=" + queryDTO.getKeyword()
+                    + ", pageNum=" + queryDTO.getPageNum()
+                    + ", pageSize=" + queryDTO.getPageSize());
+            
             Page<MatchmakerVO> page = matchmakerService.getMatchmakerPage(queryDTO);
             return Result.success(page);
         } catch (Exception e) {

+ 24 - 0
service/homePage/src/main/java/com/zhentao/controller/SuccessCaseController.java

@@ -142,8 +142,32 @@ public class SuccessCaseController {
     @PutMapping("/api/case/update/{caseId}")
     public Result<String> updateCase(@PathVariable Integer caseId, @RequestBody SuccessCase successCase) {
         try {
+            System.out.println("===== 更新案例 =====");
+            System.out.println("案例ID: " + caseId);
+            System.out.println("接收到的案例数据: " + successCase);
+            System.out.println("caseStatus: " + successCase.getCaseStatus());
+            
+            // 检查案例是否存在
+            SuccessCase existCase = successCaseService.getAdminCaseById(caseId);
+            if (existCase == null) {
+                return Result.error("案例不存在");
+            }
+            
+            // 确保 ID 正确设置
+            successCase.setCaseId(caseId);
+            
+            // 如果 caseStatus 为 null,保留原值
+            if (successCase.getCaseStatus() == null) {
+                successCase.setCaseStatus(existCase.getCaseStatus());
+                System.out.println("caseStatus 为空,保留原值: " + existCase.getCaseStatus());
+            } else {
+                System.out.println("准备更新 caseStatus: " + successCase.getCaseStatus());
+            }
+            
             boolean success = successCaseService.updateCase(caseId, successCase);
             if (success) {
+                System.out.println("更新成功");
+                System.out.println("==================");
                 return Result.success("更新成功");
             } else {
                 return Result.error("更新失败");

+ 15 - 0
service/homePage/src/main/java/com/zhentao/dto/MatchmakerQueryDTO.java

@@ -1,56 +1,71 @@
 package com.zhentao.dto;
 
+import com.fasterxml.jackson.annotation.JsonAlias;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
 import lombok.Data;
 
 /**
  * 红娘查询DTO
  */
 @Data
+@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class)
 public class MatchmakerQueryDTO {
     
     /**
      * 红娘类型:1-兼职 2-全职
      */
+    @JsonProperty("matchmakerType")
+    @JsonAlias({"matchmaker_type", "matchmakerType"})
     private Integer matchmakerType;
     
     /**
      * 红娘等级
      */
+    @JsonProperty("level")
     private Integer level;
     
     /**
      * 省份ID
      */
+    @JsonProperty("provinceId")
     private Integer provinceId;
     
     /**
      * 城市ID
      */
+    @JsonProperty("cityId")
     private Integer cityId;
     
     /**
      * 搜索关键词(姓名、电话)
      */
+    @JsonProperty("keyword")
     private String keyword;
     
     /**
      * 页码
      */
+    @JsonProperty("pageNum")
     private Integer pageNum = 1;
     
     /**
      * 每页大小
      */
+    @JsonProperty("pageSize")
     private Integer pageSize = 10;
     
     /**
      * 排序字段:success_couples(成功案例数)、level(等级)
      */
+    @JsonProperty("orderBy")
     private String orderBy = "success_couples";
     
     /**
      * 排序方式:asc、desc
      */
+    @JsonProperty("orderType")
     private String orderType = "desc";
 }
 

+ 3 - 0
service/homePage/src/main/java/com/zhentao/entity/Course.java

@@ -1,6 +1,7 @@
 package com.zhentao.entity;
 
 import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
@@ -29,6 +30,8 @@ public class Course implements Serializable {
     /**
      * 课程分类名称(如:婚姻沟通技巧、情感修复等)
      */
+    @TableField("category_name")
+    @JsonProperty("category_name")
     private String categoryName;
     
     /**

+ 11 - 0
service/homePage/src/main/java/com/zhentao/entity/SuccessCase.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -22,52 +23,62 @@ public class SuccessCase implements Serializable {
      * 案例ID
      */
     @TableId(value = "case_id", type = IdType.AUTO)
+    @JsonProperty("caseId")  // 确保JSON序列化为驼峰格式
     private Integer caseId;
     
     /**
      * 案例编号
      */
+    @JsonProperty("caseNo")  // 确保JSON序列化为驼峰格式
     private String caseNo;
     
     /**
      * 男方昵称(脱敏)
      */
+    @JsonProperty("maleUserNickname")  // 确保JSON序列化为驼峰格式
     private String maleUserNickname;
     
     /**
      * 女方昵称(脱敏)
      */
+    @JsonProperty("femaleUserNickname")  // 确保JSON序列化为驼峰格式
     private String femaleUserNickname;
     
     /**
      * 案例状态:1-展示中,0-隐藏
      */
+    @JsonProperty("caseStatus")  // 确保JSON序列化为驼峰格式
     private Integer caseStatus;
     
     /**
      * 案例引言/金句
      */
+    @JsonProperty("quote")  // 确保JSON序列化为驼峰格式
     private String quote;
     
     /**
      * 案例故事
      */
+    @JsonProperty("story")  // 确保JSON序列化为驼峰格式
     private String story;
     
     /**
      * 结婚日期
      */
+    @JsonProperty("marriageDate")  // 确保JSON序列化为驼峰格式
     private String marriageDate;
     
     /**
      * 红娘姓名
      */
+    @JsonProperty("matchmakerName")  // 确保JSON序列化为驼峰格式
     private String matchmakerName;
     
     /**
      * 创建时间
      */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonProperty("createdAt")  // 确保JSON序列化为驼峰格式
     private LocalDateTime createdAt;
 }
 

+ 15 - 0
service/homePage/src/main/java/com/zhentao/vo/MatchmakerVO.java

@@ -83,16 +83,31 @@ public class MatchmakerVO {
      */
     private Integer successCouples;
     
+    /**
+     * 省份ID
+     */
+    private Integer provinceId;
+    
     /**
      * 省份名称
      */
     private String provinceName;
     
+    /**
+     * 城市ID
+     */
+    private Integer cityId;
+    
     /**
      * 城市名称
      */
     private String cityName;
     
+    /**
+     * 区域ID
+     */
+    private Integer areaId;
+    
     /**
      * 区域名称
      */

+ 9 - 0
service/homePage/src/main/resources/mapper/MatchmakerMapper.xml

@@ -18,8 +18,11 @@
         <result column="profile" property="profile"/>
         <result column="status" property="status"/>
         <result column="created_at" property="createdAt"/>
+        <result column="province_id" property="provinceId"/>
         <result column="province_name" property="provinceName"/>
+        <result column="city_id" property="cityId"/>
         <result column="city_name" property="cityName"/>
+        <result column="area_id" property="areaId"/>
         <result column="area_name" property="areaName"/>
     </resultMap>
     
@@ -40,8 +43,11 @@
             m.profile,
             m.status,
             m.created_at,
+            m.province_id,
             p.name AS province_name,
+            m.city_id,
             c.name AS city_name,
+            m.area_id,
             a.name AS area_name
         FROM matchmakers m
         LEFT JOIN province p ON m.province_id = p.id
@@ -91,8 +97,11 @@
             m.profile,
             m.status,
             m.created_at,
+            m.province_id,
             p.name AS province_name,
+            m.city_id,
             c.name AS city_name,
+            m.area_id,
             a.name AS area_name
         FROM matchmakers m
         LEFT JOIN province p ON m.province_id = p.id

+ 0 - 38
service/homePage/src/test/java/com/zhentao/AppTest.java

@@ -1,38 +0,0 @@
-package com.zhentao;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-/**
- * Unit test for simple App.
- */
-public class AppTest 
-    extends TestCase
-{
-    /**
-     * Create the test case
-     *
-     * @param testName name of the test case
-     */
-    public AppTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * @return the suite of tests being tested
-     */
-    public static Test suite()
-    {
-        return new TestSuite( AppTest.class );
-    }
-
-    /**
-     * Rigourous Test :-)
-     */
-    public void testApp()
-    {
-        assertTrue( true );
-    }
-}

+ 0 - 38
service/websocket/src/test/java/com/zhentao/AppTest.java

@@ -1,38 +0,0 @@
-package com.zhentao;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-/**
- * Unit test for simple App.
- */
-public class AppTest 
-    extends TestCase
-{
-    /**
-     * Create the test case
-     *
-     * @param testName name of the test case
-     */
-    public AppTest( String testName )
-    {
-        super( testName );
-    }
-
-    /**
-     * @return the suite of tests being tested
-     */
-    public static Test suite()
-    {
-        return new TestSuite( AppTest.class );
-    }
-
-    /**
-     * Rigourous Test :-)
-     */
-    public void testApp()
-    {
-        assertTrue( true );
-    }
-}