Quellcode durchsuchen

Merge branch 'cjp' into test_dev

caojp vor 1 Monat
Ursprung
Commit
25e105dadf
26 geänderte Dateien mit 1471 neuen und 28 gelöschten Zeilen
  1. 3 0
      marriageAdmin-vue/src/config/api.js
  2. 1 0
      marriageAdmin-vue/src/layouts/MainLayout.vue
  3. 6 0
      marriageAdmin-vue/src/router/index.js
  4. 359 0
      marriageAdmin-vue/src/views/matchmaker/CaseAudit.vue
  5. 16 9
      marriageAdmin-vue/src/views/matchmaker/MatchmakerAudit.vue
  6. 10 0
      marriageAdmin-vue/src/views/user/UserList.vue
  7. 242 0
      service/admin/src/main/java/com/zhentao/controller/MatchmakerSuccessCaseUploadController.java
  8. 46 2
      service/admin/src/main/java/com/zhentao/controller/UserController.java
  9. 26 0
      service/admin/src/main/java/com/zhentao/entity/Area.java
  10. 26 0
      service/admin/src/main/java/com/zhentao/entity/City.java
  11. 4 0
      service/admin/src/main/java/com/zhentao/entity/MarrApply.java
  12. 5 0
      service/admin/src/main/java/com/zhentao/entity/Matchmaker.java
  13. 217 0
      service/admin/src/main/java/com/zhentao/entity/MatchmakerSuccessCaseUpload.java
  14. 24 0
      service/admin/src/main/java/com/zhentao/entity/Province.java
  15. 13 0
      service/admin/src/main/java/com/zhentao/mapper/AreaMapper.java
  16. 13 0
      service/admin/src/main/java/com/zhentao/mapper/CityMapper.java
  17. 18 0
      service/admin/src/main/java/com/zhentao/mapper/MatchmakerSuccessCaseUploadMapper.java
  18. 13 0
      service/admin/src/main/java/com/zhentao/mapper/ProvinceMapper.java
  19. 13 0
      service/admin/src/main/java/com/zhentao/service/MatchmakerSuccessCaseUploadService.java
  20. 186 16
      service/admin/src/main/java/com/zhentao/service/impl/MarrApplyServiceImpl.java
  21. 22 0
      service/admin/src/main/java/com/zhentao/service/impl/MatchmakerSuccessCaseUploadServiceImpl.java
  22. 132 0
      service/admin/src/main/java/com/zhentao/vo/MatchmakerSuccessCaseUploadVO.java
  23. 1 0
      service/admin/src/main/java/com/zhentao/vo/UserVO.java
  24. 3 1
      service/admin/src/main/resources/com/zhentao/mapper/MarrApplyMapper.xml
  25. 37 0
      service/admin/src/main/resources/com/zhentao/mapper/MatchmakerSuccessCaseUploadMapper.xml
  26. 35 0
      service/admin/src/main/resources/com/zhentao/mapper/UsersMapper.xml

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

@@ -50,6 +50,9 @@ export const API_ENDPOINTS = {
   MATCHMAKER_AUDIT_LIST: '/admin/marr-apply/list',
   MATCHMAKER_AUDIT_APPROVE: '/admin/marr-apply/approve',
   MATCHMAKER_AUDIT_DELETE: '/admin/marr-apply/delete',
+  SUCCESS_CASE_UPLOAD_LIST: '/admin/success-case-upload/list',
+  SUCCESS_CASE_UPLOAD_APPROVE: '/admin/success-case-upload/approve',
+  SUCCESS_CASE_UPLOAD_REJECT: '/admin/success-case-upload/reject',
   
   // 课程管理
   COURSE_LIST: '/api/course/list',

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

@@ -59,6 +59,7 @@
           <el-menu-item index="/matchmaker/audit">红娘审核</el-menu-item>
           <el-menu-item index="/matchmaker/create">添加红娘</el-menu-item>
           <el-menu-item index="/matchmaker/points-product">积分商品</el-menu-item>
+          <el-menu-item index="/matchmaker/case-audit">案例审核</el-menu-item>
         </el-sub-menu>
         
         <el-sub-menu index="/course">

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

@@ -115,6 +115,12 @@ const router = createRouter({
               name: 'PointsProduct',
               component: () => import('@/views/points-product/PointsProductList.vue'),
               meta: { title: '积分商品' }
+            },
+            {
+              path: 'case-audit',
+              name: 'CaseAudit',
+              component: () => import('@/views/matchmaker/CaseAudit.vue'),
+              meta: { title: '案例审核' }
             }
           ]
         },

+ 359 - 0
marriageAdmin-vue/src/views/matchmaker/CaseAudit.vue

