Explorar el Código

Merge branch 'mzh' into test_dev

# Conflicts:
#	LiangZhiYUMao/pages.json
#	LiangZhiYUMao/pages/index/index.vue
#	LiangZhiYUMao/pages/matchmaker-workbench/mine.vue
#	service/homePage/src/main/java/com/zhentao/controller/MyResourceController.java
#	service/homePage/src/main/java/com/zhentao/service/MyResourceService.java
#	service/websocket/src/main/resources/application.yml
mazhenhang hace 1 mes
padre
commit
08f98e9072
Se han modificado 20 ficheros con 3378 adiciones y 53 borrados
  1. 14 0
      LiangZhiYUMao/pages.json
  2. 5 1
      LiangZhiYUMao/pages/index/index.vue
  3. 760 0
      LiangZhiYUMao/pages/matchmaker-workbench/audit-records.vue
  4. 55 5
      LiangZhiYUMao/pages/matchmaker-workbench/message.vue
  5. 411 31
      LiangZhiYUMao/pages/matchmaker-workbench/mine.vue
  6. 968 0
      LiangZhiYUMao/pages/matchmaker-workbench/success-case-upload.vue
  7. 94 0
      LiangZhiYUMao/utils/api.js
  8. 16 0
      gateway/src/main/resources/application.yml
  9. 235 10
      service/homePage/src/main/java/com/zhentao/controller/MyResourceController.java
  10. 12 2
      service/homePage/src/main/java/com/zhentao/service/MyResourceService.java
  11. 81 0
      service/homePage/src/main/java/com/zhentao/service/impl/MyResourceServiceImpl.java
  12. 5 0
      service/websocket/src/main/java/com/zhentao/config/MinioConfig.java
  13. 385 0
      service/websocket/src/main/java/com/zhentao/controller/SuccessCaseUploadController.java
  14. 52 0
      service/websocket/src/main/java/com/zhentao/dto/SuccessCaseUploadDTO.java
  15. 132 0
      service/websocket/src/main/java/com/zhentao/entity/MatchmakerSuccessCaseUpload.java
  16. 25 0
      service/websocket/src/main/java/com/zhentao/mapper/MatchmakerSuccessCaseUploadMapper.java
  17. 17 0
      service/websocket/src/main/java/com/zhentao/service/MatchmakerSuccessCaseUploadService.java
  18. 37 0
      service/websocket/src/main/java/com/zhentao/service/impl/MatchmakerSuccessCaseUploadServiceImpl.java
  19. 70 1
      service/websocket/src/main/java/com/zhentao/utils/MinioUtil.java
  20. 4 3
      service/websocket/src/main/resources/application.yml

+ 14 - 0
LiangZhiYUMao/pages.json

@@ -77,6 +77,13 @@
 				"navigationStyle": "custom"
 			}
 		},
+		{
+			"path": "pages/matchmaker-workbench/audit-records",
+			"style": {
+				"navigationBarTitleText": "审核记录",
+				"navigationStyle": "custom"
+			}
+		},
 		{
 			"path": "pages/matchmaker-workbench/mine",
 			"style": {
@@ -133,6 +140,13 @@
 				"navigationStyle": "custom"
 			}
 		},
+		{
+			"path": "pages/matchmaker-workbench/success-case-upload",
+			"style": {
+				"navigationBarTitleText": "撮合成功审核",
+				"navigationStyle": "custom"
+			}
+		},
 		{
 			"path": "pages/matchmaker-workbench/my-activities",
 			"style": {

+ 5 - 1
LiangZhiYUMao/pages/index/index.vue

@@ -420,7 +420,11 @@
 				}
 				
 				uni.navigateTo({
-					url: '/pages/matchmaker-workbench/index'
+					url: '/pages/matchmaker-workbench/index',
+					fail: (err) => {
+						console.error('❌ 跳转红娘工作台失败:', err)
+						uni.showToast({ title: '跳转失败', icon: 'none' })
+					}
 				})
 			},
 			

+ 760 - 0
LiangZhiYUMao/pages/matchmaker-workbench/audit-records.vue

