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 756e6f6..15be11f 100644 Binary files a/miniapp/static/ic_check_s.png and b/miniapp/static/ic_check_s.png differ diff --git a/miniapp/static/ic_empty.png b/miniapp/static/ic_empty.png new file mode 100644 index 0000000..80b7a94 Binary files /dev/null and b/miniapp/static/ic_empty.png differ 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,