Selaa lähdekoodia

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

YH_0525 1 kuukausi sitten
vanhempi
commit
218367e9f4

+ 15 - 1
marriageAdmin-vue/src/views/banner/BannerList.vue

@@ -299,7 +299,21 @@ const loadBannerList = async () => {
     if (response.code === 200) {
       // MyBatis-Plus的Page对象使用records字段
       bannerList.value = response.data.records || []
-      total.value = response.data.total || 0
+      // 确保正确读取total字段
+      // MyBatis-Plus的Page对象序列化后total字段可能是数字或字符串
+      const totalValue = response.data.total
+      if (totalValue !== undefined && totalValue !== null) {
+        total.value = Number(totalValue) || 0
+      } else {
+        // 如果total字段不存在,但有records数据,可能是后端分页插件未配置
+        // 对于分页数据,应该从后端获取正确的total,这里先使用records.length作为临时方案
+        // 注意:这只适用于当前页数据等于总数据的情况(即所有数据都在第一页)
+        if (bannerList.value.length > 0 && currentPage.value === 1 && pageSize.value >= bannerList.value.length) {
+          total.value = bannerList.value.length
+        } else {
+          total.value = 0
+        }
+      }
     }
   } catch (error) {
     console.error('加载轮播图列表失败:', error)

+ 3 - 2
marriageAdmin-vue/src/views/course/CourseList.vue

@@ -146,8 +146,9 @@ const loadList = async () => {
       params: { page: currentPage.value, pageSize: pageSize.value }
     })
     if (response.code === 200) {
-      list.value = response.data.list || response.data || []
-      total.value = response.data.total || list.value.length
+      // 后端返回格式: {list: [], total: xx}
+      list.value = response.data.list || []
+      total.value = response.data.total || 0
     }
   } catch (error) {
     console.error('加载课程列表失败:', error)

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

@@ -163,14 +163,50 @@ const loadList = async () => {
       params.keyword = filters.keyword.trim()
     }
     
+    console.log('📤 请求参数:', params)
     const response = await request.post(API_ENDPOINTS.MATCHMAKER_LIST, params)
+    console.log('📥 响应数据:', response)
+    
     if (response.code === 200) {
-      list.value = response.data.records || response.data.list || response.data || []
-      total.value = response.data.total || list.value.length
+      // MyBatis-Plus的Page对象使用records字段
+      const pageData = response.data
+      console.log('📄 分页数据:', pageData)
+      
+      // 读取列表数据
+      list.value = pageData?.records || pageData?.list || (Array.isArray(pageData) ? pageData : []) || []
+      
+      // 读取总数 - 优先从total字段读取,支持多种可能的字段名
+      let totalValue = pageData?.total
+      if (totalValue === undefined || totalValue === null) {
+        totalValue = pageData?.totalCount || pageData?.totalElements
+      }
+      
+      console.log('📊 读取到的total值:', totalValue, '类型:', typeof totalValue)
+      
+      if (totalValue !== undefined && totalValue !== null) {
+        // 转换为数字,即使是字符串"0"也要正确处理
+        const numTotal = Number(totalValue)
+        total.value = isNaN(numTotal) ? 0 : numTotal
+      } else {
+        // 如果total字段不存在,尝试从其他字段获取
+        // 如果当前是第一页且返回的数据量等于pageSize,可能是所有数据都在第一页
+        // 但这种情况无法准确判断总数,所以设置为0
+        console.warn('⚠️ 未找到total字段,使用0作为默认值')
+        total.value = 0
+      }
+      
+      console.log('✅ 最终结果 - 列表数量:', list.value.length, '总数:', total.value)
+    } else {
+      console.error('❌ 请求失败:', response.message || response.msg)
+      ElMessage.error(response.message || response.msg || '加载失败')
+      list.value = []
+      total.value = 0
     }
   } catch (error) {
-    console.error('加载失败:', error)
-    ElMessage.error('加载失败')
+    console.error('❌ 加载失败:', error)
+    ElMessage.error('加载失败: ' + (error.message || '未知错误'))
+    list.value = []
+    total.value = 0
   } finally {
     loading.value = false
   }