@@ -0,0 +1,760 @@
+<template>
+	<view class="audit-records">
+		<!-- 顶部导航栏 -->
+		<view class="header">
+			<view class="back-btn" @click="goBack"></view>
+			<text class="header-title">撮合成功通知</text>
+			<view class="placeholder"></view>
+		</view>
+
+		<scroll-view scroll-y class="content" @scrolltolower="loadMore">
+			<!-- 加载中 -->
+			<view v-if="loading && records.length === 0" class="loading-container">
+				<text>加载中...</text>
+			</view>
+			
+			<!-- 记录列表 -->
+			<view v-else-if="records.length > 0" class="record-list">
+				<view 
+					v-for="record in records" 
+					:key="record.id" 
+					class="record-item"
+					:class="{ unread: !record.isRead && (record.auditStatus === 1 || record.auditStatus === 2) }"
+					@click="openDetail(record)"
+				>
+					<!-- 状态标签 -->
+					<view class="status-tag" 
+						:class="{
+							'status-pending': record.auditStatus === 0,
+							'status-success': record.auditStatus === 1,
+							'status-fail': record.auditStatus === 2,
+							'status-checking': record.auditStatus === 3
+						}">
+						{{ getStatusText(record.auditStatus) }}
+					</view>
+					
+					<!-- 未读红点 -->
+					<view v-if="!record.isRead && (record.auditStatus === 1 || record.auditStatus === 2)" class="unread-dot"></view>
+					
+					<!-- 记录内容 -->
+					<view class="record-content">
+						<view class="record-title">
+							<text class="couple-names">{{ record.maleRealName }} & {{ record.femaleRealName }}</text>
+							<text class="case-type">{{ record.caseType === 1 ? '订婚' : '结婚' }}</text>
+						</view>
+						<view class="record-summary">
+							<text v-if="record.auditStatus === 0">您提交的撮合成功案例正在等待审核</text>
+							<text v-else-if="record.auditStatus === 3">您的案例正在核实中,请耐心等待</text>
+							<text v-else-if="record.auditStatus === 1">
+								恭喜!审核通过,获得{{ record.pointsReward || 0 }}积分
+								<text v-if="record.cashReward > 0">+{{ record.cashReward }}元现金</text>
+								奖励
+							</text>
+							<text v-else-if="record.auditStatus === 2">审核未通过:{{ record.auditRemark || '未说明原因' }}</text>
+						</view>
+						<view class="record-time">{{ formatTime(record.createdAt) }}</view>
+					</view>
+				</view>
+				
+				<!-- 加载更多 -->
+				<view v-if="loading" class="loading-more">
+					<text>加载中...</text>
+				</view>
+				<view v-else-if="!hasMore" class="no-more">
+					<text>没有更多了</text>
+				</view>
+			</view>
+			
+			<!-- 空状态 -->
+			<view v-else class="empty-container">
+				<view class="empty-icon">📋</view>
+				<text class="empty-text">暂无审核记录</text>
+				<text class="empty-hint">提交撮合成功案例后,审核进度将在这里显示</text>
+			</view>
+		</scroll-view>
+
+		<!-- 详情弹窗 -->
+		<uni-popup ref="detailPopup" type="center">
+			<view class="detail-popup" v-if="currentRecord">
+				<view class="popup-header">
+					<text class="popup-title">审核详情</text>
+					<view class="close-btn" @click="closeDetail">×</view>
+				</view>
+				
+				<view class="popup-content">
+					<!-- 状态 -->
+					<view class="detail-status" 
+						:class="{
+							'status-pending': currentRecord.auditStatus === 0,
+							'status-success': currentRecord.auditStatus === 1,
+							'status-fail': currentRecord.auditStatus === 2,
+							'status-checking': currentRecord.auditStatus === 3
+						}">
+						<view class="status-icon" v-if="currentRecord.auditStatus !== 2">
+							<text v-if="currentRecord.auditStatus === 0">⏳</text>
+							<text v-else-if="currentRecord.auditStatus === 3">🔍</text>
+							<text v-else-if="currentRecord.auditStatus === 1">✅</text>
+						</view>
+						<text class="status-text">{{ getStatusText(currentRecord.auditStatus) }}</text>
+					</view>
+					
+					<!-- 案例信息 -->
+					<view class="detail-section">
+						<view class="section-title">案例信息</view>
+						<view class="info-row">
+							<text class="info-label">男方姓名</text>
+							<text class="info-value">{{ currentRecord.maleRealName }}</text>
+						</view>
+						<view class="info-row">
+							<text class="info-label">女方姓名</text>
+							<text class="info-value">{{ currentRecord.femaleRealName }}</text>
+						</view>
+						<view class="info-row">
+							<text class="info-label">案例类型</text>
+							<text class="info-value">{{ currentRecord.caseType === 1 ? '订婚' : '结婚' }}</text>
+						</view>
+						<view class="info-row" v-if="currentRecord.caseDate">
+							<text class="info-label">成功日期</text>
+							<text class="info-value">{{ currentRecord.caseDate }}</text>
+						</view>
+						<view class="info-row">
+							<text class="info-label">提交时间</text>
+							<text class="info-value">{{ formatTime(currentRecord.createdAt) }}</text>
+						</view>
+					</view>
+					
+					<!-- 审核通过:显示奖励 -->
+					<view v-if="currentRecord.auditStatus === 1" class="detail-section reward-section">
+						<view class="section-title">🎉 奖励信息</view>
+						<view class="reward-content">
+							<view class="reward-item" v-if="currentRecord.pointsReward > 0">
+								<text class="reward-icon">🏆</text>
+								<text class="reward-text">积分奖励:+{{ currentRecord.pointsReward }}积分</text>
+							</view>
+							<view class="reward-item" v-if="currentRecord.cashReward > 0">
+								<text class="reward-icon">💰</text>
+								<text class="reward-text">现金奖励:+{{ currentRecord.cashReward }}元</text>
+							</view>
+							<view class="reward-status">
+								<text v-if="currentRecord.rewardStatus === 1" class="issued">奖励已发放</text>
+								<text v-else class="pending">奖励待发放</text>
+							</view>
+						</view>
+					</view>
+					
+					<!-- 审核失败:显示原因 -->
+					<view v-if="currentRecord.auditStatus === 2" class="detail-section fail-section">
+						<view class="section-title">审核未通过</view>
+						<view class="fail-reason">
+							<text>{{ currentRecord.auditRemark || '未说明具体原因,如有疑问请联系客服' }}</text>
+						</view>
+						<view class="fail-hint" @click="goToResubmit">
+							<text>您可以修改后重新提交审核</text>
+							<text class="resubmit-link">去提交 ></text>
+						</view>
+					</view>
+					
+					<!-- 待审核/核实中 -->
+					<view v-if="currentRecord.auditStatus === 0 || currentRecord.auditStatus === 3" class="detail-section pending-section">
+						<view class="section-title">{{ currentRecord.auditStatus === 0 ? '⏳ 等待审核' : '🔍 核实中' }}</view>
+						<view class="pending-hint">
+							<text v-if="currentRecord.auditStatus === 0">您的案例已提交,工作人员将尽快审核,请耐心等待</text>
+							<text v-else>工作人员正在核实您的案例信息,请保持电话畅通</text>
+						</view>
+					</view>
+					
+					<!-- 审核时间 -->
+					<view v-if="currentRecord.updatedAt" class="detail-section">
+						<view class="info-row">
+							<text class="info-label">审核时间</text>
+							<text class="info-value">{{ formatTime(currentRecord.updatedAt) }}</text>
+						</view>
+					</view>
+				</view>
+				
+				<view class="popup-footer">
+					<button class="confirm-btn" @click="closeDetail">我知道了</button>
+				</view>
+			</view>
+		</uni-popup>
+	</view>
+</template>
+
+<script>
+import api from '@/utils/api.js'
+
+export default {
+	data() {
+		return {
+			loading: false,
+			records: [],
+			currentRecord: null,
+			matchmakerId: null,
+			pageNum: 1,
+			pageSize: 20,
+			hasMore: true
+		}
+	},
+	
+	onLoad() {
+		this.loadMatchmakerInfo()
+	},
+	
+	onShow() {
+		// 页面显示时刷新列表
+		if (this.matchmakerId) {
+			this.refreshList()
+		}
+	},
+	
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		},
+		
+		async loadMatchmakerInfo() {
+			try {
+				const userInfo = uni.getStorageSync('userInfo')
+				if (userInfo && userInfo.userId) {
+					const matchmakerInfo = await api.matchmaker.getByUserId(userInfo.userId)
+					if (matchmakerInfo) {
+						this.matchmakerId = matchmakerInfo.matchmakerId || matchmakerInfo.matchmaker_id || null
+						if (this.matchmakerId) {
+							this.loadRecords()
+						} else {
+							uni.showToast({ title: '未找到红娘信息', icon: 'none' })
+						}
+					}
+				}
+			} catch (e) {
+				console.error('获取红娘信息失败', e)
+				uni.showToast({ title: '获取红娘信息失败', icon: 'none' })
+			}
+		},
+		
+		async loadRecords() {
+			if (this.loading || !this.hasMore) return
+			
+			this.loading = true
+			try {
+				const res = await api.successCaseUpload.getAuditRecords(
+					this.matchmakerId, 
+					null, // 不筛选状态,显示全部
+					this.pageNum, 
+					this.pageSize
+				)
+				
+				const pageData = res?.records || res?.data?.records || []
+				const total = res?.total || res?.data?.total || 0
+				
+				if (this.pageNum === 1) {
+					this.records = pageData
+				} else {
+					this.records = [...this.records, ...pageData]
+				}
+				
+				this.hasMore = this.records.length < total
+				this.pageNum++
+				
+			} catch (e) {
+				console.error('加载审核记录失败', e)
+				uni.showToast({ title: '加载失败', icon: 'none' })
+			} finally {
+				this.loading = false
+			}
+		},
+		
+		refreshList() {
+			this.pageNum = 1
+			this.hasMore = true
+			this.records = []
+			this.loadRecords()
+		},
+		
+		loadMore() {
+			if (this.hasMore && !this.loading) {
+				this.loadRecords()
+			}
+		},
+		
+		async openDetail(record) {
+			this.currentRecord = record
+			this.$refs.detailPopup.open()
+			
+			// 如果是已审核完成且未读,标记为已读
+			if ((record.auditStatus === 1 || record.auditStatus === 2) && !record.isRead) {
+				try {
+					await api.successCaseUpload.markAsRead(record.id)
+					// 更新本地状态
+					record.isRead = 1
+				} catch (e) {
+					console.error('标记已读失败', e)
+				}
+			}
+		},
+		
+		closeDetail() {
+			this.$refs.detailPopup.close()
+			this.currentRecord = null
+		},
+		
+		// 跳转到重新提交审核页面
+		goToResubmit() {
+			if (!this.currentRecord) return
+			// 关闭弹窗
+			this.$refs.detailPopup.close()
+			// 跳转到提交页面,携带记录ID用于回显数据
+			uni.navigateTo({
+				url: `/pages/matchmaker-workbench/success-case-upload?resubmitId=${this.currentRecord.id}`
+			})
+		},
+		
+		getStatusText(status) {
+			const statusMap = {
+				0: '待审核',
+				1: '审核通过',
+				2: '审核失败',
+				3: '核实中'
+			}
+			return statusMap[status] || '未知'
+		},
+		
+		// getStatusClass 方法已移除,改用内联对象语法
+		
+		formatTime(timeStr) {
+			if (!timeStr) return ''
+			const date = new Date(timeStr)
+			const now = new Date()
+			const diff = now - date
+			
+			// 一分钟内
+			if (diff < 60000) {
+				return '刚刚'
+			}
+			// 一小时内
+			if (diff < 3600000) {
+				return Math.floor(diff / 60000) + '分钟前'
+			}
+			// 今天
+			if (date.toDateString() === now.toDateString()) {
+				return date.getHours() + ':' + String(date.getMinutes()).padStart(2, '0')
+			}
+			// 昨天
+			const yesterday = new Date(now)
+			yesterday.setDate(yesterday.getDate() - 1)
+			if (date.toDateString() === yesterday.toDateString()) {
+				return '昨天 ' + date.getHours() + ':' + String(date.getMinutes()).padStart(2, '0')
+			}
+			// 更早
+			const month = date.getMonth() + 1
+			const day = date.getDate()
+			return `${month}-${day} ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.audit-records {
+	min-height: 100vh;
+	background: linear-gradient(180deg, #fff1f7 0%, #f6ebff 45%, #fbf7ff 75%, #ffffff 100%);
+	display: flex;
+	flex-direction: column;
+}
+
+/* 顶部导航栏 */
+.header {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 25rpx 30rpx;
+	padding-top: calc(25rpx + env(safe-area-inset-top));
+	background: transparent;
+
+	.back-btn {
+		width: 70rpx;
+		height: 70rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		background: rgba(240, 240, 240, 0.5);
+		border-radius: 50%;
+		background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23333"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>');
+		background-size: 40rpx 40rpx;
+		background-repeat: no-repeat;
+		background-position: center;
+	}
+
+	.header-title {
+		font-size: 38rpx;
+		font-weight: bold;
+		color: #333;
+	}
+
+	.placeholder {
+		width: 70rpx;
+	}
+}
+
+.content {
+	flex: 1;
+	padding: 20rpx 24rpx;
+	box-sizing: border-box;
+}
+
+/* 记录列表 */
+.record-list {
+	padding-bottom: 40rpx;
+}
+
+.record-item {
+	background: #ffffff;
+	border-radius: 24rpx;
+	padding: 28rpx;
+	margin-bottom: 20rpx;
+	box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.05);
+	position: relative;
+	
+	&.unread {
+		background: linear-gradient(135deg, #fff8fb 0%, #fef5ff 100%);
+		border: 2rpx solid rgba(233, 30, 99, 0.15);
+	}
+}
+
+/* 状态标签 */
+.status-tag {
+	position: absolute;
+	top: 0;
+	left: 28rpx;
+	padding: 6rpx 16rpx;
+	border-radius: 0 0 12rpx 12rpx;
+	font-size: 22rpx;
+	font-weight: 500;
+	
+	&.status-pending {
+		background: #fff3e0;
+		color: #f57c00;
+	}
+	
+	&.status-success {
+		background: #e8f5e9;
+		color: #43a047;
+	}
+	
+	&.status-fail {
+		background: #ffebee;
+		color: #e53935;
+	}
+	
+	&.status-checking {
+		background: #e3f2fd;
+		color: #1976d2;
+	}
+}
+
+/* 未读红点 */
+.unread-dot {
+	position: absolute;
+	top: 20rpx;
+	right: 20rpx;
+	width: 16rpx;
+	height: 16rpx;
+	background: #e91e63;
+	border-radius: 50%;
+}
+
+/* 记录内容 */
+.record-content {
+	padding-top: 30rpx;
+}
+
+.record-title {
+	display: flex;
+	align-items: center;
+	margin-bottom: 12rpx;
+	
+	.couple-names {
+		font-size: 32rpx;
+		font-weight: 600;
+		color: #333;
+		margin-right: 16rpx;
+	}
+	
+	.case-type {
+		font-size: 24rpx;
+		color: #9c27b0;
+		background: #f3e5f5;
+		padding: 4rpx 12rpx;
+		border-radius: 8rpx;
+	}
+}
+
+.record-summary {
+	font-size: 26rpx;
+	color: #666;
+	line-height: 1.5;
+	margin-bottom: 12rpx;
+}
+
+.record-time {
+	font-size: 24rpx;
+	color: #999;
+}
+
+/* 加载状态 */
+.loading-container,
+.empty-container {
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	align-items: center;
+	padding: 100rpx 0;
+	color: #999;
+	font-size: 28rpx;
+}
+
+.empty-icon {
+	font-size: 80rpx;
+	margin-bottom: 20rpx;
+}
+
+.empty-text {
+	font-size: 32rpx;
+	color: #666;
+	margin-bottom: 12rpx;
+}
+
+.empty-hint {
+	font-size: 26rpx;
+	color: #999;
+}
+
+.loading-more,
+.no-more {
+	text-align: center;
+	padding: 30rpx 0;
+	color: #999;
+	font-size: 26rpx;
+}
+
+/* 详情弹窗 */
+.detail-popup {
+	width: 650rpx;
+	background: #ffffff;
+	border-radius: 24rpx;
+	overflow: hidden;
+}
+
+.popup-header {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 30rpx;
+	border-bottom: 1rpx solid #f0f0f0;
+	
+	.popup-title {
+		font-size: 34rpx;
+		font-weight: 600;
+		color: #333;
+	}
+	
+	.close-btn {
+		width: 50rpx;
+		height: 50rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 40rpx;
+		color: #999;
+	}
+}
+
+.popup-content {
+	padding: 30rpx;
+	max-height: 70vh;
+	overflow-y: auto;
+}
+
+/* 详情状态 */
+.detail-status {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	padding: 30rpx;
+	border-radius: 16rpx;
+	margin-bottom: 30rpx;
+	
+	&.status-pending {
+		background: #fff3e0;
+	}
+	
+	&.status-success {
+		background: #e8f5e9;
+	}
+	
+	&.status-fail {
+		background: #ffebee;
+	}
+	
+	&.status-checking {
+		background: #e3f2fd;
+	}
+	
+	.status-icon {
+		font-size: 60rpx;
+		margin-bottom: 12rpx;
+	}
+	
+	.status-text {
+		font-size: 32rpx;
+		font-weight: 600;
+	}
+}
+
+/* 详情区块 */
+.detail-section {
+	margin-bottom: 30rpx;
+	
+	.section-title {
+		font-size: 28rpx;
+		font-weight: 600;
+		color: #333;
+		margin-bottom: 16rpx;
+		padding-bottom: 12rpx;
+		border-bottom: 1rpx solid #f0f0f0;
+	}
+}
+
+.info-row {
+	display: flex;
+	justify-content: space-between;
+	padding: 12rpx 0;
+	
+	.info-label {
+		font-size: 26rpx;
+		color: #999;
+	}
+	
+	.info-value {
+		font-size: 26rpx;
+		color: #333;
+	}
+}
+
+/* 奖励区块 */
+.reward-section {
+	background: linear-gradient(135deg, #fff8e1 0%, #ffecb3 100%);
+	padding: 24rpx;
+	border-radius: 16rpx;
+	
+	.section-title {
+		border-bottom: none;
+		padding-bottom: 0;
+	}
+}
+
+.reward-content {
+	.reward-item {
+		display: flex;
+		align-items: center;
+		padding: 12rpx 0;
+		
+		.reward-icon {
+			font-size: 36rpx;
+			margin-right: 12rpx;
+		}
+		
+		.reward-text {
+			font-size: 28rpx;
+			color: #333;
+			font-weight: 500;
+		}
+	}
+	
+	.reward-status {
+		margin-top: 16rpx;
+		text-align: center;
+		
+		.issued {
+			color: #43a047;
+			font-size: 26rpx;
+		}
+		
+		.pending {
+			color: #f57c00;
+			font-size: 26rpx;
+		}
+	}
+}
+
+/* 失败区块 */
+.fail-section {
+	background: #fff5f5;
+	padding: 24rpx;
+	border-radius: 16rpx;
+	
+	.section-title {
+		border-bottom: none;
+		padding-bottom: 0;
+		color: #e53935;
+	}
+	
+	.fail-reason {
+		font-size: 28rpx;
+		color: #333;
+		line-height: 1.6;
+		padding: 12rpx 0;
+	}
+	
+	.fail-hint {
+		font-size: 24rpx;
+		color: #999;
+		margin-top: 12rpx;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		
+		.resubmit-link {
+			color: #9c27b0;
+			font-weight: 500;
+		}
+	}
+}
+
+/* 待审核区块 */
+.pending-section {
+	background: #f5f5f5;
+	padding: 24rpx;
+	border-radius: 16rpx;
+	
+	.section-title {
+		border-bottom: none;
+		padding-bottom: 0;
+	}
+	
+	.pending-hint {
+		font-size: 26rpx;
+		color: #666;
+		line-height: 1.6;
+	}
+}
+
+/* 弹窗底部 */
+.popup-footer {
+	padding: 20rpx 30rpx 30rpx;
+	
+	.confirm-btn {
+		width: 100%;
+		height: 88rpx;
+		background: linear-gradient(135deg, #e91e63 0%, #9c27b0 100%);
+		color: #ffffff;
+		font-size: 32rpx;
+		font-weight: 500;
+		border-radius: 44rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		border: none;
+	}
+}
+</style>

+ 55 - 5
LiangZhiYUMao/pages/matchmaker-workbench/message.vue

@@ -24,13 +24,18 @@
 				</view>
 				
 				<!-- 撮合成功通知卡片 -->
-				<view class="message-item match-success" v-if="matchSuccessNotification">
+				<view class="message-item match-success" v-if="matchSuccessNotification" @click="openAuditRecords">
 					<view class="message-icon heart"></view>
 					<view class="message-body">
 						<text class="message-type">{{ matchSuccessNotification.title }}</text>
 						<text class="message-content">{{ matchSuccessNotification.content }}</text>
 					</view>
-					<text class="message-time">{{ matchSuccessNotification.timeText }}</text>
+					<view class="message-right-area">
+						<text class="message-time">{{ matchSuccessNotification.timeText }}</text>
+						<view v-if="matchSuccessUnread > 0" class="match-unread-badge">
+							{{ matchSuccessUnread > 99 ? '99+' : matchSuccessUnread }}
+						</view>
+					</view>
 				</view>
 				
 				<!-- 今天 -->
@@ -181,6 +186,10 @@
 				totalUnreadCount: 0,
 				// 系统消息未读数
 				systemUnread: 0,
+				// 撮合成功通知未读数
+				matchSuccessUnread: 0,
+				// 红娘ID
+				matchmakerId: null,
 				
 				// 顶部系统通知占位(后续可从后端加载)
 				systemNotification: {
@@ -237,6 +246,8 @@
 			}
 			// 同步系统消息未读
 			this.loadSystemUnread()
+			// 同步撮合成功通知未读
+			this.loadMatchSuccessUnread()
 		},
 		
 		onUnload() {
@@ -316,6 +327,26 @@
 				})
 			},
 
+			// 跳转审核记录列表
+			openAuditRecords() {
+				uni.navigateTo({
+					url: '/pages/matchmaker-workbench/audit-records'
+				})
+			},
+
+			// 加载撮合成功通知未读数
+			async loadMatchSuccessUnread() {
+				try {
+					if (!this.matchmakerId) return
+					const res = await api.successCaseUpload.getUnreadCount(this.matchmakerId)
+					if (res && res.code === 200) {
+						this.matchSuccessUnread = res.data || 0
+					}
+				} catch (e) {
+					console.log('加载撮合成功通知未读数失败', e)
+				}
+			},
+
 			// 初始化 IM
 			async initIM() {
 				try {
@@ -342,9 +373,13 @@
 					
 					this.matchmakerInfo = res[1].data.data
 					this.imUserId = this.matchmakerInfo.imUserId  // m_1
+					this.matchmakerId = this.matchmakerInfo.matchmakerId || this.matchmakerInfo.matchmaker_id
 					
 					console.log('✅ 红娘信息:', this.matchmakerInfo)
 					
+					// 加载撮合成功通知未读数
+					this.loadMatchSuccessUnread()
+					
 					// 3. 获取 UserSig
 					const sigRes = await uni.request({
 						url: `http://localhost:8083/api/im/getUserSig?userId=${this.imUserId}`,
@@ -375,7 +410,6 @@
 					this.addListeners()
 					
 					// 7. 加载会话列表
-					// 6. 加载会话列表
 					await this.loadConversationList()
 					
 				} catch (error) {
@@ -652,7 +686,6 @@
 			justify-content: center;
 			background: rgba(240, 240, 240, 0.5);
 			border-radius: 50%;
-			background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23333"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>');
 			background-size: 40rpx 40rpx;
 			background-repeat: no-repeat;
 			background-position: center;
@@ -763,10 +796,27 @@
 				line-height: 1.4;
 			}
 
+			.message-right-area {
+				display: flex;
+				flex-direction: column;
+				align-items: flex-end;
+				margin-left: 20rpx;
+			}
+
 			.message-time {
 				font-size: 24rpx;
 				color: #a691c0;
-				margin-top: auto;
+				margin-bottom: 10rpx;
+			}
+
+			.match-unread-badge {
+				background: #ff5c9a;
+				color: #FFFFFF;
+				font-size: 20rpx;
+				padding: 4rpx 12rpx;
+				border-radius: 20rpx;
+				min-width: 36rpx;
+				text-align: center;
 			}
 		}
 

+ 411 - 31
LiangZhiYUMao/pages/matchmaker-workbench/mine.vue

@@ -70,6 +70,19 @@
             <text class="settings-text">编辑资料</text>
             <view class="arrow-right"></view>
           </view>
+				<!-- 设置选项 -->
+				<view class="settings-section">
+					<view class="settings-item" @click="handleSuccessCaseUpload">
+						<view class="settings-icon success-case"></view>
+						<text class="settings-text">撮合成功审核</text>
+						<view class="arrow-right"></view>
+					</view>
+
+					<view class="settings-item" @click="handleEditProfile">
+						<view class="settings-icon user"></view>
+						<text class="settings-text">编辑资料</text>
+						<view class="arrow-right"></view>
+					</view>
 
           <view class="settings-item" @click="handleAccountSettings">
             <view class="settings-icon info"></view>
