From 181ffdae66717ad61000e6ef6e02c446479b17cf Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Mon, 5 Jan 2026 20:57:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9UI.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/components/ImageUpload/index.vue | 9 +- miniapp/components/Empty/index.vue | 4 +- miniapp/components/UserCard/index.vue | 32 +- miniapp/pages.json | 12 +- miniapp/pages/chat/index.vue | 1152 ++++++++---- miniapp/pages/index/index.vue | 225 ++- miniapp/pages/interact/favoritedMe.vue | 4 +- miniapp/pages/interact/myFavorite.vue | 4 +- miniapp/pages/interact/myUnlocked.vue | 4 +- miniapp/pages/interact/myViewed.vue | 4 +- miniapp/pages/interact/unlockedMe.vue | 4 +- miniapp/pages/interact/viewedMe.vue | 4 +- miniapp/pages/profile/detail.vue | 135 +- miniapp/pages/realname/index.vue | 516 ++++-- miniapp/pages/search/index.vue | 1538 ++++++++++------- miniapp/pages/search/result.vue | 369 ++++ miniapp/static/ic_check_s.png | Bin 749 -> 731 bytes miniapp/static/ic_empty.png | Bin 0 -> 19716 bytes miniapp/utils/cityData.js | 147 ++ miniapp/utils/image.js | 4 +- .../Controllers/AdminUploadController.cs | 14 +- .../Controllers/UserController.cs | 16 +- .../Properties/launchSettings.json | 24 + .../DTOs/Responses/ProfileResponses.cs | 45 + .../Interfaces/IInteractService.cs | 7 + .../Services/AdminUserService.cs | 2 +- .../Services/InteractService.cs | 18 + .../Services/ProfileService.cs | 2 + 28 files changed, 2931 insertions(+), 1364 deletions(-) create mode 100644 miniapp/pages/search/result.vue create mode 100644 miniapp/static/ic_empty.png create mode 100644 miniapp/utils/cityData.js create mode 100644 server/src/XiangYi.AppApi/Properties/launchSettings.json diff --git a/admin/src/components/ImageUpload/index.vue b/admin/src/components/ImageUpload/index.vue index 5a333b1..7c08334 100644 --- a/admin/src/components/ImageUpload/index.vue +++ b/admin/src/components/ImageUpload/index.vue @@ -60,9 +60,8 @@ import { ElMessage } from 'element-plus' import type { UploadFile, UploadFiles, UploadRawFile, UploadUserFile } from 'element-plus' import { getToken } from '@/utils/auth' -// 获取后端基础地址(去掉 /api 后缀) -const API_BASE = import.meta.env.VITE_API_BASE_URL || '' -const SERVER_BASE = API_BASE.replace(/\/api$/, '') +// 获取静态资源服务器地址(AppApi,用于图片等静态资源) +const STATIC_BASE = import.meta.env.VITE_STATIC_BASE_URL || 'http://localhost:5001' // 处理图片URL,将相对路径转换为完整URL function getFullImageUrl(url: string): string { @@ -71,8 +70,8 @@ function getFullImageUrl(url: string): string { if (url.startsWith('http://') || url.startsWith('https://')) { return url } - // 相对路径,拼接服务器地址 - return `${SERVER_BASE}${url.startsWith('/') ? '' : '/'}${url}` + // 相对路径,拼接静态资源服务器地址 + return `${STATIC_BASE}${url.startsWith('/') ? '' : '/'}${url}` } interface Props { diff --git a/miniapp/components/Empty/index.vue b/miniapp/components/Empty/index.vue index bcfd7ca..644654f 100644 --- a/miniapp/components/Empty/index.vue +++ b/miniapp/components/Empty/index.vue @@ -49,8 +49,8 @@ export default { emits: ['click'], data() { return { - defaultImage: '/static/empty.png', - hasDefaultImage: false // 默认不使用图片,使用emoji + defaultImage: '/static/ic_empty.png', + hasDefaultImage: true // 使用空占位图 } }, methods: { diff --git a/miniapp/components/UserCard/index.vue b/miniapp/components/UserCard/index.vue index f3ce59f..d8c401f 100644 --- a/miniapp/components/UserCard/index.vue +++ b/miniapp/components/UserCard/index.vue @@ -236,7 +236,7 @@ export default { diff --git a/miniapp/pages/index/index.vue b/miniapp/pages/index/index.vue index 2739181..751a596 100644 --- a/miniapp/pages/index/index.vue +++ b/miniapp/pages/index/index.vue @@ -14,28 +14,36 @@ - + @@ -51,7 +59,11 @@ - 今日推荐 + + 今日优质推荐 + 已更新 + + 每天早上五点准时更新 @@ -70,15 +82,8 @@ - + { pageLoading.value = true try { + // 获取状态栏高度 + uni.getSystemInfo({ + success: (res) => { + statusBarHeight.value = res.statusBarHeight || 20 + } + }) + // 恢复用户状态 userStore.restoreFromStorage() @@ -423,7 +438,24 @@ // 联系用户 const handleUserContact = (userId) => { - // 检查是否完善资料 + // 1. 首先检查是否已登录 + if (!userStore.isLoggedIn) { + uni.showModal({ + title: '提示', + content: '请先登录后再联系对方', + confirmText: '去登录', + success: (res) => { + if (res.confirm) { + uni.navigateTo({ + url: '/pages/auth/login' + }) + } + } + }) + return + } + + // 2. 检查是否完善资料 if (!userStore.isProfileCompleted) { uni.showModal({ title: '提示', @@ -440,7 +472,7 @@ return } - // 跳转到聊天页面(后续会实现解锁逻辑) + // 3. 跳转到聊天页面(后续会实现解锁逻辑) uni.navigateTo({ url: `/pages/chat/index?targetUserId=${userId}` }) @@ -487,6 +519,7 @@ }) return { + statusBarHeight, pageLoading, listLoading, noMoreData, @@ -518,13 +551,6 @@ handleRefresh } }, - // 下拉刷新 - async onPullDownRefresh() { - if (this.loadRecommendList) { - await this.loadRecommendList() - } - uni.stopPullDownRefresh() - }, // 页面显示 onShow() { // 每次显示页面时检查弹窗 @@ -554,72 +580,84 @@ flex-shrink: 0; } - // Banner 轮播图区域 + // Banner区域 .banner-section { - width: 100%; position: relative; + width: 100%; + } - .banner-overlay { - position: absolute; - top: 0; - left: 0; - right: 0; - z-index: 10; - padding: 20rpx 24rpx; - padding-top: calc(20rpx + env(safe-area-inset-top)); + // Banner 轮播图 + .banner-swiper { + width: 100%; + height: 420rpx; + + .banner-image { + width: 100%; + height: 100%; + } + } + + .banner-placeholder { + width: 100%; + height: 420rpx; + background: linear-gradient(135deg, #FFB6C1 0%, #FFC0CB 100%); + } + + // 浮层(导航栏+搜索框) + .banner-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 10; + } + + // 自定义导航栏 + .custom-navbar { + .navbar-content { + height: 44px; + display: flex; + align-items: center; + justify-content: center; .header-title { font-size: 36rpx; font-weight: 600; color: #fff; - margin-top: 96rpx; text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2); - margin-bottom: 28rpx; - text-align: center; - } - - .search-bar { - display: flex; - align-items: center; - justify-content: center; - background: rgba(255, 255, 255, 0.95); - border-radius: 40rpx; - padding: 20rpx 24rpx; - - .search-icon { - font-size: 28rpx; - margin-right: 12rpx; - color: #999; - } - - .search-placeholder { - font-size: 28rpx; - color: #999; - } } } + } - .banner-swiper { - width: 100%; - height: 420rpx; + // 搜索框 + .search-section { + padding: 16rpx 24rpx; - .banner-image { - width: 100%; - height: 100%; + .search-bar { + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.95); + border-radius: 40rpx; + padding: 20rpx 24rpx; + box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); + + .search-icon { + font-size: 28rpx; + margin-right: 12rpx; + color: #999; } - } - .banner-placeholder { - width: 100%; - height: 420rpx; - background: linear-gradient(135deg, #FFB6C1 0%, #FFC0CB 100%); + .search-placeholder { + font-size: 28rpx; + color: #999; + } } } // 金刚位导航 .kingkong-section { padding: 20rpx; - background-color: #fff; .kingkong-grid { display: flex; @@ -650,15 +688,38 @@ // 推荐标题栏 .section-header { display: flex; + flex-direction: column; align-items: center; - justify-content: space-between; - padding: 20rpx; + justify-content: center; + padding: 5rpx 20rpx 24rpx; background-color: #f8f8f8; - .section-title { - font-size: 32rpx; - font-weight: 600; - color: #333; + .section-title-wrapper { + display: flex; + align-items: center; + margin-bottom: 8rpx; + + .section-title-main { + font-size: 36rpx; + font-weight: 600; + color: #333; + } + + .section-title-highlight { + font-size: 42rpx; + color: #FF6A6A; + background: transparent; + padding: 0; + border-radius: 0; + font-weight: 600; + font-style: italic; + box-shadow: none; + } + } + + .section-subtitle { + font-size: 24rpx; + color: #999; } .section-more { diff --git a/miniapp/pages/interact/favoritedMe.vue b/miniapp/pages/interact/favoritedMe.vue index ffce3d4..5b80c7a 100644 --- a/miniapp/pages/interact/favoritedMe.vue +++ b/miniapp/pages/interact/favoritedMe.vue @@ -128,8 +128,8 @@ export default { const api = activeTab.value === 'favoritedMe' ? getFavoritedMe : getMyFavorite const res = await api(pageIndex.value, pageSize) - if (res && res.success) { - const newList = res.data?.list || [] + if (res && (res.success || res.code === 0)) { + const newList = res.data?.items || [] if (refresh) { list.value = newList diff --git a/miniapp/pages/interact/myFavorite.vue b/miniapp/pages/interact/myFavorite.vue index 5a3c051..705b83e 100644 --- a/miniapp/pages/interact/myFavorite.vue +++ b/miniapp/pages/interact/myFavorite.vue @@ -128,8 +128,8 @@ export default { const api = activeTab.value === 'favoritedMe' ? getFavoritedMe : getMyFavorite const res = await api(pageIndex.value, pageSize) - if (res && res.success) { - const newList = res.data?.list || [] + if (res && (res.success || res.code === 0)) { + const newList = res.data?.items || [] if (refresh) { list.value = newList diff --git a/miniapp/pages/interact/myUnlocked.vue b/miniapp/pages/interact/myUnlocked.vue index 20804d5..5609e67 100644 --- a/miniapp/pages/interact/myUnlocked.vue +++ b/miniapp/pages/interact/myUnlocked.vue @@ -128,8 +128,8 @@ export default { const api = activeTab.value === 'unlockedMe' ? getUnlockedMe : getMyUnlocked const res = await api(pageIndex.value, pageSize) - if (res && res.success) { - const newList = res.data?.list || [] + if (res && (res.success || res.code === 0)) { + const newList = res.data?.items || [] if (refresh) { list.value = newList diff --git a/miniapp/pages/interact/myViewed.vue b/miniapp/pages/interact/myViewed.vue index b857711..73069c8 100644 --- a/miniapp/pages/interact/myViewed.vue +++ b/miniapp/pages/interact/myViewed.vue @@ -128,8 +128,8 @@ export default { const api = activeTab.value === 'viewedMe' ? getViewedMe : getMyViewed const res = await api(pageIndex.value, pageSize) - if (res && res.success) { - const newList = res.data?.list || [] + if (res && (res.success || res.code === 0)) { + const newList = res.data?.items || [] if (refresh) { list.value = newList diff --git a/miniapp/pages/interact/unlockedMe.vue b/miniapp/pages/interact/unlockedMe.vue index d553b53..339ecfd 100644 --- a/miniapp/pages/interact/unlockedMe.vue +++ b/miniapp/pages/interact/unlockedMe.vue @@ -128,8 +128,8 @@ export default { const api = activeTab.value === 'unlockedMe' ? getUnlockedMe : getMyUnlocked const res = await api(pageIndex.value, pageSize) - if (res && res.success) { - const newList = res.data?.list || [] + if (res && (res.success || res.code === 0)) { + const newList = res.data?.items || [] if (refresh) { list.value = newList diff --git a/miniapp/pages/interact/viewedMe.vue b/miniapp/pages/interact/viewedMe.vue index 96e81d9..49d033b 100644 --- a/miniapp/pages/interact/viewedMe.vue +++ b/miniapp/pages/interact/viewedMe.vue @@ -128,8 +128,8 @@ export default { const api = activeTab.value === 'viewedMe' ? getViewedMe : getMyViewed const res = await api(pageIndex.value, pageSize) - if (res && res.success) { - const newList = res.data?.list || [] + if (res && (res.success || res.code === 0)) { + const newList = res.data?.items || [] if (refresh) { list.value = newList diff --git a/miniapp/pages/profile/detail.vue b/miniapp/pages/profile/detail.vue index 4eb8ab8..08d2b18 100644 --- a/miniapp/pages/profile/detail.vue +++ b/miniapp/pages/profile/detail.vue @@ -52,6 +52,10 @@ {{ userDetail.nickname }} ({{ relationshipText }}) + + 已实名 + 会员 + 相亲ID: {{ userDetail.xiangQinNo }} @@ -64,10 +68,6 @@ {{ genderText }} · {{ userDetail.birthYear }}年 - - 已实名 - 会员 - @@ -259,7 +259,7 @@ import { ref, computed, onMounted } from 'vue' import { useUserStore } from '@/store/user.js' import { getUserDetail } from '@/api/user.js' -import { favorite, unlock, recordView } from '@/api/interact.js' +import { favorite, unlock } from '@/api/interact.js' import { getFullImageUrl } from '@/utils/image.js' import Loading from '@/components/Loading/index.vue' import Popup from '@/components/Popup/index.vue' @@ -459,14 +459,14 @@ const loadUserDetail = async () => { loading.value = true try { const res = await getUserDetail(userId.value) - if (res && res.code === 0 && res.data) { + // 后端返回 code: 0 表示成功 + if (res && (res.success || res.code === 0) && res.data) { userDetail.value = res.data isFavorited.value = res.data.isFavorited || false isUnlocked.value = res.data.isUnlocked || false remainingUnlockQuota.value = res.data.remainingUnlockQuota || 0 - // 记录浏览 - await recordView(userId.value) + // 已登录用户记录浏览(后端已处理,这里不需要再调用) } } catch (error) { console.error('加载用户详情失败:', error) @@ -492,9 +492,24 @@ const previewPhoto = (index) => { // 收藏处理 const handleFavorite = async () => { + // 检查是否登录 + if (!userStore.isLoggedIn) { + uni.showModal({ + title: '提示', + content: '请先登录后再收藏', + confirmText: '去登录', + success: (res) => { + if (res.confirm) { + uni.navigateTo({ url: '/pages/login/index' }) + } + } + }) + return + } + try { const res = await favorite(userId.value) - if (res && res.code === 0) { + if (res && (res.success || res.code === 0)) { isFavorited.value = res.data?.isFavorited ?? !isFavorited.value uni.showToast({ title: isFavorited.value ? '收藏成功' : '已取消收藏', @@ -513,6 +528,21 @@ const handleShare = () => { // 联系处理 const handleContact = () => { + // 检查是否登录 + if (!userStore.isLoggedIn) { + uni.showModal({ + title: '提示', + content: '请先登录后再联系对方', + confirmText: '去登录', + success: (res) => { + if (res.confirm) { + uni.navigateTo({ url: '/pages/login/index' }) + } + } + }) + return + } + if (!userStore.isProfileCompleted) { showProfilePopup.value = true return @@ -550,7 +580,7 @@ const handleConfirmUnlock = async () => { try { const res = await unlock(userId.value) - if (res && res.code === 0) { + if (res && (res.success || res.code === 0)) { isUnlocked.value = true showUnlockPopup.value = false remainingUnlockQuota.value = Math.max(0, remainingUnlockQuota.value - 1) @@ -569,6 +599,21 @@ const handleGoMember = () => { // 拨打电话 const handleCall = () => { + // 检查是否登录 + if (!userStore.isLoggedIn) { + uni.showModal({ + title: '提示', + content: '请先登录后再联系对方', + confirmText: '去登录', + success: (res) => { + if (res.confirm) { + uni.navigateTo({ url: '/pages/login/index' }) + } + } + }) + return + } + if (!isUnlocked.value) { showUnlockPopup.value = true return @@ -641,16 +686,16 @@ defineExpose({ padding: 0 24rpx; .navbar-back { - width: 60rpx; - height: 60rpx; + width: 80rpx; + height: 80rpx; display: flex; align-items: center; justify-content: center; .back-icon { - font-size: 48rpx; + font-size: 64rpx; color: #fff; - font-weight: 300; + font-weight: 400; } } @@ -702,6 +747,8 @@ defineExpose({ display: flex; align-items: center; margin-bottom: 8rpx; + flex-wrap: wrap; + gap: 8rpx; .nickname { font-size: 32rpx; @@ -712,7 +759,28 @@ defineExpose({ .relationship { font-size: 28rpx; color: #666; + } + + .header-tags { + display: flex; + gap: 8rpx; margin-left: 8rpx; + + .tag { + font-size: 20rpx; + padding: 4rpx 12rpx; + border-radius: 16rpx; + + &.tag-realname { + background: #e8f5e9; + color: #4caf50; + } + + &.tag-member { + background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); + color: #ff9800; + } + } } } @@ -758,9 +826,8 @@ defineExpose({ .gender-year-row { display: flex; align-items: center; + justify-content: space-between; margin-bottom: 24rpx; - flex-wrap: wrap; - gap: 16rpx; .gender-year { font-size: 40rpx; @@ -772,29 +839,7 @@ defineExpose({ } } - .tags-row { - display: flex; - gap: 12rpx; - - .tag { - font-size: 22rpx; - padding: 6rpx 16rpx; - border-radius: 20rpx; - - &.tag-realname { - background: #e8f5e9; - color: #4caf50; - } - - &.tag-member { - background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); - color: #ff9800; - } - } - } - .share-btn { - margin-left: auto; padding: 12rpx 32rpx; font-size: 26rpx; color: #ff6b6b; @@ -1034,7 +1079,7 @@ defineExpose({ height: 88rpx; line-height: 88rpx; font-size: 30rpx; - border-radius: 44rpx; + border-radius: 16rpx; border: none; &::after { @@ -1043,23 +1088,21 @@ defineExpose({ } .btn-greet { - background: linear-gradient(135deg, #ff8a8a 0%, #ff6b6b 100%); + background: #4cd964; color: #fff; } .btn-favorite { - background: #fff; - color: #666; - border: 2rpx solid #ddd; + background: #f5d742; + color: #fff; &.active { - color: #ff6b6b; - border-color: #ff6b6b; + background: #e5c732; } } .btn-call { - background: linear-gradient(135deg, #ffb5b5 0%, #ff9a9a 100%); + background: #ff9a9a; color: #fff; } } diff --git a/miniapp/pages/realname/index.vue b/miniapp/pages/realname/index.vue index 3ec86c2..cca6c05 100644 --- a/miniapp/pages/realname/index.vue +++ b/miniapp/pages/realname/index.vue @@ -3,8 +3,24 @@ + + + + + + + + + + + + 实名认证 + + + + - + 实名认证已完成 @@ -25,198 +41,174 @@ - - - - - - 1 - 支付费用 - - - - 2 - 上传证件 - - - - 3 - 认证完成 - + + + + + {{ currentStep > 1 ? '✓' : '1' }} + 支付费用 - - - - 🛡️ - 实名认证 - 完成实名认证,提升信任度,获得更多关注 - - - - - 展示"已实名"徽章 - - - - 提升资料可信度 - - - - 获得更多用户关注 - + + + {{ currentStep > 2 ? '✓' : '2' }} + 上传证件 - - - 认证费用 - - ¥ - {{ verificationFee }} - + + + 3 + 认证完成 - - - - - - - - - - - 支付费用 + + + + + + 🛡️ + 实名认证 + 完成实名认证,提升信任度,获得更多关注 + + + + + 展示"已实名"徽章 + + + + 提升资料可信度 + + + + 获得更多用户关注 + - - - 2 - 上传证件 - - - - 3 - 认证完成 + + + 认证费用 + + ¥ + {{ verificationFee }} + - - 请上传身份证照片 - 请确保照片清晰、完整,信息可辨认 - - - - 身份证人像面 - - - - 📷 - 点击上传 + + + + 请上传身份证照片 + 请确保照片清晰、完整,信息可辨认 + + + + 身份证人像面 + + + + 📷 + 点击上传 + - - - - - 身份证国徽面 - - - - 📷 - 点击上传 + + + + 身份证国徽面 + + + + 📷 + 点击上传 + - - - - 温馨提示: - • 请上传本人有效身份证照片 - • 照片需清晰可辨,无遮挡 - • 您的信息将被严格保密 + + + 温馨提示: + • 请上传本人有效身份证照片 + • 照片需清晰可辨,无遮挡 + • 您的信息将被严格保密 + - - + + + + + 实名认证成功 + 您的身份信息已通过验证 + + + + 姓名: + {{ maskedName }} + + + 身份证号: + {{ maskedIdNumber }} + + + - + - - - - - - - 支付费用 - - - - - 上传证件 - - - - 3 - 认证完成 - - - + + + - - - 实名认证成功 - 您的身份信息已通过验证 - - - - 姓名: - {{ maskedName }} - - - 身份证号: - {{ maskedIdNumber }} - - - + - - - + @@ -227,7 +219,7 @@ import { ref, computed, onMounted } from 'vue' import { useUserStore } from '@/store/user.js' import { createOrder } from '@/api/order.js' import { maskName, maskIdNumber } from '@/utils/format.js' -import { get, post } from '@/api/request.js' +import { get } from '@/api/request.js' import { getToken } from '@/utils/storage.js' import Loading from '@/components/Loading/index.vue' @@ -242,6 +234,9 @@ export default { setup() { const userStore = useUserStore() + // 状态栏高度 + const statusBarHeight = ref(20) + // 页面状态 const pageLoading = ref(true) const paying = ref(false) @@ -258,6 +253,20 @@ export default { // 认证结果 const verificationResult = ref(null) + // 获取系统信息 + const getSystemInfo = () => { + uni.getSystemInfo({ + success: (res) => { + statusBarHeight.value = res.statusBarHeight || 20 + } + }) + } + + // 返回上一页 + const handleBack = () => { + uni.navigateBack() + } + // 计算属性 const isVerified = computed(() => userStore.isRealName) @@ -308,6 +317,7 @@ export default { const initPage = async () => { pageLoading.value = true try { + getSystemInfo() userStore.restoreFromStorage() await getRealNameStatus() } catch (error) { @@ -552,6 +562,7 @@ export default { }) return { + statusBarHeight, pageLoading, paying, submitting, @@ -563,6 +574,7 @@ export default { canSubmit, maskedName, maskedIdNumber, + handleBack, handlePayment, chooseIdCardFront, chooseIdCardBack, @@ -580,13 +592,71 @@ export default { diff --git a/miniapp/pages/search/result.vue b/miniapp/pages/search/result.vue new file mode 100644 index 0000000..43befad --- /dev/null +++ b/miniapp/pages/search/result.vue @@ -0,0 +1,369 @@ + + + + + diff --git a/miniapp/static/ic_check_s.png b/miniapp/static/ic_check_s.png index 756e6f6570ca863fca349da480f95becd3c977c0..15be11f2b788f7c930aa2fc3709558d9db4e7848 100644 GIT binary patch delta 707 zcmV;!0zCcg1=|IXB!7@eL_t(|0jyQOYZO5g{@&~gDNZ?iho~gF}|aZo}BLjfwlx% zd_1aA=_w@GV)8Nsm)W0#NXVWlBWyAdJV52(t48sL768%dVqRc-SC&V5tQN^^af-7)SNu;l~49KJ`&A-In zuN^4<;7Du-Rt~O`0GH68B;dKZw^}F+;q=*85R9Qw6dA|mO*F>nmpPLKKqLB{G!4GN zff-t}3dS8Eb}G>z+mg?&N^U;S8Tk*@@Xp+QILSzT=6~(MZKUdmgKEY62D_*$r6qfo zrR@C_$v!m5$l~0`YFRpidjJU?M0U+@GL@(eNdlgq{d6L552(9b0k*xLZqg)SFd;pG z6Hw&~SAaKk+iN4q!tA$`&%6fr!NFm(&38aejKuy4%kdd*#qKq)B~_hM!Mc;h><$EW zgI1$$yMJYb_{}%1Tam#Ix+z?>^Do2Dw&T9{2h)@h204TB1jYnCoVUPWOlLeM!8`$b zNdD$CmdaLQkNumVz(e{1RjCo!vp-nS>-2dQufpB!8kwL_t(|0jyQeYZE~f{$@yGgbFz%A%akqO1IF1e}V@OIYtl^ z+C#m05LEQ0pn^xiUIcIU5J69kcu-ML58k}_Bk4g=6k1Hw9&$*NZKmHFvdLzWrb(K| zyxn;-?|t9Qn|Zq$10?kYGwC9(QD3J@_?0T6kuJf|UZ?$ndVeGF9n%9K(3c=l#L?6m z9&xq^g?rS~3OJ$^A{I;`Lpw{o8>{07NmnJt&N+`B0y-~5flta{sSSudB^OgDa#818 z&n|-W8g9`cPv9xOM{RK~>PZWu9or_b>;|{7Um-@agoOac5=j2Yz7h=X_f4#a!Qop1 z=I=g1`yN%Eg?=p&N6 ziUV^l{(oU@#E{p2POv~U^5F@$ulY|nIE6CFdak1D8`!Mug6vw$KqCsdj#W%fYJLn? zK(PeEM8=D>C(@YvCBYOhOCX(!hsWVNFJt4VV6af12C;1d8IZyv`+4?j)R7u5N|jky z6pV0S7lBL&dp!n^&IPgeQUd#2)QZ%JWWn|CAx;nn$se81)D4dD+jJ8KZwO>7o^PLH zzh#Wyr0ewvJTB^j#7L~GI{*Lx|NnQICPe@M00v1!K~w_(n_lqJ#$a4D00000NkvXX Hu0mjfn!8Hl diff --git a/miniapp/static/ic_empty.png b/miniapp/static/ic_empty.png new file mode 100644 index 0000000000000000000000000000000000000000..80b7a94db0123322b6444a01566777bd9b43c463 GIT binary patch literal 19716 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≶5E5Nmo~mHdbaKYt^80< zaE<-A`r?vZ!{#;8j2M?U7yuT9s#vci$wMA{vOhB4sD5pK1NV*ah1xe6|e>Ph8&Pj%gRvqArhdhFr*OQ`oTj*uoLP?|uHnVQ&FmL$^$=;7mn%l6FCh$F`5+A;$F=}}4UNEai$S0uT`)!K5{qfmhNOCewP6t-;V zh1H9vPgsO3J2w?SE!!3x8jv#|lDei`m9;SGY~~WV(iG<6=2qIlPFj9h>+`^0J45dO zy;wZGd4&|)9$tgXDwn6$obqXYeP~fT>|vBZ^(N%Liqm=e5(bIrsb^Jse(w@Oz+p>O zgnB1kGFnS=icUl>2NdrlH+v?qUAE(u z(*r4F$M-H%Vc(ujjMtViu$pM^#kKa`cwgd{&f;Zj@v2kdOhf-bk|ucn@ALZ}zZLZ} zjNPK?&kTI;Ht$Ncqmu65>UZ59z2T9M)5S3o6Em`de74z099G!goZfIz{mo0qvuHas zzpL}R!+MZMe|R|LO7nP3a+()cpkartZaVsd|J;6;_1p%JRioPQsj=E9k&9ih8ND}F z7>mSt21JP<+roKO_?YZ!(0!L3apmF1Zlj6C!CIdaO_h>kTH_2v0H!kTsZ}`OD+urU zL$+Nx8FFraw`M74b|pvqP0(LSGt|%f#oLqQ7)Uk@l_}Qv#pyr%5O&*(mZb|-DH;8h z>uR%4g};vRYJ(zt!c#1{SkTQ@XK&XL85Sa?OkZ?*d(3ms#KQm&Q-rxNEg6E8P4lp z&()}|M$sh;;VH-Y{sSSnoT9{kurVkCPIkQ_jUWB{-Gc|YC!YUlcb&1KZ?;4V?d%|Z z#?Mr*-)S9slFX^9_^$iRZ2pnAzZ!FnZ44JoO?Vj(IueykBE){ZRKKxNa-BI+086u{ z_|t(cuHRTs4|F<(d7k&8IY=DLx+D;G9z_S$n>a4TLt;tuaa;?qL=eJm>`b{jwwqPU z)Ivic8FnD$;a00IX;{nzxS71`~o)%1Wo%JN6DAq;nkAe+MHpOq8xm|%ki`+Gwb^l!1&FzIs5 zn8Q+wzAzt7Qhp@wMzvpNfOeeEs0<22L<`s4^K+T-If^JaT~82RmKfV9qB1es@+Mwc zUfRs24)8~u1{ntQX)q8^1UCuv6$@g~u#cYU#-7wF!J)d>$eoseLdzaj{xWMa9zToy zvI+$-@Xq7->*m?1n1QS2YbfUFaXl%mt=a;nrC?3PqN?X81XY;?QrOQLD5M1o%$ZlX zwhR(vXbUX!*-?=JUM>zZjftVb0p}gLf49Lnb7U44FQ`G*F$&ekg#esq8<=L$1brZY zhx-$-JhDC{v4I}KH*bTHbc!XaEQ)AezYd=K)JCe2%D4b&;d;>!n(}}>;OM!uhK3r- zB`IhOfZ{G!tD>?I^x_zQWz!3uTO$(^B6ShbZ`P_d7(*mhw%Q1VJYM=T@Iq z@jXvP6WPCYXNN-vWHT0+k&mkWAi#bqk(fSQ7}(+G&X2%+ z*s?__K)Z3%rs}T(H&CwRvVm#>lYoFh8QPI6_*3 wI~Doi$>ib&4ujd)IHyYIV=$Oa=u3$#MoqDzV#C(#HT;6HwPIO{E!<=O2cKX|hyVZp literal 0 HcmV?d00001 diff --git a/miniapp/utils/cityData.js b/miniapp/utils/cityData.js new file mode 100644 index 0000000..41f892a --- /dev/null +++ b/miniapp/utils/cityData.js @@ -0,0 +1,147 @@ +// 中国省市数据 +export const provinceData = [ + { + name: '北京市', + cities: ['北京'] + }, + { + name: '上海市', + cities: ['上海'] + }, + { + name: '天津市', + cities: ['天津'] + }, + { + name: '重庆市', + cities: ['重庆'] + }, + { + name: '广东省', + cities: ['广州', '深圳', '东莞', '佛山', '珠海', '惠州', '中山', '汕头', '江门', '湛江', '肇庆', '茂名', '揭阳', '梅州', '清远', '阳江', '韶关', '河源', '云浮', '汕尾', '潮州'] + }, + { + name: '江苏省', + cities: ['南京', '苏州', '无锡', '常州', '南通', '徐州', '扬州', '盐城', '泰州', '镇江', '淮安', '连云港', '宿迁'] + }, + { + name: '浙江省', + cities: ['杭州', '宁波', '温州', '绍兴', '嘉兴', '金华', '台州', '湖州', '丽水', '衢州', '舟山'] + }, + { + name: '山东省', + cities: ['济南', '青岛', '烟台', '潍坊', '临沂', '淄博', '济宁', '泰安', '威海', '德州', '聊城', '菏泽', '滨州', '枣庄', '日照', '东营'] + }, + { + name: '河南省', + cities: ['郑州', '洛阳', '南阳', '许昌', '周口', '新乡', '商丘', '信阳', '平顶山', '驻马店', '安阳', '焦作', '开封', '濮阳', '漯河', '三门峡', '鹤壁'] + }, + { + name: '四川省', + cities: ['成都', '绵阳', '德阳', '宜宾', '南充', '泸州', '达州', '乐山', '内江', '自贡', '眉山', '广安', '遂宁', '资阳', '攀枝花', '广元', '雅安', '巴中'] + }, + { + name: '湖北省', + cities: ['武汉', '宜昌', '襄阳', '荆州', '黄冈', '十堰', '孝感', '荆门', '咸宁', '鄂州', '黄石', '随州'] + }, + { + name: '湖南省', + cities: ['长沙', '株洲', '湘潭', '衡阳', '岳阳', '常德', '郴州', '邵阳', '怀化', '永州', '益阳', '娄底', '张家界'] + }, + { + name: '福建省', + cities: ['福州', '厦门', '泉州', '漳州', '莆田', '宁德', '三明', '南平', '龙岩'] + }, + { + name: '安徽省', + cities: ['合肥', '芜湖', '蚌埠', '阜阳', '淮南', '安庆', '马鞍山', '宿州', '滁州', '六安', '淮北', '铜陵', '亳州', '宣城', '池州', '黄山'] + }, + { + name: '河北省', + cities: ['石家庄', '唐山', '保定', '邯郸', '廊坊', '沧州', '邢台', '衡水', '秦皇岛', '张家口', '承德'] + }, + { + name: '陕西省', + cities: ['西安', '咸阳', '宝鸡', '渭南', '汉中', '榆林', '延安', '安康', '商洛', '铜川'] + }, + { + name: '辽宁省', + cities: ['沈阳', '大连', '鞍山', '锦州', '抚顺', '营口', '盘锦', '朝阳', '丹东', '辽阳', '本溪', '葫芦岛', '铁岭', '阜新'] + }, + { + name: '江西省', + cities: ['南昌', '赣州', '九江', '宜春', '吉安', '上饶', '抚州', '景德镇', '萍乡', '新余', '鹰潭'] + }, + { + name: '山西省', + cities: ['太原', '大同', '运城', '临汾', '晋中', '长治', '晋城', '忻州', '吕梁', '朔州', '阳泉'] + }, + { + name: '云南省', + cities: ['昆明', '曲靖', '大理', '玉溪', '红河', '楚雄', '文山', '昭通', '普洱', '保山', '丽江', '临沧'] + }, + { + name: '广西', + cities: ['南宁', '柳州', '桂林', '玉林', '梧州', '北海', '贵港', '钦州', '百色', '河池', '来宾', '崇左', '防城港', '贺州'] + }, + { + name: '贵州省', + cities: ['贵阳', '遵义', '毕节', '黔南', '黔东南', '六盘水', '铜仁', '安顺', '黔西南'] + }, + { + name: '黑龙江省', + cities: ['哈尔滨', '大庆', '齐齐哈尔', '牡丹江', '绥化', '佳木斯', '鸡西', '双鸭山', '鹤岗', '黑河', '伊春', '七台河'] + }, + { + name: '吉林省', + cities: ['长春', '吉林', '四平', '延边', '松原', '白城', '通化', '白山', '辽源'] + }, + { + name: '内蒙古', + cities: ['呼和浩特', '包头', '鄂尔多斯', '赤峰', '通辽', '呼伦贝尔', '巴彦淖尔', '乌兰察布', '锡林郭勒', '乌海', '兴安盟', '阿拉善盟'] + }, + { + name: '新疆', + cities: ['乌鲁木齐', '昌吉', '巴音郭楞', '伊犁', '阿克苏', '喀什', '哈密', '克拉玛依', '博尔塔拉', '吐鲁番', '和田', '石河子', '塔城', '阿勒泰'] + }, + { + name: '甘肃省', + cities: ['兰州', '天水', '白银', '庆阳', '平凉', '酒泉', '张掖', '武威', '定西', '金昌', '陇南', '临夏', '嘉峪关', '甘南'] + }, + { + name: '海南省', + cities: ['海口', '三亚', '儋州', '琼海', '文昌', '万宁', '东方', '五指山'] + }, + { + name: '宁夏', + cities: ['银川', '吴忠', '石嘴山', '中卫', '固原'] + }, + { + name: '青海省', + cities: ['西宁', '海东', '海西', '海北', '海南', '黄南', '果洛', '玉树'] + }, + { + name: '西藏', + cities: ['拉萨', '日喀则', '昌都', '林芝', '山南', '那曲', '阿里'] + }, + { + name: '香港', + cities: ['香港'] + }, + { + name: '澳门', + cities: ['澳门'] + }, + { + name: '台湾', + cities: ['台北', '高雄', '台中', '台南', '新北', '桃园'] + } +] + +// 热门城市 +export const hotCities = [ + '北京', '上海', '广州', '深圳', + '杭州', '成都', '武汉', '南京', + '苏州', '西安', '重庆', '天津', + '长沙', '郑州', '青岛', '厦门' +] diff --git a/miniapp/utils/image.js b/miniapp/utils/image.js index 6b21565..0921a49 100644 --- a/miniapp/utils/image.js +++ b/miniapp/utils/image.js @@ -3,8 +3,8 @@ * 将相对路径转换为完整的后端URL */ -// 后端服务器基础地址(图片资源 - 使用AdminApi,因为图片存储在AdminApi的wwwroot下) -const IMAGE_BASE_URL = 'http://localhost:5000' +// 后端服务器基础地址(图片资源 - 使用AppApi,因为图片存储在AppApi的wwwroot下) +const IMAGE_BASE_URL = 'http://localhost:5001' /** * 获取完整的图片URL diff --git a/server/src/XiangYi.AdminApi/Controllers/AdminUploadController.cs b/server/src/XiangYi.AdminApi/Controllers/AdminUploadController.cs index 0f9f9c8..59dfe39 100644 --- a/server/src/XiangYi.AdminApi/Controllers/AdminUploadController.cs +++ b/server/src/XiangYi.AdminApi/Controllers/AdminUploadController.cs @@ -98,7 +98,17 @@ public class AdminUploadController : ControllerBase /// private async Task SaveToLocalAsync(IFormFile file, string fileName, string folder) { - var uploadPath = Path.Combine(_environment.WebRootPath ?? "wwwroot", "uploads", folder); + // 使用 AppApi 的 wwwroot 目录作为共享存储 + var appApiBasePath = Path.GetFullPath(Path.Combine(_environment.ContentRootPath, "..", "XiangYi.AppApi", "wwwroot")); + var uploadPath = Path.Combine(appApiBasePath, "uploads", folder); + + // 如果 AppApi 路径不存在,回退到当前项目的 wwwroot + if (!Directory.Exists(appApiBasePath)) + { + _logger.LogWarning("AppApi wwwroot 目录不存在,使用本地存储: {Path}", appApiBasePath); + uploadPath = Path.Combine(_environment.WebRootPath ?? "wwwroot", "uploads", folder); + } + if (!Directory.Exists(uploadPath)) { Directory.CreateDirectory(uploadPath); @@ -107,6 +117,8 @@ public class AdminUploadController : ControllerBase var filePath = Path.Combine(uploadPath, fileName); using var stream = new FileStream(filePath, FileMode.Create); await file.CopyToAsync(stream); + + _logger.LogInformation("文件保存到: {FilePath}", filePath); // 返回相对URL return $"/uploads/{folder}/{fileName}"; diff --git a/server/src/XiangYi.AppApi/Controllers/UserController.cs b/server/src/XiangYi.AppApi/Controllers/UserController.cs index 2ccff66..ec3c45d 100644 --- a/server/src/XiangYi.AppApi/Controllers/UserController.cs +++ b/server/src/XiangYi.AppApi/Controllers/UserController.cs @@ -61,6 +61,7 @@ public class UserController : ControllerBase /// 用户详情请求 /// 用户详情 [HttpPost("detail")] + [AllowAnonymous] public async Task> GetUserDetail([FromBody] UserDetailRequest request) { if (request.UserId <= 0) @@ -70,8 +71,11 @@ public class UserController : ControllerBase var currentUserId = GetCurrentUserId(); - // 记录浏览 - await _interactService.RecordViewAsync(currentUserId, request.UserId); + // 已登录用户记录浏览(排除查看自己) + if (currentUserId > 0 && currentUserId != request.UserId) + { + await _interactService.RecordViewAsync(currentUserId, request.UserId); + } // 获取用户资料 var profile = await _profileService.GetByUserIdAsync(request.UserId); @@ -81,6 +85,14 @@ public class UserController : ControllerBase return ApiResponse.Error(ErrorCodes.UserNotFound, "用户不存在"); } + // 已登录用户查询互动状态(排除查看自己) + if (currentUserId > 0 && currentUserId != request.UserId) + { + profile.IsFavorited = await _interactService.IsFavoritedAsync(currentUserId, request.UserId); + profile.IsUnlocked = await _interactService.IsUnlockedAsync(currentUserId, request.UserId); + profile.RemainingUnlockQuota = await _interactService.GetRemainingUnlockQuotaAsync(currentUserId); + } + return ApiResponse.Success(profile); } diff --git a/server/src/XiangYi.AppApi/Properties/launchSettings.json b/server/src/XiangYi.AppApi/Properties/launchSettings.json new file mode 100644 index 0000000..08e5dd6 --- /dev/null +++ b/server/src/XiangYi.AppApi/Properties/launchSettings.json @@ -0,0 +1,24 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7001;http://localhost:5001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/server/src/XiangYi.Application/DTOs/Responses/ProfileResponses.cs b/server/src/XiangYi.Application/DTOs/Responses/ProfileResponses.cs index 419da49..882d8ad 100644 --- a/server/src/XiangYi.Application/DTOs/Responses/ProfileResponses.cs +++ b/server/src/XiangYi.Application/DTOs/Responses/ProfileResponses.cs @@ -159,6 +159,51 @@ public class ProfileResponse /// 是否实名认证 /// public bool IsRealName { get; set; } + + ///