+ 181 - 34
marriageAdmin-vue/src/views/report/ReportList.vue

@@ -40,7 +40,25 @@
         <el-table-column prop="description" label="描述" min-width="220" show-overflow-tooltip />
         <el-table-column label="截图" width="120">
           <template #default="{ row }">
-            <el-image v-if="parseScreens(row.screenshots).length" :src="formatMedia(parseScreens(row.screenshots)[0])" :preview-src-list="parseScreens(row.screenshots).map(formatMedia)" fit="cover" style="width:64px;height:64px;border-radius:6px;" />
+            <template v-if="getScreenshotUrls(row).length">
+              <el-image 
+                :src="getScreenshotUrls(row)[0]" 
+                :preview-src-list="getScreenshotUrls(row)" 
+                fit="cover" 
+                style="width:64px;height:64px;border-radius:6px;cursor:pointer;"
+                :lazy="true"
+                :hide-on-click-modal="true"
+                :data-report-id="row.reportId"
+                @error="(e) => handleImageError(e, row)"
+              >
+                <template #error>
+                  <div class="img-error" :title="`URL: ${getScreenshotUrls(row)[0]}`">
+                    <el-icon><Picture /></el-icon>
+                    <span>加载失败</span>
+                  </div>
+                </template>
+              </el-image>
+            </template>
             <div v-else class="img-placeholder">无</div>
           </template>
         </el-table-column>
@@ -80,7 +98,23 @@
       <div v-else>
         <div class="dynamic-content">{{ dynamicDialog.data?.content || '(无内容)' }}</div>
         <div class="media-list">
-          <el-image v-for="(m, idx) in dynamicDialog.medias" :key="idx" :src="formatMedia(m)" :preview-src-list="dynamicDialog.medias.map(formatMedia)" fit="cover" style="width:96px;height:96px;border-radius:6px;margin-right:8px;margin-bottom:8px;" />
+          <el-image 
+            v-for="(m, idx) in dynamicDialog.medias" 
+            :key="idx" 
+            :src="m" 
+            :preview-src-list="dynamicDialog.medias" 
+            fit="cover" 
+            style="width:96px;height:96px;border-radius:6px;margin-right:8px;margin-bottom:8px;cursor:pointer;"
+            :lazy="true"
+            @error="handleImageError"
+          >
+            <template #error>
+              <div class="img-error" style="width:96px;height:96px;">
+                <el-icon><Picture /></el-icon>
+                <span>加载失败</span>
+              </div>
+            </template>
+          </el-image>
         </div>
       </div>
     </el-dialog>
@@ -132,6 +166,7 @@
 <script setup>
 import { ref, reactive, onMounted } from 'vue'
 import { ElMessage } from 'element-plus'
+import { Picture } from '@element-plus/icons-vue'
 import request from '@/utils/request'
 import { API_ENDPOINTS, API_BASE_URL } from '@/config/api'
 