@@ -133,7 +146,7 @@
               <text class="stats-unit">天</text>
             </view>
           </view>
-          
+
           <view class="calendar-container">
             <view class="calendar-header">
               <text class="calendar-title">{{ calendarTitle }}</text>
@@ -164,7 +177,7 @@
               </view>
             </view>
           </view>
-          
+
           <view class="sign-in-reward">
             <text class="reward-title">签到奖励</text>
             <text class="reward-points">+5积分</text>
@@ -349,7 +362,7 @@ export default {
           console.warn('没有有效的userId,无法检查签到状态')
           return
         }
-        
+
         // 首先获取红娘信息,以获取makerId
         const matchmakerInfo = await api.matchmaker.getByUserId(userInfo.userId)
         if (!matchmakerInfo || !matchmakerInfo.matchmakerId && !matchmakerInfo.matchmaker_id) {
@@ -360,9 +373,9 @@ export default {
           console.warn('没有有效的makerId,无法检查签到状态')
           return
         }
-        
+
         const makerId = matchmakerInfo.matchmakerId || matchmakerInfo.matchmaker_id
-        
+
         // 检查今日是否已签到
         const res = await api.matchmaker.checkinStatus(makerId)
         console.log('检查签到状态返回结果:', res)
@@ -384,21 +397,21 @@ export default {
           }
         }
         this.isSignedToday = !!signed
-        
+
         // 获取签到统计
         const stats = await api.matchmaker.checkinStats(makerId)
         console.log('获取签到统计返回结果:', stats)
-        
+
         // 初始化签到记录数组
         this.signedDays = []
-        
+
         if (stats) {
           this.continuousDays = stats.continuousDays || 0
           this.totalDays = stats.totalDays || 0
-          
+
           // 检查stats中是否包含本月的签到记录
           let signedDays = []
-          
+
           // 情况1: stats直接包含signedDays数组
           if (stats.signedDays && Array.isArray(stats.signedDays)) {
             signedDays = stats.signedDays
@@ -428,16 +441,16 @@ export default {
               }
               return days
             }
-            
+
             // 先处理stats对象本身
             signedDays = processStats(stats)
-            
+
             // 如果stats.data存在,也处理一下
             if (stats.data && typeof stats.data === 'object') {
               signedDays = [...signedDays, ...processStats(stats.data)]
             }
           }
-          
+
           // 检查签到状态响应中是否包含签到记录
           if (signedDays.length === 0 && res && typeof res === 'object') {
             const processRes = (obj) => {
@@ -447,24 +460,24 @@ export default {
               }
               return days
             }
-            
+
             signedDays = processRes(res)
-            
+
             if (res.data && typeof res.data === 'object') {
               signedDays = [...signedDays, ...processRes(res.data)]
             }
           }
-          
+
           // 检查今日是否已签到,如果已签到但不在signedDays中,添加进去
           const today = new Date().getDate()
           if (this.isSignedToday && !signedDays.includes(today)) {
             signedDays.push(today)
           }
-          
+
           // 去重并排序
           this.signedDays = [...new Set(signedDays)].sort((a, b) => a - b)
         }
-        
+
         console.log('最终的signedDays:', this.signedDays)
       } catch (error) {
         console.error('检查签到状态失败:', error)
@@ -501,15 +514,15 @@ export default {
           }, 1000)
           return
         }
-        
+
         // 使用userId进行签到(后端会根据userId查询matchmaker并添加积分)
         const res = await api.matchmaker.doCheckin(userInfo.userId)
         console.log('签到API返回结果:', res)
-        
+
         // 判断签到结果
         let success = false
         let alreadySigned = false
-        
+
         // 后端返回格式: { code: 200, msg: "签到成功", data: true/false }
         if (res === true) {
           success = true
@@ -522,7 +535,7 @@ export default {
             alreadySigned = true
           }
         }
