2 Commits 9cf83857ee ... dfac66b82b

Author SHA1 Message Date
  caojp dfac66b82b Merge remote-tracking branch 'origin/test_dev' into test_dev 21 hours ago
  caojp aa7fc88d8a 编辑图片 21 hours ago

+ 0 - 1
marriageAdmin-vue/src/views/dynamic/DynamicList.vue

@@ -39,7 +39,6 @@
             <div v-else class="cover-placeholder">无图</div>
           </template>
         </el-table-column>
-        <el-table-column prop="userId" label="用户ID" width="80" />
         <el-table-column prop="userNickname" label="用户昵称" width="120" />
         <el-table-column prop="content" label="动态内容" min-width="300" show-overflow-tooltip />
         <el-table-column prop="likeCount" label="点赞数" width="80" />

+ 2 - 10
marriageAdmin-vue/src/views/report/ReportList.vue

@@ -19,11 +19,6 @@
               <el-option label="已处理" :value="2" />
               <el-option label="已驳回" :value="3" />
             </el-select>
-            <el-input v-model="filters.dynamicId" placeholder="动态ID" clearable style="width: 160px" @keyup.enter.native="loadList">
-              <template #append>
-                <el-button icon="Search" @click="loadList" />
-              </template>
-            </el-input>
           </el-space>
         </el-col>
       </el-row>
@@ -32,8 +27,6 @@
     <el-card shadow="never" class="table-card">
       <el-table v-loading="loading" :data="list" stripe>
         <el-table-column type="index" label="序号" width="60" />
-        <el-table-column prop="reportId" label="举报ID" width="90" />
-        <el-table-column prop="dynamicId" label="动态ID" width="90" />
         <el-table-column label="举报人" width="120">
           <template #default="{ row }">
             {{ row.reporterName || (`用户${row.reporterId ?? ''}`) }}
@@ -243,11 +236,10 @@ const pageSize = ref(10)
 const total = ref(0)
 const list = ref([])
 
-const filters = reactive({ status: undefined, dynamicId: '' })
+const filters = reactive({ status: undefined })
 
 const resetAndReload = () => {
   filters.status = undefined
-  filters.dynamicId = ''
   loadList()
 }
 