@@ -154,11 +189,42 @@ const statusType = (s) => ({0:'danger',1:'warning',2:'success',3:'info'}[s] || '
 const typeText = (t) => ({ spam:'垃圾广告', porn:'色情低俗', violence:'暴力违法', attack:'人身攻击', fake:'虚假信息', plagiarism:'抄袭侵权', other:'其他' }[t] || t || '其他')
 const typeTagType = (t) => ({ spam:'warning', porn:'danger', violence:'danger', attack:'danger', fake:'warning', plagiarism:'warning', other:'info' }[t] || 'info')
 
-const BASE = import.meta.env.DEV ? 'http://localhost:8083' : API_BASE_URL || ''
+// 获取基础URL(用于图片资源)
+const getBaseUrl = () => {
+  // 开发环境:图片资源通过8083端口访问
+  if (import.meta.env.DEV) {
+    return 'http://localhost:8083'
+  }
+  // 生产环境:使用配置的API基础URL
+  return API_BASE_URL || ''
+}
+
+// 格式化媒体URL(与DynamicDetail.vue保持一致)
 const formatMedia = (u) => {
   if (!u) return ''
-  const url = String(u).trim()
-  return /^https?:\/\//i.test(url) ? url : `${BASE}${url}`
+  
+  let url = String(u).trim()
+  if (!url) return ''
+  
+  // 去除引号
+  if ((url.startsWith('"') && url.endsWith('"')) || (url.startsWith("'") && url.endsWith("'"))) {
+    url = url.substring(1, url.length - 1)
+  }
+  
+  // 如果已经是完整的URL,直接返回
+  if (url.startsWith('http')) {
+    return url
+  }
+  
+  // 获取BASE URL并拼接
+  const baseUrl = getBaseUrl()
+  
+  // 确保url以/开头
+  if (!url.startsWith('/')) {
+    url = '/' + url
+  }
+  
+  return `${baseUrl}${url}`
 }
 
 // 从后端临时塞入的 handleResult 前缀里解析昵称(@nick:李娜 ...)
@@ -169,18 +235,86 @@ const extractNick = (row) => {
   return `用户${row?.reporterId ?? ''}`
 }
 
+// 解析截图数据(与DynamicDetail.vue保持一致)
 const parseScreens = (raw) => {
   if (!raw) return []
+  
+  let urls = []
+  const trimmed = String(raw).trim()
+  
+  // 尝试解析JSON数组格式
   try {
-    const v = typeof raw === 'string' ? raw.trim() : raw
-    if (typeof v === 'string' && v.startsWith('[')) {
-      return JSON.parse(v)
-    }
-    if (typeof v === 'string') {
-      return v.split(',').map(s => s.trim()).filter(Boolean)
+    if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
+      const arr = JSON.parse(trimmed)
+      if (Array.isArray(arr)) {
+        urls = arr
+      }
     }
-    return Array.isArray(v) ? v : []
-  } catch { return [] }
+  } catch (e) {
+    // JSON解析失败,忽略错误,继续尝试其他格式
+  }
+  
+  // 如果不是JSON数组,尝试按逗号分隔
+  if (!urls.length) {
+    urls = trimmed.split(',').map((item) => item.trim()).filter(Boolean)
+  }
+  
+  return urls
+}
+
+// 获取格式化后的截图URL列表
+const getScreenshotUrls = (row) => {
+  if (!row?.screenshots) {
+    return []
+  }
+  
+  const screens = parseScreens(row.screenshots)
+  if (!screens || screens.length === 0) {
+    return []
+  }
+  
+  const urls = screens.map(formatMedia).filter(Boolean)
+  
+  // 详细调试日志
+  if (urls.length > 0) {
+    console.log('📸 举报ID:', row.reportId, {
+      '原始数据': row.screenshots,
+      '解析后数组': screens,
+      '格式化后URLs': urls,
+      'BASE URL': getBaseUrl()
+    })
+  } else {
+    console.warn('⚠️ 举报ID:', row.reportId, '解析后无有效URL,原始数据:', row.screenshots)
+  }
+  
+  return urls
+}
+
+// 图片加载错误处理
+const handleImageError = (e, row) => {
+  const img = e.target || e
+  const src = img?.src || img?.currentSrc || 'unknown'
+  const reportId = row?.reportId || img?.dataset?.reportId || 'unknown'
+  const urls = row ? getScreenshotUrls(row) : []
+  
+  console.error('❌ 图片加载失败:', {
+    reportId,
+    src,
+    '原始screenshots': row?.screenshots,
+    '解析后URLs': urls,
+    'BASE URL': getBaseUrl(),
+    error: e
+  })
+  
+  // 检查URL格式
+  if (src && !src.startsWith('http')) {
+    console.warn('⚠️ 图片URL格式可能不正确:', src)
+  }
+  
+  // 检查URL是否可访问
+  if (src && src.startsWith('http')) {
+    console.warn('⚠️ 图片URL格式正确但加载失败,可能是文件不存在或CORS问题:', src)
+  }
 }
 
 const loadList = async () => {
@@ -192,6 +326,17 @@ const loadList = async () => {
     if (res.code === 200) {
       list.value = res.data.list || []
       total.value = res.data.total || list.value.length
+      
+      // 调试日志:检查截图数据
+      console.log('📋 举报列表加载成功,共', list.value.length, '条')
+      list.value.forEach((row, idx) => {
+        if (row.screenshots) {
+          const urls = getScreenshotUrls(row)
+          console.log(`  [${idx + 1}] 举报ID: ${row.reportId}, 原始screenshots:`, row.screenshots, '解析后URLs:', urls)
+        }
+      })
+    } else {
+      console.error('加载举报列表失败:', res.msg || '未知错误')
     }
   } catch (e) {
     console.error('加载举报失败', e)
@@ -219,30 +364,30 @@ const openDynamic = async (row) => {
   const dynamicId = row?.dynamicId
   if (!dynamicId) {
     dynamicDialog.data = { content: '未获取到动态ID' }
-    dynamicDialog.medias = parseScreens(row?.screenshots)
+    dynamicDialog.medias = getScreenshotUrls(row)
     dynamicDialog.visible = true
     return
   }
-  dynamicDialog.visible = true
-  dynamicDialog.loading = true
-  dynamicDialog.data = null
-  dynamicDialog.medias = []
-  try {
-    const res = await request.get(`${API_ENDPOINTS.DYNAMIC_DETAIL}/${dynamicId}`)
-    if (res.code === 200 && res.data) {
-      dynamicDialog.data = res.data
-      const medias = parseMediaUrls(res.data?.mediaUrls)
-      dynamicDialog.medias = medias.length ? medias : parseScreens(row?.screenshots)
-    } else {
-      dynamicDialog.data = { content: res.msg || '动态不存在或已被删除' }
-      dynamicDialog.medias = parseScreens(row?.screenshots)
-    }
-  } catch (e) {
-    console.error('获取动态详情失败', e)
-    dynamicDialog.data = { content: e?.message ? `加载失败:${e.message}` : '加载失败' }
-    dynamicDialog.medias = parseScreens(row?.screenshots)
-  }
-  finally { dynamicDialog.loading = false }
+      dynamicDialog.visible = true
+      dynamicDialog.loading = true
+      dynamicDialog.data = null
+      dynamicDialog.medias = []
+      try {
+        const res = await request.get(`${API_ENDPOINTS.DYNAMIC_DETAIL}/${dynamicId}`)
+        if (res.code === 200 && res.data) {
+          dynamicDialog.data = res.data
+          const medias = parseMediaUrls(res.data?.mediaUrls)
+          dynamicDialog.medias = medias.length ? medias.map(formatMedia) : getScreenshotUrls(row)
+        } else {
+          dynamicDialog.data = { content: res.msg || '动态不存在或已被删除' }
+          dynamicDialog.medias = getScreenshotUrls(row)
+        }
+      } catch (e) {
+        console.error('获取动态详情失败', e)
+        dynamicDialog.data = { content: e?.message ? `加载失败:${e.message}` : '加载失败' }
+        dynamicDialog.medias = getScreenshotUrls(row)
+      }
+      finally { dynamicDialog.loading = false }
 }
 
 // 处理举报
@@ -312,6 +457,8 @@ const openUnban = (row) => {
 .table-card { margin-top: 20px; }
 .pagination-container { display: flex; justify-content: flex-end; margin-top: 20px; }
 .img-placeholder { width:64px;height:64px;border-radius:6px;background:#f5f7fa;color:#909399;display:flex;align-items:center;justify-content:center;font-size:12px; }
+.img-error { width:64px;height:64px;border-radius:6px;background:#fef0f0;color:#f56c6c;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:10px; }
+.img-error .el-icon { font-size:20px;margin-bottom:2px; }
 .dynamic-content { margin-bottom: 10px; color:#333; }
 .media-list { display:flex; flex-wrap: wrap; }
 .loading { color:#909399; }

+ 3 - 2
marriageAdmin-vue/src/views/user/UserVipList.vue

@@ -70,7 +70,8 @@ const loadList = async () => {
       params: { page: currentPage.value, pageSize: pageSize.value }
     })
     if (response.code === 200) {
-      const dataList = response.data.list || response.data || []
+      // 后端返回格式: {list: [], total: xx}
+      const dataList = response.data.list || []
       // 兼容处理:统一字段格式为驼峰
       list.value = dataList.map(item => ({
         ...item,
@@ -81,7 +82,7 @@ const loadList = async () => {
         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
+      total.value = response.data.total || 0
       console.log('VIP用户列表数据:', list.value)
     }
   } catch (error) {

+ 3 - 2
marriageAdmin-vue/src/views/vip/VipPackageList.vue

@@ -224,8 +224,9 @@ const loadList = async () => {
     const response = await request.get('/admin/vip/package/list', { params })
     
     if (response.code === 200) {
-      list.value = response.data.records || response.data.list || response.data || []
-      total.value = response.data.total || list.value.length
+      // 后端返回格式: Result<Page<VipPackage>>,使用records字段
+      list.value = response.data.records || []
+      total.value = response.data.total || 0
     }
   } catch (error) {
     console.error('加载失败:', error)

+ 14 - 4
service/homePage/src/main/java/com/zhentao/service/impl/MatchmakerServiceImpl.java

@@ -64,15 +64,22 @@ public class MatchmakerServiceImpl extends ServiceImpl<MatchmakerMapper, Matchma
         try {
             Page<MatchmakerVO> cachedPage = (Page<MatchmakerVO>) redisTemplate.opsForValue().get(cacheKey);
             if (cachedPage != null) {
-                System.out.println("✅ 从Redis缓存获取红娘列表: " + cacheKey);
-                return cachedPage;
+                // 检查缓存数据是否异常:如果total为0但records不为空,说明缓存数据有问题,需要清除缓存
+                if (cachedPage.getTotal() == 0 && cachedPage.getRecords() != null && !cachedPage.getRecords().isEmpty()) {
+                    System.out.println("⚠️ 检测到缓存数据异常(total=0但records不为空),清除缓存并重新查询: " + cacheKey);
+                    redisTemplate.delete(cacheKey);
+                    // 继续执行数据库查询
+                } else {
+                    System.out.println("✅ 从Redis缓存获取红娘列表: " + cacheKey + ", total=" + cachedPage.getTotal() + ", records=" + cachedPage.getRecords().size());
+                    return cachedPage;
+                }
             }
         } catch (Exception e) {
             System.err.println("⚠️ Redis读取失败,将直接查询数据库: " + e.getMessage());
         }
         
         // 3. 缓存未命中或Redis异常,从数据库查询
-        System.out.println("❌ 缓存未命中或Redis异常,从数据库查询: " + cacheKey);
+        System.out.println("📊 从数据库查询红娘列表: " + cacheKey);
         Page<MatchmakerVO> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
         
         Page<MatchmakerVO> result = matchmakerMapper.selectMatchmakerPage(
@@ -89,7 +96,10 @@ public class MatchmakerServiceImpl extends ServiceImpl<MatchmakerMapper, Matchma
         // 4. 处理数据
         result.getRecords().forEach(this::processMatchmakerVO);
         
-        // 5. 尝试存入Redis缓存(带异常处理)
+        // 5. 打印查询结果日志
+        System.out.println("📈 查询结果 - total: " + result.getTotal() + ", records: " + result.getRecords().size() + ", current: " + result.getCurrent() + ", size: " + result.getSize());
+        
+        // 6. 尝试存入Redis缓存(带异常处理)
         try {
             redisTemplate.opsForValue().set(
                     cacheKey,