-        
+
         if (success) {
           uni.showToast({
             title: '签到成功,+5积分',
@@ -650,6 +663,364 @@ export default {
     }
   }
 }
+	import api from '../../utils/api.js'
+	export default {
+		data() {
+			return {
+				profile: {
+					realName: '',
+					avatarUrl: '',
+					badge: '',
+					rating: 5.0,
+					level: '',
+					levelName: '',
+					nextLevelName: '',
+					points: 0,
+					currentLevelPoints: 0,
+					nextLevelPoints: 100,
+					pointsToNextLevel: 0,
+					levelProgress: 0
+				},
+				// 签到相关
+				isSignedToday: false,
+				continuousDays: 0,
+				totalDays: 0,
+				currentDate: new Date(),
+				calendarTitle: '',
+				calendarDays: []
+			}
+		},
+		onLoad() {
+			this.loadProfileData()
+			this.checkSignInStatus()
+			this.generateCalendar()
+		},
+		onShow() {
+			// 从编辑资料页返回时,自动刷新个人资料
+			this.loadProfileData()
+		},
+		computed: {
+			unreadCount() {
+				return this.$store.getters.getTotalUnread || 0
+			}
+		},
+		methods: {
+			// 生成日历数据
+			generateCalendar() {
+				const date = this.currentDate || new Date()
+				const year = date.getFullYear()
+				const month = date.getMonth()
+				// 设置日历标题
+				this.calendarTitle = `${year}年·${month + 1}月`
+				// 获取当月第一天
+				const firstDay = new Date(year, month, 1)
+				// 获取当月第一天是星期几(0-6,0表示周日)
+				const firstDayWeek = firstDay.getDay()
+				// 获取当月最后一天
+				const lastDay = new Date(year, month + 1, 0)
+				// 获取当月最后一天的日期
+				const lastDayDate = lastDay.getDate()
+				// 获取上个月最后一天的日期
+				const prevMonthLastDay = new Date(year, month, 0).getDate()
+				const days = []
+				// 添加上个月的日期
+				for (let i = firstDayWeek - 1; i >= 0; i--) {
+					days.push({
+						date: prevMonthLastDay - i,
+						isCurrentMonth: false,
+						isToday: false,
+						isChecked: false
+					})
+				}
+				// 添加当月的日期
+				const today = new Date()
+				const isCurrentMonth = today.getFullYear() === year && today.getMonth() === month
+				for (let i = 1; i <= lastDayDate; i++) {
+					const isToday = isCurrentMonth && today.getDate() === i
+					days.push({
+						date: i,
+						isCurrentMonth: true,
+						isToday: isToday,
+						isChecked: isToday && this.isSignedToday
+					})
+				}
+				// 添加下个月的日期,补满6行
+				const totalDays = 42 // 6行7列
+				const nextMonthDays = totalDays - days.length
+				for (let i = 1; i <= nextMonthDays; i++) {
+					days.push({
+						date: i,
+						isCurrentMonth: false,
+						isToday: false,
+						isChecked: false
+					})
+				}
+				this.calendarDays = days
+			},
+			// 加载个人资料数据
+			async loadProfileData() {
+				try {
+					const userInfo = uni.getStorageSync('userInfo')
+					console.log('从本地存储获取到的用户信息:', userInfo)
+					// 模拟用户信息,用于测试
+					const testUserId = 19
+					const userId = userInfo && userInfo.userId ? userInfo.userId : testUserId
+					console.log('使用的userId:', userId)
+					// 调用API获取红娘信息
+					console.log('开始调用API获取红娘信息...')
+					const matchmakerInfo = await api.matchmaker.getByUserId(userId)
+					console.log('API调用结果:', matchmakerInfo)
+					if (matchmakerInfo) {
+						const levelNames = ['', '青铜', '白银', '黄金', '铂金', '钻石']
+						const currentLevel = matchmakerInfo.level || 1
+						const nextLevel = currentLevel < 5 ? currentLevel + 1 : 5
+						const nextLevelName = levelNames[nextLevel]
+						const defaultAvatars = {
+							male: 'http://115.190.125.125:9000/dynamic-comments/dynamics/5c645152-9940-41d3-83a9-69ee6e0c0aaa.png',
+							female: 'http://115.190.125.125:9000/dynamic-comments/dynamics/c7fb04d7-ee4d-4b3d-bcef-f246da9c841f.png'
+						}
+						const avatarUrl = matchmakerInfo.gender === 1 ? defaultAvatars.male : defaultAvatars.female
+						console.log('获取到的红娘信息:', matchmakerInfo)
+						this.profile = {
+							realName: matchmakerInfo.real_name || '',
+							avatarUrl: avatarUrl,
+							badge: matchmakerInfo.type_name || '',
+							rating: matchmakerInfo.rating || 5.0,
+							level: currentLevel,
+							levelName: matchmakerInfo.level_name || levelNames[currentLevel],
+							nextLevelName: nextLevelName,
+							points: matchmakerInfo.points || 0,
+							currentLevelPoints: matchmakerInfo.current_level_points || 0,
+							nextLevelPoints: matchmakerInfo.next_level_points || 100,
+							pointsToNextLevel: matchmakerInfo.points_to_next_level || 0,
+							levelProgress: matchmakerInfo.level_progress || 0
+						}
+						console.log('更新后的profile数据:', this.profile)
+					} else {
+						console.log('matchmakerInfo为空,无法更新profile数据')
+					}
+				} catch (error) {
+					console.error('加载个人资料失败:', error)
+					uni.showToast({
+						title: '加载失败,请稍后重试: ' + error.message,
+						icon: 'none'
+					})
+				}
+			},
+			// 返回上一页
+			goBack() {
+				uni.navigateBack()
+			},
+			// 检查今日是否已签到
+			async checkSignInStatus() {
+				try {
+					const userInfo = uni.getStorageSync('userInfo')
+					const userId = userInfo && userInfo.userId ? userInfo.userId : null
+					if (userId) {
+						const res = await api.matchmaker.checkinStatus(userId)
+						console.log('检查签到状态返回结果:', res)
+						let signed = false
+						if (typeof res === 'boolean') {
+							signed = res
+						} else if (typeof res === 'string') {
+							signed = res === 'true'
+						} else if (typeof res === 'number') {
+							signed = res === 1
+						} else if (res && typeof res === 'object') {
+							if (typeof res.todaySigned === 'boolean') signed = res.todaySigned
+							else if (typeof res.signed === 'boolean') signed = res.signed
+							else if (typeof res.isSignedToday === 'boolean') signed = res.isSignedToday
+							else if (typeof res.data === 'object' && res.data) {
+								if (typeof res.data.todaySigned === 'boolean') signed = res.data.todaySigned
+								else if (typeof res.data.signed === 'boolean') signed = res.data.signed
+								else if (typeof res.data.isSignedToday === 'boolean') signed = res.data.isSignedToday
+							}
+						}
+						this.isSignedToday = !!signed
+						const stats = await api.matchmaker.checkinStats(userId)
+						console.log('获取签到统计返回结果:', stats)
+						if (stats) {
+							this.continuousDays = stats.continuousDays || 0
+							this.totalDays = stats.totalDays || 0
+						}
+					} else {
+						this.isSignedToday = false
+						this.continuousDays = 0
+						this.totalDays = 0
+						console.warn('没有有效的userId,无法检查签到状态')
+					}
+				} catch (error) {
+					console.error('检查签到状态失败:', error)
+					this.isSignedToday = false
+					this.continuousDays = 0
+					this.totalDays = 0
+				}
+			},
+			// 显示签到弹框
+			showSignInPopup() {
+				this.$refs.signInPopup.open()
+			},
+			// 关闭签到弹框
+			closeSignInPopup() {
+				this.$refs.signInPopup.close()
+			},
+			// 执行签到
+			async doSignIn() {
+				try {
+					const userInfo = uni.getStorageSync('userInfo')
+					const userId = userInfo && userInfo.userId ? userInfo.userId : null
+					if (userId) {
+						const res = await api.matchmaker.doCheckin(userId)
+						console.log('签到API返回结果:', res)
+						let success = false
+						let alreadySigned = false
+						if (typeof res === 'boolean') {
+							success = res
+						} else if (typeof res === 'string') {
+							success = res === 'true' || res === 'ok' || res === 'success'
+						} else if (res && typeof res === 'object') {
+							if (typeof res.success === 'boolean') success = res.success
+							if (typeof res.todaySigned === 'boolean') alreadySigned = res.todaySigned
+							else if (typeof res.signed === 'boolean') alreadySigned = res.signed
+							else if (typeof res.isSignedToday === 'boolean') alreadySigned = res.isSignedToday
+							if (res.data && typeof res.data === 'object') {
+								if (typeof res.data.success === 'boolean') success = res.data.success
+								if (typeof res.data.todaySigned === 'boolean') alreadySigned = res.data.todaySigned
+								else if (typeof res.data.signed === 'boolean') alreadySigned = res.data.signed
+								else if (typeof res.data.isSignedToday === 'boolean') alreadySigned = res.data.isSignedToday
+							}
+						}
+						if (success) {
+							uni.showToast({
+								title: '签到成功',
+								icon: 'success'
+							})
+							this.isSignedToday = true
+							this.generateCalendar()
+							this.loadProfileData()
+							this.closeSignInPopup()
+						} else if (alreadySigned) {
+							uni.showToast({
+								title: '今日已签到',
+								icon: 'none'
+							})
+						} else {
+							uni.showToast({
+								title: '签到失败,请稍后重试',
+								icon: 'none'
+							})
+						}
+					} else {
+						uni.showToast({
+							title: '请先登录',
+							icon: 'none'
+						})
+						setTimeout(() => {
+							uni.navigateTo({
+								url: '/pages/page3/page3'
+							})
+						}, 1000)
+					}
+				} catch (error) {
+					console.error('签到失败:', error)
+					uni.showToast({
+						title: error.msg || '签到失败,请稍后重试',
+						icon: 'none'
+					})
+				}
+			},
+			// 签到(旧方法,保持兼容)
+			handleSignIn() {
+				this.showSignInPopup()
+			},
+			// 我的资源
+			handleMyResources() {
+				uni.navigateTo({
+					url: '/pages/matchmaker-workbench/my-resources'
+				})
+			},
+			// 活动中心
+			handleActivityCenter() {
+				uni.navigateTo({
+					url: '/pages/activities/list'
+				})
+			},
+			// 积分商城
+			handlePointsMall() {
+				console.log('积分商城')
+				uni.showToast({
+					title: '积分商城开发中',
+					icon: 'none'
+				})
+			},
+			// 撮合成功审核
+			handleSuccessCaseUpload() {
+				console.log('撮合成功审核')
+				uni.navigateTo({
+					url: '/pages/matchmaker-workbench/success-case-upload'
+				})
+			},
+			// 编辑资料
+			handleEditProfile() {
+				console.log('编辑资料')
+				uni.navigateTo({
+					url: '/pages/matchmaker-workbench/edit-profile'
+				})
+			},
+			// 账户设置
+			handleAccountSettings() {
+				console.log('账户设置')
+				uni.showToast({
+					title: '账户设置开发中',
+					icon: 'none'
+				})
+			},
+			// 退出登录
+			handleLogout() {
+				uni.showModal({
+					title: '退出登录',
+					content: '确定要退出登录吗?',
+					success: (res) => {
+						if (res.confirm) {
+							uni.removeStorageSync('token')
+							uni.removeStorageSync('userInfo')
+							uni.navigateTo({
+								url: '/pages/page3/page3'
+							})
+						}
+					}
+				})
+			},
+			// 导航到工作台
+			navigateToWorkbench() {
+				uni.navigateTo({
+					url: '/pages/matchmaker-workbench/index'
+				})
+			},
+			// 导航到我的资源
+			navigateToMyResources() {
+				uni.navigateTo({
+					url: '/pages/matchmaker-workbench/my-resources'
+				})
+			},
+			// 导航到排行榜
+			navigateToRanking() {
+				uni.navigateTo({
+					url: '/pages/matchmaker-workbench/ranking'
+				})
+			},
+			// 导航到消息
+			navigateToMessage() {
+				uni.navigateTo({
+					url: '/pages/matchmaker-workbench/message'
+				})
+			},
+			// 导航到我的
+			navigateToMine() {
+				// 已在我的页面,无需跳转
+			}
+		}
+	}
 </script>
 
 <style lang="scss" scoped>
@@ -946,7 +1317,7 @@ export default {
     .settings-icon.logout {
       background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z"/></svg>');
     }
-    
+
     .settings-icon.info {
       background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>');
     }
@@ -956,6 +1327,15 @@ export default {
       font-size: 30rpx;
       color: #333;
     }
+			.settings-icon.success-case {
+				background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23E91E63"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>');
+			}
+
+			.settings-text {
+				flex: 1;
+				font-size: 30rpx;
+				color: #333;
+			}
 
     .arrow-right {
       width: 24rpx;
@@ -1109,31 +1489,31 @@ export default {
       padding: 20rpx;
       background: #FFF3E0;
       border-radius: 15rpx;
-      
+
       .stats-item {
         display: flex;
         flex-direction: column;
         align-items: center;
         gap: 10rpx;
-        
+
         .stats-label {
           font-size: 24rpx;
           color: #666;
         }
-        
+
         .stats-value {
           font-size: 48rpx;
           font-weight: bold;
           color: #E91E63;
         }
-        
+
         .stats-unit {
           font-size: 24rpx;
           color: #666;
         }
       }
     }
-    
+
     .calendar-container {
       background: #FFFFFF;
       border-radius: 15rpx;
@@ -1202,13 +1582,13 @@ export default {
             color: #E91E63;
             font-weight: bold;
           }
-          
+
           &.today-checked {
             background: #E91E63;
             color: #FFFFFF;
             font-weight: bold;
           }
-          
+
           .check-mark {
             position: absolute;
             bottom: 5rpx;
@@ -1223,7 +1603,7 @@ export default {
             align-items: center;
             justify-content: center;
           }
-          
+
           .today-checked .check-mark {
             background: #FFFFFF;
             color: #E91E63;

+ 968 - 0
LiangZhiYUMao/pages/matchmaker-workbench/success-case-upload.vue

@@ -0,0 +1,968 @@
+<template>
+	<view class="success-case-upload">
+		<!-- 顶部导航栏 -->
+		<view class="header">
+			<view class="back-btn" @click="goBack"></view>
+			<text class="header-title">撮合成功审核</text>
+			<view class="placeholder"></view>
+		</view>
+
+		<scroll-view scroll-y class="content">
+			<view class="form-container">
+				<!-- 男方信息 -->
+				<view class="form-section">
+					<view class="section-title">
+						<text class="title-icon male">♂</text>
+						<text class="title-text">男方信息</text>
+					</view>
+					
+					<!-- 选择男方用户 -->
+					<view class="form-item">
+						<text class="form-label">选择男方用户 <text class="required">*</text></text>
+						<view class="picker-wrapper" @click="openMalePicker">
+							<view class="selected-user" v-if="selectedMale">
+								<image class="user-avatar" :src="selectedMale.avatarUrl || '/static/default-avatar.svg'" mode="aspectFill"></image>
+								<text class="user-name">{{ selectedMale.name }}</text>
+								<text class="user-age" v-if="selectedMale.age">{{ selectedMale.age }}岁</text>
+							</view>
+							<text class="placeholder-text" v-else>请选择或搜索男方用户</text>
+							<view class="arrow-down"></view>
+						</view>
+					</view>
+					
+					<!-- 男方真实姓名 -->
+					<view class="form-item">
+						<text class="form-label">男方真实姓名 <text class="required">*</text></text>
+						<input class="form-input" v-model="formData.maleRealName" placeholder="请输入男方真实姓名" />
+					</view>
+				</view>
+
+				<!-- 女方信息 -->
+				<view class="form-section">
+					<view class="section-title">
+						<text class="title-icon female">♀</text>
+						<text class="title-text">女方信息</text>
+					</view>
+					
+					<!-- 选择女方用户 -->
+					<view class="form-item">
+						<text class="form-label">选择女方用户 <text class="required">*</text></text>
+						<view class="picker-wrapper" @click="openFemalePicker">
+							<view class="selected-user" v-if="selectedFemale">
+								<image class="user-avatar" :src="selectedFemale.avatarUrl || '/static/default-avatar.svg'" mode="aspectFill"></image>
+								<text class="user-name">{{ selectedFemale.name }}</text>
+								<text class="user-age" v-if="selectedFemale.age">{{ selectedFemale.age }}岁</text>
+							</view>
+							<text class="placeholder-text" v-else>请选择或搜索女方用户</text>
+							<view class="arrow-down"></view>
+						</view>
+					</view>
+					
+					<!-- 女方真实姓名 -->
+					<view class="form-item">
+						<text class="form-label">女方真实姓名 <text class="required">*</text></text>
+						<input class="form-input" v-model="formData.femaleRealName" placeholder="请输入女方真实姓名" />
+					</view>
+				</view>
+
+				<!-- 成功凭证 -->
+				<view class="form-section">
+					<view class="section-title">
+						<text class="title-icon proof">📷</text>
+						<text class="title-text">成功凭证</text>
+					</view>
+					
+					<view class="form-item">
+						<text class="form-label">上传凭证图片 <text class="required">*</text></text>
+						<text class="form-hint">请上传结婚证、订婚照片等凭证(最多9张)</text>
+						<view class="image-upload-area">
+							<view class="image-item" v-for="(img, index) in proofImages" :key="index">
+								<image class="uploaded-image" :src="img" mode="aspectFill"></image>
+								<view class="delete-btn" @click="removeImage(index)">×</view>
+							</view>
+							<view class="add-image-btn" v-if="proofImages.length < 9" @click="chooseImage">
+								<text class="add-icon">+</text>
+								<text class="add-text">添加图片</text>
+							</view>
+						</view>
+					</view>
+				</view>
+
+				<!-- 案例信息 -->
+				<view class="form-section">
+					<view class="section-title">
+						<text class="title-icon info">💕</text>
+						<text class="title-text">案例信息</text>
+					</view>
+					
+					<!-- 案例类型 -->
+					<view class="form-item">
+						<text class="form-label">案例类型 <text class="required">*</text></text>
+						<view class="radio-group">
+							<view class="radio-item" :class="{ active: formData.caseType === 1 }" @click="formData.caseType = 1">
+								<view class="radio-circle"></view>
+								<text class="radio-text">订婚</text>
+							</view>
+							<view class="radio-item" :class="{ active: formData.caseType === 2 }" @click="formData.caseType = 2">
+								<view class="radio-circle"></view>
+								<text class="radio-text">结婚</text>
+							</view>
+						</view>
+					</view>
+					
+					<!-- 成功日期 -->
+					<view class="form-item">
+						<text class="form-label">成功日期</text>
+						<picker mode="date" :value="formData.caseDate" @change="onDateChange">
+							<view class="date-picker">
+								<text class="date-text" v-if="formData.caseDate">{{ formData.caseDate }}</text>
+								<text class="placeholder-text" v-else>请选择日期</text>
+								<view class="calendar-icon">📅</view>
+							</view>
+						</picker>
+					</view>
+				</view>
+
+				<!-- 提交按钮 -->
+				<view class="submit-section">
+					<button class="submit-btn" :disabled="submitting" @click="submitForm">
+						<text v-if="submitting">提交中...</text>
+						<text v-else>提交审核</text>
+					</button>
+					<text class="submit-hint">提交后将由平台审核,审核通过后可获得积分奖励</text>
+				</view>
+			</view>
+		</scroll-view>
+
+		<!-- 用户选择弹窗 -->
+		<uni-popup ref="userPickerPopup" type="bottom">
+			<view class="user-picker-popup">
+				<view class="popup-header">
+					<text class="popup-title">{{ pickerGender === 1 ? '选择男方用户' : '选择女方用户' }}</text>
+					<view class="close-btn" @click="closeUserPicker">×</view>
+				</view>
+				
+				<!-- 搜索框 -->
+				<view class="search-box">
+					<input class="search-input" v-model="searchKeyword" placeholder="搜索姓名或手机号" @input="onSearchInput" />
+					<view class="search-icon">🔍</view>
+				</view>
+				
+				<!-- 用户列表 -->
+				<scroll-view scroll-y class="user-list">
+					<view class="user-item" v-for="user in filteredUsers" :key="user.resourceId" @click="selectUser(user)">
+						<image class="user-avatar" :src="user.avatarUrl || '/static/default-avatar.svg'" mode="aspectFill"></image>
+						<view class="user-info">
+							<text class="user-name">{{ user.name }}</text>
+							<text class="user-detail">{{ user.age }}岁 · {{ user.phone }}</text>
+						</view>
+					</view>
+					<view class="empty-tip" v-if="filteredUsers.length === 0">
+						<text>暂无匹配的用户</text>
+					</view>
+				</scroll-view>
+			</view>
+		</uni-popup>
+	</view>
+</template>
+
+<script>
+import api from '@/utils/api.js'
+
+export default {
+	data() {
+		return {
+			matchmakerId: null,
+			formData: {
+				maleRealName: '',
+				femaleRealName: '',
+				caseType: 2, // 默认结婚
+				caseDate: ''
+			},
+			selectedMale: null,
+			selectedFemale: null,
+			proofImages: [],
+			submitting: false,
+			resubmitId: null, // 重新提交时的原记录ID
+			
+			// 用户选择器
+			pickerGender: 1, // 1-男 2-女
+			searchKeyword: '',
+			maleUsers: [],
+			femaleUsers: [],
+			searchTimer: null
+		}
+	},
+	computed: {
+		filteredUsers() {
+			const users = this.pickerGender === 1 ? this.maleUsers : this.femaleUsers
+			if (!this.searchKeyword) {
+				return users
+			}
+			const keyword = this.searchKeyword.toLowerCase()
+			return users.filter(u => 
+				(u.name && u.name.toLowerCase().includes(keyword)) ||
+				(u.phone && u.phone.includes(keyword))
+			)
+		}
+	},
+	onLoad(options) {
+		this.loadMatchmakerInfo()
+		// 检查是否是重新提交
+		if (options.resubmitId) {
+			this.resubmitId = options.resubmitId
+			this.loadResubmitData(options.resubmitId)
+		}
+	},
+	onShow() {
+		this.loadResources()
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		},
+		
+		async loadMatchmakerInfo() {
+			try {
+				const userInfo = uni.getStorageSync('userInfo')
+				if (userInfo && userInfo.userId) {
+					// 通过userId获取红娘信息
+					const matchmakerInfo = await api.matchmaker.getByUserId(userInfo.userId)
+					console.log('红娘信息:', matchmakerInfo)
+					if (matchmakerInfo) {
+						// 兼容不同字段名
+						this.matchmakerId = matchmakerInfo.matchmakerId || matchmakerInfo.matchmaker_id || null
+						console.log('获取到红娘ID:', this.matchmakerId)
+					}
+					if (!this.matchmakerId) {
+						uni.showToast({ title: '未找到红娘信息', icon: 'none' })
+					}
+				}
+			} catch (e) {
+				console.error('获取红娘信息失败', e)
+				uni.showToast({ title: '获取红娘信息失败', icon: 'none' })
+			}
+		},
+		
+		// 加载重新提交的数据进行回显
+		async loadResubmitData(id) {
+			try {
+				const res = await api.successCaseUpload.getAuditRecordDetail(id)
+				console.log('回显数据:', res)
+				if (res) {
+					// 回显表单数据
+					this.formData.maleRealName = res.maleRealName || ''
+					this.formData.femaleRealName = res.femaleRealName || ''
+					this.formData.caseType = res.caseType || 2
+					this.formData.caseDate = res.caseDate || ''
+					
+					// 回显凭证图片(存储格式为JSON数组字符串)
+					if (res.proofImages) {
+						try {
+							const images = JSON.parse(res.proofImages)
+							this.proofImages = Array.isArray(images) ? images : []
+						} catch (e) {
+							// 兼容逗号分隔格式
+							this.proofImages = res.proofImages.split(',').filter(img => img)
+						}
+					}
+					
+					// 回显男方用户信息
+					if (res.maleUserId) {
+						const maleInfo = res.maleUserInfo || {}
+						this.selectedMale = {
+							userId: res.maleUserId,
+							name: maleInfo.name || res.maleRealName,
+							avatarUrl: maleInfo.avatarUrl || '',
+							age: maleInfo.age || null
+						}
+					}
+					
+					// 回显女方用户信息
+					if (res.femaleUserId) {
+						const femaleInfo = res.femaleUserInfo || {}
+						this.selectedFemale = {
+							userId: res.femaleUserId,
+							name: femaleInfo.name || res.femaleRealName,
+							avatarUrl: femaleInfo.avatarUrl || '',
+							age: femaleInfo.age || null
+						}
+					}
+				}
+			} catch (e) {
+				console.error('加载回显数据失败', e)
+				uni.showToast({ title: '加载数据失败', icon: 'none' })
+			}
+		},
+		
+		async loadResources() {
+			if (!this.matchmakerId) {
+				setTimeout(() => this.loadResources(), 500)
+				return
+			}
+			
+			try {
+				// 加载男方用户列表(只查询已注册用户,user_id不为空)
+				const maleRes = await api.myResource.getRegisteredDropdown(this.matchmakerId, 1)
+				console.log('男方资源返回:', maleRes)
+				this.maleUsers = Array.isArray(maleRes) ? maleRes : (maleRes?.data || [])
+				
+				// 加载女方用户列表(只查询已注册用户,user_id不为空)
+				const femaleRes = await api.myResource.getRegisteredDropdown(this.matchmakerId, 2)
+				console.log('女方资源返回:', femaleRes)
+				this.femaleUsers = Array.isArray(femaleRes) ? femaleRes : (femaleRes?.data || [])
+			} catch (e) {
+				console.error('加载资源列表失败', e)
+			}
+		},
+		
+		openMalePicker() {
+			this.pickerGender = 1
+			this.searchKeyword = ''
+			this.$refs.userPickerPopup.open()
+		},
+		
+		openFemalePicker() {
+			this.pickerGender = 2
+			this.searchKeyword = ''
+			this.$refs.userPickerPopup.open()
+		},
+		
+		closeUserPicker() {
+			this.$refs.userPickerPopup.close()
+		},
+		
+		selectUser(user) {
+			if (this.pickerGender === 1) {
+				this.selectedMale = user
+				// 自动填充真实姓名(每次选择都更新)
+				this.formData.maleRealName = user.name || ''
+			} else {
+				this.selectedFemale = user
+				// 自动填充真实姓名(每次选择都更新)
+				this.formData.femaleRealName = user.name || ''
+			}
+			this.closeUserPicker()
+		},
+		
+		onSearchInput() {
+			// 防抖搜索
+			if (this.searchTimer) {
+				clearTimeout(this.searchTimer)
+			}
+			this.searchTimer = setTimeout(() => {
+				this.searchResources()
+			}, 300)
+		},
+		
+		async searchResources() {
+			if (!this.matchmakerId) return
+			
+			try {
+				// 如果关键词为空,重新加载当前性别的完整列表
+				if (!this.searchKeyword || !this.searchKeyword.trim()) {
+					const res = await api.myResource.getRegisteredDropdown(this.matchmakerId, this.pickerGender)
+					console.log('重新加载资源列表:', res)
+					const users = Array.isArray(res) ? res : (res?.data || [])
+					if (this.pickerGender === 1) {
+						this.maleUsers = users
+					} else {
+						this.femaleUsers = users
+					}
+					return
+				}
+				
+				// 使用已注册用户搜索接口(只返回user_id不为空的资源)
+				const res = await api.myResource.searchRegistered(this.matchmakerId, this.searchKeyword.trim(), this.pickerGender)
+				console.log('搜索资源返回:', res)
+				const users = Array.isArray(res) ? res : (res?.data || [])
+				if (this.pickerGender === 1) {
+					this.maleUsers = users
+				} else {
+					this.femaleUsers = users
+				}
+			} catch (e) {
+				console.error('搜索资源失败', e)
+			}
+		},
+		
+		async chooseImage() {
+			try {
+				// uni.chooseImage 在 Promise 模式下返回 [err, res] 格式
+				const [err, res] = await uni.chooseImage({
+					count: 9 - this.proofImages.length,
+					sizeType: ['compressed'],
+					sourceType: ['album', 'camera']
+				})
+				
+				console.log('选择图片返回 err:', err)
+				console.log('选择图片返回 res:', res)
+				
+				if (err) {
+					console.error('选择图片失败', err)
+					return
+				}
+				
+				if (res && res.tempFilePaths && res.tempFilePaths.length > 0) {
+					uni.showLoading({ title: '上传中...' })
+					
+					for (const tempPath of res.tempFilePaths) {
+						console.log('准备上传图片:', tempPath)
+						try {
+							const uploadRes = await this.uploadImage(tempPath)
+							console.log('上传结果:', uploadRes)
+							if (uploadRes) {
+								this.proofImages.push(uploadRes)
+							}
+						} catch (e) {
+							console.error('上传图片失败', e)
+							uni.showToast({ title: '图片上传失败', icon: 'none' })
+						}
+					}
+					
+					uni.hideLoading()
+					console.log('当前图片列表:', this.proofImages)
+				}
+			} catch (e) {
+				console.error('选择图片异常', e)
+			}
+		},
+		
+		async uploadImage(tempPath) {
+			return new Promise((resolve, reject) => {
+				uni.uploadFile({
+					url: 'http://localhost:8083/api/success-case-upload/upload-image',
+					filePath: tempPath,
+					name: 'file',
+					success: (res) => {
+						try {
+							const data = JSON.parse(res.data)
+							if (data.code === 200 && data.data) {
+								resolve(data.data)
+							} else {
+								uni.showToast({ title: data.message || '上传失败', icon: 'none' })
+								reject(new Error(data.message))
+							}
+						} catch (e) {
+							reject(e)
+						}
+					},
+					fail: (err) => {
+						reject(err)
+					}
+				})
+			})
+		},
+		
+		removeImage(index) {
+			this.proofImages.splice(index, 1)
+		},
+		
+		onDateChange(e) {
+			this.formData.caseDate = e.detail.value
+		},
+		
+		async submitForm() {
+			// 表单验证
+			if (!this.selectedMale) {
+				uni.showToast({ title: '请选择男方用户', icon: 'none' })
+				return
+			}
+			if (!this.selectedFemale) {
+				uni.showToast({ title: '请选择女方用户', icon: 'none' })
+				return
+			}
+			if (this.selectedMale.userId === this.selectedFemale.userId) {
+				uni.showToast({ title: '男方和女方不能是同一人', icon: 'none' })
+				return
+			}
+			if (!this.formData.maleRealName.trim()) {
+				uni.showToast({ title: '请填写男方真实姓名', icon: 'none' })
+				return
+			}
+			if (!this.formData.femaleRealName.trim()) {
+				uni.showToast({ title: '请填写女方真实姓名', icon: 'none' })
+				return
+			}
+			if (this.proofImages.length === 0) {
+				uni.showToast({ title: '请上传至少一张成功凭证图片', icon: 'none' })
+				return
+			}
+			
+			this.submitting = true
+			
+			try {
+				const submitData = {
+					matchmakerId: this.matchmakerId,
+					maleUserId: this.selectedMale.userId,  // 传递users表的user_id
+					femaleUserId: this.selectedFemale.userId,  // 传递users表的user_id
+					maleRealName: this.formData.maleRealName.trim(),
+					femaleRealName: this.formData.femaleRealName.trim(),
+					proofImages: this.proofImages,
+					caseType: this.formData.caseType,
+					caseDate: this.formData.caseDate || null
+				}
+				
+				const res = await api.successCaseUpload.submit(submitData)
+				console.log('提交结果:', res)
+				
+				// request函数成功时返回的是data字段的内容(这里是id)
+				// 如果能走到这里说明请求成功了
+				if (res !== undefined && res !== null) {
+					uni.showToast({ title: '提交成功', icon: 'success' })
+					setTimeout(() => {
+						uni.navigateBack()
+					}, 1500)
+				} else {
+					uni.showToast({ title: '提交失败', icon: 'none' })
+				}
+			} catch (e) {
+				console.error('提交失败', e)
+				uni.showToast({ title: '提交失败,请稍后重试', icon: 'none' })
+			} finally {
+				this.submitting = false
+			}
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+/* 确保所有元素使用border-box */
+* {
+	box-sizing: border-box;
+}
+
+.success-case-upload {
+	min-height: 100vh;
+	background: linear-gradient(180deg, #f8e1f4 0%, #fff5f5 100%);
+	display: flex;
+	flex-direction: column;
+	width: 100%;
+	overflow-x: hidden;
+}
+
+.header {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 60rpx 30rpx 30rpx;
+	background: linear-gradient(135deg, #d63384 0%, #9c27b0 100%);
+	
+	.back-btn {
+		width: 60rpx;
+		height: 60rpx;
+		background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'/%3E%3C/svg%3E") no-repeat center;
+		background-size: contain;
+	}
+	
+	.header-title {
+		font-size: 36rpx;
+		font-weight: bold;
+		color: #fff;
+	}
+	
+	.placeholder {
+		width: 60rpx;
+	}
+}
+
+.content {
+	flex: 1;
+	padding: 30rpx 30rpx 30rpx 30rpx;
+	box-sizing: border-box;
+}
+
+.form-container {
+	display: flex;
+	flex-direction: column;
+	gap: 30rpx;
+	width: 100%;
+}
+
+.form-section {
+	background: #fff;
+	border-radius: 20rpx;
+	padding: 30rpx;
+	box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
+	box-sizing: border-box;
+	width: 100%;
+}
+
+.section-title {
+	display: flex;
+	align-items: center;
+	margin-bottom: 30rpx;
+	
+	.title-icon {
+		font-size: 36rpx;
+		margin-right: 16rpx;
+		
+		&.male { color: #2196F3; }
+		&.female { color: #E91E63; }
+		&.proof { color: #FF9800; }
+		&.info { color: #9C27B0; }
+	}
+	
+	.title-text {
+		font-size: 32rpx;
+		font-weight: bold;
+		color: #333;
+	}
+}
+
+.form-item {
+	margin-bottom: 30rpx;
+	
+	&:last-child {
+		margin-bottom: 0;
+	}
+}
+
+.form-label {
+	display: block;
+	font-size: 28rpx;
+	color: #666;
+	margin-bottom: 16rpx;
+	
+	.required {
+		color: #E91E63;
+	}
+}
+
+.form-hint {
+	display: block;
+	font-size: 24rpx;
+	color: #999;
+	margin-bottom: 16rpx;
+}
+
+.form-input {
+	width: 100%;
+	height: 88rpx;
+	background: #f5f5f5;
+	border-radius: 12rpx;
+	padding: 0 24rpx;
+	font-size: 28rpx;
+	box-sizing: border-box;
+	border: none;
+}
+
+.form-textarea {
+	width: 100%;
+	height: 200rpx;
+	background: #f5f5f5;
+	border-radius: 12rpx;
+	padding: 24rpx;
+	font-size: 28rpx;
+	box-sizing: border-box;
+}
+
+.char-count {
+	display: block;
+	text-align: right;
+	font-size: 24rpx;
+	color: #999;
+	margin-top: 8rpx;
+}
+
+.picker-wrapper {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	height: 88rpx;
+	background: #f5f5f5;
+	border-radius: 12rpx;
+	padding: 0 24rpx;
+	
+	.placeholder-text {
+		color: #999;
+		font-size: 28rpx;
+	}
+	
+	.arrow-down {
+		width: 24rpx;
+		height: 24rpx;
+		border-right: 4rpx solid #999;
+		border-bottom: 4rpx solid #999;
+		transform: rotate(45deg);
+	}
+}
+
+.selected-user {
+	display: flex;
+	align-items: center;
+	flex: 1;
+	
+	.user-avatar {
+		width: 56rpx;
+		height: 56rpx;
+		border-radius: 50%;
+		margin-right: 16rpx;
+	}
+	
+	.user-name {
+		font-size: 28rpx;
+		color: #333;
+		margin-right: 16rpx;
+	}
+	
+	.user-age {
+		font-size: 24rpx;
+		color: #999;
+	}
+}
+
+.image-upload-area {
+	display: flex;
+	flex-wrap: wrap;
+	gap: 20rpx;
+}
+
+.image-item {
+	position: relative;
+	width: 200rpx;
+	height: 200rpx;
+	
+	.uploaded-image {
+		width: 100%;
+		height: 100%;
+		border-radius: 12rpx;
+	}
+	
+	.delete-btn {
+		position: absolute;
+		top: -16rpx;
+		right: -16rpx;
+		width: 40rpx;
+		height: 40rpx;
+		background: #E91E63;
+		border-radius: 50%;
+		color: #fff;
+		font-size: 28rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+}
+
+.add-image-btn {
+	width: 200rpx;
+	height: 200rpx;
+	background: #f5f5f5;
+	border: 2rpx dashed #ccc;
+	border-radius: 12rpx;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	
+	.add-icon {
+		font-size: 60rpx;
+		color: #999;
+	}
+	
+	.add-text {
+		font-size: 24rpx;
+		color: #999;
+		margin-top: 8rpx;
+	}
+}
+
+.radio-group {
+	display: flex;
+	gap: 40rpx;
+}
+
+.radio-item {
+	display: flex;
+	align-items: center;
+	padding: 20rpx 40rpx;
+	background: #f5f5f5;
+	border-radius: 12rpx;
+	
+	&.active {
+		background: linear-gradient(135deg, #d63384 0%, #9c27b0 100%);
+		
+		.radio-circle {
+			border-color: #fff;
+			
+			&::after {
+				content: '';
+				width: 16rpx;
+				height: 16rpx;
+				background: #fff;
+				border-radius: 50%;
+			}
+		}
+		
+		.radio-text {
+			color: #fff;
+		}
+	}
+	
+	.radio-circle {
+		width: 32rpx;
+		height: 32rpx;
+		border: 4rpx solid #999;
+		border-radius: 50%;
+		margin-right: 16rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+	
+	.radio-text {
+		font-size: 28rpx;
+		color: #333;
+	}
+}
+
+.date-picker {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	height: 88rpx;
+	background: #f5f5f5;
+	border-radius: 12rpx;
+	padding: 0 24rpx;
+	
+	.date-text {
+		font-size: 28rpx;
+		color: #333;
+	}
+	
+	.placeholder-text {
+		font-size: 28rpx;
+		color: #999;
+	}
+	
+	.calendar-icon {
+		font-size: 36rpx;
+	}
+}
+
+.submit-section {
+	margin-top: 20rpx;
+	padding: 0 0 60rpx 0;
+	width: 100%;
+	box-sizing: border-box;
+}
+
+.submit-btn {
+	width: 100%;
+	height: 96rpx;
+	background: linear-gradient(135deg, #d63384 0%, #9c27b0 100%);
+	border-radius: 48rpx;
+	color: #fff;
+	font-size: 32rpx;
+	font-weight: bold;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	border: none;
+	
+	&[disabled] {
+		opacity: 0.6;
+	}
+}
+
+.submit-hint {
+	display: block;
+	text-align: center;
+	font-size: 24rpx;
+	color: #999;
+	margin-top: 20rpx;
+}
+
+/* 用户选择弹窗 */
+.user-picker-popup {
+	background: #fff;
+	border-radius: 30rpx 30rpx 0 0;
+	max-height: 80vh;
+	display: flex;
+	flex-direction: column;
+}
+
+.popup-header {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 30rpx;
+	border-bottom: 1rpx solid #eee;
+	
+	.popup-title {
+		font-size: 32rpx;
+		font-weight: bold;
+		color: #333;
+	}
+	
+	.close-btn {
+		font-size: 48rpx;
+		color: #999;
+		line-height: 1;
+	}
+}
+
+.search-box {
+	display: flex;
+	align-items: center;
+	margin: 20rpx 30rpx;
+	background: #f5f5f5;
+	border-radius: 40rpx;
+	padding: 0 30rpx;
+	
+	.search-input {
+		flex: 1;
+		height: 80rpx;
+		font-size: 28rpx;
+	}
+	
+	.search-icon {
+		font-size: 36rpx;
+	}
+}
+
+.user-list {
+	flex: 1;
+	max-height: 60vh;
+	padding: 0 30rpx;
+}
+
+.user-item {
+	display: flex;
+	align-items: center;
+	padding: 24rpx 0;
+	border-bottom: 1rpx solid #f5f5f5;
+	
+	.user-avatar {
+		width: 80rpx;
+		height: 80rpx;
+		border-radius: 50%;
+		margin-right: 24rpx;
+	}
+	
+	.user-info {
+		flex: 1;
+		
+		.user-name {
+			display: block;
+			font-size: 30rpx;
+			color: #333;
+			margin-bottom: 8rpx;
+		}
+		
+		.user-detail {
+			font-size: 24rpx;
+			color: #999;
+		}
+	}
+}
+
+.empty-tip {
+	padding: 60rpx 0;
+	text-align: center;
+	color: #999;
+	font-size: 28rpx;
+}
+</style>

+ 94 - 0
LiangZhiYUMao/utils/api.js

@@ -866,6 +866,100 @@ export default {
       method: 'POST',
       data: { makerId, ruleType, reason }
     })
+  },
+
+  // 我的资源相关(通过网关访问8081服务)
+  myResource: {
+    // 获取资源列表
+    getList: (matchmakerId, keyword, pageNum = 1, pageSize = 10) => {
+      let url = `/my-resource/list?matchmakerId=${matchmakerId}&pageNum=${pageNum}&pageSize=${pageSize}`
+      if (keyword) {
+        url += `&keyword=${encodeURIComponent(keyword)}`
+      }
+      return request({ url })
+    },
+    
+    // 搜索资源(按姓名或手机号)
+    search: (matchmakerId, keyword, gender) => {
+      let url = `/my-resource/search?matchmakerId=${matchmakerId}`
+      if (keyword) {
+        url += `&keyword=${encodeURIComponent(keyword)}`
+      }
+      if (gender) {
+        url += `&gender=${gender}`
+      }
+      return request({ url })
+    },
+    
+    // 获取资源下拉列表
+    getDropdown: (matchmakerId, gender) => {
+      let url = `/my-resource/dropdown?matchmakerId=${matchmakerId}`
+      if (gender) {
+        url += `&gender=${gender}`
+      }
+      return request({ url })
+    },
+    
+    // 获取已注册用户的资源下拉列表(user_id不为空)
+    getRegisteredDropdown: (matchmakerId, gender) => {
+      let url = `/my-resource/registered-dropdown?matchmakerId=${matchmakerId}`
+      if (gender) {
+        url += `&gender=${gender}`
+      }
+      return request({ url })
+    },
+    
+    // 搜索已注册用户的资源(user_id不为空)
+    searchRegistered: (matchmakerId, keyword, gender) => {
+      let url = `/my-resource/registered-search?matchmakerId=${matchmakerId}`
+      if (keyword) {
+        url += `&keyword=${encodeURIComponent(keyword)}`
+      }
+      if (gender) {
+        url += `&gender=${gender}`
+      }
+      return request({ url })
+    }
+  },
+
+  // 撮合成功案例上传相关(通过网关访问1004服务)
+  successCaseUpload: {
+    // 提交成功案例
+    submit: (data) => request({
+      url: '/success-case-upload/submit',
+      method: 'POST',
+      data
+    }),
+    
+    // 获取成功案例列表
+    getList: (matchmakerId, pageNum = 1, pageSize = 10) => request({
+      url: `/success-case-upload/list?matchmakerId=${matchmakerId}&pageNum=${pageNum}&pageSize=${pageSize}`
+    }),
+    
+    // 获取审核记录列表
+    getAuditRecords: (matchmakerId, auditStatus, pageNum = 1, pageSize = 20) => {
+      let url = `/success-case-upload/audit-records?matchmakerId=${matchmakerId}&pageNum=${pageNum}&pageSize=${pageSize}`
+      if (auditStatus !== null && auditStatus !== undefined) {
+        url += `&auditStatus=${auditStatus}`
+      }
+      return request({ url })
+    },
+    
+    // 获取审核记录详情
+    getAuditRecordDetail: (id) => request({
+      url: `/success-case-upload/audit-records/${id}`
+    }),
+    
+    // 标记审核记录为已读
+    markAsRead: (id) => request({
+      url: `/success-case-upload/audit-records/${id}/read`,
+      method: 'POST'
+    }),
+    
+    // 获取未读审核记录数量
+    getUnreadCount: (matchmakerId) => request({
+      url: `/success-case-upload/audit-records/unread-count?matchmakerId=${matchmakerId}`
+    })
   }
 }
 

+ 16 - 0
gateway/src/main/resources/application.yml

@@ -188,6 +188,22 @@ spring:
           filters:
             - StripPrefix=0
 
+        # 我的资源服务路由(homePage服务)
+        - id: my-resource-route
+          uri: http://localhost:8081
+          predicates:
+            - Path=/api/my-resource/**
+          filters:
+            - StripPrefix=0
+
+        # 撮合成功案例上传路由(websocket服务)
+        - id: success-case-upload-route
+          uri: http://localhost:1004
+          predicates:
+            - Path=/api/success-case-upload/**
+          filters:
+            - StripPrefix=0
+
         # 首页服务路由(兜底路由)
         - id: homepage-route
           uri: http://localhost:8081

+ 235 - 10
service/homePage/src/main/java/com/zhentao/controller/MyResourceController.java

@@ -35,7 +35,7 @@ public class MyResourceController {
     
     @Autowired
     private MatchmakerService matchmakerService;
-    
+
     /**
      * 添加资源信息
      * 
@@ -152,7 +152,7 @@ public class MyResourceController {
                     QueryWrapper<Matchmaker> matchmakerQueryWrapper = new QueryWrapper<>();
                     matchmakerQueryWrapper.eq("user_id", currentUserId);
                     Matchmaker matchmaker = matchmakerService.getOne(matchmakerQueryWrapper);
-                    
+
                     if (matchmaker != null && matchmaker.getMatchmakerId() != null) {
                         // 查询到红娘信息,设置matchmaker_id
                         myResource.setMatchmakerId(matchmaker.getMatchmakerId());
@@ -204,7 +204,7 @@ public class MyResourceController {
                     QueryWrapper<Matchmaker> matchmakerQueryWrapper = new QueryWrapper<>();
                     matchmakerQueryWrapper.eq("user_id", currentUserId);
                     Matchmaker matchmaker = matchmakerService.getOne(matchmakerQueryWrapper);
-                    
+
                     if (matchmaker != null && matchmaker.getMatchmakerId() != null) {
                         // 查询到红娘信息,使用查询到的matchmaker_id
                         matchmakerId = matchmaker.getMatchmakerId();
@@ -358,7 +358,7 @@ public class MyResourceController {
                 System.out.println("  - originalPhone是否为null: " + (result.getOriginalPhone() == null));
                 System.out.println("  - originalPhone是否为空字符串: " + (result.getOriginalPhone() != null && result.getOriginalPhone().isEmpty()));
             }
-            
+
             System.out.println("匹配成功,返回 " + matchResults.size() + " 个结果");
             return Result.success(matchResults);
         } catch (Exception e) {
@@ -616,11 +616,11 @@ public class MyResourceController {
             return Result.error("获取最新跟进记录失败:" + e.getMessage());
         }
     }
-    
+
     /**
      * 根据标签名称分页查询资源列表(包含用户头像)
      * 用于优质资源列表等场景
-     * 
+     *
      * @param tagName 标签名称(如"优质资源")
      * @param keyword 搜索关键词(可选)
      * @param pageNum 页码(默认1)
@@ -641,13 +641,13 @@ public class MyResourceController {
                 QueryWrapper<Matchmaker> matchmakerQueryWrapper = new QueryWrapper<>();
                 matchmakerQueryWrapper.eq("user_id", currentUserId);
                 Matchmaker matchmaker = matchmakerService.getOne(matchmakerQueryWrapper);
-                
+
                 if (matchmaker != null && matchmaker.getMatchmakerId() != null) {
                     matchmakerId = matchmaker.getMatchmakerId();
                     System.out.println("根据user_id=" + currentUserId + "查询到matchmaker_id=" + matchmakerId);
                 }
             }
-            
+
             Page<MyResourceVO> page = myResourceService.getResourceListByTagName(tagName, keyword, matchmakerId, pageNum, pageSize);
             return Result.success(page);
         } catch (Exception e) {
@@ -655,10 +655,10 @@ public class MyResourceController {
             return Result.error("查询资源列表失败:" + e.getMessage());
         }
     }
-    
+
     /**
      * 根据资源ID获取标签ID列表
-     * 
+     *
      * @param resourceId 资源ID
      * @return 标签ID列表
      */
@@ -672,5 +672,230 @@ public class MyResourceController {
             return Result.error("查询标签ID列表失败:" + e.getMessage());
         }
     }
+
+    /**
+     * 搜索资源(按姓名或手机号)
+     * 用于撮合成功审核页面的下拉搜索
+     *
+     * @param matchmakerId 红娘ID
+     * @param keyword 搜索关键词(姓名或手机号)
+     * @param gender 性别筛选(1-男,2-女,可选)
+     * @return 匹配的资源列表
+     */
+    @GetMapping("/search")
+    public Result<java.util.List<MyResourceVO>> searchResources(
+            @RequestParam Integer matchmakerId,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Integer gender) {
+        try {
+            System.out.println("=== 搜索资源 ===");
+            System.out.println("matchmakerId: " + matchmakerId);
+            System.out.println("keyword: " + keyword);
+            System.out.println("gender: " + gender);
+
+            java.util.List<MyResourceVO> resources = myResourceService.searchResources(matchmakerId, keyword, gender);
+
+            System.out.println("搜索结果数量: " + resources.size());
+            return Result.success(resources);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("搜索资源失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取资源下拉列表(用于撮合成功审核页面)
+     * 返回简化的资源信息:resourceId, name, avatarUrl, gender, phone
+     *
+     * @param matchmakerId 红娘ID
+     * @param gender 性别筛选(1-男,2-女)
+     * @return 资源下拉列表
+     */
+    @GetMapping("/dropdown")
+    public Result<java.util.List<java.util.Map<String, Object>>> getResourceDropdown(
+            @RequestParam Integer matchmakerId,
+            @RequestParam(required = false) Integer gender) {
+        try {
+            System.out.println("=== 获取资源下拉列表 ===");
+            System.out.println("matchmakerId: " + matchmakerId);
+            System.out.println("gender: " + gender);
+
+            java.util.List<MyResourceVO> resources = myResourceService.searchResources(matchmakerId, null, gender);
+
+            // 转换为简化的下拉列表格式
+            java.util.List<java.util.Map<String, Object>> dropdown = new java.util.ArrayList<>();
+            for (MyResourceVO resource : resources) {
+                java.util.Map<String, Object> item = new java.util.HashMap<>();
+                item.put("resourceId", resource.getResourceId());
+                item.put("name", resource.getName());
+                item.put("avatarUrl", resource.getAvatarUrl());
+                item.put("gender", resource.getGender());
+                item.put("phone", resource.getPhone());
+                item.put("age", resource.getAge());
+                dropdown.add(item);
+            }
+
+            System.out.println("下拉列表数量: " + dropdown.size());
+            return Result.success(dropdown);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取资源下拉列表失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取已注册用户的资源下拉列表(用于撮合成功审核页面)
+     * 只返回user_id不为空的资源(即已注册为平台用户的资源)
+     *
+     * @param matchmakerId 红娘ID
+     * @param gender 性别筛选(1-男,2-女)
+     * @return 已注册用户的资源下拉列表
+     */
+    @GetMapping("/registered-dropdown")
+    public Result<java.util.List<java.util.Map<String, Object>>> getRegisteredResourceDropdown(
+            @RequestParam Integer matchmakerId,
+            @RequestParam(required = false) Integer gender) {
+        try {
+            System.out.println("=== 获取已注册用户资源下拉列表 ===");
+            System.out.println("matchmakerId: " + matchmakerId);
+            System.out.println("gender: " + gender);
+
+            // 查询资源列表
+            com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<MyResource> queryWrapper =
+                new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
+            queryWrapper.eq("matchmaker_id", matchmakerId);
+            // 只查询user_id不为空的资源(已注册用户)
+            queryWrapper.isNotNull("user_id");
+
+            // 性别筛选
+            if (gender != null) {
+                queryWrapper.eq("gender", gender);
+            }
+
+            // 按创建时间降序
+            queryWrapper.orderByDesc("create_time");
+            queryWrapper.last("LIMIT 100");
+
+            java.util.List<MyResource> resources = myResourceService.list(queryWrapper);
+            System.out.println("查询到已注册资源数量: " + resources.size());
+
+            // 转换为简化的下拉列表格式,并查询用户头像
+            java.util.List<java.util.Map<String, Object>> dropdown = new java.util.ArrayList<>();
+            for (MyResource resource : resources) {
+                java.util.Map<String, Object> item = new java.util.HashMap<>();
+                item.put("resourceId", resource.getResourceId());
+                item.put("userId", resource.getUserId());
+                item.put("name", resource.getName());
+                item.put("gender", resource.getGender());
+                item.put("phone", resource.getPhone());
+                item.put("age", resource.getAge());
+
+                // 查询用户头像
+                String avatarUrl = "/static/default-avatar.svg";
+                if (resource.getUserId() != null) {
+                    com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<User> userQuery =
+                        new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
+                    userQuery.eq("user_id", resource.getUserId());
+                    User user = userMapper.selectOne(userQuery);
+                    if (user != null && user.getAvatarUrl() != null) {
+                        avatarUrl = user.getAvatarUrl();
+                    }
+                }
+                item.put("avatarUrl", avatarUrl);
+
+                dropdown.add(item);
+            }
+
+            System.out.println("已注册用户下拉列表数量: " + dropdown.size());
+            return Result.success(dropdown);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("获取已注册用户资源下拉列表失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 搜索已注册用户的资源(按姓名或手机号)
+     * 只返回user_id不为空的资源
+     *
+     * @param matchmakerId 红娘ID
+     * @param keyword 搜索关键词(姓名或手机号)
+     * @param gender 性别筛选(1-男,2-女,可选)
+     * @return 匹配的已注册用户资源列表
+     */
+    @GetMapping("/registered-search")
+    public Result<java.util.List<java.util.Map<String, Object>>> searchRegisteredResources(
+            @RequestParam Integer matchmakerId,
+            @RequestParam(required = false) String keyword,
+            @RequestParam(required = false) Integer gender) {
+        try {
+            System.out.println("=== 搜索已注册用户资源 ===");
+            System.out.println("matchmakerId: " + matchmakerId);
+            System.out.println("keyword: " + keyword);
+            System.out.println("gender: " + gender);
+
+            // 构建查询条件
+            com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<MyResource> queryWrapper =
+                new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
+            queryWrapper.eq("matchmaker_id", matchmakerId);
+            // 只查询user_id不为空的资源(已注册用户)
+            queryWrapper.isNotNull("user_id");
+
+            // 性别筛选
+            if (gender != null) {
+                queryWrapper.eq("gender", gender);
+            }
+
+            // 关键词搜索(姓名或手机号)
+            if (keyword != null && !keyword.trim().isEmpty()) {
+                String kw = keyword.trim();
+                queryWrapper.and(wrapper ->
+                    wrapper.like("name", kw)
+                           .or()
+                           .like("phone", kw)
+                );
+            }
+
+            // 按创建时间降序
+            queryWrapper.orderByDesc("create_time");
+            queryWrapper.last("LIMIT 100");
+
+            java.util.List<MyResource> resources = myResourceService.list(queryWrapper);
+            System.out.println("搜索到已注册资源数量: " + resources.size());
+
+            // 转换为简化的列表格式,并查询用户头像
+            java.util.List<java.util.Map<String, Object>> result = new java.util.ArrayList<>();
+            for (MyResource resource : resources) {
+                java.util.Map<String, Object> item = new java.util.HashMap<>();
+                item.put("resourceId", resource.getResourceId());
+                item.put("userId", resource.getUserId());
+                item.put("name", resource.getName());
+                item.put("gender", resource.getGender());
+                item.put("phone", resource.getPhone());
+                item.put("age", resource.getAge());
+
+                // 查询用户头像
+                String avatarUrl = "/static/default-avatar.svg";
+                if (resource.getUserId() != null) {
+                    com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<User> userQuery =
+                        new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
+                    userQuery.eq("user_id", resource.getUserId());
+                    User user = userMapper.selectOne(userQuery);
+                    if (user != null && user.getAvatarUrl() != null) {
+                        avatarUrl = user.getAvatarUrl();
+                    }
+                }
+                item.put("avatarUrl", avatarUrl);
+
+                result.add(item);
+            }
+
+            System.out.println("搜索结果数量: " + result.size());
+            return Result.success(result);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("搜索已注册用户资源失败:" + e.getMessage());
+        }
+    }
 }
 

+ 12 - 2
service/homePage/src/main/java/com/zhentao/service/MyResourceService.java

@@ -73,7 +73,7 @@ public interface MyResourceService extends IService<MyResource> {
      * @return 匹配结果列表(最多3个,按匹配度降序)
      */
     java.util.List<com.zhentao.vo.MatchResultVO> preciseMatch(Integer resourceId);
-    
+
     /**
      * 根据标签名称分页查询资源列表(包含用户头像)
      * @param tagName 标签名称
@@ -84,11 +84,21 @@ public interface MyResourceService extends IService<MyResource> {
      * @return 分页数据(包含头像)
      */
     Page<com.zhentao.vo.MyResourceVO> getResourceListByTagName(String tagName, String keyword, Integer excludeMatchmakerId, Integer pageNum, Integer pageSize);
-    
+
     /**
      * 根据资源ID获取标签ID列表
      * @param resourceId 资源ID
      * @return 标签ID列表
      */
     List<Integer> getTagIdsByResourceId(Integer resourceId);
+
+    /**
+     * 搜索资源(按姓名或手机号)
+     * 用于撮合成功审核页面的下拉搜索
+     * @param matchmakerId 红娘ID
+     * @param keyword 搜索关键词(姓名或手机号)
+     * @param gender 性别筛选(1-男,2-女,可选)
+     * @return 匹配的资源列表
+     */
+    java.util.List<com.zhentao.vo.MyResourceVO> searchResources(Integer matchmakerId, String keyword, Integer gender);
 }

+ 81 - 0
service/homePage/src/main/java/com/zhentao/service/impl/MyResourceServiceImpl.java

@@ -891,6 +891,87 @@ public class MyResourceServiceImpl extends ServiceImpl<MyResourceMapper, MyResou
         }
         return sb.length() > 0 ? sb.toString().trim() : "暂无要求";
     }
+
+    @Override
+    public List<MyResourceVO> searchResources(Integer matchmakerId, String keyword, Integer gender) {
+        System.out.println("=== searchResources 开始 ===");
+        System.out.println("matchmakerId: " + matchmakerId + ", keyword: " + keyword + ", gender: " + gender);
+        
+        // 构建查询条件
+        QueryWrapper<MyResource> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("matchmaker_id", matchmakerId);
+        
+        // 性别筛选
+        if (gender != null) {
+            queryWrapper.eq("gender", gender);
+        }
+        
+        // 关键词搜索(姓名或手机号)
+        if (StringUtils.hasText(keyword)) {
+            queryWrapper.and(wrapper -> 
+                wrapper.like("name", keyword)
+                       .or()
+                       .like("phone", keyword)
+            );
+        }
+        
+        // 按创建时间降序
+        queryWrapper.orderByDesc("create_time");
+        
+        // 限制返回数量,避免数据过多
+        queryWrapper.last("LIMIT 100");
+        
+        List<MyResource> resources = myResourceMapper.selectList(queryWrapper);
+        System.out.println("查询到资源数量: " + resources.size());
+        
+        // 转换为VO并填充头像
+        List<MyResourceVO> voList = new ArrayList<>();
+        for (MyResource resource : resources) {
+            MyResourceVO vo = new MyResourceVO();
+            vo.setResourceId(resource.getResourceId());
+            vo.setMatchmakerId(resource.getMatchmakerId());
+            vo.setName(resource.getName());
+            vo.setAge(resource.getAge());
+            vo.setGender(resource.getGender());
+            vo.setConstellation(resource.getConstellation());
+            vo.setHeight(resource.getHeight());
+            vo.setWeight(resource.getWeight());
+            vo.setMarrStatus(resource.getMarrStatus());
+            vo.setDiploma(resource.getDiploma());
+            vo.setIncome(resource.getIncome());
+            vo.setAddress(resource.getAddress());
+            vo.setDomicile(resource.getDomicile());
+            vo.setOccupation(resource.getOccupation());
+            vo.setHouse(resource.getHouse());
+            vo.setPhone(resource.getPhone());
+            vo.setBackupPhone(resource.getBackupPhone());
+            vo.setCar(resource.getCar());
+            vo.setMateSelectionCriteria(resource.getMateSelectionCriteria());
+            vo.setCreateTime(resource.getCreateTime());
+            vo.setUpdateTime(resource.getUpdateTime());
+            vo.setIsUser(resource.getIsUser());
+            
+            // 查询用户头像
+            if (resource.getIsUser() != null && resource.getIsUser() == 1) {
+                // 根据手机号查询用户头像
+                QueryWrapper<User> userQuery = new QueryWrapper<>();
+                userQuery.eq("phone", resource.getPhone());
+                User user = userMapper.selectOne(userQuery);
+                if (user != null && user.getAvatarUrl() != null) {
+                    vo.setAvatarUrl(user.getAvatarUrl());
+                } else {
+                    vo.setAvatarUrl("/static/default-avatar.svg");
+                }
+            } else {
+                vo.setAvatarUrl("/static/default-avatar.svg");
+            }
+            
+            voList.add(vo);
+        }
+        
+        System.out.println("返回VO数量: " + voList.size());
+        return voList;
+    }
 }
 
 

+ 5 - 0
service/websocket/src/main/java/com/zhentao/config/MinioConfig.java

@@ -36,6 +36,11 @@ public class MinioConfig {
      */
     private String bucketName;
 
+    /**
+     * 成功凭证存储桶名称
+     */
+    private String proofBucketName = "Proof-of-success";
+
     /**
      * 创建MinioClient Bean
      */

+ 385 - 0
service/websocket/src/main/java/com/zhentao/controller/SuccessCaseUploadController.java

@@ -0,0 +1,385 @@
+package com.zhentao.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.zhentao.dto.SuccessCaseUploadDTO;
+import com.zhentao.entity.MatchmakerSuccessCaseUpload;
+import com.zhentao.service.MatchmakerSuccessCaseUploadService;
+import com.zhentao.utils.MinioUtil;
+import lombok.extern.slf4j.Slf4j;
+import lombok.var;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 成功案例上传控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/success-case-upload")
+public class SuccessCaseUploadController {
+
+    @Autowired
+    private MatchmakerSuccessCaseUploadService successCaseUploadService;
+
+    @Autowired
+    private com.zhentao.mapper.MatchmakerSuccessCaseUploadMapper successCaseUploadMapper;
+
+    @Autowired
+    private MinioUtil minioUtil;
+
+    @Value("${minio.proofBucketName:Proof-of-success}")
+    private String proofBucketName;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    /**
+     * 上传成功凭证图片
+     * @param file 图片文件
+     * @return 图片URL
+     */
+    @PostMapping("/upload-image")
+    public ResponseEntity<Map<String, Object>> uploadImage(@RequestParam("file") MultipartFile file) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            if (file.isEmpty()) {
+                result.put("code", 400);
+                result.put("message", "请选择要上传的图片");
+                return ResponseEntity.badRequest().body(result);
+            }
+
+            // 验证文件类型
+            String contentType = file.getContentType();
+            if (contentType == null || !contentType.startsWith("image/")) {
+                result.put("code", 400);
+                result.put("message", "只能上传图片文件");
+                return ResponseEntity.badRequest().body(result);
+            }
+
+            // 上传到MinIO的Proof-of-success桶
+            String imageUrl = minioUtil.uploadFileToProofBucket(
+                    file.getBytes(),
+                    file.getOriginalFilename(),
+                    contentType
+            );
+
+            result.put("code", 200);
+            result.put("message", "上传成功");
+            result.put("data", imageUrl);
+            log.info("成功凭证图片上传成功: {}", imageUrl);
+            return ResponseEntity.ok(result);
+
+        } catch (Exception e) {
+            log.error("图片上传失败", e);
+            result.put("code", 500);
+            result.put("message", "图片上传失败:" + e.getMessage());
+            return ResponseEntity.internalServerError().body(result);
+        }
+    }
+
+    /**
+     * 提交成功案例
+     * @param dto 成功案例数据
+     * @return 提交结果
+     */
+    @PostMapping("/submit")
+    public ResponseEntity<Map<String, Object>> submitSuccessCase(@RequestBody SuccessCaseUploadDTO dto) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            // 参数校验
+            if (dto.getMatchmakerId() == null) {
+                result.put("code", 400);
+                result.put("message", "红娘ID不能为空");
+                return ResponseEntity.badRequest().body(result);
+            }
+            if (dto.getMaleUserId() == null) {
+                result.put("code", 400);
+                result.put("message", "请选择男方用户");
+                return ResponseEntity.badRequest().body(result);
+            }
+            if (dto.getFemaleUserId() == null) {
+                result.put("code", 400);
+                result.put("message", "请选择女方用户");
+                return ResponseEntity.badRequest().body(result);
+            }
+            if (dto.getMaleUserId().equals(dto.getFemaleUserId())) {
+                result.put("code", 400);
+                result.put("message", "男方和女方不能是同一人");
+                return ResponseEntity.badRequest().body(result);
+            }
+            if (dto.getMaleRealName() == null || dto.getMaleRealName().trim().isEmpty()) {
+                result.put("code", 400);
+                result.put("message", "请填写男方真实姓名");
+                return ResponseEntity.badRequest().body(result);
+            }
+            if (dto.getFemaleRealName() == null || dto.getFemaleRealName().trim().isEmpty()) {
+                result.put("code", 400);
+                result.put("message", "请填写女方真实姓名");
+                return ResponseEntity.badRequest().body(result);
+            }
+            if (dto.getProofImages() == null || dto.getProofImages().isEmpty()) {
+                result.put("code", 400);
+                result.put("message", "请上传至少一张成功凭证图片");
+                return ResponseEntity.badRequest().body(result);
+            }
+            if (dto.getCaseType() == null || (dto.getCaseType() != 1 && dto.getCaseType() != 2)) {
+                result.put("code", 400);
+                result.put("message", "请选择案例类型(订婚或结婚)");
+                return ResponseEntity.badRequest().body(result);
+            }
+
+            // 构建实体
+            MatchmakerSuccessCaseUpload upload = new MatchmakerSuccessCaseUpload();
+            upload.setMatchmakerId(dto.getMatchmakerId());
+            upload.setMaleUserId(dto.getMaleUserId());
+            upload.setFemaleUserId(dto.getFemaleUserId());
+            upload.setMaleRealName(dto.getMaleRealName().trim());
+            upload.setFemaleRealName(dto.getFemaleRealName().trim());
+            
+            // 将图片列表转为JSON字符串
+            upload.setProofImages(objectMapper.writeValueAsString(dto.getProofImages()));
+            upload.setCaseType(dto.getCaseType());
+            
+            // 解析日期
+            if (dto.getCaseDate() != null && !dto.getCaseDate().isEmpty()) {
+                upload.setCaseDate(LocalDate.parse(dto.getCaseDate(), DateTimeFormatter.ISO_LOCAL_DATE));
+            }
+
+            // 保存
+            boolean success = successCaseUploadService.submitSuccessCase(upload);
+            
+            if (success) {
+                result.put("code", 200);
+                result.put("message", "提交成功,请等待审核");
+                result.put("data", upload.getId());
+                log.info("成功案例提交成功: id={}, matchmakerId={}", upload.getId(), upload.getMatchmakerId());
+            } else {
+                result.put("code", 500);
+                result.put("message", "提交失败,请稍后重试");
+            }
+            
+            return ResponseEntity.ok(result);
+
+        } catch (Exception e) {
+            log.error("提交成功案例失败", e);
+            result.put("code", 500);
+            result.put("message", "提交失败:" + e.getMessage());
+            return ResponseEntity.internalServerError().body(result);
+        }
+    }
+
+    /**
+     * 获取红娘的成功案例列表
+     * @param matchmakerId 红娘ID
+     * @return 成功案例列表
+     */
+    @GetMapping("/list")
+    public ResponseEntity<Map<String, Object>> getSuccessCaseList(
+            @RequestParam Integer matchmakerId,
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @RequestParam(defaultValue = "10") Integer pageSize) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            var page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<MatchmakerSuccessCaseUpload>(pageNum, pageSize);
+            var queryWrapper = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MatchmakerSuccessCaseUpload>();
+            queryWrapper.eq(MatchmakerSuccessCaseUpload::getMatchmakerId, matchmakerId)
+                    .orderByDesc(MatchmakerSuccessCaseUpload::getCreatedAt);
+            
+            var pageResult = successCaseUploadService.page(page, queryWrapper);
+            
+            result.put("code", 200);
+            result.put("message", "查询成功");
+            result.put("data", pageResult);
+            return ResponseEntity.ok(result);
+
+        } catch (Exception e) {
+            log.error("查询成功案例列表失败", e);
+            result.put("code", 500);
+            result.put("message", "查询失败:" + e.getMessage());
+            return ResponseEntity.internalServerError().body(result);
+        }
+    }
+
+    /**
+     * 获取审核记录列表(用于消息通知页面)
+     * @param matchmakerId 红娘ID
+     * @param auditStatus 审核状态筛选(可选):0-待审核 1-审核通过 2-审核失败 3-核实中
+     * @return 审核记录列表
+     */
+    @GetMapping("/audit-records")
+    public ResponseEntity<Map<String, Object>> getAuditRecords(
+            @RequestParam Integer matchmakerId,
+            @RequestParam(required = false) Integer auditStatus,
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @RequestParam(defaultValue = "20") Integer pageSize) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            var page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<MatchmakerSuccessCaseUpload>(pageNum, pageSize);
+            var queryWrapper = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MatchmakerSuccessCaseUpload>();
+            queryWrapper.eq(MatchmakerSuccessCaseUpload::getMatchmakerId, matchmakerId);
+            
+            // 如果指定了审核状态,则筛选
+            if (auditStatus != null) {
+                queryWrapper.eq(MatchmakerSuccessCaseUpload::getAuditStatus, auditStatus);
+            }
+            
+            // 按创建时间降序
+            queryWrapper.orderByDesc(MatchmakerSuccessCaseUpload::getCreatedAt);
+            
+            var pageResult = successCaseUploadService.page(page, queryWrapper);
+            
+            result.put("code", 200);
+            result.put("message", "查询成功");
+            result.put("data", pageResult);
+            return ResponseEntity.ok(result);
+
+        } catch (Exception e) {
+            log.error("查询审核记录列表失败", e);
+            result.put("code", 500);
+            result.put("message", "查询失败:" + e.getMessage());
+            return ResponseEntity.internalServerError().body(result);
+        }
+    }
+
+    /**
+     * 获取审核记录详情(包含男女双方用户信息)
+     * @param id 记录ID
+     * @return 记录详情
+     */
+    @GetMapping("/audit-records/{id}")
+    public ResponseEntity<Map<String, Object>> getAuditRecordDetail(@PathVariable Long id) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            MatchmakerSuccessCaseUpload record = successCaseUploadService.getById(id);
+            if (record == null) {
+                result.put("code", 404);
+                result.put("message", "记录不存在");
+                return ResponseEntity.ok(result);
+            }
+            
+            // 构建返回数据,包含案例信息和用户信息
+            Map<String, Object> data = new HashMap<>();
+            data.put("id", record.getId());
+            data.put("matchmakerId", record.getMatchmakerId());
+            data.put("maleUserId", record.getMaleUserId());
+            data.put("femaleUserId", record.getFemaleUserId());
+            data.put("maleRealName", record.getMaleRealName());
+            data.put("femaleRealName", record.getFemaleRealName());
+            data.put("proofImages", record.getProofImages());
+            data.put("caseType", record.getCaseType());
+            data.put("caseDate", record.getCaseDate());
+            data.put("auditStatus", record.getAuditStatus());
+            data.put("auditRemark", record.getAuditRemark());
+            data.put("pointsReward", record.getPointsReward());
+            data.put("cashReward", record.getCashReward());
+            data.put("rewardStatus", record.getRewardStatus());
+            data.put("createdAt", record.getCreatedAt());
+            data.put("auditedAt", record.getAuditedAt());
+            data.put("isRead", record.getIsRead());
+            
+            // 查询男方用户信息
+            if (record.getMaleUserId() != null) {
+                Map<String, Object> maleUserInfo = successCaseUploadMapper.getUserBasicInfo(record.getMaleUserId());
+                if (maleUserInfo != null) {
+                    data.put("maleUserInfo", maleUserInfo);
+                }
+            }
+            
+            // 查询女方用户信息
+            if (record.getFemaleUserId() != null) {
+                Map<String, Object> femaleUserInfo = successCaseUploadMapper.getUserBasicInfo(record.getFemaleUserId());
+                if (femaleUserInfo != null) {
+                    data.put("femaleUserInfo", femaleUserInfo);
+                }
+            }
+            
+            result.put("code", 200);
+            result.put("message", "查询成功");
+            result.put("data", data);
+            return ResponseEntity.ok(result);
+
+        } catch (Exception e) {
+            log.error("查询审核记录详情失败", e);
+            result.put("code", 500);
+            result.put("message", "查询失败:" + e.getMessage());
+            return ResponseEntity.internalServerError().body(result);
+        }
+    }
+
+    /**
+     * 标记审核记录为已读
+     * @param id 记录ID
+     * @return 操作结果
+     */
+    @PostMapping("/audit-records/{id}/read")
+    public ResponseEntity<Map<String, Object>> markAsRead(@PathVariable Long id) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            MatchmakerSuccessCaseUpload record = successCaseUploadService.getById(id);
+            if (record == null) {
+                result.put("code", 404);
+                result.put("message", "记录不存在");
+                return ResponseEntity.ok(result);
+            }
+            
+            // 标记为已读
+            record.setIsRead(1);
+            boolean success = successCaseUploadService.updateById(record);
+            
+            if (success) {
+                result.put("code", 200);
+                result.put("message", "标记成功");
+            } else {
+                result.put("code", 500);
+                result.put("message", "标记失败");
+            }
+            return ResponseEntity.ok(result);
+
+        } catch (Exception e) {
+            log.error("标记已读失败", e);
+            result.put("code", 500);
+            result.put("message", "操作失败:" + e.getMessage());
+            return ResponseEntity.internalServerError().body(result);
+        }
+    }
+
+    /**
+     * 获取未读审核记录数量
+     * @param matchmakerId 红娘ID
+     * @return 未读数量
+     */
+    @GetMapping("/audit-records/unread-count")
+    public ResponseEntity<Map<String, Object>> getUnreadCount(@RequestParam Integer matchmakerId) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            var queryWrapper = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MatchmakerSuccessCaseUpload>();
+            queryWrapper.eq(MatchmakerSuccessCaseUpload::getMatchmakerId, matchmakerId)
+                    .and(w -> w.isNull(MatchmakerSuccessCaseUpload::getIsRead)
+                            .or()
+                            .eq(MatchmakerSuccessCaseUpload::getIsRead, 0))
+                    // 只统计已审核完成的(审核通过或审核失败)
+                    .in(MatchmakerSuccessCaseUpload::getAuditStatus, 1, 2);
+            
+            long count = successCaseUploadService.count(queryWrapper);
+            
+            result.put("code", 200);
+            result.put("message", "查询成功");
+            result.put("data", count);
+            return ResponseEntity.ok(result);
+
+        } catch (Exception e) {
+            log.error("查询未读数量失败", e);
+            result.put("code", 500);
+            result.put("message", "查询失败:" + e.getMessage());
+            return ResponseEntity.internalServerError().body(result);
+        }
+    }
+}

+ 52 - 0
service/websocket/src/main/java/com/zhentao/dto/SuccessCaseUploadDTO.java

@@ -0,0 +1,52 @@
+package com.zhentao.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 成功案例上传DTO
+ */
+@Data
+public class SuccessCaseUploadDTO {
+
+    /**
+     * 红娘ID
+     */
+    private Integer matchmakerId;
+
+    /**
+     * 男方用户ID
+     */
+    private Integer maleUserId;
+
+    /**
+     * 女方用户ID
+     */
+    private Integer femaleUserId;
+
+    /**
+     * 男方真实姓名
+     */
+    private String maleRealName;
+
+    /**
+     * 女方真实姓名
+     */
+    private String femaleRealName;
+
+    /**
+     * 成功凭证图片URL列表
+     */
+    private List<String> proofImages;
+
+    /**
+     * 案例类型:1-订婚 2-结婚
+     */
+    private Integer caseType;
+
+    /**
+     * 成功日期(格式:yyyy-MM-dd)
+     */
+    private String caseDate;
+}

+ 132 - 0
service/websocket/src/main/java/com/zhentao/entity/MatchmakerSuccessCaseUpload.java

@@ -0,0 +1,132 @@
+package com.zhentao.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 红娘上传成功案例实体类
+ */
+@Data
+@TableName("matchmaker_success_case_upload")
+public class MatchmakerSuccessCaseUpload implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id", 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;
+
+    /**
+     * 成功日期(结婚日期/订婚日期)
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private LocalDate 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")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createdAt;
+
+    /**
+     * 更新时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updatedAt;
+
+    /**
+     * 审核时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime auditedAt;
+}

+ 25 - 0
service/websocket/src/main/java/com/zhentao/mapper/MatchmakerSuccessCaseUploadMapper.java

@@ -0,0 +1,25 @@
+package com.zhentao.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.entity.MatchmakerSuccessCaseUpload;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.Map;
+
+/**
+ * 红娘上传成功案例Mapper
+ */
+@Mapper
+public interface MatchmakerSuccessCaseUploadMapper extends BaseMapper<MatchmakerSuccessCaseUpload> {
+    
+    /**
+     * 根据用户ID查询用户基本信息(头像、年龄等)
+     * @param userId 用户ID(users表的user_id)
+     * @return 用户信息Map
+     */
+    @Select("SELECT user_id as userId, nickname as name, avatar_url as avatarUrl, " +
+            "TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) as age " +
+            "FROM users WHERE user_id = #{userId}")
+    Map<String, Object> getUserBasicInfo(Integer userId);
+}

+ 17 - 0
service/websocket/src/main/java/com/zhentao/service/MatchmakerSuccessCaseUploadService.java

@@ -0,0 +1,17 @@
+package com.zhentao.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.entity.MatchmakerSuccessCaseUpload;
+
+/**
+ * 红娘上传成功案例Service接口
+ */
+public interface MatchmakerSuccessCaseUploadService extends IService<MatchmakerSuccessCaseUpload> {
+
+    /**
+     * 提交成功案例
+     * @param upload 成功案例数据
+     * @return 是否成功
+     */
+    boolean submitSuccessCase(MatchmakerSuccessCaseUpload upload);
+}

+ 37 - 0
service/websocket/src/main/java/com/zhentao/service/impl/MatchmakerSuccessCaseUploadServiceImpl.java

@@ -0,0 +1,37 @@
+package com.zhentao.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.entity.MatchmakerSuccessCaseUpload;
+import com.zhentao.mapper.MatchmakerSuccessCaseUploadMapper;
+import com.zhentao.service.MatchmakerSuccessCaseUploadService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+
+/**
+ * 红娘上传成功案例Service实现
+ */
+@Slf4j
+@Service
+public class MatchmakerSuccessCaseUploadServiceImpl 
+        extends ServiceImpl<MatchmakerSuccessCaseUploadMapper, MatchmakerSuccessCaseUpload> 
+        implements MatchmakerSuccessCaseUploadService {
+
+    @Override
+    public boolean submitSuccessCase(MatchmakerSuccessCaseUpload upload) {
+        // 设置默认值
+        upload.setAuditStatus(0); // 待审核
+        upload.setPointsReward(0);
+        upload.setCashReward(new java.math.BigDecimal("0.00"));
+        upload.setRewardStatus(0); // 未发放
+        upload.setIsPublished(0); // 未发布
+        upload.setCreatedAt(LocalDateTime.now());
+        upload.setUpdatedAt(LocalDateTime.now());
+        
+        log.info("提交成功案例: matchmakerId={}, maleUserId={}, femaleUserId={}", 
+                upload.getMatchmakerId(), upload.getMaleUserId(), upload.getFemaleUserId());
+        
+        return this.save(upload);
+    }
+}

+ 70 - 1
service/websocket/src/main/java/com/zhentao/utils/MinioUtil.java

@@ -24,12 +24,15 @@ public class MinioUtil {
     @Autowired
     private MinioClient minioClient;
 
-    @Value("${minio.bucket-name}")
+    @Value("${minio.bucketName}")
     private String bucketName;
 
     @Value("${minio.endpoint}")
     private String endpoint;
 
+    @Value("${minio.proofBucketName:Proof-of-success}")
+    private String proofBucketName;
+
     /**
      * 初始化,检查并创建bucket
      */
@@ -257,4 +260,70 @@ public class MinioUtil {
             return false;
         }
     }
+
+    /**
+     * 上传文件到成功凭证桶(Proof-of-success)
+     *
+     * @param fileBytes 文件字节数组
+     * @param fileName  文件名
+     * @param contentType 文件类型
+     * @return 文件访问URL
+     */
+    public String uploadFileToProofBucket(byte[] fileBytes, String fileName, String contentType) throws Exception {
+        // 确保桶存在
+        createBucketIfNotExists(proofBucketName);
+        
+        try (java.io.InputStream inputStream = new java.io.ByteArrayInputStream(fileBytes)) {
+            // 生成唯一文件名
+            String objectName = generateProofFileName(fileName);
+            
+            // 上传文件
+            minioClient.putObject(
+                    PutObjectArgs.builder()
+                            .bucket(proofBucketName)
+                            .object(objectName)
+                            .stream(inputStream, fileBytes.length, -1)
+                            .contentType(contentType)
+                            .build()
+            );
+            
+            log.info("✅ 成功凭证图片上传成功: bucket={}, object={}", proofBucketName, objectName);
+            
+            // 返回文件访问URL
+            return String.format("%s/%s/%s", endpoint, proofBucketName, objectName);
+        }
+    }
+
+    /**
+     * 检查并创建指定的bucket
+     */
+    private void createBucketIfNotExists(String targetBucket) throws Exception {
+        boolean exists = minioClient.bucketExists(
+                BucketExistsArgs.builder()
+                        .bucket(targetBucket)
+                        .build()
+        );
+
+        if (!exists) {
+            minioClient.makeBucket(
+                    MakeBucketArgs.builder()
+                            .bucket(targetBucket)
+                            .build()
+            );
+            log.info("✅ 创建Bucket成功: {}", targetBucket);
+        }
+    }
+
+    /**
+     * 生成成功凭证文件名
+     *
+     * @param originalFileName 原始文件名
+     * @return 唯一文件名
+     */
+    private String generateProofFileName(String originalFileName) {
+        String uuid = java.util.UUID.randomUUID().toString().replace("-", "");
+        String extension = getFileExtension(originalFileName);
+        String dateFolder = new java.text.SimpleDateFormat("yyyy/MM/dd").format(new java.util.Date());
+        return String.format("proof/%s/%s%s", dateFolder, uuid, extension);
+    }
 }

+ 4 - 3
service/websocket/src/main/resources/application.yml

@@ -81,6 +81,7 @@ tim:
 # MinIO 对象存储配置
 minio:
   endpoint: http://115.190.125.125:9000
-  access-key: minioadmin
-  secret-key: minioadmin
-  bucket-name: minion-voice-files
+  accessKey: minioadmin
+  secretKey: minioadmin
+  bucketName: minion-voice-files
+  proofBucketName: proof-of-success