@@ -0,0 +1,359 @@
+<template>
+  <div class="case-audit-container">
+    <h2 class="page-title">案例审核</h2>
+
+    <el-card shadow="never" class="toolbar-card">
+      <el-space wrap>
+        <el-input
+          v-model="filters.maleRealName"
+          placeholder="按男方姓名模糊搜索"
+          clearable
+          style="width: 200px"
+          @keyup.enter="loadList"
+        >
+          <template #append>
+            <el-button icon="Search" @click="loadList" />
+          </template>
+        </el-input>
+        <el-input
+          v-model="filters.femaleRealName"
+          placeholder="按女方姓名模糊搜索"
+          clearable
+          style="width: 200px"
+          @keyup.enter="loadList"
+        >
+          <template #append>
+            <el-button icon="Search" @click="loadList" />
+          </template>
+        </el-input>
+        <el-select
+          v-model="filters.auditStatus"
+          placeholder="审核状态"
+          clearable
+          style="width: 150px"
+          @change="loadList"
+        >
+          <el-option label="待审核" :value="0" />
+          <el-option label="审核通过" :value="1" />
+          <el-option label="审核失败" :value="2" />
+        </el-select>
+        <el-button icon="Refresh" @click="resetFilters">重置</el-button>
+      </el-space>
+    </el-card>
+
+    <el-card shadow="never" class="table-card">
+      <el-table v-loading="loading" :data="list" stripe>
+        <template #empty>
+          <div class="custom-empty-state">
+            <el-icon class="empty-icon"><Document /></el-icon>
+            <div class="empty-text">暂无案例记录</div>
+            <div class="empty-hint">等待红娘上传成功案例后在此审核</div>
+          </div>
+        </template>
+
+        <el-table-column type="index" label="序号" width="60" />
+        <el-table-column prop="maleRealName" label="男方姓名" min-width="120">
+          <template #default="{ row }">
+            <span class="data-highlight">{{ row.maleRealName || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="femaleRealName" label="女方姓名" min-width="120">
+          <template #default="{ row }">
+            <span class="data-highlight">{{ row.femaleRealName || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="caseType" label="案例类型" width="100" align="center">
+          <template #default="{ row }">
+            <el-tag size="small" :type="row.caseType === 1 ? 'success' : 'warning'">
+              {{ row.caseType === 1 ? '订婚' : row.caseType === 2 ? '领证结婚' : '-' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="caseDate" label="成功日期" width="120" align="center">
+          <template #default="{ row }">
+            <span class="text-muted">{{ formatDate(row.caseDate) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="auditStatus" label="审核状态" width="120" align="center">
+          <template #default="{ row }">
+            <el-tag
+              size="small"
+              :type="getAuditStatusType(row.auditStatus)"
+              effect="light"
+            >
+              {{ getAuditStatusText(row.auditStatus) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="auditRemark" label="审核备注" min-width="200" show-overflow-tooltip>
+          <template #default="{ row }">
+            <span class="text-muted">{{ row.auditRemark || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createdAt" label="上传时间" width="180">
+          <template #default="{ row }">
+            <span class="text-muted">{{ formatDateTime(row.createdAt) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="updatedAt" label="更改时间" width="180">
+          <template #default="{ row }">
+            <span class="text-muted">{{ formatDateTime(row.updatedAt) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="auditedAt" label="审核时间" width="180">
+          <template #default="{ row }">
+            <span class="text-muted">{{ formatDateTime(row.auditedAt) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="auditDuration" label="审核耗时" width="120" align="center">
+          <template #default="{ row }">
+            <span v-if="row.auditDuration" class="data-highlight">{{ row.auditDuration }}</span>
+            <span v-else style="color: #999;">-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="120" fixed="right">
+          <template #default="{ row }">
+            <el-button
+              type="primary"
+              size="small"
+              @click="handleAudit(row)"
+              :disabled="row.auditStatus === 1 || row.auditStatus === 2"
+            >
+              审核
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination-container">
+        <el-pagination
+          v-model:current-page="currentPage"
+          v-model:page-size="pageSize"
+          :total="total"
+          :page-sizes="[10, 20, 50, 100]"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="loadList"
+          @current-change="loadList"
+        />
+      </div>
+    </el-card>
+
+    <!-- 审核弹框 -->
+    <el-dialog
+      v-model="auditDialogVisible"
+      title="案例审核"
+      width="500px"
+      :close-on-click-modal="false"
+    >
+      <el-form :model="auditForm" label-width="100px">
+        <el-form-item label="男方姓名">
+          <span>{{ currentRow?.maleRealName || '-' }}</span>
+        </el-form-item>
+        <el-form-item label="女方姓名">
+          <span>{{ currentRow?.femaleRealName || '-' }}</span>
+        </el-form-item>
+        <el-form-item label="审核操作" required>
+          <el-radio-group v-model="auditForm.action">
+            <el-radio :label="1">审核通过</el-radio>
+            <el-radio :label="2">审核不通过</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item
+          v-if="auditForm.action === 2"
+          label="审核备注"
+          required
+          :rules="[{ required: true, message: '请输入审核备注', trigger: 'blur' }]"
+        >
+          <el-input
+            v-model="auditForm.auditRemark"
+            type="textarea"
+            :rows="4"
+            placeholder="请输入审核不通过的原因(失败原因)"
+            maxlength="500"
+            show-word-limit
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="auditDialogVisible = false">取消</el-button>
+        <el-button
+          type="primary"
+          @click="submitAudit"
+          :loading="auditing"
+          :disabled="auditForm.action === 2 && !auditForm.auditRemark?.trim()"
+        >
+          确定
+        </el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Document } from '@element-plus/icons-vue'
+import request from '@/utils/request'
+import { API_ENDPOINTS } from '@/config/api'
+
+const loading = ref(false)
+const list = ref([])
+const total = ref(0)
+const currentPage = ref(1)
+const pageSize = ref(10)
+const auditing = ref(false)
+const auditDialogVisible = ref(false)
+const currentRow = ref(null)
+
+const filters = reactive({
+  maleRealName: '',
+  femaleRealName: '',
+  auditStatus: null
+})
+
+const auditForm = reactive({
+  action: 1, // 1-通过, 2-不通过
+  auditRemark: ''
+})
+
+const loadList = async () => {
+  loading.value = true
+  try {
+    const params = {
+      page: currentPage.value,
+      pageSize: pageSize.value
+    }
+    if (filters.maleRealName && filters.maleRealName.trim()) {
+      params.maleRealName = filters.maleRealName.trim()
+    }
+    if (filters.femaleRealName && filters.femaleRealName.trim()) {
+      params.femaleRealName = filters.femaleRealName.trim()
+    }
+    if (filters.auditStatus !== null && filters.auditStatus !== undefined) {
+      params.auditStatus = filters.auditStatus
+    }
+    const res = await request.get(API_ENDPOINTS.SUCCESS_CASE_UPLOAD_LIST, { params })
+    if (res.code === 200) {
+      const data = res.data || {}
+      list.value = data.list || data.records || []
+      total.value = data.total || list.value.length
+    }
+  } catch (error) {
+    console.error('加载案例列表失败:', error)
+    ElMessage.error('加载案例列表失败')
+  } finally {
+    loading.value = false
+  }
+}
+
+const resetFilters = () => {
+  filters.maleRealName = ''
+  filters.femaleRealName = ''
+  filters.auditStatus = null
+  currentPage.value = 1
+  loadList()
+}
+
+const getAuditStatusText = (status) => {
+  const statusMap = {
+    0: '待审核',
+    1: '审核通过',
+    2: '审核失败'
+  }
+  return statusMap[status] || '未知'
+}
+
+const getAuditStatusType = (status) => {
+  const typeMap = {
+    0: 'warning',
+    1: 'success',
+    2: 'danger'
+  }
+  return typeMap[status] || ''
+}
+
+const formatDate = (date) => {
+  if (!date) return '-'
+  const d = new Date(date)
+  return d.toLocaleDateString('zh-CN')
+}
+
+const formatDateTime = (date) => {
+  if (!date) return '-'
+  const d = new Date(date)
+  return d.toLocaleString('zh-CN', {
+    year: 'numeric',
+    month: '2-digit',
+    day: '2-digit',
+    hour: '2-digit',
+    minute: '2-digit'
+  })
+}
+
+const handleAudit = (row) => {
+  if (row.auditStatus === 1 || row.auditStatus === 2) {
+    ElMessage.warning('该案例已审核,无需重复操作')
+    return
+  }
+  currentRow.value = row
+  auditForm.action = 1
+  auditForm.auditRemark = ''
+  auditDialogVisible.value = true
+}
+
+const submitAudit = async () => {
+  if (!currentRow.value) {
+    ElMessage.error('请选择要审核的案例')
+    return
+  }
+
+  if (auditForm.action === 2 && !auditForm.auditRemark?.trim()) {
+    ElMessage.warning('审核不通过时,必须填写审核备注')
+    return
+  }
+
+  try {
+    auditing.value = true
+    let res
+
+    if (auditForm.action === 1) {
+      // 审核通过
+      res = await request.post(
+        `${API_ENDPOINTS.SUCCESS_CASE_UPLOAD_APPROVE}/${currentRow.value.id}`
+      )
+    } else {
+      // 审核不通过
+      res = await request.post(
+        `${API_ENDPOINTS.SUCCESS_CASE_UPLOAD_REJECT}/${currentRow.value.id}`,
+        {
+          auditRemark: auditForm.auditRemark.trim()
+        }
+      )
+    }
+
+    if (res.code === 200) {
+      ElMessage.success(auditForm.action === 1 ? '审核通过成功' : '审核不通过操作成功')
+      auditDialogVisible.value = false
+      loadList()
+    } else {
+      ElMessage.error(res.msg || '审核操作失败')
+    }
+  } catch (error) {
+    console.error('审核操作失败:', error)
+    ElMessage.error(error.response?.data?.msg || error.message || '审核操作失败')
+  } finally {
+    auditing.value = false
+  }
+}
+
+onMounted(loadList)
+</script>
+
+<style scoped>
+@import '@/assets/list-common.css';
+
+.case-audit-container {
+  padding: 0;
+}
+</style>
+

+ 16 - 9
marriageAdmin-vue/src/views/matchmaker/MatchmakerAudit.vue

@@ -155,9 +155,9 @@ const resetFilters = () => {
   loadList()
 }
 
-// 判断是否已审核通过(根据updateMan字段判断
+// 判断是否已审核通过(根据status字段判断,status=0表示审核通过
 const isApproved = (row) => {
-  return !!(row.updateMan || row.update_man)
+  return row.status === 0
 }
 
 // 审核通过
@@ -167,14 +167,20 @@ const handleApprove = async (row) => {
     return
   }
   
+  if (row.status === 0) {
+    ElMessage.warning('该申请已审核通过,无需重复操作')
+    return
+  }
+  
   try {
     await ElMessageBox.confirm(
-      `确定要通过 ${row.name || '该用户'} 的红娘申请吗?通过后该用户将成为红娘。`,
-      '确认审核',
+      `确定要通过 ${row.name || '该用户'} 的红娘申请吗?\n通过后该用户将成为红娘,此操作将更新审核状态。`,
+      '确认审核通过',
       {
-        confirmButtonText: '确定',
+        confirmButtonText: '确定通过',
         cancelButtonText: '取消',
-        type: 'warning'
+        type: 'warning',
+        dangerouslyUseHTMLString: false
       }
     )
     
@@ -205,12 +211,13 @@ const handleApprove = async (row) => {
 const handleDelete = async (row) => {
   try {
     await ElMessageBox.confirm(
-      `确定要删除 ${row.name || '该用户'} 的红娘申请吗?此操作不可恢复。`,
+      `确定要删除 ${row.name || '该用户'} 的红娘申请吗?\n删除后该申请将不再显示在列表中,此操作为逻辑删除。`,
       '确认删除',
       {
-        confirmButtonText: '确定',
+        confirmButtonText: '确定删除',
         cancelButtonText: '取消',
-        type: 'warning'
+        type: 'warning',
+        dangerouslyUseHTMLString: false
       }
     )
     

+ 10 - 0
marriageAdmin-vue/src/views/user/UserList.vue

@@ -111,6 +111,12 @@
           </template>
         </el-table-column>
         <el-table-column prop="createdAt" label="注册时间" width="170" />
+        <el-table-column prop="auditedAt" label="审核时间" width="120" align="center">
+          <template #default="{ row }">
+            <span v-if="row.auditedAt" class="data-highlight">{{ row.auditedAt }}</span>
+            <span v-else style="color: #999;">-</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="lastLoginAt" label="最后登录" width="170">
           <template #default="{ row }">
             {{ row.lastLoginAt || '-' }}
@@ -226,6 +232,10 @@
         </el-descriptions-item>
         <el-descriptions-item label="注册时间" :span="2">{{ currentUser.createdAt }}</el-descriptions-item>
         <el-descriptions-item label="最后更新" :span="2">{{ currentUser.updatedAt }}</el-descriptions-item>
+        <el-descriptions-item label="审核时间" :span="2">
+          <span v-if="currentUser.auditedAt" class="data-highlight">{{ currentUser.auditedAt }}</span>
+          <span v-else>-</span>
+        </el-descriptions-item>
         <el-descriptions-item label="最后登录" :span="2">{{ currentUser.lastLoginAt || '-' }}</el-descriptions-item>
         <el-descriptions-item label="最后活跃" :span="2">{{ currentUser.lastActiveAt || '-' }}</el-descriptions-item>
         <el-descriptions-item label="头像" :span="2">

+ 242 - 0
service/admin/src/main/java/com/zhentao/controller/MatchmakerSuccessCaseUploadController.java

@@ -0,0 +1,242 @@
+package com.zhentao.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zhentao.common.Result;
+import com.zhentao.entity.Matchmaker;
+import com.zhentao.entity.MatchmakerSuccessCaseUpload;
+import com.zhentao.mapper.MatchmakerMapper;
+import com.zhentao.service.MatchmakerSuccessCaseUploadService;
+import com.zhentao.vo.MatchmakerSuccessCaseUploadVO;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 红娘成功案例审核控制器
+ */
+@RestController
+@RequestMapping("/admin/success-case-upload")
+@CrossOrigin(origins = "*")
+public class MatchmakerSuccessCaseUploadController {
+
+    @Autowired
+    private MatchmakerSuccessCaseUploadService successCaseUploadService;
+
+    @Autowired
+    private MatchmakerMapper matchmakerMapper;
+
+    /**
+     * 案例列表(分页查询)
+     * 支持按男方姓名、女方姓名模糊查询,按审核状态筛选
+     */
+    @GetMapping("/list")
+    public Result<Map<String, Object>> list(
+            @RequestParam(defaultValue = "1") Integer page,
+            @RequestParam(defaultValue = "10") Integer pageSize,
+            @RequestParam(required = false) String maleRealName,
+            @RequestParam(required = false) String femaleRealName,
+            @RequestParam(required = false) Integer auditStatus) {
+        try {
+            Page<MatchmakerSuccessCaseUpload> pageInfo = new Page<>(page, pageSize);
+            QueryWrapper<MatchmakerSuccessCaseUpload> queryWrapper = new QueryWrapper<>();
+
+            // 男方姓名模糊查询
+            if (maleRealName != null && !maleRealName.trim().isEmpty()) {
+                queryWrapper.like("male_real_name", maleRealName.trim());
+            }
+
+            // 女方姓名模糊查询
+            if (femaleRealName != null && !femaleRealName.trim().isEmpty()) {
+                queryWrapper.like("female_real_name", femaleRealName.trim());
+            }
+
+            // 审核状态筛选
+            if (auditStatus != null) {
+                queryWrapper.eq("audit_status", auditStatus);
+            }
+
+            // 按创建时间倒序
+            queryWrapper.orderByDesc("created_at");
+
+            Page<MatchmakerSuccessCaseUpload> result = successCaseUploadService.page(pageInfo, queryWrapper);
+
+            // 转换为VO列表
+            List<MatchmakerSuccessCaseUploadVO> voList = new ArrayList<>();
+            for (MatchmakerSuccessCaseUpload upload : result.getRecords()) {
+                MatchmakerSuccessCaseUploadVO vo = convertToVO(upload);
+                voList.add(vo);
+            }
+
+            Map<String, Object> data = new HashMap<>();
+            data.put("list", voList);
+            data.put("total", result.getTotal());
+            data.put("page", page);
+            data.put("pageSize", pageSize);
+
+            return Result.success(data);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("查询案例列表失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 审核通过
+     * 审核通过时,根据points_reward字段的值给对应红娘增加积分
+     */
+    @PostMapping("/approve/{id}")
+    @Transactional(rollbackFor = Exception.class)
+    public Result<String> approve(@PathVariable Long id) {
+        try {
+            MatchmakerSuccessCaseUpload upload = successCaseUploadService.getById(id);
+            if (upload == null) {
+                return Result.error("案例不存在");
+            }
+
+            // 根据matchmakerId查找红娘并增加积分
+            if (upload.getMatchmakerId() != null) {
+                // 获取积分奖励值,如果为null或小于等于0则不增加积分
+                Integer pointsReward = upload.getPointsReward();
+                if (pointsReward != null && pointsReward > 0) {
+                    Matchmaker matchmaker = matchmakerMapper.selectById(upload.getMatchmakerId());
+                    if (matchmaker != null) {
+                        // 获取当前积分,如果为null则默认为0
+                        Integer currentPoints = matchmaker.getPoints() != null ? matchmaker.getPoints() : 0;
+                        // 积分加上pointsReward的值
+                        matchmaker.setPoints(currentPoints + pointsReward);
+                        // 更新红娘积分
+                        matchmakerMapper.updateById(matchmaker);
+                    } else {
+                        return Result.error("红娘信息不存在");
+                    }
+                }
+            }
+
+            upload.setAuditStatus(1); // 审核通过
+            upload.setAuditedAt(new Date());
+            upload.setUpdatedAt(new Date());
+
+            boolean success = successCaseUploadService.updateById(upload);
+            if (success) {
+                Integer pointsReward = upload.getPointsReward();
+                if (pointsReward != null && pointsReward > 0) {
+                    return Result.success("审核通过成功,已为红娘增加" + pointsReward + "积分");
+                } else {
+                    return Result.success("审核通过成功");
+                }
+            } else {
+                return Result.error("审核通过失败");
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("审核通过失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 审核不通过
+     */
+    @PostMapping("/reject/{id}")
+    public Result<String> reject(@PathVariable Long id, @RequestBody Map<String, String> request) {
+        try {
+            MatchmakerSuccessCaseUpload upload = successCaseUploadService.getById(id);
+            if (upload == null) {
+                return Result.error("案例不存在");
+            }
+
+            String auditRemark = request.get("auditRemark");
+            if (auditRemark == null || auditRemark.trim().isEmpty()) {
+                return Result.error("审核备注不能为空");
+            }
+
+            upload.setAuditStatus(2); // 审核失败
+            upload.setAuditRemark(auditRemark.trim());
+            upload.setAuditedAt(new Date());
+            upload.setUpdatedAt(new Date());
+
+            boolean success = successCaseUploadService.updateById(upload);
+            if (success) {
+                return Result.success("审核不通过操作成功");
+            } else {
+                return Result.error("审核不通过操作失败");
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("审核不通过操作失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 转换为VO对象
+     */
+    private MatchmakerSuccessCaseUploadVO convertToVO(MatchmakerSuccessCaseUpload upload) {
+        MatchmakerSuccessCaseUploadVO vo = new MatchmakerSuccessCaseUploadVO();
+        BeanUtils.copyProperties(upload, vo);
+        
+        // 计算审核耗时(审核时间 - 上传时间)
+        vo.setAuditDuration(calculateAuditDuration(upload.getAuditedAt(), upload.getCreatedAt()));
+        
+        return vo;
+    }
+    
+    /**
+     * 计算审核耗时(审核时间 - 上传时间)
+     */
+    private String calculateAuditDuration(Date auditedAt, Date createdAt) {
+        if (auditedAt == null || createdAt == null) {
+            return "-";
+        }
+        
+        try {
+            Instant auditInstant = auditedAt.toInstant();
+            Instant createInstant = createdAt.toInstant();
+            Duration duration = Duration.between(createInstant, auditInstant);
+            
+            return formatDuration(duration);
+        } catch (Exception e) {
+            return "-";
+        }
+    }
+    
+    /**
+     * 格式化时间差为可读字符串
+     */
+    private String formatDuration(Duration duration) {
+        long totalSeconds = duration.getSeconds();
+        if (totalSeconds < 0) {
+            return "-";
+        }
+        
+        long days = totalSeconds / 86400;
+        long hours = (totalSeconds % 86400) / 3600;
+        long minutes = (totalSeconds % 3600) / 60;
+        long seconds = totalSeconds % 60;
+        
+        StringBuilder sb = new StringBuilder();
+        if (days > 0) {
+            sb.append(days).append("天");
+        }
+        if (hours > 0) {
+            sb.append(hours).append("小时");
+        }
+        if (minutes > 0) {
+            sb.append(minutes).append("分钟");
+        }
+        if (seconds > 0 && days == 0 && hours == 0) {
+            sb.append(seconds).append("秒");
+        }
+        
+        return sb.length() > 0 ? sb.toString() : "0秒";
+    }
+}
+

+ 46 - 2
service/admin/src/main/java/com/zhentao/controller/UserController.java

@@ -106,6 +106,9 @@ public class UserController {
         UserVO vo = new UserVO();
         BeanUtils.copyProperties(user, vo);
         
+        // 计算审核时间(updatedAt - createdAt)
+        calculateAuditedAt(vo, user);
+        
         // 填充VIP信息
         fillVipInfo(vo, user.getUserId());
         
@@ -115,6 +118,45 @@ public class UserController {
         return vo;
     }
     
+    /**
+     * 计算审核时间(updatedAt - createdAt)
+     */
+    private void calculateAuditedAt(UserVO vo, Users user) {
+        if (user.getUpdatedAt() != null && user.getCreatedAt() != null) {
+            Duration duration = Duration.between(user.getCreatedAt(), user.getUpdatedAt());
+            vo.setAuditedAt(formatDuration(duration));
+        } else {
+            vo.setAuditedAt(null);
+        }
+    }
+    
+    /**
+     * 格式化时间差为可读字符串
+     */
+    private String formatDuration(Duration duration) {
+        long totalSeconds = duration.getSeconds();
+        long days = totalSeconds / 86400;
+        long hours = (totalSeconds % 86400) / 3600;
+        long minutes = (totalSeconds % 3600) / 60;
+        long seconds = totalSeconds % 60;
+        
+        StringBuilder sb = new StringBuilder();
+        if (days > 0) {
+            sb.append(days).append("天");
+        }
+        if (hours > 0) {
+            sb.append(hours).append("小时");
+        }
+        if (minutes > 0) {
+            sb.append(minutes).append("分钟");
+        }
+        if (seconds > 0 && days == 0 && hours == 0) {
+            sb.append(seconds).append("秒");
+        }
+        
+        return sb.length() > 0 ? sb.toString() : "0秒";
+    }
+    
     /**
      * 填充VIP信息
      */
@@ -260,13 +302,15 @@ public class UserController {
      * 用户详情
      */
     @GetMapping("/detail/{userId}")
-    public Result<Users> detail(@PathVariable Integer userId) {
+    public Result<UserVO> detail(@PathVariable Integer userId) {
         try {
             Users user = usersMapper.selectById(userId);
             if (user == null) {
                 return Result.error("用户不存在");
             }
-            return Result.success(user);
+            // 转换为UserVO以包含审核时间等计算字段
+            UserVO vo = convertToUserVO(user);
+            return Result.success(vo);
         } catch (Exception e) {
             e.printStackTrace();
             return Result.error("查询失败:" + e.getMessage());

+ 26 - 0
service/admin/src/main/java/com/zhentao/entity/Area.java

@@ -0,0 +1,26 @@
+package com.zhentao.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 区域实体类
+ */
+@Data
+@TableName("area")
+public class Area implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+    
+    private String name;
+    
+    private Integer cityId;
+}
+

+ 26 - 0
service/admin/src/main/java/com/zhentao/entity/City.java

@@ -0,0 +1,26 @@
+package com.zhentao.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 城市实体类
+ */
+@Data
+@TableName("city")
+public class City implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+    
+    private String name;
+    
+    private Integer provinceId;
+}
+

+ 4 - 0
service/admin/src/main/java/com/zhentao/entity/MarrApply.java

@@ -94,6 +94,10 @@ public class MarrApply {
      * 状态0-正常,1-禁止
      */
     private Integer status;
+    /**
+     * 删除状态0-正常,1-禁止   默认为0
+     * */
+    private String idStatus;
 
     @Override
     public boolean equals(Object that) {

+ 5 - 0
service/admin/src/main/java/com/zhentao/entity/Matchmaker.java

@@ -23,6 +23,11 @@ public class Matchmaker implements Serializable {
     @TableId(value = "matchmaker_id", type = IdType.AUTO)
     private Integer matchmakerId;
     
+    /**
+     * 红娘对应的用户ID
+     */
+    private Integer userId;
+    
     private String username;
     private String password;
     private String phone;

+ 217 - 0
service/admin/src/main/java/com/zhentao/entity/MatchmakerSuccessCaseUpload.java

@@ -0,0 +1,217 @@
+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 java.math.BigDecimal;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 红娘上传成功案例表
+ * @TableName matchmaker_success_case_upload
+ */
+@TableName(value ="matchmaker_success_case_upload")
+@Data
+public class MatchmakerSuccessCaseUpload {
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 撮合红娘ID
+     */
+    private Integer matchmakerId;
+
+    /**
+     * 男方用户ID
+     */
+    private Integer maleUserId;
+
+    /**
+     * 女方用户ID
+     */
+    private Integer femaleUserId;
+
+    /**
+     * 男方真实姓名
+     */
+    private String maleRealName;
+
+    /**
+     * 女方真实姓名
+     */
+    private String femaleRealName;
+
+    /**
+     * 成功凭证图片路径(JSON数组格式)
+     */
+    private String proofImages;
+
+    /**
+     * 案例类型: 1-订婚 2-领证结婚
+     */
+    private Integer caseType;
+
+    /**
+     * 成功日期(结婚日期/确定关系日期)
+     */
+    private Date caseDate;
+
+    /**
+     * 审核状态:0-待审核 1-审核通过 2-审核失败 3-核实中
+     */
+    private Integer auditStatus;
+
+    /**
+     * 审核备注(失败原因)
+     */
+    private String auditRemark;
+
+    /**
+     * 审核人ID(管理员)
+     */
+    private Integer auditorId;
+
+    /**
+     * 积分奖励
+     */
+    private Integer pointsReward;
+
+    /**
+     * 现金奖励
+     */
+    private BigDecimal cashReward;
+
+    /**
+     * 奖励状态:0-未发放 1-已发放
+     */
+    private Integer rewardStatus;
+
+    /**
+     * 是否发布到成功案例展示:0-否 1-是
+     */
+    private Integer isPublished;
+
+    /**
+     * 关联的成功案例ID
+     */
+    private Integer publishedCaseId;
+
+    /**
+     * 0未读         1已读
+     */
+    private Integer isRead;
+
+    /**
+     * 上传时间
+     */
+    private Date createdAt;
+
+    /**
+     * 更新时间
+     */
+    private Date updatedAt;
+
+    /**
+     * 审核时间
+     */
+    private Date auditedAt;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        MatchmakerSuccessCaseUpload other = (MatchmakerSuccessCaseUpload) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getMatchmakerId() == null ? other.getMatchmakerId() == null : this.getMatchmakerId().equals(other.getMatchmakerId()))
+            && (this.getMaleUserId() == null ? other.getMaleUserId() == null : this.getMaleUserId().equals(other.getMaleUserId()))
+            && (this.getFemaleUserId() == null ? other.getFemaleUserId() == null : this.getFemaleUserId().equals(other.getFemaleUserId()))
+            && (this.getMaleRealName() == null ? other.getMaleRealName() == null : this.getMaleRealName().equals(other.getMaleRealName()))
+            && (this.getFemaleRealName() == null ? other.getFemaleRealName() == null : this.getFemaleRealName().equals(other.getFemaleRealName()))
+            && (this.getProofImages() == null ? other.getProofImages() == null : this.getProofImages().equals(other.getProofImages()))
+            && (this.getCaseType() == null ? other.getCaseType() == null : this.getCaseType().equals(other.getCaseType()))
+            && (this.getCaseDate() == null ? other.getCaseDate() == null : this.getCaseDate().equals(other.getCaseDate()))
+            && (this.getAuditStatus() == null ? other.getAuditStatus() == null : this.getAuditStatus().equals(other.getAuditStatus()))
+            && (this.getAuditRemark() == null ? other.getAuditRemark() == null : this.getAuditRemark().equals(other.getAuditRemark()))
+            && (this.getAuditorId() == null ? other.getAuditorId() == null : this.getAuditorId().equals(other.getAuditorId()))
+            && (this.getPointsReward() == null ? other.getPointsReward() == null : this.getPointsReward().equals(other.getPointsReward()))
+            && (this.getCashReward() == null ? other.getCashReward() == null : this.getCashReward().equals(other.getCashReward()))
+            && (this.getRewardStatus() == null ? other.getRewardStatus() == null : this.getRewardStatus().equals(other.getRewardStatus()))
+            && (this.getIsPublished() == null ? other.getIsPublished() == null : this.getIsPublished().equals(other.getIsPublished()))
+            && (this.getPublishedCaseId() == null ? other.getPublishedCaseId() == null : this.getPublishedCaseId().equals(other.getPublishedCaseId()))
+            && (this.getIsRead() == null ? other.getIsRead() == null : this.getIsRead().equals(other.getIsRead()))
+            && (this.getCreatedAt() == null ? other.getCreatedAt() == null : this.getCreatedAt().equals(other.getCreatedAt()))
+            && (this.getUpdatedAt() == null ? other.getUpdatedAt() == null : this.getUpdatedAt().equals(other.getUpdatedAt()))
+            && (this.getAuditedAt() == null ? other.getAuditedAt() == null : this.getAuditedAt().equals(other.getAuditedAt()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getMatchmakerId() == null) ? 0 : getMatchmakerId().hashCode());
+        result = prime * result + ((getMaleUserId() == null) ? 0 : getMaleUserId().hashCode());
+        result = prime * result + ((getFemaleUserId() == null) ? 0 : getFemaleUserId().hashCode());
+        result = prime * result + ((getMaleRealName() == null) ? 0 : getMaleRealName().hashCode());
+        result = prime * result + ((getFemaleRealName() == null) ? 0 : getFemaleRealName().hashCode());
+        result = prime * result + ((getProofImages() == null) ? 0 : getProofImages().hashCode());
+        result = prime * result + ((getCaseType() == null) ? 0 : getCaseType().hashCode());
+        result = prime * result + ((getCaseDate() == null) ? 0 : getCaseDate().hashCode());
+        result = prime * result + ((getAuditStatus() == null) ? 0 : getAuditStatus().hashCode());
+        result = prime * result + ((getAuditRemark() == null) ? 0 : getAuditRemark().hashCode());
+        result = prime * result + ((getAuditorId() == null) ? 0 : getAuditorId().hashCode());
+        result = prime * result + ((getPointsReward() == null) ? 0 : getPointsReward().hashCode());
+        result = prime * result + ((getCashReward() == null) ? 0 : getCashReward().hashCode());
+        result = prime * result + ((getRewardStatus() == null) ? 0 : getRewardStatus().hashCode());
+        result = prime * result + ((getIsPublished() == null) ? 0 : getIsPublished().hashCode());
+        result = prime * result + ((getPublishedCaseId() == null) ? 0 : getPublishedCaseId().hashCode());
+        result = prime * result + ((getIsRead() == null) ? 0 : getIsRead().hashCode());
+        result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
+        result = prime * result + ((getUpdatedAt() == null) ? 0 : getUpdatedAt().hashCode());
+        result = prime * result + ((getAuditedAt() == null) ? 0 : getAuditedAt().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", matchmakerId=").append(matchmakerId);
+        sb.append(", maleUserId=").append(maleUserId);
+        sb.append(", femaleUserId=").append(femaleUserId);
+        sb.append(", maleRealName=").append(maleRealName);
+        sb.append(", femaleRealName=").append(femaleRealName);
+        sb.append(", proofImages=").append(proofImages);
+        sb.append(", caseType=").append(caseType);
+        sb.append(", caseDate=").append(caseDate);
+        sb.append(", auditStatus=").append(auditStatus);
+        sb.append(", auditRemark=").append(auditRemark);
+        sb.append(", auditorId=").append(auditorId);
+        sb.append(", pointsReward=").append(pointsReward);
+        sb.append(", cashReward=").append(cashReward);
+        sb.append(", rewardStatus=").append(rewardStatus);
+        sb.append(", isPublished=").append(isPublished);
+        sb.append(", publishedCaseId=").append(publishedCaseId);
+        sb.append(", isRead=").append(isRead);
+        sb.append(", createdAt=").append(createdAt);
+        sb.append(", updatedAt=").append(updatedAt);
+        sb.append(", auditedAt=").append(auditedAt);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 24 - 0
service/admin/src/main/java/com/zhentao/entity/Province.java

@@ -0,0 +1,24 @@
+package com.zhentao.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 省份实体类
+ */
+@Data
+@TableName("province")
+public class Province implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+    
+    private String name;
+}
+

+ 13 - 0
service/admin/src/main/java/com/zhentao/mapper/AreaMapper.java

@@ -0,0 +1,13 @@
+package com.zhentao.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.entity.Area;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 区域Mapper
+ */
+@Mapper
+public interface AreaMapper extends BaseMapper<Area> {
+}
+

+ 13 - 0
service/admin/src/main/java/com/zhentao/mapper/CityMapper.java

@@ -0,0 +1,13 @@
+package com.zhentao.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.entity.City;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 城市Mapper
+ */
+@Mapper
+public interface CityMapper extends BaseMapper<City> {
+}
+

+ 18 - 0
service/admin/src/main/java/com/zhentao/mapper/MatchmakerSuccessCaseUploadMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.mapper;
+
+import com.zhentao.entity.MatchmakerSuccessCaseUpload;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author 联想
+* @description 针对表【matchmaker_success_case_upload(红娘上传成功案例表)】的数据库操作Mapper
+* @createDate 2025-12-12 10:29:21
+* @Entity com.zhentao.entity.MatchmakerSuccessCaseUpload
+*/
+public interface MatchmakerSuccessCaseUploadMapper extends BaseMapper<MatchmakerSuccessCaseUpload> {
+
+}
+
+
+
+

+ 13 - 0
service/admin/src/main/java/com/zhentao/mapper/ProvinceMapper.java

@@ -0,0 +1,13 @@
+package com.zhentao.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.entity.Province;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 省份Mapper
+ */
+@Mapper
+public interface ProvinceMapper extends BaseMapper<Province> {
+}
+

+ 13 - 0
service/admin/src/main/java/com/zhentao/service/MatchmakerSuccessCaseUploadService.java

@@ -0,0 +1,13 @@
+package com.zhentao.service;
+
+import com.zhentao.entity.MatchmakerSuccessCaseUpload;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author 联想
+* @description 针对表【matchmaker_success_case_upload(红娘上传成功案例表)】的数据库操作Service
+* @createDate 2025-12-12 10:29:21
+*/
+public interface MatchmakerSuccessCaseUploadService extends IService<MatchmakerSuccessCaseUpload> {
+
+}

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

@@ -17,6 +17,13 @@ import org.springframework.transaction.annotation.Transactional;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.springframework.util.StringUtils;
 import org.springframework.web.client.RestTemplate;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.zhentao.entity.Province;
+import com.zhentao.entity.City;
+import com.zhentao.entity.Area;
+import com.zhentao.mapper.ProvinceMapper;
+import com.zhentao.mapper.CityMapper;
+import com.zhentao.mapper.AreaMapper;
 
 import java.time.LocalDateTime;
 import java.util.Date;
@@ -38,6 +45,15 @@ public class MarrApplyServiceImpl extends ServiceImpl<MarrApplyMapper, MarrApply
     @Autowired
     private MatchmakerMapper matchmakerMapper;
     
+    @Autowired
+    private ProvinceMapper provinceMapper;
+    
+    @Autowired
+    private CityMapper cityMapper;
+    
+    @Autowired
+    private AreaMapper areaMapper;
+    
     private final RestTemplate restTemplate = new RestTemplate();
     
     // websocket 服务的 IM 接口地址
@@ -81,18 +97,46 @@ public class MarrApplyServiceImpl extends ServiceImpl<MarrApplyMapper, MarrApply
         }
         System.out.println("✅ 已更新 users 表,is_matchmaker = 1");
         
-        // 4. 创建 matchmakers 记录
-        Matchmaker matchmaker = new Matchmaker();
+        // 4. 创建或更新 matchmakers 记录
+        // 先检查该用户是否已经存在红娘记录
+        QueryWrapper<Matchmaker> matchmakerWrapper = new QueryWrapper<>();
+        matchmakerWrapper.eq("user_id", apply.getUserId());
+        Matchmaker existingMatchmaker = matchmakerMapper.selectOne(matchmakerWrapper);
         
-        // 从 marr_apply 表获取数据
-        matchmaker.setRealName(apply.getName());           // 真实姓名
+        Matchmaker matchmaker;
+        boolean isUpdate = false;
+        
+        if (existingMatchmaker != null) {
+            // 如果已存在,则更新现有记录
+            matchmaker = existingMatchmaker;
+            isUpdate = true;
+            System.out.println("⚠️ 该用户已存在红娘记录,将更新现有记录,matchmaker_id = " + matchmaker.getMatchmakerId());
+        } else {
+            // 如果不存在,创建新记录
+            matchmaker = new Matchmaker();
+            matchmaker.setCreateTime(LocalDateTime.now());
+        }
+        
+        // 从 marr_apply 表获取数据(按照用户要求映射)
+        matchmaker.setUserId(apply.getUserId());           // 用户ID
+        matchmaker.setRealName(apply.getName());           // 姓名 -> 真实姓名
         matchmaker.setPhone(apply.getPhone());             // 手机号
         matchmaker.setEmail(apply.getEmail());             // 邮箱
         matchmaker.setGender(apply.getGender());           // 性别
-        matchmaker.setProfile(apply.getIntroduction());    // 个人简介
+        matchmaker.setProfile(apply.getExperience());      // 经验 -> 个人简介
+        
+        // 解析地区信息:area 字段包含三级联动,需要解析为 provinceId, cityId, areaId
+        parseAreaToIds(apply.getArea(), matchmaker);
         
         // 从 users 表获取数据
-        matchmaker.setUsername(user.getNickname() != null ? user.getNickname() : user.getPhone()); // 用户名(优先使用昵称,否则使用手机号)
+        String baseUsername = user.getNickname() != null ? user.getNickname() : user.getPhone(); // 用户名(优先使用昵称,否则使用手机号)
+        
+        // 检查用户名是否已存在(如果不存在记录或用户名发生变化)
+        if (!isUpdate || matchmaker.getUsername() == null || !baseUsername.equals(matchmaker.getUsername())) {
+            String finalUsername = generateUniqueUsername(baseUsername, matchmaker.getUserId());
+            matchmaker.setUsername(finalUsername);
+        }
+        
         matchmaker.setPassword(user.getPassword());        // 密码(使用用户的密码)
         matchmaker.setBirthDate(user.getBirthDate());      // 出生日期(更准确)
         
@@ -106,18 +150,28 @@ public class MarrApplyServiceImpl extends ServiceImpl<MarrApplyMapper, MarrApply
         }
         
         // 设置默认值(无法从其他表获取的字段)
-        matchmaker.setMatchmakerType(1);                   // 默认类型:1-普通红娘
-        matchmaker.setLevel(1);                            // 默认等级:1级
-        matchmaker.setSuccessCouples(0);                   // 初始成功撮合数:0
+        if (!isUpdate) {
+            matchmaker.setMatchmakerType(1);                   // 默认类型:1-普通红娘
+            matchmaker.setLevel(1);                            // 默认等级:1级
+            matchmaker.setSuccessCouples(0);                   // 初始成功撮合数:0
+        }
         matchmaker.setStatus(1);                           // 状态:1-正常
-        matchmaker.setCreateTime(LocalDateTime.now());
         matchmaker.setUpdateTime(LocalDateTime.now());
         
-        int insertResult = matchmakerMapper.insert(matchmaker);
-        if (insertResult <= 0) {
-            throw new RuntimeException("创建红娘记录失败");
+        int result;
+        if (isUpdate) {
+            result = matchmakerMapper.updateById(matchmaker);
+            if (result <= 0) {
+                throw new RuntimeException("更新红娘记录失败");
+            }
+            System.out.println("✅ 已更新 matchmakers 记录,matchmaker_id = " + matchmaker.getMatchmakerId());
+        } else {
+            result = matchmakerMapper.insert(matchmaker);
+            if (result <= 0) {
+                throw new RuntimeException("创建红娘记录失败");
+            }
+            System.out.println("✅ 已创建 matchmakers 记录,matchmaker_id = " + matchmaker.getMatchmakerId());
         }
-        System.out.println("✅ 已创建 matchmakers 记录,matchmaker_id = " + matchmaker.getMatchmakerId());
         
         // 5. 导入到腾讯云 IM(使用 m_ + matchmaker_id)
         String imUserId = "m_" + matchmaker.getMatchmakerId();
@@ -141,7 +195,8 @@ public class MarrApplyServiceImpl extends ServiceImpl<MarrApplyMapper, MarrApply
             // 导入失败不影响审核流程,只记录日志
         }
         
-        // 6. 更新申请记录的更新人和更新时间
+        // 6. 更新申请记录的审核状态、更新人和更新时间
+        apply.setStatus(0); // 0-正常(审核通过)
         apply.setUpdateTime(new Date());
         apply.setUpdateMan("系统");
         this.updateById(apply);
@@ -153,7 +208,122 @@ public class MarrApplyServiceImpl extends ServiceImpl<MarrApplyMapper, MarrApply
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean delete(Long applyId) {
-        return this.removeById(applyId);
+        // 逻辑删除:将 idStatus 设置为 "1"(禁止状态)
+        MarrApply marrApply = new MarrApply();
+        marrApply.setApplyId(applyId);
+        marrApply.setIdStatus("1"); // 1-禁止(逻辑删除)
+        return this.updateById(marrApply);
+    }
+    
+    /**
+     * 解析地区字符串,将省市区名称转换为对应的ID
+     * area 格式可能是:"北京市 北京市 朝阳区" 或 "北京市 北京市 朝阳区"(用空格分隔)
+     * 
+     * @param area 地区字符串
+     * @param matchmaker 红娘对象,用于设置 provinceId, cityId, areaId
+     */
+    private void parseAreaToIds(String area, Matchmaker matchmaker) {
+        if (area == null || area.trim().isEmpty()) {
+            System.out.println("⚠️ 地区信息为空,跳过解析");
+            return;
+        }
+        
+        try {
+            // 按空格分割地区字符串
+            String[] areaParts = area.trim().split("\\s+");
+            
+            if (areaParts.length >= 1 && provinceMapper != null) {
+                // 解析省份
+                String provinceName = areaParts[0].trim();
+                QueryWrapper<Province> provinceWrapper = new QueryWrapper<>();
+                provinceWrapper.eq("name", provinceName);
+                Province province = provinceMapper.selectOne(provinceWrapper);
+                if (province != null) {
+                    matchmaker.setProvinceId(province.getId());
+                    System.out.println("✅ 解析省份: " + provinceName + " -> " + province.getId());
+                } else {
+                    System.out.println("⚠️ 未找到省份: " + provinceName);
+                }
+            }
+            
+            if (areaParts.length >= 2 && cityMapper != null && matchmaker.getProvinceId() != null) {
+                // 解析城市
+                String cityName = areaParts[1].trim();
+                QueryWrapper<City> cityWrapper = new QueryWrapper<>();
+                cityWrapper.eq("name", cityName);
+                cityWrapper.eq("province_id", matchmaker.getProvinceId());
+                City city = cityMapper.selectOne(cityWrapper);
+                if (city != null) {
+                    matchmaker.setCityId(city.getId());
+                    System.out.println("✅ 解析城市: " + cityName + " -> " + city.getId());
+                } else {
+                    System.out.println("⚠️ 未找到城市: " + cityName);
+                }
+            }
+            
+            if (areaParts.length >= 3 && areaMapper != null && matchmaker.getCityId() != null) {
+                // 解析区域
+                String areaName = areaParts[2].trim();
+                QueryWrapper<Area> areaWrapper = new QueryWrapper<>();
+                areaWrapper.eq("name", areaName);
+                areaWrapper.eq("city_id", matchmaker.getCityId());
+                Area areaObj = areaMapper.selectOne(areaWrapper);
+                if (areaObj != null) {
+                    matchmaker.setAreaId(areaObj.getId());
+                    System.out.println("✅ 解析区域: " + areaName + " -> " + areaObj.getId());
+                } else {
+                    System.out.println("⚠️ 未找到区域: " + areaName);
+                }
+            }
+            
+            if (areaParts.length < 3) {
+                System.out.println("⚠️ 地区信息不完整,只有 " + areaParts.length + " 部分: " + area);
+            }
+        } catch (Exception e) {
+            System.err.println("❌ 解析地区信息失败: " + e.getMessage());
+            e.printStackTrace();
+            // 解析失败不影响审核流程,只记录日志
+        }
+    }
+    
+    /**
+     * 生成唯一的用户名
+     * 如果用户名已存在,则添加后缀(_1, _2, ...)直到找到不存在的用户名
+     * 
+     * @param baseUsername 基础用户名
+     * @param currentUserId 当前用户ID(如果更新现有记录,排除自己)
+     * @return 唯一的用户名
+     */
+    private String generateUniqueUsername(String baseUsername, Integer currentUserId) {
+        if (baseUsername == null || baseUsername.trim().isEmpty()) {
+            baseUsername = "user_" + System.currentTimeMillis();
+        }
+        
+        String username = baseUsername.trim();
+        int suffix = 0;
+        
+        while (true) {
+            QueryWrapper<Matchmaker> wrapper = new QueryWrapper<>();
+            wrapper.eq("username", username);
+            // 如果当前用户ID不为空,排除自己的记录(用于更新场景)
+            if (currentUserId != null) {
+                wrapper.ne("user_id", currentUserId);
+            }
+            
+            Matchmaker existing = matchmakerMapper.selectOne(wrapper);
+            
+            if (existing == null) {
+                // 用户名不存在,可以使用
+                if (suffix > 0) {
+                    System.out.println("⚠️ 用户名 '" + baseUsername + "' 已存在,使用新用户名: " + username);
+                }
+                return username;
+            }
+            
+            // 用户名已存在,添加后缀
+            suffix++;
+            username = baseUsername + "_" + suffix;
+        }
     }
 }
 

+ 22 - 0
service/admin/src/main/java/com/zhentao/service/impl/MatchmakerSuccessCaseUploadServiceImpl.java

@@ -0,0 +1,22 @@
+package com.zhentao.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.entity.MatchmakerSuccessCaseUpload;
+import com.zhentao.service.MatchmakerSuccessCaseUploadService;
+import com.zhentao.mapper.MatchmakerSuccessCaseUploadMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author 联想
+* @description 针对表【matchmaker_success_case_upload(红娘上传成功案例表)】的数据库操作Service实现
+* @createDate 2025-12-12 10:29:21
+*/
+@Service
+public class MatchmakerSuccessCaseUploadServiceImpl extends ServiceImpl<MatchmakerSuccessCaseUploadMapper, MatchmakerSuccessCaseUpload>
+    implements MatchmakerSuccessCaseUploadService{
+
+}
+
+
+
+

+ 132 - 0
service/admin/src/main/java/com/zhentao/vo/MatchmakerSuccessCaseUploadVO.java

@@ -0,0 +1,132 @@
+package com.zhentao.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 红娘成功案例上传VO
+ */
+@Data
+public class MatchmakerSuccessCaseUploadVO implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 撮合红娘ID
+     */
+    private Integer matchmakerId;
+
+    /**
+     * 男方用户ID
+     */
+    private Integer maleUserId;
+
+    /**
+     * 女方用户ID
+     */
+    private Integer femaleUserId;
+
+    /**
+     * 男方真实姓名
+     */
+    private String maleRealName;
+
+    /**
+     * 女方真实姓名
+     */
+    private String femaleRealName;
+
+    /**
+     * 成功凭证图片路径(JSON数组格式)
+     */
+    private String proofImages;
+
+    /**
+     * 案例类型: 1-订婚 2-领证结婚
+     */
+    private Integer caseType;
+
+    /**
+     * 成功日期(结婚日期/确定关系日期)
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date caseDate;
+
+    /**
+     * 审核状态:0-待审核 1-审核通过 2-审核失败 3-核实中
+     */
+    private Integer auditStatus;
+
+    /**
+     * 审核备注(失败原因)
+     */
+    private String auditRemark;
+
+    /**
+     * 审核人ID(管理员)
+     */
+    private Integer auditorId;
+
+    /**
+     * 积分奖励
+     */
+    private Integer pointsReward;
+
+    /**
+     * 现金奖励
+     */
+    private BigDecimal cashReward;
+
+    /**
+     * 奖励状态:0-未发放 1-已发放
+     */
+    private Integer rewardStatus;
+
+    /**
+     * 是否发布到成功案例展示:0-否 1-是
+     */
+    private Integer isPublished;
+
+    /**
+     * 关联的成功案例ID
+     */
+    private Integer publishedCaseId;
+
+    /**
+     * 0未读         1已读
+     */
+    private Integer isRead;
+
+    /**
+     * 上传时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdAt;
+
+    /**
+     * 更新时间(更改时间)
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedAt;
+
+    /**
+     * 审核时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date auditedAt;
+    
+    /**
+     * 审核耗时(审核时间 - 上传时间,格式化的可读字符串)
+     */
+    private String auditDuration;
+}
+

+ 1 - 0
service/admin/src/main/java/com/zhentao/vo/UserVO.java

@@ -47,6 +47,7 @@ public class UserVO implements Serializable {
     
     // 计算字段
     private Integer age;
+    private String auditedAt;  // 审核时间(updatedAt - createdAt,格式化的时间差)
     
     // VIP信息
     private Boolean isVip;  // 是否VIP

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

@@ -21,12 +21,13 @@
             <result property="updateTime" column="update_time" />
             <result property="updateMan" column="update_man" />
             <result property="status" column="status" />
+            <result property="idStatus" column="id_status" />
     </resultMap>
 
     <sql id="Base_Column_List">
         apply_id,user_id,name,phone,email,age,
         gender,area,experience,server_time,introduction,
-        create_time,create_man,update_time,update_man,status
+        create_time,create_man,update_time,update_man,status,id_status
     </sql>
 
     <select id="selectPageByCondition" resultMap="BaseResultMap">
@@ -34,6 +35,7 @@
         <include refid="Base_Column_List"/>
         FROM marr_apply
         <where>
+            AND (id_status = '0' OR id_status IS NULL)
             <if test="name != null and name != ''">
                 AND name LIKE CONCAT('%', #{name}, '%')
             </if>

+ 37 - 0
service/admin/src/main/resources/com/zhentao/mapper/MatchmakerSuccessCaseUploadMapper.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.mapper.MatchmakerSuccessCaseUploadMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.entity.MatchmakerSuccessCaseUpload">
+            <id property="id" column="id" />
+            <result property="matchmakerId" column="matchmaker_id" />
+            <result property="maleUserId" column="male_user_id" />
+            <result property="femaleUserId" column="female_user_id" />
+            <result property="maleRealName" column="male_real_name" />
+            <result property="femaleRealName" column="female_real_name" />
+            <result property="proofImages" column="proof_images" />
+            <result property="caseType" column="case_type" />
+            <result property="caseDate" column="case_date" />
+            <result property="auditStatus" column="audit_status" />
+            <result property="auditRemark" column="audit_remark" />
+            <result property="auditorId" column="auditor_id" />
+            <result property="pointsReward" column="points_reward" />
+            <result property="cashReward" column="cash_reward" />
+            <result property="rewardStatus" column="reward_status" />
+            <result property="isPublished" column="is_published" />
+            <result property="publishedCaseId" column="published_case_id" />
+            <result property="isRead" column="is_read" />
+            <result property="createdAt" column="created_at" />
+            <result property="updatedAt" column="updated_at" />
+            <result property="auditedAt" column="audited_at" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,matchmaker_id,male_user_id,female_user_id,male_real_name,female_real_name,
+        proof_images,case_type,case_date,audit_status,audit_remark,
+        auditor_id,points_reward,cash_reward,reward_status,is_published,
+        published_case_id,is_read,created_at,updated_at,audited_at
+    </sql>
+</mapper>

+ 35 - 0
service/admin/src/main/resources/com/zhentao/mapper/UsersMapper.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.mapper.UsersMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.entity.Users">
+            <id property="userId" column="user_id" />
+            <result property="phone" column="phone" />
+            <result property="email" column="email" />
+            <result property="nickname" column="nickname" />
+            <result property="password" column="password" />
+            <result property="gender" column="gender" />
+            <result property="birthDate" column="birth_date" />
+            <result property="avatarUrl" column="avatar_url" />
+            <result property="status" column="status" />
+            <result property="sourceChannel" column="source_channel" />
+            <result property="isProfileComplete" column="is_profile_complete" />
+            <result property="createdAt" column="created_at" />
+            <result property="updatedAt" column="updated_at" />
+            <result property="consentToCollect" column="consent_to_collect" />
+            <result property="lastLoginAt" column="last_login_at" />
+            <result property="lastActiveAt" column="last_active_at" />
+            <result property="hasWechatLogin" column="has_wechat_login" />
+            <result property="isMatchmaker" column="is_matchmaker" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        user_id,phone,email,nickname,password,gender,
+        birth_date,avatar_url,status,source_channel,is_profile_complete,
+        created_at,updated_at,consent_to_collect,last_login_at,last_active_at,
+        has_wechat_login,is_matchmaker
+    </sql>
+</mapper>
+