+ /// 是否已收藏(当前用户对该用户) + /// + public bool IsFavorited { get; set; } + + /// + /// 是否已解锁(当前用户对该用户) + /// + public bool IsUnlocked { get; set; } + + /// + /// 剩余解锁次数 + /// + public int RemainingUnlockQuota { get; set; } + + /// + /// 头像URL + /// + public string? Avatar { get; set; } + + /// + /// 是否独居 + /// + public bool IsLivingAlone { get; set; } + + /// + /// 父母状况 + /// + public string? ParentStatus { get; set; } + + /// + /// 父母是否退休 + /// + public bool IsParentRetired { get; set; } + + /// + /// 父母现居城市 + /// + public string? ParentCity { get; set; } + + /// + /// 父母是否有房 + /// + public bool ParentHasHouse { get; set; } } /// diff --git a/server/src/XiangYi.Application/Interfaces/IInteractService.cs b/server/src/XiangYi.Application/Interfaces/IInteractService.cs index 8fd6b4c..c1b4c6c 100644 --- a/server/src/XiangYi.Application/Interfaces/IInteractService.cs +++ b/server/src/XiangYi.Application/Interfaces/IInteractService.cs @@ -79,4 +79,11 @@ public interface IInteractService /// 检查是否已解锁 /// Task IsUnlockedAsync(long userId, long targetUserId); + + /// + /// 获取剩余解锁次数 + /// + /// 用户ID + /// 剩余解锁次数 + Task GetRemainingUnlockQuotaAsync(long userId); } diff --git a/server/src/XiangYi.Application/Services/AdminUserService.cs b/server/src/XiangYi.Application/Services/AdminUserService.cs index 668f4d2..6eece93 100644 --- a/server/src/XiangYi.Application/Services/AdminUserService.cs +++ b/server/src/XiangYi.Application/Services/AdminUserService.cs @@ -452,7 +452,7 @@ public class AdminUserService : IAdminUserService Phone = MaskPhone(user.Phone), Gender = user.Gender, GenderText = GetGenderText(user.Gender), - City = user.City, + City = profile?.WorkCity ?? user.City, Status = user.Status, StatusText = GetStatusText(user.Status), IsProfileCompleted = user.IsProfileCompleted, diff --git a/server/src/XiangYi.Application/Services/InteractService.cs b/server/src/XiangYi.Application/Services/InteractService.cs index c9d33b4..fd35741 100644 --- a/server/src/XiangYi.Application/Services/InteractService.cs +++ b/server/src/XiangYi.Application/Services/InteractService.cs @@ -477,6 +477,24 @@ public class InteractService : IInteractService u.UserId == userId && u.TargetUserId == targetUserId); } + /// + public async Task GetRemainingUnlockQuotaAsync(long userId) + { + var user = await _userRepository.GetByIdAsync(userId); + if (user == null) + { + return 0; + } + + // 会员有无限次数,返回一个大数 + if (user.IsMember) + { + return 999; + } + + return user.ContactCount; + } + #endregion #region 私有方法 diff --git a/server/src/XiangYi.Application/Services/ProfileService.cs b/server/src/XiangYi.Application/Services/ProfileService.cs index 03ca739..a0a2837 100644 --- a/server/src/XiangYi.Application/Services/ProfileService.cs +++ b/server/src/XiangYi.Application/Services/ProfileService.cs @@ -180,6 +180,7 @@ public class ProfileService : IProfileService UserId = userId, Nickname = user.Nickname, XiangQinNo = user.XiangQinNo, + Avatar = user.Avatar, Photos = photos.OrderBy(p => p.Sort).Select(p => new PhotoResponse { Id = p.Id, @@ -200,6 +201,7 @@ public class ProfileService : IProfileService UserId = userId, Nickname = user.Nickname, XiangQinNo = user.XiangQinNo, + Avatar = user.Avatar, Relationship = profile.Relationship, Surname = profile.Surname, ChildGender = profile.ChildGender,