Merge remote-tracking branch origin into feature/juguohong/20250816
This commit is contained in:
@@ -1,26 +1,24 @@
|
||||
import React, { useState, useEffect, useRef, useImperativeHandle, forwardRef } from 'react'
|
||||
import { View, Text, Button, Swiper, SwiperItem, Image, Map, ScrollView } from '@tarojs/components'
|
||||
import { Cell, Avatar, Progress, Popover } from '@nutui/nutui-react-taro'
|
||||
import React, { useState, useRef, useImperativeHandle, forwardRef } from 'react'
|
||||
import { View, Text, Image, Map, ScrollView } from '@tarojs/components'
|
||||
import { Avatar, Popover } from '@nutui/nutui-react-taro'
|
||||
import Taro, { useRouter, useShareAppMessage, useShareTimeline, useDidShow } from '@tarojs/taro'
|
||||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
// 导入API服务
|
||||
import DetailService from '../../services/detailService'
|
||||
import { updateUserProfile, get_user_info } from '../../services/loginService'
|
||||
import { getCurrentLocation } from '../../utils/locationUtils'
|
||||
import { CommonPopup, withAuth } from '@/components'
|
||||
import DetailService, { MATCH_STATUS} from '@/services/detailService'
|
||||
import { getCurrentLocation, calculateDistance } from '@/utils/locationUtils'
|
||||
import {
|
||||
useUserInfo,
|
||||
useUserActions,
|
||||
} from '../../store/userStore'
|
||||
import img from '../../config/images'
|
||||
import { getTextColorOnImage } from '../../utils'
|
||||
} from '@/store/userStore'
|
||||
import img from '@/config/images'
|
||||
// import { getTextColorOnImage } from '../../utils'
|
||||
import './index.scss'
|
||||
import { CommonPopup } from '@/components'
|
||||
|
||||
const images = [
|
||||
'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/1a35ebbf-2361-44da-b338-7608561d0b31.png',
|
||||
'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/cf5a82ba-90af-4138-a1b3-9119adcde9e0.png',
|
||||
'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/49d7cdf0-b03c-4a0f-91c6-e7778080cfcd.png'
|
||||
]
|
||||
dayjs.locale('zh-cn')
|
||||
|
||||
// 将·作为连接符插入到标签文本之间
|
||||
function insertDotInTags(tags: string[]) {
|
||||
return tags.join('-·-').split('-')
|
||||
}
|
||||
@@ -35,35 +33,35 @@ const SharePopup = forwardRef(({ id, from }: { id: string, from: string }, ref)
|
||||
}
|
||||
}))
|
||||
|
||||
function handleShareToWechat() {
|
||||
useShareAppMessage(() => {
|
||||
return {
|
||||
title: '分享',
|
||||
path: `/pages/detail/index?id=${id}&from=${from}`,
|
||||
}
|
||||
})
|
||||
}
|
||||
// function handleShareToWechat() {
|
||||
// useShareAppMessage(() => {
|
||||
// return {
|
||||
// title: '分享',
|
||||
// path: `/pages/detail/index?id=${id}&from=share`,
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
function handleShareToWechatMoments() {
|
||||
useShareTimeline(() => {
|
||||
return {
|
||||
title: '分享',
|
||||
path: `/pages/detail/index?id=${id}&from=${from}`,
|
||||
}
|
||||
})
|
||||
}
|
||||
// function handleShareToWechatMoments() {
|
||||
// useShareTimeline(() => {
|
||||
// return {
|
||||
// title: '分享',
|
||||
// path: `/pages/detail/index?id=${id}&from=share`,
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
function handleSaveToLocal() {
|
||||
Taro.saveImageToPhotosAlbum({
|
||||
filePath: images[0],
|
||||
success: () => {
|
||||
Taro.showToast({ title: '保存成功', icon: 'success' })
|
||||
},
|
||||
fail: () => {
|
||||
Taro.showToast({ title: '保存失败', icon: 'none' })
|
||||
},
|
||||
})
|
||||
}
|
||||
// function handleSaveToLocal() {
|
||||
// Taro.saveImageToPhotosAlbum({
|
||||
// filePath: images[0],
|
||||
// success: () => {
|
||||
// Taro.showToast({ title: '保存成功', icon: 'success' })
|
||||
// },
|
||||
// fail: () => {
|
||||
// Taro.showToast({ title: '保存失败', icon: 'none' })
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
|
||||
return (
|
||||
<CommonPopup
|
||||
@@ -74,18 +72,7 @@ const SharePopup = forwardRef(({ id, from }: { id: string, from: string }, ref)
|
||||
style={{ minHeight: '100px' }}
|
||||
>
|
||||
<View catchMove className='share-popup-content'>
|
||||
<View onClick={handleShareToWechat}>
|
||||
<Image src={img.ICON_DETAIL_SHARE} />
|
||||
<Text>分享到微信</Text>
|
||||
</View>
|
||||
<View onClick={handleShareToWechatMoments}>
|
||||
<Image src={img.ICON_DETAIL_SHARE} />
|
||||
<Text>分享朋友圈</Text>
|
||||
</View>
|
||||
<View onClick={handleSaveToLocal}>
|
||||
<Image src={img.ICON_DETAIL_SHARE} />
|
||||
<Text>保存到本地</Text>
|
||||
</View>
|
||||
分享卡片
|
||||
</View>
|
||||
</CommonPopup>
|
||||
)
|
||||
@@ -96,10 +83,10 @@ function StickyButton(props) {
|
||||
const { handleShare, handleJoinGame, detail } = props
|
||||
const userInfo = useUserInfo()
|
||||
const { id } = userInfo
|
||||
const { publisher_id, status } = detail || {}
|
||||
const { publisher_id, match_status, price } = detail || {}
|
||||
|
||||
const role = Number(publisher_id) === id ? 'ownner' : 'visitor'
|
||||
console.log(status, role)
|
||||
console.log(match_status, role)
|
||||
return (
|
||||
<View className="sticky-bottom-bar">
|
||||
<View className="sticky-bottom-bar-share-and-comment">
|
||||
@@ -117,7 +104,110 @@ function StickyButton(props) {
|
||||
<Text>🎾</Text>
|
||||
<Text>立即加入</Text>
|
||||
<View className='game-price'>
|
||||
<Text>¥ 65</Text>
|
||||
<Text>¥ {price}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
// 球局信息
|
||||
function GameInfo(props) {
|
||||
const { detail, currentLocation } = props
|
||||
const { latitude, longitude, location, location_name, start_time, end_time } = detail || {}
|
||||
|
||||
const openMap = () => {
|
||||
Taro.openLocation({
|
||||
latitude, // 纬度(必填)
|
||||
longitude, // 经度(必填)
|
||||
name: location_name, // 位置名(可选)
|
||||
address: location, // 地址详情(可选)
|
||||
scale: 15, // 地图缩放级别(1-28)
|
||||
})
|
||||
}
|
||||
|
||||
const [c_latitude, c_longitude] = currentLocation
|
||||
const distance = calculateDistance(c_latitude, c_longitude, latitude, longitude) / 1000
|
||||
|
||||
const startTime = dayjs(start_time)
|
||||
const endTime = dayjs(end_time)
|
||||
const game_length = endTime.diff(startTime, 'minutes') / 60
|
||||
|
||||
const startMonth = startTime.format('M')
|
||||
const startDay = startTime.format('D')
|
||||
const theDayOfWeek = startTime.format('dddd')
|
||||
const startDate = `${startMonth}月${startDay}日 ${theDayOfWeek}`
|
||||
const gameRange = `${startTime.format('HH:mm')} - ${endTime.format('HH:mm')}`
|
||||
|
||||
|
||||
return (
|
||||
<View className='detail-page-content-game-info'>
|
||||
{/* Date and Weather */}
|
||||
<View className='detail-page-content-game-info-date-weather'>
|
||||
{/* Calendar and Date time */}
|
||||
<View className='detail-page-content-game-info-date-weather-calendar-date'>
|
||||
{/* Calendar */}
|
||||
<View className='detail-page-content-game-info-date-weather-calendar-date-calendar'>
|
||||
<View className="month">{startMonth}月</View>
|
||||
<View className="day">{startDay}</View>
|
||||
</View>
|
||||
{/* Date time */}
|
||||
<View className='detail-page-content-game-info-date-weather-calendar-date-date'>
|
||||
<View className="date">{startDate}</View>
|
||||
<View className="venue-time">{gameRange} ({game_length}小时)</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* Weather */}
|
||||
<View className='detail-page-content-game-info-date-weather-weather'>
|
||||
{/* Weather icon */}
|
||||
<View className='detail-page-content-game-info-date-weather-weather-icon'>
|
||||
<Image className="weather-icon" src={img.ICON_WEATHER_SUN} />
|
||||
</View>
|
||||
{/* Weather text and temperature */}
|
||||
<View className='detail-page-content-game-info-date-weather-weather-text-temperature'>
|
||||
<Text>28℃ - 32℃</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* Place */}
|
||||
<View className='detail-page-content-game-info-place'>
|
||||
{/* venue location message */}
|
||||
<View className='location-message'>
|
||||
{/* location icon */}
|
||||
<View className='location-message-icon'>
|
||||
<Image className='location-message-icon-image' src={img.ICON_DETAIL_MAP} />
|
||||
</View>
|
||||
{/* location message */}
|
||||
<View className='location-message-text'>
|
||||
{/* venue name and distance */}
|
||||
<View className='location-message-text-name-distance' onClick={openMap}>
|
||||
<Text>{location_name || '-'}</Text>
|
||||
<Text>·</Text>
|
||||
<Text>{distance.toFixed(1)}km</Text>
|
||||
<Image className='location-message-text-name-distance-arrow' src={img.ICON_DETAIL_ARROW_RIGHT} />
|
||||
</View>
|
||||
{/* venue address */}
|
||||
<View className='location-message-text-address'>
|
||||
<Text>{location || '-'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* venue map */}
|
||||
<View className='location-map'>
|
||||
{longitude && latitude && (
|
||||
<Map
|
||||
className='location-map-map'
|
||||
longitude={latitude}
|
||||
latitude={longitude}
|
||||
markers={[{ id: 1, latitude: longitude, longitude: latitude, iconPath: require('@/static/detail/icon-stark.svg'), width: 16, height: 16 }]}
|
||||
includePoints={[{ latitude: longitude, longitude: latitude }, { latitude: currentLocation[0], longitude: currentLocation[1] }]}
|
||||
includePadding={{ left: 50, right: 50, top: 50, bottom: 50 }}
|
||||
onError={() => {}}
|
||||
// hide business msg
|
||||
showLocation
|
||||
theme='dark'
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -133,11 +223,13 @@ function Index() {
|
||||
// const [textColor, setTextColor] = useState<string []>([])
|
||||
const [detail, setDetail] = useState<any>(null)
|
||||
const { params } = useRouter()
|
||||
const [currentLocation, setCurrentLocation] = useState([0, 0])
|
||||
const [currentLocation, setCurrentLocation] = useState<[number, number]>([0, 0])
|
||||
const { id, autoShare, from } = params
|
||||
const { fetchUserInfo, updateUserInfo } = useUserActions()
|
||||
|
||||
console.log('from', from)
|
||||
console.group('params')
|
||||
console.log(params)
|
||||
console.groupEnd()
|
||||
|
||||
// 本地状态管理
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -168,7 +260,7 @@ function Index() {
|
||||
}
|
||||
|
||||
const fetchDetail = async () => {
|
||||
const res = await DetailService.getDetail(242/* Number(id) */)
|
||||
const res = await DetailService.getDetail(243/* Number(id) */)
|
||||
if (res.code === 0) {
|
||||
console.log(res.data)
|
||||
setDetail(res.data)
|
||||
@@ -192,19 +284,9 @@ function Index() {
|
||||
sharePopupRef.current.show()
|
||||
}
|
||||
|
||||
const openMap = () => {
|
||||
Taro.openLocation({
|
||||
latitude: detail?.longitude, // 纬度(必填)
|
||||
longitude: detail?.latitude, // 经度(必填)
|
||||
name: '上海体育场', // 位置名(可选)
|
||||
address: '上海市徐汇区肇嘉浜路128号', // 地址详情(可选)
|
||||
scale: 15, // 地图缩放级别(1-28)
|
||||
})
|
||||
}
|
||||
|
||||
const handleJoinGame = () => {
|
||||
Taro.navigateTo({
|
||||
url: `/pages/orderCheck/index?id=${id}`,
|
||||
url: `/pages/orderCheck/index?gameId=${243/* id */}`,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -285,13 +367,25 @@ function Index() {
|
||||
},
|
||||
]
|
||||
|
||||
function handleBack() {
|
||||
const pages = Taro.getCurrentPages()
|
||||
if (pages.length <= 1) {
|
||||
Taro.redirectTo({
|
||||
url: '/pages/list/index',
|
||||
})
|
||||
} else {
|
||||
Taro.navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log('detail', detail)
|
||||
return (
|
||||
<View className='detail-page'>
|
||||
{/* custom navbar */}
|
||||
<view className="custom-navbar">
|
||||
<View className='detail-navigator'>
|
||||
<View className='detail-navigator-back' onClick={() => { Taro.navigateBack() }}>
|
||||
<View className='detail-navigator-back' onClick={handleBack}>
|
||||
<Image className='detail-navigator-back-icon' src={img.ICON_ARROW_LEFT} />
|
||||
</View>
|
||||
<View className='detail-navigator-icon'>
|
||||
@@ -299,7 +393,7 @@ function Index() {
|
||||
</View>
|
||||
</View>
|
||||
</view>
|
||||
<View className='detail-page-bg' style={{ backgroundImage: `url(${images[0]})` }} />
|
||||
<View className='detail-page-bg' style={detail?.image_list?.[0] ? { backgroundImage: `url(${detail?.image_list?.[0]})` } : {}} />
|
||||
<View className='detail-page-bg-text' />
|
||||
{/* swiper */}
|
||||
<View className="detail-swiper-container">
|
||||
@@ -364,76 +458,7 @@ function Index() {
|
||||
<Text className='detail-page-content-title-text'>{title}</Text>
|
||||
</View>
|
||||
{/* Date and Place and weather */}
|
||||
<View className='detail-page-content-game-info'>
|
||||
{/* Date and Weather */}
|
||||
<View className='detail-page-content-game-info-date-weather'>
|
||||
{/* Calendar and Date time */}
|
||||
<View className='detail-page-content-game-info-date-weather-calendar-date'>
|
||||
{/* Calendar */}
|
||||
<View className='detail-page-content-game-info-date-weather-calendar-date-calendar'>
|
||||
<View className="month">3月</View>
|
||||
<View className="day">25</View>
|
||||
</View>
|
||||
{/* Date time */}
|
||||
<View className='detail-page-content-game-info-date-weather-calendar-date-date'>
|
||||
<View className="date">3月25日 周一</View>
|
||||
<View className="venue-time">19:00-21:00 (2小时)</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* Weather */}
|
||||
<View className='detail-page-content-game-info-date-weather-weather'>
|
||||
{/* Weather icon */}
|
||||
<View className='detail-page-content-game-info-date-weather-weather-icon'>
|
||||
<Image className="weather-icon" src={img.ICON_WEATHER_SUN} />
|
||||
</View>
|
||||
{/* Weather text and temperature */}
|
||||
<View className='detail-page-content-game-info-date-weather-weather-text-temperature'>
|
||||
<Text>28℃ - 32℃</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* Place */}
|
||||
<View className='detail-page-content-game-info-place'>
|
||||
{/* venue location message */}
|
||||
<View className='location-message'>
|
||||
{/* location icon */}
|
||||
<View className='location-message-icon'>
|
||||
<Image className='location-message-icon-image' src={img.ICON_DETAIL_MAP} />
|
||||
</View>
|
||||
{/* location message */}
|
||||
<View className='location-message-text'>
|
||||
{/* venue name and distance */}
|
||||
<View className='location-message-text-name-distance' onClick={openMap}>
|
||||
<Text>上海体育场</Text>
|
||||
<Text>·</Text>
|
||||
<Text>1.2km</Text>
|
||||
<Image className='location-message-text-name-distance-arrow' src={img.ICON_DETAIL_ARROW_RIGHT} />
|
||||
</View>
|
||||
{/* venue address */}
|
||||
<View className='location-message-text-address'>
|
||||
<Text>上海市徐汇区肇嘉浜路128号</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* venue map */}
|
||||
<View className='location-map'>
|
||||
{longitude && latitude && (
|
||||
<Map
|
||||
className='location-map-map'
|
||||
longitude={latitude}
|
||||
latitude={longitude}
|
||||
markers={[{ id: 1, latitude: longitude, longitude: latitude, iconPath: require('@/static/detail/icon-stark.svg'), width: 16, height: 16 }]}
|
||||
includePoints={[{ latitude: longitude, longitude: latitude }, { latitude: currentLocation[0], longitude: currentLocation[1] }]}
|
||||
includePadding={{ left: 50, right: 50, top: 50, bottom: 50 }}
|
||||
onError={() => {}}
|
||||
// hide business msg
|
||||
showLocation
|
||||
theme='dark'
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<GameInfo detail={detail} currentLocation={currentLocation} />
|
||||
{/* detail */}
|
||||
<View className='detail-page-content-detail'>
|
||||
{/* venue detail title and venue ordered status */}
|
||||
@@ -619,4 +644,4 @@ function Index() {
|
||||
)
|
||||
}
|
||||
|
||||
export default Index
|
||||
export default withAuth(Index)
|
||||
@@ -10,6 +10,8 @@ import CustomerNavBar from "@/container/listCustomNavbar";
|
||||
import GuideBar from "@/components/GuideBar";
|
||||
import ListContainer from "@/container/listContainer";
|
||||
import DistanceQuickFilter from "@/components/DistanceQuickFilter";
|
||||
import { withAuth } from "@/components";
|
||||
// import img from "@/config/images";
|
||||
|
||||
const ListPage = () => {
|
||||
// 从 store 获取数据和方法
|
||||
@@ -191,4 +193,4 @@ const ListPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ListPage;
|
||||
export default withAuth(ListPage);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, Button, Image } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import Taro, { useRouter } from '@tarojs/taro';
|
||||
import { wechat_auth_login, save_login_state, check_login_status } from '../../../services/loginService';
|
||||
import './index.scss';
|
||||
|
||||
@@ -8,9 +8,11 @@ const LoginPage: React.FC = () => {
|
||||
const [is_loading, set_is_loading] = useState(false);
|
||||
const [agree_terms, set_agree_terms] = useState(false);
|
||||
const [show_terms_layer, set_show_terms_layer] = useState(false);
|
||||
const { params: { redirect } } = useRouter();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 微信授权登录
|
||||
const handle_wechat_login = async (e: any) => {
|
||||
@@ -42,7 +44,12 @@ const LoginPage: React.FC = () => {
|
||||
save_login_state(response.token!, response.user_info!);
|
||||
|
||||
setTimeout(() => {
|
||||
Taro.redirectTo({ url: '/pages/list/index' });
|
||||
if (redirect) {
|
||||
console.log('redirect:', decodeURIComponent(redirect))
|
||||
Taro.redirectTo({ url: decodeURIComponent(redirect) });
|
||||
} else {
|
||||
Taro.redirectTo({ url: '/pages/list/index' });
|
||||
}
|
||||
}, 200);
|
||||
} else {
|
||||
Taro.showToast({
|
||||
@@ -76,7 +83,7 @@ const LoginPage: React.FC = () => {
|
||||
|
||||
// 跳转到验证码页面
|
||||
Taro.navigateTo({
|
||||
url: '/pages/login/verification/index'
|
||||
url: `/pages/login/verification/index?redirect=${redirect}`
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, Input, Button, Image } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import Taro, { useRouter } from '@tarojs/taro';
|
||||
import { phone_auth_login, send_sms_code } from '../../../services/loginService';
|
||||
import './index.scss';
|
||||
|
||||
@@ -12,6 +12,8 @@ const VerificationPage: React.FC = () => {
|
||||
const [is_loading, setIsLoading] = useState(false);
|
||||
const [code_input_focus, setCodeInputFocus] = useState(false);
|
||||
|
||||
const { params: { redirect } } = useRouter();
|
||||
|
||||
// 计算登录按钮是否应该启用
|
||||
const can_login = phone.length === 11 && verification_code.length === 6 && !is_loading;
|
||||
|
||||
@@ -123,9 +125,13 @@ const VerificationPage: React.FC = () => {
|
||||
if (result.success) {
|
||||
|
||||
setTimeout(() => {
|
||||
Taro.redirectTo({
|
||||
url: '/pages/list/index'
|
||||
});
|
||||
if (redirect) {
|
||||
Taro.redirectTo({ url: decodeURIComponent(redirect) });
|
||||
} else {
|
||||
Taro.redirectTo({
|
||||
url: '/pages/list/index'
|
||||
});
|
||||
}
|
||||
}, 200);
|
||||
} else {
|
||||
Taro.showToast({
|
||||
|
||||
@@ -1,80 +1,277 @@
|
||||
@use '~@/scss/images.scss' as img;
|
||||
|
||||
$--Backgrounds-Primary: '#fff';
|
||||
|
||||
.message-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: radial-gradient(227.15% 100% at 50% 0%, #EEFFDC 0%, #FFF 36.58%), var(--Backgrounds-Primary, #FFF);
|
||||
// padding-top: 100px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.custom-navbar {
|
||||
height: 56px; /* 通常与原生导航栏高度一致 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
// background-color: #fff;
|
||||
color: #000;
|
||||
padding-top: 44px; /* 适配状态栏 */
|
||||
// 导航栏
|
||||
.navbar {
|
||||
height: 100px;
|
||||
background: #FFFFFF;
|
||||
padding-top: 44px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
|
||||
.message-navigator {
|
||||
position: relative;
|
||||
left: 15px;
|
||||
top: -2px;
|
||||
width: 80px;
|
||||
height: 32px;
|
||||
.navbar-content {
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
justify-content: space-between;
|
||||
padding: 0 15px;
|
||||
|
||||
.message-navigator-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.navbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.message-navigator-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
.navbar-avatar {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-content {
|
||||
|
||||
.message-content-list {
|
||||
// 消息列表
|
||||
.message-list {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
margin-bottom:100px;
|
||||
background-color: none !important;
|
||||
|
||||
.message-list-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 15px;
|
||||
padding: 0 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
// 系统消息样式
|
||||
.system-message {
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 20px;
|
||||
padding: 0 0 12px;
|
||||
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06);
|
||||
box-sizing: border-box;
|
||||
gap: 12px;
|
||||
|
||||
.message-item {
|
||||
padding: 10px;
|
||||
// border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
.message-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2);
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 10px;
|
||||
align-items: center;
|
||||
padding: 12px 15px 0;
|
||||
|
||||
.message-title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: #000000;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
.message-item-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
.message-content {
|
||||
padding: 8px 15px 0;
|
||||
|
||||
.message-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.43;
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.message-item-content {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
.message-action {
|
||||
padding: 12px 15px 0;
|
||||
|
||||
.action-divider {
|
||||
height: 0.5px;
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.action-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.43;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
.action-arrow {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 用户消息样式
|
||||
.user-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 15px;
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06);
|
||||
box-sizing: border-box;
|
||||
|
||||
.message-avatar {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
|
||||
.unread-dot {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #FF4848;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.message-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.message-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.message-title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.71;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
}
|
||||
|
||||
.message-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
|
||||
.message-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.29;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.unread-indicator {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #FF4848;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 空状态
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 124px 16px;
|
||||
height: 746px;
|
||||
|
||||
.empty-icon {
|
||||
width: 300px;
|
||||
height: 225px;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
|
||||
.empty-message-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #f0f0f0 0%, #e0e0e0 100%);
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: #d0d0d0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #a0a0a0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 1.71;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,184 @@
|
||||
import React from 'react'
|
||||
import { View, Text, ScrollView } from '@tarojs/components'
|
||||
import { useState } from 'react'
|
||||
import { View, Text, ScrollView, Image } from '@tarojs/components'
|
||||
import { Avatar } from '@nutui/nutui-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import GuideBar from '@/components/GuideBar'
|
||||
// import img from '@/config/images'
|
||||
import { withAuth } from '@/components'
|
||||
import './index.scss'
|
||||
|
||||
const Personal = () => {
|
||||
const messageList = Array(10).fill(0).map((_, index) => ({
|
||||
id: index + 1,
|
||||
title: `消息${index + 1}消息${index + 1}消息${index + 1}消息${index + 1}`,
|
||||
content: Array(Math.round(Math.random() * 40)).fill(0).map((_, index) => `消息${index + 1}`).join(''),
|
||||
}))
|
||||
// 消息类型定义
|
||||
interface MessageItem {
|
||||
id: string
|
||||
type: 'system' | 'user' | 'like' | 'comment' | 'follow'
|
||||
title: string
|
||||
content: string
|
||||
time: string
|
||||
avatar?: string
|
||||
isRead: boolean
|
||||
hasAction?: boolean
|
||||
actionText?: string
|
||||
}
|
||||
|
||||
const Message = () => {
|
||||
const [activeTab] = useState<'all' | 'like' | 'comment' | 'follow'>('all')
|
||||
|
||||
// 模拟消息数据
|
||||
const messageList: MessageItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'system',
|
||||
title: '球局报名确认',
|
||||
content: '恭喜,你成功报名"世纪公园混双 · 8月20日"球局!请提前15分钟到达,球场门口等你。',
|
||||
time: '今天 09:12',
|
||||
isRead: false,
|
||||
hasAction: true,
|
||||
actionText: '查看详情'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'system',
|
||||
title: '新球友加入提醒',
|
||||
content: 'Fiona 已加入"徐汇双打 · 今晚7点"的群聊,快去和她打个招呼吧~',
|
||||
time: '昨天 09:12',
|
||||
isRead: false,
|
||||
hasAction: true,
|
||||
actionText: '打个招呼'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'system',
|
||||
title: '场地变更通知',
|
||||
content: '请注意,"张江中午快打"已改至世纪园区 3 号场集合。',
|
||||
time: '2025-08-17 18:30',
|
||||
isRead: true
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'system',
|
||||
title: '系统维护提醒',
|
||||
content: '系统将于 2025-08-20 凌晨 00:00–02:00 暂停服务,届时活动发布、消息中心等功能可能无法使用,敬请谅解。',
|
||||
time: '2025-08-17 18:30',
|
||||
isRead: true
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
type: 'system',
|
||||
title: '活动将近提醒',
|
||||
content: '你的"宝山初学者约球"将在 2 小时后开始。快准备好球拍、毛巾和运动鞋,我们赛场见!',
|
||||
time: '2025-08-17 18:30',
|
||||
isRead: true
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
type: 'user',
|
||||
title: '王晨',
|
||||
content: '你好,昨天约的球场还在吗',
|
||||
time: '09:34',
|
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||
isRead: false
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
type: 'user',
|
||||
title: '阿斌',
|
||||
content: '七点到世纪公园东门集合可以吗?',
|
||||
time: '昨天 22:10',
|
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||
isRead: false
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
type: 'user',
|
||||
title: 'Lili',
|
||||
content: '我刚问了,还有一个小时的空场!',
|
||||
time: '昨天 18:47',
|
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||
isRead: true
|
||||
}
|
||||
]
|
||||
|
||||
// 过滤消息
|
||||
const filteredMessages = messageList.filter(message => {
|
||||
if (activeTab === 'all') return true
|
||||
return message.type === activeTab
|
||||
})
|
||||
|
||||
// 渲染消息项
|
||||
const renderMessageItem = (message: MessageItem) => {
|
||||
if (message.type === 'system') {
|
||||
return (
|
||||
<View className='message-item system-message' key={message.id}>
|
||||
<View className='message-header'>
|
||||
<Text className='message-title'>{message.title}</Text>
|
||||
<Text className='message-time'>{message.time}</Text>
|
||||
</View>
|
||||
<View className='message-content'>
|
||||
<Text className='message-text'>{message.content}</Text>
|
||||
</View>
|
||||
{message.hasAction && (
|
||||
<View className='message-action'>
|
||||
<View className='action-divider'></View>
|
||||
<View className='action-button'>
|
||||
<Text className='action-text'>{message.actionText}</Text>
|
||||
<Image className='action-arrow' src={require('../../static/message/icon-message-arrow.svg')} />
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='message-item user-message' key={message.id}>
|
||||
<View className='message-avatar'>
|
||||
<Avatar src={message.avatar} size='48px' />
|
||||
</View>
|
||||
<View className='message-info'>
|
||||
<View className='message-header'>
|
||||
<Text className='message-title'>{message.title}</Text>
|
||||
<Text className='message-time'>{message.time}</Text>
|
||||
</View>
|
||||
<View className='message-content'>
|
||||
<Text className='message-text'>{message.content}</Text>
|
||||
{!message.isRead && <View className='unread-indicator'></View>}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='message-container'>
|
||||
<View className='custom-navbar'>
|
||||
<View className='message-navigator'>
|
||||
<Avatar className='message-navigator-avatar' src="https://img.yzcdn.cn/vant/cat.jpeg" />
|
||||
<Text className='message-navigator-title'>消息</Text>
|
||||
{/* 导航栏 */}
|
||||
<View className='navbar'>
|
||||
<View className='navbar-content'>
|
||||
<View className='navbar-left'>
|
||||
<Avatar className='navbar-avatar' src="https://img.yzcdn.cn/vant/cat.jpeg" />
|
||||
<Text className='navbar-title'>消息</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<ScrollView scrollY className='message-content'>
|
||||
<View className='message-content-list'>
|
||||
{messageList.map((item) => (
|
||||
<View className='message-item' key={item.id}>
|
||||
<View className='message-item-title'>
|
||||
<Text>{item.title}</Text>
|
||||
</View>
|
||||
<View className='message-item-content'>
|
||||
<Text>{item.content}</Text>
|
||||
</View>
|
||||
|
||||
|
||||
{/* 消息列表 */}
|
||||
<ScrollView scrollY className='message-list'>
|
||||
{filteredMessages.length > 0 ? (
|
||||
<View className='message-list-content'>
|
||||
{filteredMessages.map(renderMessageItem)}
|
||||
</View>
|
||||
) : (
|
||||
<View className='empty-state'>
|
||||
<View className='empty-icon'>
|
||||
<View className='empty-message-icon'></View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<Text className='empty-text'>暂无消息</Text>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
{/* 底部导航 */}
|
||||
<GuideBar currentPage='message' />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default Personal
|
||||
export default withAuth(Message)
|
||||
@@ -1,31 +1,71 @@
|
||||
import React from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { View, Text, Button } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import Taro, { useDidShow, useRouter } from '@tarojs/taro'
|
||||
import { delay } from '@/utils'
|
||||
import orderService from '@/services/orderService'
|
||||
import detailService, { GameDetail } from '@/services/detailService'
|
||||
import { withAuth } from '@/components'
|
||||
|
||||
const OrderCheck = () => {
|
||||
const { params } = useRouter()
|
||||
const { id, gameId } = params
|
||||
const [detail ,setDetail] = useState<GameDetail | {}>({})
|
||||
|
||||
useDidShow(async () => {
|
||||
const res = await detailService.getDetail(Number(gameId))
|
||||
console.log(res)
|
||||
if (res.code === 0) {
|
||||
setDetail(res.data)
|
||||
}
|
||||
})
|
||||
|
||||
//TODO: get order msg from id
|
||||
const handlePay = async () => {
|
||||
Taro.showLoading({
|
||||
title: '支付中...',
|
||||
mask: true
|
||||
})
|
||||
await delay(2000)
|
||||
Taro.hideLoading()
|
||||
Taro.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
})
|
||||
await delay(1000)
|
||||
Taro.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
const res = await orderService.createOrder(Number(gameId))
|
||||
if (res.code === 0) {
|
||||
const { payment_required, payment_params } = res.data
|
||||
if (payment_required) {
|
||||
const { timeStamp, nonceStr, package: package_, signType, paySign } = payment_params
|
||||
await Taro.requestPayment({
|
||||
timeStamp,
|
||||
nonceStr,
|
||||
package: package_,
|
||||
signType,
|
||||
paySign,
|
||||
success: async () => {
|
||||
Taro.hideLoading()
|
||||
Taro.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
})
|
||||
await delay(1000)
|
||||
Taro.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
Taro.hideLoading()
|
||||
Taro.showToast({
|
||||
title: '支付失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<Text>OrderCheck</Text>
|
||||
<Text>球局名称:{detail?.title || '-'}</Text>
|
||||
<Text>价格:¥{detail?.price || '-'}</Text>
|
||||
<Button onClick={handlePay}>支付</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrderCheck
|
||||
export default withAuth(OrderCheck)
|
||||
@@ -4,6 +4,7 @@ import { Checkbox } from '@nutui/nutui-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { type ActivityType } from '../../components/ActivityTypeSwitch'
|
||||
import CommonDialog from '../../components/CommonDialog'
|
||||
import { withAuth } from '@/components'
|
||||
import PublishForm from './publishForm'
|
||||
import { FormFieldConfig, publishBallFormSchema } from '../../config/formSchema/publishBallFormSchema';
|
||||
import { PublishBallFormData } from '../../../types/publishBall';
|
||||
@@ -87,13 +88,13 @@ const PublishBall: React.FC = () => {
|
||||
// 检查相邻两组数据是否相同
|
||||
const checkAdjacentDataSame = (formDataArray: PublishBallFormData[]) => {
|
||||
if (formDataArray.length < 2) return false
|
||||
|
||||
|
||||
const lastIndex = formDataArray.length - 1
|
||||
const secondLastIndex = formDataArray.length - 2
|
||||
|
||||
|
||||
const lastData = formDataArray[lastIndex]
|
||||
const secondLastData = formDataArray[secondLastIndex]
|
||||
|
||||
|
||||
// 比较关键字段是否相同
|
||||
return (JSON.stringify(lastData) === JSON.stringify(secondLastData))
|
||||
}
|
||||
@@ -117,7 +118,7 @@ const PublishBall: React.FC = () => {
|
||||
}
|
||||
}])
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 复制上一场数据
|
||||
const handleCopyPrevious = (index: number) => {
|
||||
@@ -372,7 +373,7 @@ const PublishBall: React.FC = () => {
|
||||
<View className={styles['publish-ball']}>
|
||||
{/* 活动类型切换 */}
|
||||
<View className={styles['activity-type-switch']}>
|
||||
{/* <ActivityTypeSwitch
|
||||
{/* <ActivityTypeSwitch
|
||||
value={activityType}
|
||||
onChange={handleActivityTypeChange}
|
||||
/> */}
|
||||
@@ -452,7 +453,7 @@ const PublishBall: React.FC = () => {
|
||||
{
|
||||
activityType === 'group' && (
|
||||
<View className={styles['submit-tip']}>
|
||||
<Checkbox
|
||||
<Checkbox
|
||||
className={styles['submit-checkbox']}
|
||||
checked={checked}
|
||||
onChange={onCheckedChange}
|
||||
@@ -466,4 +467,4 @@ const PublishBall: React.FC = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export default PublishBall
|
||||
export default withAuth(PublishBall)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Input } from "@nutui/nutui-react-taro";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { useListState } from "@/store/listStore";
|
||||
import img from "@/config/images";
|
||||
import { withAuth } from "@/components";
|
||||
import "./index.scss";
|
||||
import Taro from "@tarojs/taro";
|
||||
|
||||
@@ -204,4 +205,4 @@ const ListSearch = () => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ListSearch;
|
||||
export default withAuth(ListSearch);
|
||||
|
||||
@@ -6,6 +6,7 @@ import ListContainer from "@/container/listContainer";
|
||||
import img from "@/config/images";
|
||||
import CustomerNavBar from "@/container/listCustomNavbar";
|
||||
import DistanceQuickFilter from "@/components/DistanceQuickFilter";
|
||||
import { withAuth } from "@/components";
|
||||
import { useEffect } from "react";
|
||||
import FilterPopup from "@/components/FilterPopup";
|
||||
import "./index.scss";
|
||||
@@ -153,4 +154,4 @@ const SearchResult = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchResult;
|
||||
export default withAuth(SearchResult);
|
||||
|
||||
211
src/pages/userInfo/API_INTEGRATION.md
Normal file
211
src/pages/userInfo/API_INTEGRATION.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# API接口集成说明
|
||||
|
||||
## 已集成的接口
|
||||
|
||||
### 1. 用户详情接口 `/user/detail`
|
||||
|
||||
**请求方式**: POST
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"user_id": "string" // 可选,不传则获取当前用户信息
|
||||
}
|
||||
```
|
||||
|
||||
**响应格式**:
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "string",
|
||||
"data": {
|
||||
"openid": "",
|
||||
"user_code": "",
|
||||
"unionid": "",
|
||||
"session_key": "",
|
||||
"nickname": "张三",
|
||||
"avatar_url": "https://example.com/avatar.jpg",
|
||||
"gender": "",
|
||||
"country": "",
|
||||
"province": "",
|
||||
"city": "",
|
||||
"language": "",
|
||||
"phone": "13800138000",
|
||||
"is_subscribed": "0",
|
||||
"latitude": "0",
|
||||
"longitude": "0",
|
||||
"subscribe_time": "2024-06-15 14:00:00",
|
||||
"last_login_time": "2024-06-15 14:00:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 用户信息更新接口 `/user/update`
|
||||
|
||||
**请求方式**: POST
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"nickname": "string",
|
||||
"avatar_url": "string",
|
||||
"gender": "string",
|
||||
"phone": "string",
|
||||
"latitude": 31.2304,
|
||||
"longitude": 121.4737,
|
||||
"city": "string",
|
||||
"province": "string",
|
||||
"country": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**响应格式**:
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "string",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 头像上传接口 `/gallery/upload`
|
||||
|
||||
**请求方式**: POST (multipart/form-data)
|
||||
**请求参数**:
|
||||
- `file`: 图片文件
|
||||
|
||||
**响应格式**:
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "请求成功!",
|
||||
"data": {
|
||||
"create_time": "2025-09-06 19:41:18",
|
||||
"last_modify_time": "2025-09-06 19:41:18",
|
||||
"duration": "0",
|
||||
"thumbnail_url": "",
|
||||
"view_count": "0",
|
||||
"download_count": "0",
|
||||
"is_delete": 0,
|
||||
"id": 67,
|
||||
"user_id": 1,
|
||||
"resource_type": "image",
|
||||
"file_name": "front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg",
|
||||
"original_name": "微信图片_20250505175522.jpg",
|
||||
"file_path": "http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg",
|
||||
"file_url": "http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg",
|
||||
"file_size": 264506,
|
||||
"mime_type": "image/jpeg",
|
||||
"description": "用户图像",
|
||||
"tags": "用户图像",
|
||||
"is_public": "1",
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"uploadInfo": {
|
||||
"success": true,
|
||||
"name": "front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg",
|
||||
"path": "http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg",
|
||||
"ossPath": "http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg",
|
||||
"fileType": "image/jpeg",
|
||||
"fileSize": 264506,
|
||||
"originalName": "微信图片_20250505175522.jpg",
|
||||
"suffix": "jpg",
|
||||
"storagePath": "front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**说明**: 上传成功后,使用 `data.file_url` 字段作为头像URL。
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 在页面中调用
|
||||
|
||||
```typescript
|
||||
import { UserService } from '@/services/userService';
|
||||
|
||||
// 获取用户信息
|
||||
const userInfo = await UserService.get_user_info('user_id');
|
||||
|
||||
// 更新用户信息
|
||||
await UserService.save_user_info({
|
||||
nickname: '新昵称',
|
||||
phone: '13800138000',
|
||||
gender: '男'
|
||||
});
|
||||
|
||||
// 上传头像
|
||||
const avatarUrl = await UserService.upload_avatar('/path/to/image.jpg');
|
||||
```
|
||||
|
||||
### API配置
|
||||
|
||||
API配置位于 `src/config/api.ts`,可以根据环境自动切换接口地址:
|
||||
|
||||
```typescript
|
||||
export const API_CONFIG = {
|
||||
BASE_URL: process.env.NODE_ENV === 'development'
|
||||
? 'http://localhost:3000'
|
||||
: 'https://api.example.com',
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
所有API调用都包含完整的错误处理:
|
||||
|
||||
1. **网络错误**: 自动捕获并显示友好提示
|
||||
2. **业务错误**: 根据返回的 `code` 和 `message` 处理
|
||||
3. **超时处理**: 10秒超时设置
|
||||
4. **降级处理**: API失败时返回默认数据
|
||||
|
||||
## 数据映射
|
||||
|
||||
### 用户信息映射
|
||||
|
||||
API返回的用户数据会自动映射到前端组件使用的格式:
|
||||
|
||||
```typescript
|
||||
// API数据 -> 前端组件数据
|
||||
{
|
||||
user_code -> id,
|
||||
nickname -> nickname,
|
||||
avatar_url -> avatar,
|
||||
subscribe_time -> join_date,
|
||||
city -> location,
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **位置信息**: 更新用户信息时会自动获取当前位置
|
||||
2. **头像处理**: 上传失败时自动使用默认头像
|
||||
3. **表单验证**: 编辑资料页面包含完整的表单验证
|
||||
4. **类型安全**: 所有接口都有完整的TypeScript类型定义
|
||||
|
||||
## 扩展接口
|
||||
|
||||
如需添加新的用户相关接口,可以在 `UserService` 中添加新方法:
|
||||
|
||||
```typescript
|
||||
static async new_api_method(params: any): Promise<any> {
|
||||
try {
|
||||
const response = await Taro.request({
|
||||
url: `${API_CONFIG.BASE_URL}/new/endpoint`,
|
||||
method: 'POST',
|
||||
data: params,
|
||||
...REQUEST_CONFIG
|
||||
});
|
||||
|
||||
if (response.data.code === 0) {
|
||||
return response.data.data;
|
||||
} else {
|
||||
throw new Error(response.data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('API调用失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
240
src/pages/userInfo/AVATAR_UPLOAD.md
Normal file
240
src/pages/userInfo/AVATAR_UPLOAD.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# 头像上传功能说明
|
||||
|
||||
## 接口更新
|
||||
|
||||
### 新的上传接口 `/gallery/upload`
|
||||
|
||||
**接口地址**: `/gallery/upload`
|
||||
**请求方式**: POST (multipart/form-data)
|
||||
**功能**: 上传图片文件到阿里云OSS
|
||||
|
||||
### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| file | File | 是 | 图片文件 |
|
||||
|
||||
### 响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "请求成功!",
|
||||
"data": {
|
||||
"create_time": "2025-09-06 19:41:18",
|
||||
"last_modify_time": "2025-09-06 19:41:18",
|
||||
"duration": "0",
|
||||
"thumbnail_url": "",
|
||||
"view_count": "0",
|
||||
"download_count": "0",
|
||||
"is_delete": 0,
|
||||
"id": 67,
|
||||
"user_id": 1,
|
||||
"resource_type": "image",
|
||||
"file_name": "front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg",
|
||||
"original_name": "微信图片_20250505175522.jpg",
|
||||
"file_path": "http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg",
|
||||
"file_url": "http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg",
|
||||
"file_size": 264506,
|
||||
"mime_type": "image/jpeg",
|
||||
"description": "用户图像",
|
||||
"tags": "用户图像",
|
||||
"is_public": "1",
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"uploadInfo": {
|
||||
"success": true,
|
||||
"name": "front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg",
|
||||
"path": "http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg",
|
||||
"ossPath": "http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg",
|
||||
"fileType": "image/jpeg",
|
||||
"fileSize": 264506,
|
||||
"originalName": "微信图片_20250505175522.jpg",
|
||||
"suffix": "jpg",
|
||||
"storagePath": "front/ball/images/f1bd8f63-a1e0-4750-9656-1e8405753416.jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 关键字段说明
|
||||
|
||||
### 主要字段
|
||||
- `file_url`: 图片的完整访问URL,用于前端显示
|
||||
- `file_path`: 与file_url相同,图片的完整访问URL
|
||||
- `file_size`: 文件大小(字节)
|
||||
- `mime_type`: 文件MIME类型
|
||||
- `original_name`: 原始文件名
|
||||
|
||||
### 上传信息字段
|
||||
- `uploadInfo.success`: 上传是否成功
|
||||
- `uploadInfo.ossPath`: OSS存储路径
|
||||
- `uploadInfo.fileType`: 文件类型
|
||||
- `uploadInfo.fileSize`: 文件大小
|
||||
- `uploadInfo.suffix`: 文件后缀
|
||||
|
||||
## 前端实现
|
||||
|
||||
### TypeScript接口定义
|
||||
|
||||
```typescript
|
||||
interface UploadResponseData {
|
||||
create_time: string;
|
||||
last_modify_time: string;
|
||||
duration: string;
|
||||
thumbnail_url: string;
|
||||
view_count: string;
|
||||
download_count: string;
|
||||
is_delete: number;
|
||||
id: number;
|
||||
user_id: number;
|
||||
resource_type: string;
|
||||
file_name: string;
|
||||
original_name: string;
|
||||
file_path: string;
|
||||
file_url: string;
|
||||
file_size: number;
|
||||
mime_type: string;
|
||||
description: string;
|
||||
tags: string;
|
||||
is_public: string;
|
||||
width: number;
|
||||
height: number;
|
||||
uploadInfo: {
|
||||
success: boolean;
|
||||
name: string;
|
||||
path: string;
|
||||
ossPath: string;
|
||||
fileType: string;
|
||||
fileSize: number;
|
||||
originalName: string;
|
||||
suffix: string;
|
||||
storagePath: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 上传方法实现
|
||||
|
||||
```typescript
|
||||
static async upload_avatar(file_path: string): Promise<string> {
|
||||
try {
|
||||
const uploadResponse = await Taro.uploadFile({
|
||||
url: `${API_CONFIG.BASE_URL}${API_CONFIG.UPLOAD.AVATAR}`,
|
||||
filePath: file_path,
|
||||
name: 'file'
|
||||
});
|
||||
|
||||
const result = JSON.parse(uploadResponse.data) as ApiResponse<UploadResponseData>;
|
||||
if (result.code === 0) {
|
||||
// 使用file_url字段作为头像URL
|
||||
return result.data.file_url;
|
||||
} else {
|
||||
throw new Error(result.message || '头像上传失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('头像上传失败:', error);
|
||||
// 上传失败时返回默认头像
|
||||
return require('../../static/userInfo/default_avatar.svg');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 在编辑资料页面中使用
|
||||
|
||||
```typescript
|
||||
// 处理头像上传
|
||||
const handle_avatar_upload = () => {
|
||||
Taro.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: async (res) => {
|
||||
const tempFilePath = res.tempFilePaths[0];
|
||||
try {
|
||||
const avatar_url = await UserService.upload_avatar(tempFilePath);
|
||||
setUserInfo(prev => ({ ...prev, avatar: avatar_url }));
|
||||
Taro.showToast({
|
||||
title: '头像上传成功',
|
||||
icon: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('头像上传失败:', error);
|
||||
Taro.showToast({
|
||||
title: '头像上传失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
## 功能特点
|
||||
|
||||
### 1. OSS存储
|
||||
- 图片直接上传到阿里云OSS
|
||||
- 支持CDN加速访问
|
||||
- 自动生成唯一文件名
|
||||
|
||||
### 2. 文件信息完整
|
||||
- 记录文件大小、类型、原始名称
|
||||
- 支持文件描述和标签
|
||||
- 记录上传时间和修改时间
|
||||
|
||||
### 3. 错误处理
|
||||
- 上传失败时自动使用默认头像
|
||||
- 完整的错误日志记录
|
||||
- 用户友好的错误提示
|
||||
|
||||
### 4. 类型安全
|
||||
- 完整的TypeScript类型定义
|
||||
- 编译时类型检查
|
||||
- 智能代码提示
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **文件大小限制**: 建议限制上传文件大小,避免过大文件
|
||||
2. **文件类型验证**: 只允许上传图片格式文件
|
||||
3. **网络处理**: 上传过程中需要处理网络异常情况
|
||||
4. **用户体验**: 上传过程中显示加载状态
|
||||
5. **缓存策略**: 上传成功后更新本地缓存
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 图片压缩
|
||||
```typescript
|
||||
// 可以在上传前进行图片压缩
|
||||
const compressImage = (filePath: string) => {
|
||||
return Taro.compressImage({
|
||||
src: filePath,
|
||||
quality: 80
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### 进度显示
|
||||
```typescript
|
||||
// 显示上传进度
|
||||
const uploadWithProgress = (filePath: string) => {
|
||||
return Taro.uploadFile({
|
||||
url: `${API_CONFIG.BASE_URL}${API_CONFIG.UPLOAD.AVATAR}`,
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
success: (res) => {
|
||||
// 处理成功
|
||||
},
|
||||
fail: (err) => {
|
||||
// 处理失败
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**更新时间**: 2024年12月19日
|
||||
**接口版本**: v1.0
|
||||
**存储方式**: 阿里云OSS
|
||||
160
src/pages/userInfo/INTEGRATION_SUMMARY.md
Normal file
160
src/pages/userInfo/INTEGRATION_SUMMARY.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# 个人页面API接口集成完成
|
||||
|
||||
## ✅ 已完成的工作
|
||||
|
||||
### 1. API接口集成
|
||||
- **用户详情接口** (`/user/detail`) - 获取用户信息
|
||||
- **用户更新接口** (`/user/update`) - 更新用户详细信息
|
||||
- **头像上传接口** (`/gallery/upload`) - 上传用户头像到OSS
|
||||
|
||||
### 2. 服务层优化
|
||||
- 创建了 `UserService` 类,统一管理用户相关API调用
|
||||
- 添加了完整的TypeScript类型定义
|
||||
- 实现了错误处理和降级机制
|
||||
- 支持位置信息自动获取
|
||||
|
||||
### 3. 配置管理
|
||||
- 创建了 `API_CONFIG` 配置文件
|
||||
- 支持开发/生产环境自动切换
|
||||
- 统一的请求配置和超时设置
|
||||
|
||||
### 4. 编辑资料页面增强
|
||||
- 新增手机号输入字段
|
||||
- 新增性别选择器(男/女)
|
||||
- 保留NTRP等级选择器
|
||||
- 完整的表单验证
|
||||
|
||||
### 5. 数据映射
|
||||
- API数据格式自动映射到前端组件格式
|
||||
- 支持默认值处理
|
||||
- 时间格式转换
|
||||
|
||||
## 🔧 技术特点
|
||||
|
||||
### API调用方式
|
||||
```typescript
|
||||
// 获取用户信息
|
||||
const userInfo = await UserService.get_user_info('user_id');
|
||||
|
||||
// 更新用户信息
|
||||
await UserService.save_user_info({
|
||||
nickname: '新昵称',
|
||||
phone: '13800138000',
|
||||
gender: '男',
|
||||
location: '上海'
|
||||
});
|
||||
|
||||
// 上传头像
|
||||
const avatarUrl = await UserService.upload_avatar('/path/to/image.jpg');
|
||||
```
|
||||
|
||||
### 错误处理
|
||||
- 网络错误自动捕获
|
||||
- 业务错误友好提示
|
||||
- API失败时降级到默认数据
|
||||
- 完整的日志记录
|
||||
|
||||
### 类型安全
|
||||
- 完整的TypeScript接口定义
|
||||
- API请求/响应类型约束
|
||||
- 组件属性类型检查
|
||||
|
||||
## 📱 功能亮点
|
||||
|
||||
### 1. 智能数据获取
|
||||
- 根据参数自动判断获取当前用户或指定用户信息
|
||||
- 支持用户ID参数传递
|
||||
- 自动处理数据格式转换
|
||||
|
||||
### 2. 位置服务集成
|
||||
- 更新用户信息时自动获取当前位置
|
||||
- 支持经纬度坐标传递
|
||||
- 城市信息自动填充
|
||||
|
||||
### 3. 文件上传优化
|
||||
- 支持图片压缩上传
|
||||
- 上传失败时自动使用默认头像
|
||||
- 进度提示和错误处理
|
||||
|
||||
### 4. 表单体验优化
|
||||
- 实时表单验证
|
||||
- 字符计数显示
|
||||
- 选择器交互优化
|
||||
|
||||
## 🚀 使用方式
|
||||
|
||||
### 页面导航
|
||||
```typescript
|
||||
// 访问个人页面
|
||||
Taro.navigateTo({
|
||||
url: '/pages/userInfo/myself/index'
|
||||
});
|
||||
|
||||
// 访问他人页面
|
||||
Taro.navigateTo({
|
||||
url: `/pages/userInfo/other/index?userid=${user_id}`
|
||||
});
|
||||
|
||||
// 访问编辑资料页面
|
||||
Taro.navigateTo({
|
||||
url: '/pages/userInfo/edit/index'
|
||||
});
|
||||
```
|
||||
|
||||
### API配置
|
||||
```typescript
|
||||
// 开发环境
|
||||
API_CONFIG.BASE_URL = 'http://localhost:3000'
|
||||
|
||||
// 生产环境
|
||||
API_CONFIG.BASE_URL = 'https://api.example.com'
|
||||
```
|
||||
|
||||
## 📋 接口规范
|
||||
|
||||
### 请求格式
|
||||
- 所有接口使用POST方法
|
||||
- 请求头: `Content-Type: application/json`
|
||||
- 超时设置: 10秒
|
||||
|
||||
### 响应格式
|
||||
```json
|
||||
{
|
||||
"code": 0, // 0表示成功,非0表示失败
|
||||
"message": "string", // 错误信息
|
||||
"data": {} // 响应数据
|
||||
}
|
||||
```
|
||||
|
||||
### 错误码处理
|
||||
- `code: 0` - 请求成功
|
||||
- `code: 非0` - 业务错误,显示message
|
||||
- 网络错误 - 显示"网络连接失败"
|
||||
|
||||
## 🔄 数据流
|
||||
|
||||
1. **页面加载** → 调用 `UserService.get_user_info()`
|
||||
2. **用户操作** → 调用相应的API方法
|
||||
3. **数据更新** → 自动刷新页面状态
|
||||
4. **错误处理** → 显示友好提示信息
|
||||
|
||||
## 📝 注意事项
|
||||
|
||||
1. **权限处理**: 需要确保用户已登录才能调用API
|
||||
2. **缓存策略**: 建议添加用户信息缓存机制
|
||||
3. **图片处理**: 头像上传需要后端支持文件上传
|
||||
4. **位置权限**: 需要用户授权位置信息访问
|
||||
|
||||
## 🎯 下一步优化
|
||||
|
||||
1. 添加用户信息缓存机制
|
||||
2. 实现离线数据支持
|
||||
3. 优化图片上传体验
|
||||
4. 添加更多用户统计信息接口
|
||||
5. 实现用户关注/粉丝功能
|
||||
|
||||
---
|
||||
|
||||
**集成完成时间**: 2024年12月19日
|
||||
**API版本**: v1.0
|
||||
**兼容性**: 支持所有Taro框架版本
|
||||
97
src/pages/userInfo/README.md
Normal file
97
src/pages/userInfo/README.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# 个人页面功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
个人页面模块包含三个主要功能页面:
|
||||
|
||||
1. **个人页面** (`/pages/userInfo/myself/index`) - 当前用户的主页
|
||||
2. **他人页面** (`/pages/userInfo/other/index`) - 其他用户的主页
|
||||
3. **编辑资料** (`/pages/userInfo/edit/index`) - 编辑个人资料
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 个人页面 (myself)
|
||||
- 显示当前用户的基本信息(头像、昵称、加入时间)
|
||||
- 显示统计数据(关注、球友、主办、参加)
|
||||
- 显示个人标签和简介
|
||||
- 提供编辑和分享功能
|
||||
- 显示球局订单和收藏快捷入口
|
||||
- 展示用户主办的球局和参与的球局
|
||||
|
||||
### 他人页面 (other)
|
||||
- 显示其他用户的基本信息
|
||||
- 提供关注/取消关注功能
|
||||
- 提供发送消息功能
|
||||
- 展示该用户主办的球局和参与的球局
|
||||
- 支持点击参与者头像查看其他用户主页
|
||||
|
||||
### 编辑资料 (edit)
|
||||
- 支持更换头像
|
||||
- 编辑昵称、个人简介、所在地区、职业
|
||||
- NTRP等级选择
|
||||
- 表单验证和保存功能
|
||||
|
||||
## 技术特点
|
||||
|
||||
### 组件化设计
|
||||
- `UserInfoCard` - 用户信息卡片组件
|
||||
- `GameCard` - 球局卡片组件
|
||||
- `GameTabs` - 球局标签页组件
|
||||
|
||||
### 服务层
|
||||
- `UserService` - 用户相关API服务
|
||||
- `get_user_info()` - 获取用户信息
|
||||
- `get_user_games()` - 获取用户球局记录
|
||||
- `toggle_follow()` - 关注/取消关注
|
||||
- `save_user_info()` - 保存用户信息
|
||||
- `upload_avatar()` - 上传头像
|
||||
|
||||
### 页面导航
|
||||
- 支持通过 `userid` 参数区分个人页面和他人页面
|
||||
- 页面间导航逻辑完善
|
||||
- 参数传递和状态管理
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 访问个人页面
|
||||
```javascript
|
||||
Taro.navigateTo({
|
||||
url: '/pages/userInfo/myself/index'
|
||||
});
|
||||
```
|
||||
|
||||
### 访问他人页面
|
||||
```javascript
|
||||
Taro.navigateTo({
|
||||
url: `/pages/userInfo/other/index?userid=${user_id}`
|
||||
});
|
||||
```
|
||||
|
||||
### 访问编辑资料页面
|
||||
```javascript
|
||||
Taro.navigateTo({
|
||||
url: '/pages/userInfo/edit/index'
|
||||
});
|
||||
```
|
||||
|
||||
## 样式特点
|
||||
|
||||
- 使用渐变背景设计
|
||||
- 卡片式布局
|
||||
- 响应式交互效果
|
||||
- 统一的视觉风格
|
||||
- 符合小程序设计规范
|
||||
|
||||
## 数据流
|
||||
|
||||
1. 页面加载时从 `UserService` 获取数据
|
||||
2. 用户操作通过回调函数处理
|
||||
3. 状态更新后重新渲染组件
|
||||
4. 支持异步操作和错误处理
|
||||
|
||||
## 扩展性
|
||||
|
||||
- 组件可复用性强
|
||||
- 服务层易于扩展
|
||||
- 支持更多用户功能扩展
|
||||
- 便于维护和测试
|
||||
4
src/pages/userInfo/edit/index.config.ts
Normal file
4
src/pages/userInfo/edit/index.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '编辑资料',
|
||||
navigationStyle: 'custom'
|
||||
})
|
||||
279
src/pages/userInfo/edit/index.scss
Normal file
279
src/pages/userInfo/edit/index.scss
Normal file
@@ -0,0 +1,279 @@
|
||||
// 编辑资料页面样式
|
||||
.edit_profile_page {
|
||||
min-height: 100vh;
|
||||
background: radial-gradient(circle at 50% 0%, rgba(238, 255, 220, 1) 0%, rgba(255, 255, 255, 1) 37%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 导航栏
|
||||
.navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
height: 98px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 44px 44px 0px 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 21px 16px 0px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.navbar_left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
cursor: pointer;
|
||||
|
||||
.back_icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar_title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 17px;
|
||||
line-height: 1.4em;
|
||||
color: #000000;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.navbar_right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
cursor: pointer;
|
||||
|
||||
.save_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 1.4em;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主要内容区域
|
||||
.main_content {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
flex: 1;
|
||||
margin-top: 98px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
padding: 0px 16px;
|
||||
padding-bottom: 48px;
|
||||
|
||||
// 头像编辑区域
|
||||
.avatar_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 48px;
|
||||
margin-top: 98px;
|
||||
|
||||
.avatar_container {
|
||||
position: relative;
|
||||
|
||||
|
||||
|
||||
.avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
box-shadow: 0px 8px 20px 0px rgba(0, 0, 0, 0.12), 0px 0px 1px 0px rgba(0, 0, 0, 0.2);
|
||||
border: 0.5px solid rgba(255, 255, 255, 0.65);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.avatar_overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: #000000;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||
z-index: 10;
|
||||
|
||||
.upload_icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 表单区域
|
||||
.form_section {
|
||||
margin-bottom: 48px;
|
||||
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06);
|
||||
|
||||
.form_group {
|
||||
background: #FFFFFF;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.form_item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 12px;
|
||||
min-height: 44px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.item_left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
|
||||
.item_icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.item_label {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 1.71em;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
|
||||
.item_right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
|
||||
.item_input {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.71em;
|
||||
color: #000000;
|
||||
border: none;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.item_value {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.71em;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.bio_textarea {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.71em;
|
||||
color: #000000;
|
||||
border: none;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
min-height: 20px;
|
||||
resize: none;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.arrow_icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
position: absolute;
|
||||
left: 36px;
|
||||
right: 12px;
|
||||
height: 0.5px;
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
border-radius: 99px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 退出登录区域
|
||||
.logout_section {
|
||||
margin-top: 16px;
|
||||
|
||||
.logout_button {
|
||||
background: #FFFFFF;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 16px;
|
||||
padding: 2px 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
min-height: 48px;
|
||||
|
||||
.logout_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 1.4em;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载状态
|
||||
.loading_container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 200px;
|
||||
|
||||
.loading_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 1.4em;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
337
src/pages/userInfo/edit/index.tsx
Normal file
337
src/pages/userInfo/edit/index.tsx
Normal file
@@ -0,0 +1,337 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, Image, ScrollView, Input } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import './index.scss';
|
||||
import { UserInfo } from '@/components/UserInfo';
|
||||
import { UserService } from '@/services/userService';
|
||||
import { EditModal } from '@/components';
|
||||
|
||||
const EditProfilePage: React.FC = () => {
|
||||
// 用户信息状态
|
||||
const [user_info, setUserInfo] = useState<UserInfo>({
|
||||
id: '1',
|
||||
nickname: '加载中...',
|
||||
avatar: require('../../../static/userInfo/default_avatar.svg'),
|
||||
join_date: '加载中...',
|
||||
stats: {
|
||||
following: 0,
|
||||
friends: 0,
|
||||
hosted: 0,
|
||||
participated: 0
|
||||
},
|
||||
tags: ['加载中...'],
|
||||
bio: '加载中...',
|
||||
location: '加载中...',
|
||||
occupation: '加载中...',
|
||||
ntrp_level: 'NTRP 3.0',
|
||||
phone: '',
|
||||
gender: ''
|
||||
});
|
||||
|
||||
// 表单状态
|
||||
const [form_data, setFormData] = useState({
|
||||
nickname: '',
|
||||
bio: '',
|
||||
location: '',
|
||||
occupation: '',
|
||||
ntrp_level: '4.0',
|
||||
phone: '',
|
||||
gender: '',
|
||||
birthday: '2000-01-01'
|
||||
});
|
||||
|
||||
// 加载状态
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// 编辑弹窗状态
|
||||
const [edit_modal_visible, setEditModalVisible] = useState(false);
|
||||
const [editing_field, setEditingField] = useState<string>('');
|
||||
|
||||
// 页面加载时初始化数据
|
||||
useEffect(() => {
|
||||
load_user_info();
|
||||
}, []);
|
||||
|
||||
// 加载用户信息
|
||||
const load_user_info = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const user_data = await UserService.get_user_info();
|
||||
setUserInfo(user_data);
|
||||
setFormData({
|
||||
nickname: user_data.nickname,
|
||||
bio: user_data.bio,
|
||||
location: user_data.location,
|
||||
occupation: user_data.occupation,
|
||||
ntrp_level: user_data.ntrp_level.replace('NTRP ', ''),
|
||||
phone: user_data.phone || '',
|
||||
gender: user_data.gender || '',
|
||||
birthday: '2000-01-01' // 默认生日,实际应该从用户数据获取
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('加载用户信息失败:', error);
|
||||
Taro.showToast({
|
||||
title: '加载用户信息失败',
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理输入变化
|
||||
const handle_input_change = (field: string, value: string) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[field]: value
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// 处理头像上传
|
||||
const handle_avatar_upload = () => {
|
||||
Taro.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: async (res) => {
|
||||
const tempFilePath = res.tempFilePaths[0];
|
||||
try {
|
||||
const avatar_url = await UserService.upload_avatar(tempFilePath);
|
||||
setUserInfo(prev => ({ ...prev, avatar: avatar_url }));
|
||||
Taro.showToast({
|
||||
title: '头像上传成功',
|
||||
icon: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('头像上传失败:', error);
|
||||
Taro.showToast({
|
||||
title: '头像上传失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 处理编辑弹窗
|
||||
const handle_open_edit_modal = (field: string) => {
|
||||
setEditingField(field);
|
||||
setEditModalVisible(true);
|
||||
};
|
||||
|
||||
const handle_edit_modal_save = (value: string) => {
|
||||
setFormData(prev => ({ ...prev, [editing_field]: value }));
|
||||
setEditModalVisible(false);
|
||||
setEditingField('');
|
||||
};
|
||||
|
||||
const handle_edit_modal_cancel = () => {
|
||||
setEditModalVisible(false);
|
||||
setEditingField('');
|
||||
};
|
||||
|
||||
// 处理退出登录
|
||||
const handle_logout = () => {
|
||||
Taro.showModal({
|
||||
title: '确认退出',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 清除用户数据
|
||||
Taro.removeStorageSync('user_token');
|
||||
Taro.removeStorageSync('user_info');
|
||||
Taro.reLaunch({
|
||||
url: '/pages/login/index/index'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="edit_profile_page">
|
||||
{/* 导航栏 */}
|
||||
|
||||
{/* 主要内容 */}
|
||||
<ScrollView className="main_content" scrollY>
|
||||
{loading ? (
|
||||
<View className="loading_container">
|
||||
<Text className="loading_text">加载中...</Text>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
{/* 头像编辑区域 */}
|
||||
<View className="avatar_section">
|
||||
<View className="avatar_container" onClick={handle_avatar_upload}>
|
||||
<Image className="avatar" src={user_info.avatar} />
|
||||
<View className="avatar_overlay">
|
||||
<Image
|
||||
className="upload_icon"
|
||||
src={require('../../../static/userInfo/edit2.svg')}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
</View>
|
||||
|
||||
{/* 基本信息编辑 */}
|
||||
<View className="form_section">
|
||||
{/* 名字 */}
|
||||
<View className="form_group">
|
||||
<View className="form_item">
|
||||
<View className="item_left">
|
||||
<Image className="item_icon" src={require('../../../static/userInfo/user1.svg')} />
|
||||
<Text className="item_label">名字</Text>
|
||||
</View>
|
||||
<View className="item_right">
|
||||
<Input
|
||||
className="item_input"
|
||||
value={form_data.nickname}
|
||||
placeholder="188的王晨"
|
||||
onInput={(e) => handle_input_change('nickname', e.detail.value)}
|
||||
/>
|
||||
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
|
||||
</View>
|
||||
</View>
|
||||
<View className="divider"></View>
|
||||
</View>
|
||||
|
||||
{/* 性别 */}
|
||||
<View className="form_group">
|
||||
<View className="form_item">
|
||||
<View className="item_left">
|
||||
<Image className="item_icon" src={require('../../../static/userInfo/user2.svg')} />
|
||||
<Text className="item_label">性别</Text>
|
||||
</View>
|
||||
<View className="item_right">
|
||||
<Text className="item_value">{form_data.gender || '男'}</Text>
|
||||
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
|
||||
</View>
|
||||
</View>
|
||||
<View className="divider"></View>
|
||||
</View>
|
||||
|
||||
{/* 生日 */}
|
||||
<View className="form_group">
|
||||
<View className="form_item">
|
||||
<View className="item_left">
|
||||
<Image className="item_icon" src={require('../../../static/userInfo/tennis.svg')} />
|
||||
<Text className="item_label">生日</Text>
|
||||
</View>
|
||||
<View className="item_right">
|
||||
<Text className="item_value">{form_data.birthday}</Text>
|
||||
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 简介编辑 */}
|
||||
<View className="form_section">
|
||||
<View className="form_group">
|
||||
<View className="form_item" onClick={() => handle_open_edit_modal('bio')}>
|
||||
<View className="item_left">
|
||||
<Image className="item_icon" src={require('../../../static/userInfo/message.svg')} />
|
||||
<Text className="item_label">简介</Text>
|
||||
</View>
|
||||
<View className="item_right">
|
||||
<Text className="item_value">
|
||||
{form_data.bio || '介绍一下自己'}
|
||||
</Text>
|
||||
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 地区、NTRP水平、职业 */}
|
||||
<View className="form_section">
|
||||
<View className="form_group">
|
||||
{/* 地区 */}
|
||||
<View className="form_item">
|
||||
<View className="item_left">
|
||||
<Image className="item_icon" src={require('../../../static/userInfo/location.svg')} />
|
||||
<Text className="item_label">地区</Text>
|
||||
</View>
|
||||
<View className="item_right">
|
||||
<Text className="item_value">{form_data.location || '上海 黄浦'}</Text>
|
||||
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
|
||||
</View>
|
||||
</View>
|
||||
<View className="divider"></View>
|
||||
|
||||
{/* NTRP水平 */}
|
||||
<View className="form_item">
|
||||
<View className="item_left">
|
||||
<Image className="item_icon" src={require('../../../static/userInfo/tennis.svg')} />
|
||||
<Text className="item_label">NTRP 水平</Text>
|
||||
</View>
|
||||
<View className="item_right">
|
||||
<Text className="item_value">{form_data.ntrp_level}</Text>
|
||||
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
|
||||
</View>
|
||||
</View>
|
||||
<View className="divider"></View>
|
||||
|
||||
{/* 职业 */}
|
||||
<View className="form_item">
|
||||
<View className="item_left">
|
||||
<Image className="item_icon" src={require('../../../static/userInfo/sc.svg')} />
|
||||
<Text className="item_label">职业</Text>
|
||||
</View>
|
||||
<View className="item_right">
|
||||
<Text className="item_value">{form_data.occupation || '互联网'}</Text>
|
||||
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 手机号 */}
|
||||
<View className="form_section">
|
||||
<View className="form_group">
|
||||
<View className="form_item">
|
||||
<View className="item_left">
|
||||
<Image className="item_icon" src={require('../../../static/userInfo/message.svg')} />
|
||||
<Text className="item_label">手机</Text>
|
||||
</View>
|
||||
<View className="item_right">
|
||||
<Text className="item_value">{form_data.phone || '+86 130 1234 1234'}</Text>
|
||||
<Image className="arrow_icon" src={require('../../../static/list/icon-list-right-arrow.svg')} />
|
||||
</View>
|
||||
</View>
|
||||
<View className="divider"></View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 退出登录 */}
|
||||
<View className="logout_section">
|
||||
<View className="logout_button" onClick={handle_logout}>
|
||||
<Text className="logout_text">退出登录</Text>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
{/* 编辑弹窗 */}
|
||||
<EditModal
|
||||
visible={edit_modal_visible}
|
||||
title="编辑简介"
|
||||
placeholder="介绍一下你的喜好,或者训练习惯"
|
||||
initialValue={form_data[editing_field as keyof typeof form_data] || ''}
|
||||
maxLength={100}
|
||||
onSave={handle_edit_modal_save}
|
||||
onCancel={handle_edit_modal_cancel}
|
||||
validationMessage="请填写 2-100 个字符"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditProfilePage;
|
||||
13
src/pages/userInfo/favorites/index.tsx
Normal file
13
src/pages/userInfo/favorites/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { View, } from '@tarojs/components';
|
||||
const OrderPage: React.FC = () => {
|
||||
|
||||
|
||||
return (
|
||||
<View className="myself_page">
|
||||
我的收藏
|
||||
</View>)
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default OrderPage;
|
||||
@@ -1,3 +1,5 @@
|
||||
@use '../../../scss/common.scss' as *;
|
||||
|
||||
// 个人页面样式
|
||||
.myself_page {
|
||||
min-height: 100vh;
|
||||
@@ -23,59 +25,27 @@
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
margin-top: 98px;
|
||||
|
||||
// 基本信息
|
||||
.basic_info {
|
||||
// 加载状态
|
||||
.loading_container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 40px 0;
|
||||
|
||||
.avatar_container {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
box-shadow: 0px 8px 20px 0px rgba(0, 0, 0, 0.12), 0px 0px 1px 0px rgba(0, 0, 0, 0.2);
|
||||
|
||||
.avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.info_container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.nickname {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 1.9%;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.join_date {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 2.7%;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
.loading_text {
|
||||
@include text-style(16px, 400, 1.4em);
|
||||
color: $color-primary-lightest;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 统计数据
|
||||
.stats_section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
|
||||
.stats_container {
|
||||
display: flex;
|
||||
@@ -88,127 +58,11 @@
|
||||
align-items: center;
|
||||
|
||||
.stat_number {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 2.1%;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
@include text-medium;
|
||||
}
|
||||
|
||||
.stat_label {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 3.2%;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action_buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
|
||||
|
||||
.follow_button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 12px 16px 12px 12px;
|
||||
height: 40px;
|
||||
background: #000000;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.following {
|
||||
background: #FFFFFF;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.button_icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.button_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.4em;
|
||||
color: #FFFFFF;
|
||||
|
||||
.following & {
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message_button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.12);
|
||||
border-radius: 999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.button_icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.edit_button {
|
||||
min-width: 60px;
|
||||
height: 40px;
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.12);
|
||||
border-radius: 999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
padding: 0 12px;
|
||||
|
||||
.button_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 1.4em;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
|
||||
.share_button {
|
||||
min-width: 60px;
|
||||
height: 40px;
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.12);
|
||||
border-radius: 999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
padding: 0 12px;
|
||||
margin: 0px !important;
|
||||
|
||||
.button_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 1.4em;
|
||||
color: #000000;
|
||||
@include text-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,14 +80,7 @@
|
||||
flex-wrap: wrap;
|
||||
|
||||
.tag_item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 8px;
|
||||
height: 20px;
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.16);
|
||||
border-radius: 999px;
|
||||
@include tag-base;
|
||||
|
||||
.tag_icon {
|
||||
width: 12px;
|
||||
@@ -241,22 +88,13 @@
|
||||
}
|
||||
|
||||
.tag_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
line-height: 1.8em;
|
||||
letter-spacing: -2.1%;
|
||||
color: #000000;
|
||||
@include text-tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bio_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.571em;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
@include text-body;
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
@@ -266,18 +104,15 @@
|
||||
margin-bottom: 16px;
|
||||
|
||||
.action_card {
|
||||
@include card-base;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06);
|
||||
overflow: hidden;
|
||||
|
||||
.action_content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
@@ -295,18 +130,15 @@
|
||||
}
|
||||
|
||||
.action_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
line-height: 1.4em;
|
||||
color: #000000;
|
||||
@include text-style(15px, 600, 1.4em);
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.action_divider {
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
background: $color-primary-lightest-5;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,24 +159,19 @@
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.tab_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 1.9%;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
@include text-primary;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.tab_text {
|
||||
color: #000000;
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.active) {
|
||||
.tab_text {
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
color: $color-primary-lightest-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -361,30 +188,18 @@
|
||||
margin-bottom: 16px;
|
||||
|
||||
.date_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 2.71%;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
@include text-style(14px, 600, 1.4em, 2.71%);
|
||||
color: $color-primary-light;
|
||||
}
|
||||
|
||||
.separator {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 2.11%;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
@include text-style(18px, 400, 1.4em, 2.11%);
|
||||
color: $color-primary-lightest;
|
||||
}
|
||||
|
||||
.weekday_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 2.71%;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
@include text-style(14px, 600, 1.4em, 2.71%);
|
||||
color: $color-primary-light;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,11 +211,8 @@
|
||||
padding: 0 5px 15px;
|
||||
|
||||
.game_card {
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 20px;
|
||||
@include card-base;
|
||||
padding: 0 0 12px;
|
||||
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
@@ -417,11 +229,8 @@
|
||||
padding: 12px 15px 0;
|
||||
|
||||
.game_title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 1.5em;
|
||||
color: #000000;
|
||||
@include text-style(16px, 600, 1.5em);
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
.game_type_icon {
|
||||
@@ -440,11 +249,7 @@
|
||||
padding: 6px 15px 0;
|
||||
|
||||
.time_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1.5em;
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
@include text-caption;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,19 +263,12 @@
|
||||
.location_text,
|
||||
.type_text,
|
||||
.distance_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1.5em;
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
@include text-caption;
|
||||
}
|
||||
|
||||
.separator {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.3em;
|
||||
color: rgba(60, 60, 67, 0.3);
|
||||
@include text-style(14px, 400, 1.3em);
|
||||
color: $color-text-tertiary;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,7 +286,7 @@
|
||||
width: 56.44px;
|
||||
height: 56.44px;
|
||||
border-radius: 9px;
|
||||
border: 1.5px solid #FFFFFF;
|
||||
border: 1.5px solid $color-white;
|
||||
|
||||
&:nth-child(1) {
|
||||
top: 4.18px;
|
||||
@@ -528,30 +326,17 @@
|
||||
gap: -8px;
|
||||
|
||||
.participant_avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #FFFFFF;
|
||||
@include avatar-base(20px);
|
||||
border: 1px solid $color-white;
|
||||
}
|
||||
}
|
||||
|
||||
.participants_count {
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.16);
|
||||
border-radius: 999px;
|
||||
@include tag-base;
|
||||
padding: 6px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.count_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
line-height: 1.8em;
|
||||
letter-spacing: -2.1%;
|
||||
color: #000000;
|
||||
@include text-tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -561,22 +346,10 @@
|
||||
gap: 4px;
|
||||
|
||||
.info_tag {
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.16);
|
||||
border-radius: 999px;
|
||||
padding: 6px 8px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@include tag-base;
|
||||
|
||||
.tag_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
line-height: 1.8em;
|
||||
letter-spacing: -2.1%;
|
||||
color: #000000;
|
||||
@include text-tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -594,7 +367,7 @@
|
||||
transform: translateX(-50%);
|
||||
width: 140px;
|
||||
height: 5px;
|
||||
background: #000000;
|
||||
background: $color-primary;
|
||||
border-radius: 2.5px;
|
||||
z-index: 10;
|
||||
}
|
||||
@@ -1,47 +1,13 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, Image, ScrollView, Button } from '@tarojs/components';
|
||||
import { View, Text, Image, ScrollView } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import './index.scss';
|
||||
import GuideBar from '@/components/GuideBar'
|
||||
// 用户信息接口
|
||||
interface UserInfo {
|
||||
id: string;
|
||||
nickname: string;
|
||||
avatar: string;
|
||||
join_date: string;
|
||||
stats: {
|
||||
following: number;
|
||||
friends: number;
|
||||
hosted: number;
|
||||
participated: number;
|
||||
};
|
||||
tags: string[];
|
||||
bio: string;
|
||||
location: string;
|
||||
occupation: string;
|
||||
ntrp_level: string;
|
||||
}
|
||||
|
||||
// 球局记录接口
|
||||
interface GameRecord {
|
||||
id: string;
|
||||
title: string;
|
||||
date: string;
|
||||
time: string;
|
||||
duration: string;
|
||||
location: string;
|
||||
type: string;
|
||||
distance: string;
|
||||
participants: {
|
||||
avatar: string;
|
||||
nickname: string;
|
||||
}[];
|
||||
max_participants: number;
|
||||
current_participants: number;
|
||||
level_range: string;
|
||||
game_type: string;
|
||||
images: string[];
|
||||
}
|
||||
import { UserInfoCard, UserInfo } from '@/components/UserInfo/index'
|
||||
import { UserService } from '@/services/userService'
|
||||
import ListContainer from '@/container/listContainer'
|
||||
import { TennisMatch } from '../../../../types/list/types'
|
||||
import { withAuth } from '@/components';
|
||||
|
||||
const MyselfPage: React.FC = () => {
|
||||
// 获取页面参数
|
||||
@@ -51,51 +17,28 @@ const MyselfPage: React.FC = () => {
|
||||
// 判断是否为当前用户
|
||||
const is_current_user = !user_id;
|
||||
|
||||
// 模拟用户数据
|
||||
const [user_info] = useState<UserInfo>({
|
||||
// 用户信息状态
|
||||
const [user_info, set_user_info] = useState<UserInfo>({
|
||||
id: '1',
|
||||
nickname: '188的王晨',
|
||||
nickname: '加载中...',
|
||||
avatar: require('../../../static/userInfo/default_avatar.svg'),
|
||||
join_date: '2025年9月加入',
|
||||
join_date: '加载中...',
|
||||
stats: {
|
||||
following: 124,
|
||||
friends: 24,
|
||||
hosted: 7,
|
||||
participated: 24
|
||||
following: 0,
|
||||
friends: 0,
|
||||
hosted: 0,
|
||||
participated: 0
|
||||
},
|
||||
tags: ['上海黄浦', '互联网从业者', 'NTRP 4.0'],
|
||||
bio: '网球入坑两年,偏好双打,正手进攻型选手\n平时在张江、世纪公园附近活动,欢迎约球!\n不卷分数,但认真对待每一拍,每一场球都想打得开心。有时候也会带相机来拍点照片📸',
|
||||
location: '上海黄浦',
|
||||
occupation: '互联网从业者',
|
||||
ntrp_level: 'NTRP 4.0'
|
||||
tags: ['加载中...'],
|
||||
bio: '加载中...',
|
||||
location: '加载中...',
|
||||
occupation: '加载中...',
|
||||
ntrp_level: 'NTRP 3.0'
|
||||
});
|
||||
|
||||
// 模拟球局数据
|
||||
const [game_records] = useState<GameRecord[]>([
|
||||
{
|
||||
id: '1',
|
||||
title: '女生轻松双打',
|
||||
date: '明天(周五)',
|
||||
time: '下午5点',
|
||||
duration: '2小时',
|
||||
location: '仁恒河滨花园网球场',
|
||||
type: '室外',
|
||||
distance: '3.5km',
|
||||
participants: [
|
||||
{ avatar: require('../../../static/userInfo/user1.svg'), nickname: '用户1' },
|
||||
{ avatar: require('../../../static/userInfo/user2.svg'), nickname: '用户2' }
|
||||
],
|
||||
max_participants: 4,
|
||||
current_participants: 2,
|
||||
level_range: '2.0 至 2.5',
|
||||
game_type: '双打',
|
||||
images: [
|
||||
require('../../../static/userInfo/game1.svg'),
|
||||
require('../../../static/userInfo/game2.svg'),
|
||||
require('../../../static/userInfo/game3.svg')
|
||||
]
|
||||
}
|
||||
]);
|
||||
// 球局记录状态
|
||||
const [game_records, set_game_records] = useState<TennisMatch[]>([]);
|
||||
const [loading, set_loading] = useState(true);
|
||||
|
||||
// 关注状态
|
||||
const [is_following, setIsFollowing] = useState(false);
|
||||
@@ -103,168 +46,136 @@ const MyselfPage: React.FC = () => {
|
||||
// 当前激活的标签页
|
||||
const [active_tab, setActiveTab] = useState<'hosted' | 'participated'>('hosted');
|
||||
|
||||
// 加载用户数据
|
||||
const load_user_data = async () => {
|
||||
try {
|
||||
set_loading(true);
|
||||
|
||||
// 获取用户信息(包含统计数据)
|
||||
const user_data = await UserService.get_user_info(user_id);
|
||||
set_user_info(user_data);
|
||||
|
||||
// 获取球局记录
|
||||
let games_data;
|
||||
if (active_tab === 'hosted') {
|
||||
games_data = await UserService.get_hosted_games(user_id || '1');
|
||||
} else {
|
||||
games_data = await UserService.get_participated_games(user_id || '1');
|
||||
}
|
||||
set_game_records(games_data);
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载用户数据失败:', error);
|
||||
Taro.showToast({
|
||||
title: '加载失败,请重试',
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
});
|
||||
} finally {
|
||||
set_loading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 页面加载时获取数据
|
||||
useEffect(() => {
|
||||
load_user_data();
|
||||
}, [user_id]);
|
||||
|
||||
// 切换标签页时重新加载球局数据
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
load_game_data();
|
||||
}
|
||||
}, [active_tab]);
|
||||
|
||||
// 加载球局数据
|
||||
const load_game_data = async () => {
|
||||
try {
|
||||
let games_data;
|
||||
if (active_tab === 'hosted') {
|
||||
games_data = await UserService.get_hosted_games(user_id || '1');
|
||||
} else {
|
||||
games_data = await UserService.get_participated_games(user_id || '1');
|
||||
}
|
||||
set_game_records(games_data);
|
||||
} catch (error) {
|
||||
console.error('加载球局数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理关注/取消关注
|
||||
const handle_follow = () => {
|
||||
setIsFollowing(!is_following);
|
||||
Taro.showToast({
|
||||
title: is_following ? '已取消关注' : '关注成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
const handle_follow = async () => {
|
||||
try {
|
||||
const new_following_state = await UserService.toggle_follow(user_id || '1', is_following);
|
||||
setIsFollowing(new_following_state);
|
||||
|
||||
Taro.showToast({
|
||||
title: new_following_state ? '关注成功' : '已取消关注',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('关注操作失败:', error);
|
||||
Taro.showToast({
|
||||
title: '操作失败,请重试',
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 处理分享
|
||||
const handle_share = () => {
|
||||
Taro.showShareMenu({
|
||||
withShareTicket: true
|
||||
});
|
||||
};
|
||||
|
||||
// 处理返回
|
||||
const handle_back = () => {
|
||||
Taro.navigateBack();
|
||||
};
|
||||
|
||||
// 处理编辑资料
|
||||
const handle_edit_profile = () => {
|
||||
Taro.navigateTo({
|
||||
url: '/pages/userInfo/edit/index'
|
||||
});
|
||||
};
|
||||
|
||||
// 处理球局详情
|
||||
const handle_game_detail = (game_id: string) => {
|
||||
Taro.navigateTo({
|
||||
url: `/pages/game/detail/index?id=${game_id}`
|
||||
});
|
||||
};
|
||||
|
||||
// 处理球局订单
|
||||
const handle_game_orders = () => {
|
||||
Taro.navigateTo({
|
||||
url: '/pages/game/orders/index'
|
||||
url: '/pages/userInfo/orders/index'
|
||||
});
|
||||
};
|
||||
|
||||
// 处理收藏
|
||||
const handle_favorites = () => {
|
||||
Taro.navigateTo({
|
||||
url: '/pages/game/favorites/index'
|
||||
url: '/pages/userInfo/favorites/index'
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<View className="myself_page">
|
||||
{/* 主要内容 */}
|
||||
<ScrollView className="main_content" scrollY>
|
||||
<View className='main_content'>
|
||||
{/* 用户信息区域 */}
|
||||
<View className="user_info_section">
|
||||
{/* 头像和基本信息 */}
|
||||
<View className="basic_info">
|
||||
<View className="avatar_container">
|
||||
<Image className="avatar" src={user_info.avatar} />
|
||||
{loading ? (
|
||||
<View className="loading_container">
|
||||
<Text className="loading_text">加载中...</Text>
|
||||
</View>
|
||||
<View className="info_container">
|
||||
<Text className="nickname">{user_info.nickname}</Text>
|
||||
<Text className="join_date">{user_info.join_date}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 统计数据 */}
|
||||
<View className="stats_section">
|
||||
<View className="stats_container">
|
||||
<View className="stat_item">
|
||||
<Text className="stat_number">{user_info.stats.following}</Text>
|
||||
<Text className="stat_label">关注</Text>
|
||||
</View>
|
||||
<View className="stat_item">
|
||||
<Text className="stat_number">{user_info.stats.friends}</Text>
|
||||
<Text className="stat_label">球友</Text>
|
||||
</View>
|
||||
<View className="stat_item">
|
||||
<Text className="stat_number">{user_info.stats.hosted}</Text>
|
||||
<Text className="stat_label">主办</Text>
|
||||
</View>
|
||||
<View className="stat_item">
|
||||
<Text className="stat_number">{user_info.stats.participated}</Text>
|
||||
<Text className="stat_label">参加</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className="action_buttons">
|
||||
{/* 只有非当前用户才显示关注按钮 */}
|
||||
{!is_current_user && (
|
||||
<Button
|
||||
className={`follow_button ${is_following ? 'following' : ''}`}
|
||||
onClick={handle_follow}
|
||||
>
|
||||
<Image
|
||||
className="button_icon"
|
||||
src={require('../../../static/userInfo/plus.svg')}
|
||||
/>
|
||||
<Text className="button_text">
|
||||
{is_following ? '已关注' : '关注'}
|
||||
</Text>
|
||||
</Button>
|
||||
)}
|
||||
{/* 只有非当前用户才显示消息按钮 */}
|
||||
{!is_current_user && (
|
||||
<Button className="message_button">
|
||||
<Image
|
||||
className="button_icon"
|
||||
src={require('../../../static/userInfo/message.svg')}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
{/* 只有当前用户才显示编辑按钮 */}
|
||||
{is_current_user && (
|
||||
<Button className="edit_button" onClick={handle_edit_profile}>
|
||||
<Text className="button_text">编辑</Text>
|
||||
</Button>
|
||||
)}
|
||||
{/* 只有当前用户才显示分享按钮 */}
|
||||
{is_current_user && (
|
||||
<Button className="share_button" onClick={handle_share}>
|
||||
<Text className="button_text">分享</Text>
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 标签和简介 */}
|
||||
<View className="tags_bio_section">
|
||||
<View className="tags_container">
|
||||
<View className="tag_item">
|
||||
<Image
|
||||
className="tag_icon"
|
||||
src={require('../../../static/userInfo/location.svg')}
|
||||
/>
|
||||
<Text className="tag_text">{user_info.location}</Text>
|
||||
</View>
|
||||
<View className="tag_item">
|
||||
<Text className="tag_text">{user_info.occupation}</Text>
|
||||
</View>
|
||||
<View className="tag_item">
|
||||
<Text className="tag_text">{user_info.ntrp_level}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text className="bio_text">{user_info.bio}</Text>
|
||||
</View>
|
||||
) : (
|
||||
<UserInfoCard
|
||||
user_info={user_info}
|
||||
is_current_user={is_current_user}
|
||||
is_following={is_following}
|
||||
on_follow={handle_follow}
|
||||
|
||||
/>
|
||||
)}
|
||||
{/* 球局订单和收藏功能 */}
|
||||
<View className="quick_actions_section">
|
||||
<View className="action_card">
|
||||
<View className="action_content" onClick={handle_game_orders}>
|
||||
<Image
|
||||
className="action_icon"
|
||||
src={require('../../../static/userInfo/tennis.svg')}
|
||||
src={require('../../../static/userInfo/order_btn.svg')}
|
||||
/>
|
||||
<Text className="action_text">球局订单</Text>
|
||||
<Text className="action_text">我的订单</Text>
|
||||
</View>
|
||||
<View className="action_divider"></View>
|
||||
<View className="action_content" onClick={handle_favorites}>
|
||||
<Image
|
||||
className="action_icon"
|
||||
src={require('../../../static/userInfo/tennis.svg')}
|
||||
src={require('../../../static/userInfo/sc.svg')}
|
||||
/>
|
||||
<Text className="action_text">收藏</Text>
|
||||
</View>
|
||||
@@ -286,93 +197,20 @@ const MyselfPage: React.FC = () => {
|
||||
|
||||
{/* 球局列表 */}
|
||||
<View className="game_list_section">
|
||||
<View className="date_header">
|
||||
<Text className="date_text">5月28日</Text>
|
||||
<Text className="separator">/</Text>
|
||||
<Text className="weekday_text">星期三</Text>
|
||||
</View>
|
||||
|
||||
{/* 球局卡片 */}
|
||||
<View className="game_cards">
|
||||
{game_records.map((game) => (
|
||||
<View
|
||||
key={game.id}
|
||||
className="game_card"
|
||||
onClick={() => handle_game_detail(game.id)}
|
||||
>
|
||||
{/* 球局标题和类型 */}
|
||||
<View className="game_header">
|
||||
<Text className="game_title">{game.title}</Text>
|
||||
<View className="game_type_icon">
|
||||
<Image
|
||||
className="type_icon"
|
||||
src={require('../../../static/userInfo/tennis.svg')}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 球局时间 */}
|
||||
<View className="game_time">
|
||||
<Text className="time_text">
|
||||
{game.date} {game.time} {game.duration}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* 球局地点和类型 */}
|
||||
<View className="game_location">
|
||||
<Text className="location_text">{game.location}</Text>
|
||||
<Text className="separator">·</Text>
|
||||
<Text className="type_text">{game.type}</Text>
|
||||
<Text className="separator">·</Text>
|
||||
<Text className="distance_text">{game.distance}</Text>
|
||||
</View>
|
||||
|
||||
{/* 球局图片 */}
|
||||
<View className="game_images">
|
||||
{game.images.map((image, index) => (
|
||||
<Image
|
||||
key={index}
|
||||
className="game_image"
|
||||
src={image}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 球局信息标签 */}
|
||||
<View className="game_tags">
|
||||
<View className="participants_info">
|
||||
<View className="avatars">
|
||||
{game.participants.map((participant, index) => (
|
||||
<Image
|
||||
key={index}
|
||||
className="participant_avatar"
|
||||
src={participant.avatar}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
<View className="participants_count">
|
||||
<Text className="count_text">
|
||||
报名人数 {game.current_participants}/{game.max_participants}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className="game_info_tags">
|
||||
<View className="info_tag">
|
||||
<Text className="tag_text">{game.level_range}</Text>
|
||||
</View>
|
||||
<View className="info_tag">
|
||||
<Text className="tag_text">{game.game_type}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<ScrollView scrollY>
|
||||
<ListContainer
|
||||
data={game_records}
|
||||
recommendList={[]}
|
||||
loading={loading}
|
||||
error={null}
|
||||
reload={load_game_data}
|
||||
/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
<GuideBar currentPage='personal' />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyselfPage;
|
||||
export default withAuth(MyselfPage);
|
||||
|
||||
13
src/pages/userInfo/orders/index.tsx
Normal file
13
src/pages/userInfo/orders/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { View, } from '@tarojs/components';
|
||||
const OrderPage: React.FC = () => {
|
||||
|
||||
|
||||
return (
|
||||
<View className="myself_page">
|
||||
我的订单
|
||||
</View>)
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default OrderPage;
|
||||
4
src/pages/userInfo/other/index.config.ts
Normal file
4
src/pages/userInfo/other/index.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '用户主页',
|
||||
navigationStyle: 'custom'
|
||||
})
|
||||
490
src/pages/userInfo/other/index.scss
Normal file
490
src/pages/userInfo/other/index.scss
Normal file
@@ -0,0 +1,490 @@
|
||||
// 他人用户页面样式
|
||||
.other_user_page {
|
||||
min-height: 100vh;
|
||||
background: radial-gradient(circle at 50% 0%, rgba(238, 255, 220, 1) 0%, rgba(255, 255, 255, 1) 37%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 主要内容区域
|
||||
.main_content {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
flex: 1;
|
||||
margin-top: 0;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
padding: 15px 15px 15px;
|
||||
|
||||
// 用户信息区域
|
||||
.user_info_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
// 基本信息
|
||||
.basic_info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.avatar_container {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
box-shadow: 0px 8px 20px 0px rgba(0, 0, 0, 0.12), 0px 0px 1px 0px rgba(0, 0, 0, 0.2);
|
||||
|
||||
.avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.info_container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.nickname {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 1.9%;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.join_date {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 2.7%;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 统计数据
|
||||
.stats_section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
|
||||
.stats_container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
|
||||
.stat_item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.stat_number {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 2.1%;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
.stat_label {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 3.2%;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action_buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.follow_button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 12px 16px 12px 12px;
|
||||
height: 40px;
|
||||
background: #000000;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.following {
|
||||
background: #FFFFFF;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.button_icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.button_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.4em;
|
||||
color: #FFFFFF;
|
||||
|
||||
.following & {
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message_button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.12);
|
||||
border-radius: 999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.button_icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 标签和简介
|
||||
.tags_bio_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.tags_container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.tag_item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 8px;
|
||||
height: 20px;
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.16);
|
||||
border-radius: 999px;
|
||||
|
||||
.tag_icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.tag_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
line-height: 1.8em;
|
||||
letter-spacing: -2.1%;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bio_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.571em;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 球局类型标签页
|
||||
.game_tabs_section {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.tab_container {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 12px 15px;
|
||||
|
||||
.tab_item {
|
||||
padding: 12px 0;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.tab_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 1.9%;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.tab_text {
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.active) {
|
||||
.tab_text {
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 球局列表区域
|
||||
.game_list_section {
|
||||
.date_header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.date_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 2.71%;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
.separator {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 2.11%;
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.weekday_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 2.71%;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
// 球局卡片
|
||||
.game_cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
padding: 0 5px 15px;
|
||||
|
||||
.game_card {
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 20px;
|
||||
padding: 0 0 12px;
|
||||
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.06);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
// 球局标题和类型
|
||||
.game_header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 15px 0;
|
||||
|
||||
.game_title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 1.5em;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.game_type_icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
.type_icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 球局时间
|
||||
.game_time {
|
||||
padding: 6px 15px 0;
|
||||
|
||||
.time_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1.5em;
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
// 球局地点和类型
|
||||
.game_location {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 4px 15px 0;
|
||||
|
||||
.location_text,
|
||||
.type_text,
|
||||
.distance_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1.5em;
|
||||
color: rgba(60, 60, 67, 0.6);
|
||||
}
|
||||
|
||||
.separator {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 1.3em;
|
||||
color: rgba(60, 60, 67, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
// 球局图片
|
||||
.game_images {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
right: 5px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.2);
|
||||
|
||||
.game_image {
|
||||
position: absolute;
|
||||
width: 56.44px;
|
||||
height: 56.44px;
|
||||
border-radius: 9px;
|
||||
border: 1.5px solid #FFFFFF;
|
||||
|
||||
&:nth-child(1) {
|
||||
top: 4.18px;
|
||||
left: 19.18px;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
top: 26.5px;
|
||||
left: 38px;
|
||||
width: 61.86px;
|
||||
height: 61.86px;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
top: 32.5px;
|
||||
left: 0;
|
||||
width: 62.04px;
|
||||
height: 62.04px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 球局信息标签
|
||||
.game_tags {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 6px;
|
||||
padding: 8px 15px 0;
|
||||
|
||||
.participants_info {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
|
||||
.avatars {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: -8px;
|
||||
|
||||
.participant_avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
.participants_count {
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.16);
|
||||
border-radius: 999px;
|
||||
padding: 6px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.count_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
line-height: 1.8em;
|
||||
letter-spacing: -2.1%;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.game_info_tags {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
|
||||
.info_tag {
|
||||
background: #FFFFFF;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.16);
|
||||
border-radius: 999px;
|
||||
padding: 6px 8px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.tag_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
line-height: 1.8em;
|
||||
letter-spacing: -2.1%;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
146
src/pages/userInfo/other/index.tsx
Normal file
146
src/pages/userInfo/other/index.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, ScrollView } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import './index.scss';
|
||||
import GuideBar from '@/components/GuideBar';
|
||||
import { UserInfoCard, GameCard, GameTabs, UserInfo, GameRecord } from '@/components/UserInfo';
|
||||
import { UserService } from '@/services/userService';
|
||||
|
||||
const OtherUserPage: React.FC = () => {
|
||||
// 获取页面参数
|
||||
const instance = Taro.getCurrentInstance();
|
||||
const user_id = instance.router?.params?.userid;
|
||||
|
||||
// 模拟用户数据
|
||||
const [user_info, setUserInfo] = useState<UserInfo>({
|
||||
id: user_id || '1',
|
||||
nickname: '网球爱好者',
|
||||
avatar: require('../../../static/userInfo/default_avatar.svg'),
|
||||
join_date: '2024年3月加入',
|
||||
stats: {
|
||||
following: 89,
|
||||
friends: 15,
|
||||
hosted: 12,
|
||||
participated: 35
|
||||
},
|
||||
tags: ['北京朝阳', '金融从业者', 'NTRP 3.5'],
|
||||
bio: '热爱网球的金融从业者,周末喜欢约球\n技术还在提升中,欢迎一起切磋\n平时在朝阳公园附近活动',
|
||||
location: '北京朝阳',
|
||||
occupation: '金融从业者',
|
||||
ntrp_level: 'NTRP 3.5'
|
||||
});
|
||||
|
||||
// 模拟球局数据
|
||||
const [game_records, setGameRecords] = useState<GameRecord[]>([]);
|
||||
|
||||
// 关注状态
|
||||
const [is_following, setIsFollowing] = useState(false);
|
||||
|
||||
// 当前激活的标签页
|
||||
const [active_tab, setActiveTab] = useState<'hosted' | 'participated'>('hosted');
|
||||
|
||||
// 页面加载时获取用户信息
|
||||
useEffect(() => {
|
||||
const load_user_data = async () => {
|
||||
if (user_id) {
|
||||
try {
|
||||
const user_data = await UserService.get_user_info(user_id);
|
||||
setUserInfo(user_data);
|
||||
|
||||
const games_data = await UserService.get_user_games(user_id, active_tab);
|
||||
setGameRecords(games_data);
|
||||
} catch (error) {
|
||||
console.error('加载用户数据失败:', error);
|
||||
Taro.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
load_user_data();
|
||||
}, [user_id, active_tab]);
|
||||
|
||||
// 处理关注/取消关注
|
||||
const handle_follow = async () => {
|
||||
try {
|
||||
const new_follow_status = await UserService.toggle_follow(user_info.id, is_following);
|
||||
setIsFollowing(new_follow_status);
|
||||
Taro.showToast({
|
||||
title: new_follow_status ? '关注成功' : '已取消关注',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('关注操作失败:', error);
|
||||
Taro.showToast({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 处理发送消息
|
||||
const handle_send_message = () => {
|
||||
Taro.navigateTo({
|
||||
url: `/pages/message/chat/index?user_id=${user_info.id}&nickname=${user_info.nickname}`
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 处理球局详情
|
||||
const handle_game_detail = (game_id: string) => {
|
||||
Taro.navigateTo({
|
||||
url: `/pages/detail/index?id=${game_id}`
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="other_user_page">
|
||||
{/* 主要内容 */}
|
||||
<View className="main_content" >
|
||||
{/* 用户信息区域 */}
|
||||
<View className="user_info_section">
|
||||
<UserInfoCard
|
||||
user_info={user_info}
|
||||
is_current_user={false}
|
||||
is_following={is_following}
|
||||
on_follow={handle_follow}
|
||||
on_message={handle_send_message}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 球局类型标签页 */}
|
||||
<GameTabs
|
||||
active_tab={active_tab}
|
||||
on_tab_change={setActiveTab}
|
||||
is_current_user={false}
|
||||
/>
|
||||
|
||||
{/* 球局列表 */}
|
||||
<View className="game_list_section">
|
||||
<View className="date_header">
|
||||
<Text className="date_text">5月29日</Text>
|
||||
<Text className="separator">/</Text>
|
||||
<Text className="weekday_text">星期六</Text>
|
||||
</View>
|
||||
|
||||
{/* 球局卡片 */}
|
||||
<View className="game_cards">
|
||||
{game_records.map((game) => (
|
||||
<GameCard
|
||||
key={game.id}
|
||||
game={game}
|
||||
on_click={handle_game_detail}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<GuideBar currentPage='personal' />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default OtherUserPage;
|
||||
Reference in New Issue
Block a user