Browse Source

推荐列表点击用户全部动态,可以查看用户评论

yuxy 1 tháng trước cách đây
mục cha
commit
6ab0512f91

+ 704 - 0
LiangZhiYUMao/pages/recommend/user-detail.vue

@@ -0,0 +1,704 @@
+<template>
+	<view class="user-detail-page">
+		<!-- 自定义导航栏 -->
+		<view class="custom-navbar">
+			<view class="navbar-left" @click="goBack">
+				<text class="back-icon">←</text>
+			</view>
+			<view class="navbar-title">用户详情</view>
+			<view class="navbar-right"></view>
+		</view>
+
+		<scroll-view scroll-y class="page-content" v-if="userInfo">
+			<!-- 用户头像和基本信息 -->
+			<view class="user-header">
+				<image 
+					:src="userInfo.avatar || userInfo.avatarUrl || '/static/close.png'" 
+					class="user-avatar" 
+					mode="aspectFill"
+					@error="onAvatarError"
+				/>
+				<view class="user-basic">
+					<view class="user-name-row">
+						<text class="user-name">{{ userInfo.nickname || '未设置' }}</text>
+						<view class="score-badge" v-if="compatibilityScore">
+							<text>匹配度 {{ fmtScore(compatibilityScore) }}</text>
+						</view>
+					</view>
+					<view class="user-meta">
+						<text v-if="userInfo.genderText">{{ userInfo.genderText }}</text>
+						<text v-if="userInfo.age">{{ userInfo.age }}岁</text>
+						<text v-if="userInfo.height">{{ userInfo.height }}cm</text>
+						<text v-if="userInfo.educationText">{{ userInfo.educationText }}</text>
+						<text v-if="userInfo.salaryText">{{ userInfo.salaryText }}</text>
+					</view>
+					<view class="user-tags" v-if="hasTags">
+						<text v-if="userInfo.star" class="tag">{{ userInfo.star }}</text>
+						<text v-if="userInfo.animal" class="tag">{{ userInfo.animal }}</text>
+						<text v-if="userInfo.jobTitle" class="tag">{{ userInfo.jobTitle }}</text>
+						<text v-for="t in parseHobby(userInfo.hobby)" :key="t" class="tag">{{ t }}</text>
+					</view>
+				</view>
+			</view>
+
+			<!-- 详细信息 -->
+			<view class="detail-section">
+				<view class="section-title">详细信息</view>
+				<view class="detail-grid">
+					<view class="detail-item" v-if="userInfo.schoolName">
+						<text class="detail-label">毕业院校</text>
+						<text class="detail-value">{{ userInfo.schoolName }}</text>
+					</view>
+					<view class="detail-item" v-if="userInfo.company">
+						<text class="detail-label">工作单位</text>
+						<text class="detail-value">{{ userInfo.company }}</text>
+					</view>
+					<view class="detail-item" v-if="userInfo.maritalText">
+						<text class="detail-label">婚姻状况</text>
+						<text class="detail-value">{{ userInfo.maritalText }}</text>
+					</view>
+					<view class="detail-item" v-if="userInfo.houseText">
+						<text class="detail-label">房产</text>
+						<text class="detail-value">{{ userInfo.houseText }}</text>
+					</view>
+					<view class="detail-item" v-if="userInfo.carText">
+						<text class="detail-label">车产</text>
+						<text class="detail-value">{{ userInfo.carText }}</text>
+					</view>
+					<view class="detail-item" v-if="userInfo.weight">
+						<text class="detail-label">体重</text>
+						<text class="detail-value">{{ userInfo.weight }}kg</text>
+					</view>
+				</view>
+			</view>
+
+			<!-- 自我简介 -->
+			<view class="detail-section" v-if="userInfo.introduction">
+				<view class="section-title">自我简介</view>
+				<view class="introduction-content">
+					<text>{{ userInfo.introduction }}</text>
+				</view>
+			</view>
+
+			<!-- 动态区域 -->
+			<view class="dynamic-section">
+				<view class="section-header">
+					<text class="section-title">动态</text>
+					<text class="section-more" @click="viewAllDynamics" v-if="dynamicList.length > 0">
+						查看全部({{ totalDynamics }}) >
+					</text>
+				</view>
+				
+				<!-- 动态列表 -->
+				<view v-if="loadingDynamics" class="loading-tip">
+					<text>加载动态中...</text>
+				</view>
+				<view v-else-if="dynamicList.length === 0" class="empty-dynamics">
+					<text class="empty-icon">📝</text>
+					<text class="empty-text">该用户还没有发布动态</text>
+				</view>
+				<view v-else class="dynamic-list">
+					<view 
+						v-for="(item, index) in dynamicList" 
+						:key="item.dynamicId || index" 
+						class="dynamic-item"
+						@click="viewDynamicDetail(item)"
+					>
+						<!-- 动态内容 -->
+						<view class="dynamic-content" v-if="item.content">
+							<text class="dynamic-text">{{ item.content }}</text>
+						</view>
+						
+						<!-- 动态媒体(照片/视频) -->
+						<view class="dynamic-media" v-if="item.mediaUrls && item.mediaUrls.length > 0">
+							<view :class="['media-grid', getGridClass(item)]">
+								<image 
+									v-for="(url, idx) in item.mediaUrls.slice(0, 9)" 
+									:key="idx"
+									:src="url" 
+									class="media-image"
+									mode="aspectFill"
+									@click.stop="previewMedia(item.mediaUrls, idx)"
+								/>
+							</view>
+						</view>
+						
+						<!-- 动态信息 -->
+						<view class="dynamic-info">
+							<text class="dynamic-time">{{ formatTime(item.createdAt) }}</text>
+							<view class="dynamic-stats">
+								<text class="stat-item">❤️ {{ item.likeCount || 0 }}</text>
+								<text class="stat-item comment-stat" @click.stop="viewDynamicComments(item)">💬 {{ item.commentCount || 0 }}</text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</scroll-view>
+
+		<!-- 加载中 -->
+		<view v-else class="loading-container">
+			<text>加载中...</text>
+		</view>
+	</view>
+</template>
+
+<script>
+import api from '@/utils/api.js'
+
+export default {
+	data() {
+		return {
+			userId: null,
+			userInfo: null,
+			compatibilityScore: null,
+			dynamicList: [],
+			loadingDynamics: false,
+			totalDynamics: 0,
+			pageNum: 1,
+			pageSize: 6
+		}
+	},
+	
+	computed: {
+		hasTags() {
+			return this.userInfo && (
+				this.userInfo.star || 
+				this.userInfo.animal || 
+				this.userInfo.jobTitle || 
+				this.userInfo.hobby
+			)
+		}
+	},
+	
+	onLoad(options) {
+		if (options.userId) {
+			this.userId = parseInt(options.userId)
+			if (options.score) {
+				this.compatibilityScore = parseFloat(options.score)
+			}
+			this.loadUserInfo()
+			this.loadUserDynamics()
+		} else {
+			uni.showToast({
+				title: '用户ID无效',
+				icon: 'none'
+			})
+			setTimeout(() => {
+				uni.navigateBack()
+			}, 1500)
+		}
+	},
+	
+	methods: {
+		// 加载用户信息
+		async loadUserInfo() {
+			try {
+				uni.showLoading({ title: '加载中...' })
+				const detail = await api.user.getDetailInfo(this.userId)
+				if (detail) {
+					// 确保photos是数组
+					if (!detail.photos || !Array.isArray(detail.photos)) {
+						detail.photos = []
+						if (detail.avatar) {
+							detail.photos.push(detail.avatar)
+						}
+					}
+					this.userInfo = detail
+				} else {
+					uni.showToast({
+						title: '用户信息不存在',
+						icon: 'none'
+					})
+					setTimeout(() => {
+						uni.navigateBack()
+					}, 1500)
+				}
+			} catch (e) {
+				console.error('获取用户信息失败:', e)
+				uni.showToast({
+					title: '获取用户信息失败',
+					icon: 'none'
+				})
+			} finally {
+				uni.hideLoading()
+			}
+		},
+		
+		// 加载用户动态
+		async loadUserDynamics() {
+			if (!this.userId) return
+			
+			this.loadingDynamics = true
+			try {
+				const currentUserId = uni.getStorageSync('userId')
+				const params = {
+					pageNum: this.pageNum,
+					pageSize: this.pageSize,
+					currentUserId: currentUserId ? parseInt(currentUserId) : null
+				}
+				const result = await api.dynamic.getUserDynamics(this.userId, params)
+				console.log('用户动态API返回:', result)
+				
+				// 处理PageResult格式:{ records: [], total: 0, current: 1, size: 10 }
+				let list = []
+				let total = 0
+				
+				if (result) {
+					// PageResult格式:{ records: [], total: 0 }
+					if (result.records && Array.isArray(result.records)) {
+						list = result.records
+						total = result.total || 0
+					}
+					// 兼容格式:{ list: [], total: 0 }
+					else if (result.list && Array.isArray(result.list)) {
+						list = result.list
+						total = result.total || result.list.length
+					}
+					// 直接是数组
+					else if (Array.isArray(result)) {
+						list = result
+						total = result.length
+					}
+				}
+				
+				// 处理mediaUrls字段(可能是字符串需要解析)
+				list = list.map(item => {
+					if (item.mediaUrls && typeof item.mediaUrls === 'string') {
+						try {
+							item.mediaUrls = JSON.parse(item.mediaUrls)
+						} catch (e) {
+							console.error('解析mediaUrls失败:', e)
+							item.mediaUrls = []
+						}
+					}
+					if (!item.mediaUrls || !Array.isArray(item.mediaUrls)) {
+						item.mediaUrls = []
+					}
+					return item
+				})
+				
+				this.dynamicList = list
+				this.totalDynamics = total
+				console.log('处理后的动态列表:', this.dynamicList)
+			} catch (e) {
+				console.error('获取用户动态失败:', e)
+				uni.showToast({
+					title: '加载动态失败',
+					icon: 'none'
+				})
+			} finally {
+				this.loadingDynamics = false
+			}
+		},
+		
+		// 查看全部动态
+		viewAllDynamics() {
+			uni.navigateTo({
+				url: `/pages/recommend/user-dynamics?userId=${this.userId}`
+			})
+		},
+		
+		// 查看动态详情
+		viewDynamicDetail(item) {
+			if (!item || !item.dynamicId) {
+				console.error('动态项无效:', item)
+				uni.showToast({
+					title: '动态信息无效',
+					icon: 'none'
+				})
+				return
+			}
+			uni.navigateTo({
+				url: `/pages/plaza/detail?dynamicId=${item.dynamicId}`
+			})
+		},
+		
+		// 查看动态评论
+		viewDynamicComments(item) {
+			if (!item || !item.dynamicId) {
+				console.error('动态项无效:', item)
+				uni.showToast({
+					title: '动态信息无效',
+					icon: 'none'
+				})
+				return
+			}
+			uni.navigateTo({
+				url: `/pages/plaza/detail?dynamicId=${item.dynamicId}&scrollToComment=true`
+			})
+		},
+		
+		// 预览媒体
+		previewMedia(urls, index) {
+			if (!urls || urls.length === 0) return
+			uni.previewImage({
+				urls: urls,
+				current: index
+			})
+		},
+		
+		// 获取网格类名
+		getGridClass(item) {
+			const count = item.mediaUrls ? item.mediaUrls.length : 0
+			if (count === 1) return 'grid-1'
+			if (count === 2 || count === 4) return 'grid-2'
+			return 'grid-3'
+		},
+		
+		// 格式化时间
+		formatTime(timeStr) {
+			if (!timeStr) return ''
+			const date = new Date(timeStr)
+			const now = new Date()
+			const diff = now - date
+			const minutes = Math.floor(diff / 60000)
+			const hours = Math.floor(diff / 3600000)
+			const days = Math.floor(diff / 86400000)
+			
+			if (minutes < 1) return '刚刚'
+			if (minutes < 60) return `${minutes}分钟前`
+			if (hours < 24) return `${hours}小时前`
+			if (days < 7) return `${days}天前`
+			return date.toLocaleDateString()
+		},
+		
+		// 格式化匹配度
+		fmtScore(s) {
+			return s ? Number(s).toFixed(1) : '0.0'
+		},
+		
+		// 解析兴趣爱好
+		parseHobby(h) {
+			try {
+				const arr = typeof h === 'string' ? JSON.parse(h) : h
+				return Array.isArray(arr) ? arr.slice(0, 6) : []
+			} catch {
+				return []
+			}
+		},
+		
+		// 头像加载错误
+		onAvatarError() {
+			if (this.userInfo) {
+				this.userInfo.avatar = '/static/close.png'
+				this.userInfo.avatarUrl = '/static/close.png'
+			}
+		},
+		
+		// 返回
+		goBack() {
+			uni.navigateBack()
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.user-detail-page {
+	min-height: 100vh;
+	background: #F5F5F5;
+	padding-top: 90rpx;
+}
+
+/* 自定义导航栏 */
+.custom-navbar {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	height: 90rpx;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 0 20rpx;
+	background: #FFFFFF;
+	border-bottom: 2rpx solid #E0E0E0;
+	z-index: 999;
+}
+
+.navbar-left,
+.navbar-right {
+	width: 80rpx;
+}
+
+.back-icon {
+	font-size: 40rpx;
+	color: #333;
+	font-weight: bold;
+}
+
+.navbar-title {
+	flex: 1;
+	text-align: center;
+	font-size: 32rpx;
+	font-weight: bold;
+	color: #333;
+}
+
+/* 页面内容 */
+.page-content {
+	height: calc(100vh - 90rpx);
+	padding: 20rpx;
+}
+
+/* 用户头部 */
+.user-header {
+	background: #FFFFFF;
+	border-radius: 16rpx;
+	padding: 30rpx;
+	margin-bottom: 20rpx;
+	display: flex;
+	align-items: flex-start;
+	border: 2rpx solid #E0E0E0;
+}
+
+.user-avatar {
+	width: 160rpx;
+	height: 160rpx;
+	border-radius: 80rpx;
+	margin-right: 24rpx;
+	border: 4rpx solid #FFE5F1;
+	background: #F5F5F5;
+}
+
+.user-basic {
+	flex: 1;
+}
+
+.user-name-row {
+	display: flex;
+	align-items: center;
+	margin-bottom: 16rpx;
+	flex-wrap: wrap;
+	gap: 12rpx;
+}
+
+.user-name {
+	font-size: 36rpx;
+	font-weight: bold;
+	color: #333;
+}
+
+.score-badge {
+	background: #FFE5F1;
+	color: #E91E63;
+	padding: 6rpx 16rpx;
+	border-radius: 20rpx;
+	font-size: 24rpx;
+	font-weight: 600;
+}
+
+.user-meta {
+	display: flex;
+	flex-wrap: wrap;
+	gap: 16rpx;
+	margin-bottom: 16rpx;
+	font-size: 26rpx;
+	color: #666;
+}
+
+.user-tags {
+	display: flex;
+	flex-wrap: wrap;
+	gap: 12rpx;
+}
+
+.tag {
+	background: #F5F5F5;
+	color: #666;
+	padding: 8rpx 16rpx;
+	border-radius: 20rpx;
+	font-size: 24rpx;
+	border: 1rpx solid #E0E0E0;
+}
+
+/* 详细信息区域 */
+.detail-section {
+	background: #FFFFFF;
+	border-radius: 16rpx;
+	padding: 24rpx;
+	margin-bottom: 20rpx;
+	border: 2rpx solid #E0E0E0;
+}
+
+.section-title {
+	font-size: 28rpx;
+	font-weight: 600;
+	color: #333;
+	margin-bottom: 20rpx;
+	padding-bottom: 12rpx;
+	border-bottom: 2rpx solid #F0F0F0;
+}
+
+.detail-grid {
+	display: grid;
+	grid-template-columns: repeat(2, 1fr);
+	gap: 20rpx;
+}
+
+.detail-item {
+	display: flex;
+	flex-direction: column;
+}
+
+.detail-label {
+	font-size: 24rpx;
+	color: #999;
+	margin-bottom: 8rpx;
+}
+
+.detail-value {
+	font-size: 26rpx;
+	color: #333;
+	font-weight: 500;
+}
+
+.introduction-content {
+	background: #F8F9FA;
+	padding: 20rpx;
+	border-radius: 8rpx;
+	border-left: 4rpx solid #E91E63;
+	line-height: 1.8;
+	font-size: 26rpx;
+	color: #666;
+}
+
+/* 动态区域 */
+.dynamic-section {
+	background: #FFFFFF;
+	border-radius: 16rpx;
+	padding: 24rpx;
+	margin-bottom: 20rpx;
+	border: 2rpx solid #E0E0E0;
+}
+
+.section-header {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	margin-bottom: 20rpx;
+	padding-bottom: 12rpx;
+	border-bottom: 2rpx solid #F0F0F0;
+}
+
+.section-more {
+	font-size: 24rpx;
+	color: #E91E63;
+}
+
+.dynamic-list {
+	display: flex;
+	flex-direction: column;
+	gap: 20rpx;
+}
+
+.dynamic-item {
+	padding: 20rpx;
+	background: #F8F9FA;
+	border-radius: 12rpx;
+	border: 1rpx solid #E0E0E0;
+}
+
+.dynamic-content {
+	margin-bottom: 16rpx;
+}
+
+.dynamic-text {
+	font-size: 26rpx;
+	color: #333;
+	line-height: 1.6;
+	display: -webkit-box;
+	-webkit-box-orient: vertical;
+	-webkit-line-clamp: 3;
+	overflow: hidden;
+}
+
+.dynamic-media {
+	margin-bottom: 16rpx;
+}
+
+.media-grid {
+	display: grid;
+	gap: 8rpx;
+}
+
+.grid-1 {
+	grid-template-columns: 1fr;
+}
+
+.grid-2 {
+	grid-template-columns: repeat(2, 1fr);
+}
+
+.grid-3 {
+	grid-template-columns: repeat(3, 1fr);
+}
+
+.media-image {
+	width: 100%;
+	height: 200rpx;
+	border-radius: 8rpx;
+	background: #F5F5F5;
+}
+
+.dynamic-info {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	font-size: 24rpx;
+	color: #999;
+}
+
+.dynamic-stats {
+	display: flex;
+	gap: 20rpx;
+}
+
+.stat-item {
+	font-size: 24rpx;
+	color: #999;
+}
+
+.comment-stat {
+	cursor: pointer;
+	transition: opacity 0.2s;
+}
+
+.comment-stat:active {
+	opacity: 0.6;
+}
+
+.empty-dynamics {
+	text-align: center;
+	padding: 60rpx 20rpx;
+}
+
+.empty-icon {
+	font-size: 80rpx;
+	display: block;
+	margin-bottom: 20rpx;
+}
+
+.empty-text {
+	font-size: 26rpx;
+	color: #999;
+}
+
+.loading-tip {
+	text-align: center;
+	padding: 40rpx;
+	color: #999;
+	font-size: 26rpx;
+}
+
+.loading-container {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	height: 100vh;
+	font-size: 28rpx;
+	color: #999;
+}
+</style>
+

+ 443 - 0
LiangZhiYUMao/pages/recommend/user-dynamics.vue

@@ -0,0 +1,443 @@
+<template>
+	<view class="user-dynamics-page">
+		<!-- 自定义导航栏 -->
+		<view class="custom-navbar">
+			<view class="navbar-left" @click="goBack">
+				<text class="back-icon">←</text>
+			</view>
+			<view class="navbar-title">用户动态</view>
+			<view class="navbar-right"></view>
+		</view>
+
+		<scroll-view 
+			scroll-y 
+			class="page-content"
+			@scrolltolower="loadMore"
+			refresher-enabled
+			:refresher-triggered="refreshing"
+			@refresherrefresh="onRefresh"
+		>
+			<!-- 动态列表 -->
+			<view v-if="loading && dynamicList.length === 0" class="loading-container">
+				<text>加载中...</text>
+			</view>
+			<view v-else-if="dynamicList.length === 0" class="empty-container">
+				<text class="empty-icon">📝</text>
+				<text class="empty-text">该用户还没有发布动态</text>
+			</view>
+			<view v-else class="dynamic-list">
+				<view 
+					v-for="(item, index) in dynamicList" 
+					:key="item.dynamicId || index" 
+					class="dynamic-card"
+					@click="viewDynamicDetail(item)"
+				>
+					<!-- 动态内容 -->
+					<view class="dynamic-content" v-if="item.content">
+						<text class="dynamic-text">{{ item.content }}</text>
+					</view>
+					
+					<!-- 动态媒体(照片/视频) -->
+					<view class="dynamic-media" v-if="item.mediaUrls && item.mediaUrls.length > 0">
+						<view :class="['media-grid', getGridClass(item)]">
+							<image 
+								v-for="(url, idx) in item.mediaUrls.slice(0, 9)" 
+								:key="idx"
+								:src="url" 
+								class="media-image"
+								mode="aspectFill"
+								@click.stop="previewMedia(item.mediaUrls, idx)"
+							/>
+						</view>
+					</view>
+					
+					<!-- 动态信息 -->
+					<view class="dynamic-info">
+						<text class="dynamic-time">{{ formatTime(item.createdAt) }}</text>
+						<view class="dynamic-stats">
+							<text class="stat-item">❤️ {{ item.likeCount || 0 }}</text>
+							<text class="stat-item comment-stat" @click.stop="viewDynamicComments(item, index)">💬 {{ item.commentCount || 0 }}</text>
+							<text class="stat-item">⭐ {{ item.favoriteCount || 0 }}</text>
+						</view>
+					</view>
+				</view>
+			</view>
+			
+			<!-- 加载更多 -->
+			<view v-if="loading && dynamicList.length > 0" class="loading-tip">
+				<text>加载中...</text>
+			</view>
+			<view v-if="noMore && dynamicList.length > 0" class="loading-tip">
+				<text>没有更多了~</text>
+			</view>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+import api from '@/utils/api.js'
+
+export default {
+	data() {
+		return {
+			userId: null,
+			dynamicList: [],
+			loading: false,
+			refreshing: false,
+			noMore: false,
+			pageNum: 1,
+			pageSize: 10
+		}
+	},
+	
+	onLoad(options) {
+		if (options.userId) {
+			this.userId = parseInt(options.userId)
+			this.loadDynamics()
+		} else {
+			uni.showToast({
+				title: '用户ID无效',
+				icon: 'none'
+			})
+			setTimeout(() => {
+				uni.navigateBack()
+			}, 1500)
+		}
+	},
+	
+	methods: {
+		// 加载动态
+		async loadDynamics(isRefresh = false) {
+			if (this.loading) return
+			if (isRefresh) {
+				this.pageNum = 1
+				this.noMore = false
+			}
+			
+			this.loading = true
+			try {
+				const currentUserId = uni.getStorageSync('userId')
+				const params = {
+					pageNum: this.pageNum,
+					pageSize: this.pageSize,
+					currentUserId: currentUserId ? parseInt(currentUserId) : null
+				}
+				const result = await api.dynamic.getUserDynamics(this.userId, params)
+				console.log('用户动态API返回:', result)
+				
+				// 处理PageResult格式:{ records: [], total: 0, current: 1, size: 10 }
+				let list = []
+				
+				if (result) {
+					// PageResult格式:{ records: [], total: 0 }
+					if (result.records && Array.isArray(result.records)) {
+						list = result.records
+					}
+					// 兼容格式:{ list: [], total: 0 }
+					else if (result.list && Array.isArray(result.list)) {
+						list = result.list
+					}
+					// 直接是数组
+					else if (Array.isArray(result)) {
+						list = result
+					}
+				}
+				
+				// 处理mediaUrls字段(可能是字符串需要解析)
+				list = list.map(item => {
+					if (item.mediaUrls && typeof item.mediaUrls === 'string') {
+						try {
+							item.mediaUrls = JSON.parse(item.mediaUrls)
+						} catch (e) {
+							console.error('解析mediaUrls失败:', e)
+							item.mediaUrls = []
+						}
+					}
+					if (!item.mediaUrls || !Array.isArray(item.mediaUrls)) {
+						item.mediaUrls = []
+					}
+					return item
+				})
+				
+				this.noMore = list.length < this.pageSize
+				
+				if (isRefresh) {
+					this.dynamicList = list
+				} else {
+					this.dynamicList = [...this.dynamicList, ...list]
+				}
+				
+				if (!this.noMore) {
+					this.pageNum++
+				}
+			} catch (e) {
+				console.error('获取用户动态失败:', e)
+				uni.showToast({
+					title: '加载失败',
+					icon: 'none'
+				})
+			} finally {
+				this.loading = false
+				this.refreshing = false
+			}
+		},
+		
+		// 加载更多
+		loadMore() {
+			if (!this.noMore && !this.loading) {
+				this.loadDynamics()
+			}
+		},
+		
+		// 下拉刷新
+		onRefresh() {
+			this.refreshing = true
+			this.loadDynamics(true)
+		},
+		
+		// 查看动态详情
+		viewDynamicDetail(item) {
+			if (!item || !item.dynamicId) {
+				console.error('动态项无效:', item)
+				uni.showToast({
+					title: '动态信息无效',
+					icon: 'none'
+				})
+				return
+			}
+			uni.navigateTo({
+				url: `/pages/plaza/detail?dynamicId=${item.dynamicId}`
+			})
+		},
+		
+		// 查看动态评论
+		viewDynamicComments(item, index) {
+			// 如果 item 无效,尝试从 dynamicList 中根据索引获取
+			if (!item || !item.dynamicId) {
+				if (typeof index === 'number' && this.dynamicList && this.dynamicList[index]) {
+					item = this.dynamicList[index]
+				}
+			}
+			
+			if (!item || !item.dynamicId) {
+				console.error('动态项无效:', item, 'index:', index, 'dynamicList:', this.dynamicList)
+				uni.showToast({
+					title: '动态信息无效',
+					icon: 'none'
+				})
+				return
+			}
+			
+			uni.navigateTo({
+				url: `/pages/plaza/detail?dynamicId=${item.dynamicId}&scrollToComment=true`
+			})
+		},
+		
+		// 预览媒体
+		previewMedia(urls, index) {
+			if (!urls || urls.length === 0) return
+			uni.previewImage({
+				urls: urls,
+				current: index
+			})
+		},
+		
+		// 获取网格类名
+		getGridClass(item) {
+			const count = item.mediaUrls ? item.mediaUrls.length : 0
+			if (count === 1) return 'grid-1'
+			if (count === 2 || count === 4) return 'grid-2'
+			return 'grid-3'
+		},
+		
+		// 格式化时间
+		formatTime(timeStr) {
+			if (!timeStr) return ''
+			const date = new Date(timeStr)
+			const now = new Date()
+			const diff = now - date
+			const minutes = Math.floor(diff / 60000)
+			const hours = Math.floor(diff / 3600000)
+			const days = Math.floor(diff / 86400000)
+			
+			if (minutes < 1) return '刚刚'
+			if (minutes < 60) return `${minutes}分钟前`
+			if (hours < 24) return `${hours}小时前`
+			if (days < 7) return `${days}天前`
+			return date.toLocaleDateString()
+		},
+		
+		// 返回
+		goBack() {
+			uni.navigateBack()
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.user-dynamics-page {
+	min-height: 100vh;
+	background: #F5F5F5;
+	padding-top: 90rpx;
+}
+
+/* 自定义导航栏 */
+.custom-navbar {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	height: 90rpx;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 0 20rpx;
+	background: #FFFFFF;
+	border-bottom: 2rpx solid #E0E0E0;
+	z-index: 999;
+}
+
+.navbar-left,
+.navbar-right {
+	width: 80rpx;
+}
+
+.back-icon {
+	font-size: 40rpx;
+	color: #333;
+	font-weight: bold;
+}
+
+.navbar-title {
+	flex: 1;
+	text-align: center;
+	font-size: 32rpx;
+	font-weight: bold;
+	color: #333;
+}
+
+/* 页面内容 */
+.page-content {
+	height: calc(100vh - 90rpx);
+	padding: 20rpx;
+}
+
+.dynamic-list {
+	display: flex;
+	flex-direction: column;
+	gap: 20rpx;
+}
+
+.dynamic-card {
+	background: #FFFFFF;
+	border-radius: 16rpx;
+	padding: 24rpx;
+	border: 2rpx solid #E0E0E0;
+}
+
+.dynamic-content {
+	margin-bottom: 20rpx;
+}
+
+.dynamic-text {
+	font-size: 28rpx;
+	color: #333;
+	line-height: 1.8;
+}
+
+.dynamic-media {
+	margin-bottom: 20rpx;
+}
+
+.media-grid {
+	display: grid;
+	gap: 12rpx;
+}
+
+.grid-1 {
+	grid-template-columns: 1fr;
+}
+
+.grid-2 {
+	grid-template-columns: repeat(2, 1fr);
+}
+
+.grid-3 {
+	grid-template-columns: repeat(3, 1fr);
+}
+
+.media-image {
+	width: 100%;
+	height: 300rpx;
+	border-radius: 12rpx;
+	background: #F5F5F5;
+}
+
+.dynamic-info {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	padding-top: 16rpx;
+	border-top: 1rpx solid #F0F0F0;
+}
+
+.dynamic-time {
+	font-size: 24rpx;
+	color: #999;
+}
+
+.dynamic-stats {
+	display: flex;
+	gap: 24rpx;
+}
+
+.stat-item {
+	font-size: 24rpx;
+	color: #666;
+}
+
+.comment-stat {
+	cursor: pointer;
+	transition: opacity 0.2s;
+}
+
+.comment-stat:active {
+	opacity: 0.6;
+}
+
+.empty-container {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	padding: 120rpx 40rpx;
+}
+
+.empty-icon {
+	font-size: 120rpx;
+	margin-bottom: 30rpx;
+}
+
+.empty-text {
+	font-size: 28rpx;
+	color: #999;
+}
+
+.loading-container {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	padding: 100rpx;
+	font-size: 28rpx;
+	color: #999;
+}
+
+.loading-tip {
+	text-align: center;
+	padding: 40rpx;
+	color: #999;
+	font-size: 26rpx;
+}
+</style>
+

+ 4 - 0
service/Essential/src/main/resources/sql/add_introduction_to_user_profile.sql

@@ -0,0 +1,4 @@
+-- 为user_profile表添加introduction(自我简介)字段
+ALTER TABLE `user_profile` 
+ADD COLUMN `introduction` TEXT COMMENT '自我简介' AFTER `hobby`;
+