@@ -396,7 +388,7 @@ const loadList = async () => {
   loading.value = true
   try {
     const res = await request.get(API_ENDPOINTS.REPORT_LIST, {
-      params: { page: currentPage.value, pageSize: pageSize.value, status: filters.status, dynamicId: filters.dynamicId || undefined }
+      params: { page: currentPage.value, pageSize: pageSize.value, status: filters.status }
     })
     if (res.code === 200) {
       list.value = res.data.list || []

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

@@ -34,6 +34,20 @@
         <el-form-item label="案例故事" prop="story">
           <el-input v-model="form.story" type="textarea" :rows="8" placeholder="请输入案例故事,详细描述两人的相识、相知、相爱过程" maxlength="2000" show-word-limit />
         </el-form-item>
+
+        <el-form-item label="案例图片" prop="imageUrl">
+          <el-upload
+            class="image-uploader"
+            action="#"
+            :show-file-list="false"
+            :before-upload="handleBeforeUpload"
+            :http-request="handleUpload"
+          >
+            <el-image v-if="form.imageUrl" :src="form.imageUrl" class="uploaded-image" fit="cover" />
+            <el-icon v-else class="uploader-icon"><Plus /></el-icon>
+          </el-upload>
+          <div class="upload-tip">支持 JPG、PNG 等图片格式,大小不超过 10MB</div>
+        </el-form-item>
         
         <el-row :gutter="20">
           <el-col :span="12">
@@ -70,6 +84,7 @@
 import { ref, reactive, onMounted } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { ElMessage } from 'element-plus'
+import { Plus } from '@element-plus/icons-vue'
 import request from '@/utils/request'
 import { API_ENDPOINTS } from '@/config/api'
 
@@ -86,6 +101,7 @@ const form = reactive({
   femaleUserNickname: '',
   quote: '',
   story: '',
+  imageUrl: '',
   marriageDate: null,
   matchmakerName: '',
   caseStatus: 1
@@ -123,7 +139,10 @@ const loadDetail = async (id) => {
     console.log('案例详情响应:', response)
     
     if (response.code === 200) {
-      Object.assign(form, response.data)
+      Object.assign(form, {
+        ...response.data,
+        imageUrl: response.data?.imageUrl || response.data?.image_url || ''
+      })
       console.log('加载的表单数据:', form)
     } else {
       ElMessage.error(response.message || '加载详情失败')
@@ -166,6 +185,52 @@ const handleSubmit = async () => {
   }
 }
 
+const handleBeforeUpload = (file) => {
+  const isImage = file.type.startsWith('image/')
+  const isLt10M = file.size / 1024 / 1024 < 10
+
+  if (!isImage) {
+    ElMessage.error('只能上传图片文件!')
+    return false
+  }
+  if (!isLt10M) {
+    ElMessage.error('图片大小不能超过 10MB!')
+    return false
+  }
+  return true
+}
+
+const handleUpload = async (options) => {
+  const formData = new FormData()
+  formData.append('file', options.file)
+
+  try {
+    const response = await request.post(API_ENDPOINTS.UPLOAD_IMAGE, formData, {
+      headers: { 'Content-Type': 'multipart/form-data' }
+    })
+
+    if (response.code === 200) {
+      let imageUrl = ''
+      if (typeof response.data === 'string') {
+        imageUrl = response.data
+      } else if (response.data && response.data.url) {
+        imageUrl = response.data.url
+      } else if (response.data && response.data.path) {
+        imageUrl = response.data.path
+      } else {
+        imageUrl = String(response.data || '')
+      }
+      form.imageUrl = imageUrl
+      ElMessage.success('图片上传成功')
+    } else {
+      ElMessage.error(response.message || '图片上传失败')
+    }
+  } catch (error) {
+    console.error('上传异常:', error)
+    ElMessage.error('图片上传失败,请重试')
+  }
+}
+
 onMounted(() => {
   const caseId = route.params.id
   if (caseId) {
@@ -178,5 +243,38 @@ onMounted(() => {
 <style scoped>
 .success-case-form-container { padding: 0; }
 .page-title { font-size: 24px; font-weight: bold; color: #333; margin: 0 0 20px 0; }
+
+.image-uploader :deep(.el-upload) {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  width: 150px;
+  height: 150px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.image-uploader :deep(.el-upload:hover) {
+  border-color: #409EFF;
+}
+
+.uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+}
+
+.uploaded-image {
+  width: 150px;
+  height: 150px;
+  display: block;
+  border-radius: 6px;
+}
+
+.upload-tip {
+  font-size: 12px;
+  color: #999;
+  margin-top: 8px;
+}
 </style>
 

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

@@ -24,6 +24,19 @@
             {{ row.caseNo || row.case_no || '-' }}
           </template>
         </el-table-column>
+        <el-table-column label="封面图" width="120" align="center">
+          <template #default="{ row }">
+            <el-image
+              v-if="getCaseCover(row)"
+              :src="getCaseCover(row)"
+              style="width: 60px; height: 40px; border-radius: 4px"
+              fit="cover"
+              :preview-src-list="[getCaseCover(row)]"
+              preview-teleported
+            />
+            <span v-else>-</span>
+          </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 />
@@ -115,7 +128,7 @@ import { ref, onMounted, computed } from 'vue'
 import { useRouter } from 'vue-router'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import request from '@/utils/request'
-import { API_ENDPOINTS } from '@/config/api'
+import { API_BASE_URL, API_ENDPOINTS } from '@/config/api'
 
 const router = useRouter()
 
@@ -125,6 +138,21 @@ const pageSize = ref(10)
 const total = ref(0)
 const list = ref([])
 
+const resolveImageUrl = (url) => {
+  if (!url || typeof url !== 'string') return ''
+  const normalized = url.trim()
+  if (/^https?:\/\//i.test(normalized)) return normalized
+  if (normalized.startsWith('//')) return `${window.location.protocol}${normalized}`
+  const base = API_BASE_URL || window.location.origin
+  if (normalized.startsWith('/')) return `${base}${normalized}`
+  return `${base}/${normalized}`
+}
+
+const getCaseCover = (row) => {
+  const url = row?.imageUrl || row?.image_url || ''
+  return resolveImageUrl(url)
+}
+
 // 详情对话框
 const detailVisible = ref(false)
 const detailLoading = ref(false)
@@ -161,6 +189,7 @@ const loadList = async () => {
         marriageDate: item.marriageDate || item.marriage_date,
         matchmakerName: item.matchmakerName || item.matchmaker_name,
         quote: item.quote,
+        imageUrl: item.imageUrl || item.image_url,
         createdAt: item.createdAt || item.created_at
       }))
       

+ 5 - 0
service/homePage/src/main/java/com/zhentao/entity/SuccessCase.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;
@@ -80,6 +81,10 @@ public class SuccessCase implements Serializable {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @JsonProperty("createdAt")  // 确保JSON序列化为驼峰格式
     private LocalDateTime createdAt;
+
+    @TableField(exist = false)
+    @JsonProperty("imageUrl")
+    private String imageUrl;
 }
 
 

+ 48 - 1
service/homePage/src/main/java/com/zhentao/service/impl/SuccessCaseServiceImpl.java

@@ -231,6 +231,18 @@ public class SuccessCaseServiceImpl implements SuccessCaseService {
         
         com.baomidou.mybatisplus.extension.plugins.pagination.Page<SuccessCase> pageResult = 
             successCaseMapper.selectPage(pageParam, queryWrapper);
+
+        if (pageResult.getRecords() != null) {
+            for (SuccessCase sc : pageResult.getRecords()) {
+                if (sc == null || sc.getCaseId() == null) {
+                    continue;
+                }
+                List<CaseImage> images = caseImageMapper.selectByCaseId(sc.getCaseId());
+                if (images != null && !images.isEmpty()) {
+                    sc.setImageUrl(images.get(0).getImageUrl());
+                }
+            }
+        }
         
         result.put("list", pageResult.getRecords());
         result.put("total", pageResult.getTotal());
@@ -245,7 +257,14 @@ public class SuccessCaseServiceImpl implements SuccessCaseService {
      */
     @Override
     public SuccessCase getAdminCaseById(Integer caseId) {
-        return successCaseMapper.selectById(caseId);
+        SuccessCase successCase = successCaseMapper.selectById(caseId);
+        if (successCase != null && successCase.getCaseId() != null) {
+            List<CaseImage> images = caseImageMapper.selectByCaseId(successCase.getCaseId());
+            if (images != null && !images.isEmpty()) {
+                successCase.setImageUrl(images.get(0).getImageUrl());
+            }
+        }
+        return successCase;
     }
     
     /**
@@ -265,6 +284,15 @@ public class SuccessCaseServiceImpl implements SuccessCaseService {
         
         // 保存到数据库
         successCaseMapper.insert(successCase);
+
+        if (successCase.getCaseId() != null && successCase.getImageUrl() != null && !successCase.getImageUrl().trim().isEmpty()) {
+            CaseImage caseImage = new CaseImage();
+            caseImage.setCaseId(successCase.getCaseId());
+            caseImage.setTimelineId(null);
+            caseImage.setImageUrl(successCase.getImageUrl().trim());
+            caseImage.setSortOrder(1);
+            caseImageMapper.insert(caseImage);
+        }
         
         return successCase;
     }
@@ -279,6 +307,25 @@ public class SuccessCaseServiceImpl implements SuccessCaseService {
         
         // 更新到数据库
         int rows = successCaseMapper.updateById(successCase);
+
+        if (successCase.getImageUrl() != null) {
+            String newUrl = successCase.getImageUrl().trim();
+            if (!newUrl.isEmpty()) {
+                List<CaseImage> images = caseImageMapper.selectByCaseId(caseId);
+                if (images != null && !images.isEmpty()) {
+                    CaseImage first = images.get(0);
+                    first.setImageUrl(newUrl);
+                    caseImageMapper.updateById(first);
+                } else {
+                    CaseImage caseImage = new CaseImage();
+                    caseImage.setCaseId(caseId);
+                    caseImage.setTimelineId(null);
+                    caseImage.setImageUrl(newUrl);
+                    caseImage.setSortOrder(1);
+                    caseImageMapper.insert(caseImage);
+                }
+            }
+        }
         
         return rows > 0;
     }