合并代码
This commit is contained in:
4
src/pages/detail/index.config.ts
Normal file
4
src/pages/detail/index.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '球局详情',
|
||||
navigationStyle: 'custom',
|
||||
})
|
||||
1034
src/pages/detail/index.scss
Normal file
1034
src/pages/detail/index.scss
Normal file
File diff suppressed because it is too large
Load Diff
556
src/pages/detail/index.tsx
Normal file
556
src/pages/detail/index.tsx
Normal file
@@ -0,0 +1,556 @@
|
||||
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 Taro, { useRouter, useShareAppMessage, useShareTimeline } from '@tarojs/taro'
|
||||
// 导入API服务
|
||||
import DetailService from '../../services/detailService'
|
||||
import {
|
||||
useUserStats,
|
||||
useUserActions
|
||||
} 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'
|
||||
]
|
||||
|
||||
function insertDotInTags(tags: string[]) {
|
||||
return tags.join('-·-').split('-')
|
||||
}
|
||||
|
||||
const SharePopup = forwardRef(({ id, from }: { id: string, from: string }, ref) => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
show: () => {
|
||||
setVisible(true)
|
||||
}
|
||||
}))
|
||||
|
||||
function handleShareToWechat() {
|
||||
useShareAppMessage(() => {
|
||||
return {
|
||||
title: '分享',
|
||||
path: `/pages/detail/index?id=${id}&from=${from}`,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleShareToWechatMoments() {
|
||||
useShareTimeline(() => {
|
||||
return {
|
||||
title: '分享',
|
||||
path: `/pages/detail/index?id=${id}&from=${from}`,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleSaveToLocal() {
|
||||
Taro.saveImageToPhotosAlbum({
|
||||
filePath: images[0],
|
||||
success: () => {
|
||||
Taro.showToast({ title: '保存成功', icon: 'success' })
|
||||
},
|
||||
fail: () => {
|
||||
Taro.showToast({ title: '保存失败', icon: 'none' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<CommonPopup
|
||||
title="分享"
|
||||
visible={visible}
|
||||
onClose={() => { setVisible(false) }}
|
||||
hideFooter
|
||||
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>
|
||||
)
|
||||
})
|
||||
|
||||
function Index() {
|
||||
// 使用Zustand store
|
||||
// const userStats = useUserStats()
|
||||
// const { incrementRequestCount, resetUserStats } = useUserActions()
|
||||
|
||||
const [current, setCurrent] = useState(0)
|
||||
const [colors, setColors] = useState<string []>([])
|
||||
const [detail, setDetail] = useState<any>(null)
|
||||
const { params } = useRouter()
|
||||
const { id, autoShare, from } = params
|
||||
|
||||
console.log('from', from)
|
||||
|
||||
// 本地状态管理
|
||||
const [loading, setLoading] = useState(false)
|
||||
const sharePopupRef = useRef<any>(null)
|
||||
|
||||
// 页面加载时获取数据
|
||||
useEffect(() => {
|
||||
fetchDetail()
|
||||
calcBgMainColors()
|
||||
}, [])
|
||||
|
||||
const fetchDetail = async () => {
|
||||
const res = await DetailService.getDetail(Number(id))
|
||||
if (res.code === 0) {
|
||||
console.log(res.data)
|
||||
setDetail(res.data)
|
||||
}
|
||||
}
|
||||
|
||||
const calcBgMainColors = async () => {
|
||||
const textcolors: string[] = []
|
||||
for (const index in images) {
|
||||
const { textColor } = await getTextColorOnImage(images[index])
|
||||
textcolors[index] = textColor
|
||||
}
|
||||
setColors(textcolors)
|
||||
}
|
||||
|
||||
function handleShare() {
|
||||
sharePopupRef.current.show()
|
||||
}
|
||||
|
||||
const openMap = () => {
|
||||
Taro.openLocation({
|
||||
latitude: detail?.latitude, // 纬度(必填)
|
||||
longitude: detail?.longitude, // 经度(必填)
|
||||
name: '上海体育场', // 位置名(可选)
|
||||
address: '上海市徐汇区肇嘉浜路128号', // 地址详情(可选)
|
||||
scale: 15, // 地图缩放级别(1-28)
|
||||
})
|
||||
}
|
||||
|
||||
const handleJoinGame = () => {
|
||||
Taro.navigateTo({
|
||||
url: `/pages/orderCheck/index?id=${id}`,
|
||||
})
|
||||
}
|
||||
|
||||
const tags = [{
|
||||
name: '🕙 急招',
|
||||
icon: '',
|
||||
}, {
|
||||
name: '🔥 本周热门',
|
||||
icon: '',
|
||||
}, {
|
||||
name: '🎉 新活动',
|
||||
icon: '',
|
||||
}, {
|
||||
name: '官方组织',
|
||||
icon: '',
|
||||
}]
|
||||
|
||||
const detailTags = ['室内', '硬地', '2号场', '有停车场', '有淋浴间', '有更衣室']
|
||||
|
||||
const { title, longitude, latitude } = detail || {}
|
||||
|
||||
const requirements = [{
|
||||
title: 'NTRP水平要求',
|
||||
desc: '2.0 - 4.5 之间',
|
||||
}, {
|
||||
title: '活动玩法',
|
||||
desc: '双打',
|
||||
}, {
|
||||
title: '人员构成',
|
||||
desc: '个人球局 · 组织者参与活动',
|
||||
}]
|
||||
|
||||
const participants = detail?.participants || []
|
||||
|
||||
const supplementalNotesTags = ['仅限男生', '装备自备', '其他']
|
||||
|
||||
const recommendGames = [
|
||||
{
|
||||
title: '黄浦日场对拉',
|
||||
time: '2025-08-25 9:00',
|
||||
timeLength: '2小时',
|
||||
venue: '上海体育场',
|
||||
veuneType: '室外',
|
||||
distance: '1.2km',
|
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||
applications: 10,
|
||||
checkedApplications: 3,
|
||||
levelRequirements: 'NTRP 3.5',
|
||||
playType: '双打',
|
||||
},
|
||||
{
|
||||
title: '黄浦夜场对拉',
|
||||
time: '2025-08-25 19:00',
|
||||
timeLength: '2小时',
|
||||
venue: '上海体育场',
|
||||
veuneType: '室外',
|
||||
distance: '1.2km',
|
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||
applications: 10,
|
||||
checkedApplications: 3,
|
||||
levelRequirements: 'NTRP 3.5',
|
||||
playType: '双打',
|
||||
},
|
||||
{
|
||||
title: '黄浦全天对拉',
|
||||
time: '2025-08-25 9:00',
|
||||
timeLength: '12小时',
|
||||
venue: '上海体育场',
|
||||
veuneType: '室外',
|
||||
distance: '1.2km',
|
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||
applications: 10,
|
||||
checkedApplications: 3,
|
||||
levelRequirements: 'NTRP 3.5',
|
||||
playType: '双打',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<View className='detail-page'>
|
||||
{/* custom navbar */}
|
||||
<view className="custom-navbar">
|
||||
<View className='detail-navigator'>
|
||||
<View className='detail-navigator-back' onClick={() => { Taro.navigateBack() }}>
|
||||
<Image className='detail-navigator-back-icon' src={img.ICON_ARROW_LEFT} />
|
||||
</View>
|
||||
<View className='detail-navigator-icon'>
|
||||
<Image className='detail-navigator-logo-icon' src={img.ICON_LOGO_GO} />
|
||||
</View>
|
||||
</View>
|
||||
</view>
|
||||
<View className='detail-page-bg' style={{ backgroundImage: `url(${images[current]})` }} />
|
||||
<View className='detail-page-bg-text' />
|
||||
{/* swiper */}
|
||||
<Swiper
|
||||
className='detail-swiper'
|
||||
indicatorDots={false}
|
||||
circular
|
||||
nextMargin="20px"
|
||||
onChange={(e) => { setCurrent(e.detail.current) }}
|
||||
>
|
||||
{images.map((imageUrl, index) => (
|
||||
<SwiperItem
|
||||
key={index}
|
||||
className='detail-swiper-item'
|
||||
>
|
||||
<Image
|
||||
src={imageUrl}
|
||||
mode="aspectFill"
|
||||
className='detail-swiper-item-image'
|
||||
style={{
|
||||
transform: index !== current ? 'scale(0.8) translateX(-12%)' : 'scale(0.95)', // 前后图缩小
|
||||
}}
|
||||
/>
|
||||
</SwiperItem>
|
||||
))}
|
||||
</Swiper>
|
||||
{/* content */}
|
||||
<View className='detail-page-content'>
|
||||
{/* avatar and tags */}
|
||||
<View className='detail-page-content-avatar-tags'>
|
||||
<View className='detail-page-content-avatar-tags-avatar'>
|
||||
{/* network image mock */}
|
||||
<Image className='detail-page-content-avatar-tags-avatar-image' src="https://img.yzcdn.cn/vant/cat.jpeg" />
|
||||
</View>
|
||||
<View className='detail-page-content-avatar-tags-tags'>
|
||||
{tags.map((tag, index) => (
|
||||
<View key={index} className='detail-page-content-avatar-tags-tags-tag'>
|
||||
{tag.icon && <Image src={tag.icon} />}
|
||||
<Text>{tag.name}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
{/* title */}
|
||||
<View className='detail-page-content-title'>
|
||||
<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'>
|
||||
<Map
|
||||
className='location-map-map'
|
||||
longitude={longitude}
|
||||
latitude={latitude}
|
||||
onError={() => {}}
|
||||
// hide business msg
|
||||
showLocation
|
||||
theme='dark'
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* detail */}
|
||||
<View className='detail-page-content-detail'>
|
||||
{/* venue detail title and venue ordered status */}
|
||||
<View className='venue-detail-title'>
|
||||
<Text>场馆详情</Text>
|
||||
<Text>·</Text>
|
||||
<Text>已订场</Text>
|
||||
<Popover
|
||||
title="场地预定截图"
|
||||
description={<View>
|
||||
<Image src="https://img.yzcdn.cn/vant/cat.jpeg" />
|
||||
</View>}
|
||||
location='top'
|
||||
visible={false}
|
||||
>
|
||||
<Image className='venue-detail-title-notice-icon' src={img.ICON_DETAIL_NOTICE} />
|
||||
</Popover>
|
||||
</View>
|
||||
{/* venue detail content */}
|
||||
<View className='venue-detail-content'>
|
||||
{/* venue detail tags */}
|
||||
<View className='venue-detail-content-tags'>
|
||||
{insertDotInTags(detailTags).map((tag, index) => (
|
||||
<View key={index} className='venue-detail-content-tags-tag'>
|
||||
<Text>{tag}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
{/* venue remarks */}
|
||||
<View className='venue-detail-content-remarks'>
|
||||
<Text>其他这是用户在场地补充描述里自己写的东西啦啦啦啦啦啦啦啦啦啦啦啦</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* gameplay requirements */}
|
||||
<View className='detail-page-content-gameplay-requirements'>
|
||||
{/* title */}
|
||||
<View className="gameplay-requirements-title">
|
||||
<Text>玩法要求</Text>
|
||||
</View>
|
||||
{/* requirements */}
|
||||
<View className='gameplay-requirements'>
|
||||
{requirements.map((item, index) => (
|
||||
<View key={index} className='gameplay-requirements-item'>
|
||||
<Text className='gameplay-requirements-item-title'>{item.title}</Text>
|
||||
<Text className='gameplay-requirements-item-desc'>{item.desc}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
{/* participants */}
|
||||
<View className='detail-page-content-participants'>
|
||||
<View className='participants-title'>
|
||||
<Text>参与者</Text>
|
||||
<Text>·</Text>
|
||||
<Text>剩余空位 3</Text>
|
||||
</View>
|
||||
<View className='participants-list'>
|
||||
{/* application */}
|
||||
<View className='participants-list-application' onClick={() => { Taro.showToast({ title: 'To be continued', icon: 'none' }) }}>
|
||||
<Image className='participants-list-application-icon' src={img.ICON_DETAIL_APPLICATION_ADD} />
|
||||
<Text className='participants-list-application-text'>申请加入</Text>
|
||||
</View>
|
||||
{/* participants list */}
|
||||
<ScrollView className='participants-list-scroll' scrollX>
|
||||
<View className='participants-list-scroll-content' style={{ width: `${participants.length * 103 + (participants.length - 1) * 8}px` }}>
|
||||
{participants.map((participant) => (
|
||||
<View key={participant.id} className='participants-list-item'>
|
||||
{/* <Avatar className='participants-list-item-avatar' src={participant.user.avatar_url} /> */}
|
||||
{/* network image mock random */}
|
||||
<Avatar className='participants-list-item-avatar' src={`https://picsum.photos/800/600?random=${participant.id}`} />
|
||||
<Text className='participants-list-item-name'>{participant.user.nickname || '未知'}</Text>
|
||||
<Text className='participants-list-item-level'>{participant.level || '未知'}</Text>
|
||||
<Text className='participants-list-item-role'>{participant.role || '参与者'}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
{/* supplemental notes */}
|
||||
<View className='detail-page-content-supplemental-notes'>
|
||||
<View className='supplemental-notes-title'>
|
||||
<Text>补充说明</Text>
|
||||
</View>
|
||||
<View className='supplemental-notes-content'>
|
||||
{/* supplemental notes tags */}
|
||||
<View className='supplemental-notes-content-tags'>
|
||||
{insertDotInTags(supplementalNotesTags).map((tag, index) => (
|
||||
<View key={index} className='supplemental-notes-content-tags-tag'>
|
||||
<Text>{tag}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
{/* supplemental notes content */}
|
||||
<View className='supplemental-notes-content-text'>
|
||||
<Text>其他这是用户在补充说明里自己写的东西啦啦啦啦啦啦啦啦啦啦啦啦其他这是用户在补充说明里自己写的东西啦啦啦啦啦啦啦啦啦啦啦啦</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* organizer and recommend games by organizer */}
|
||||
<View className='detail-page-content-organizer-recommend-games'>
|
||||
{/* orgnizer title */}
|
||||
<View className='organizer-title'>
|
||||
<Text>组织者</Text>
|
||||
</View>
|
||||
{/* organizer avatar and name */}
|
||||
<View className='organizer-avatar-name'>
|
||||
<Avatar className='organizer-avatar-name-avatar' src="https://img.yzcdn.cn/vant/cat.jpeg" />
|
||||
<View className='organizer-avatar-name-message'>
|
||||
<Text className='organizer-avatar-name-message-name'>Light</Text>
|
||||
<View className='organizer-avatar-name-message-stats'>
|
||||
<Text>已组织 8 次</Text>
|
||||
<View className='organizer-avatar-name-message-stats-separator' />
|
||||
<Text>NTRP 3.5</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className="organizer-actions">
|
||||
<View className="organizer-actions-follow">
|
||||
<Image className='organizer-actions-follow-icon' src={img.ICON_DETAIL_APPLICATION_ADD} />
|
||||
<Text className='organizer-actions-follow-text'>关注</Text>
|
||||
</View>
|
||||
<View className="organizer-actions-comment">
|
||||
<Image className='organizer-actions-comment-icon' src={img.ICON_DETAIL_COMMENT} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* recommend games by organizer */}
|
||||
<View className='organizer-recommend-games'>
|
||||
<View className='organizer-recommend-games-title'>
|
||||
<Text>TA的更多活动</Text>
|
||||
<Image className='organizer-recommend-games-title-arrow' src={img.ICON_DETAIL_ARROW_RIGHT} />
|
||||
</View>
|
||||
<ScrollView className='recommend-games-list' scrollX>
|
||||
<View className='recommend-games-list-content'>
|
||||
{recommendGames.map((game, index) => (
|
||||
<View key={index} className='recommend-games-list-item'>
|
||||
{/* game title */}
|
||||
<View className='recommend-games-list-item-title'>
|
||||
<Text>{game.title}</Text>
|
||||
<Image className='recommend-games-list-item-title-arrow' src={img.ICON_DETAIL_ARROW_RIGHT} />
|
||||
</View>
|
||||
{/* game time and range */}
|
||||
<View className='recommend-games-list-item-time-range'>
|
||||
<Text>{game.time}</Text>
|
||||
<Text>{game.timeLength}</Text>
|
||||
</View>
|
||||
{/* game location、vunue、distance */}
|
||||
<View className='recommend-games-list-item-location-venue-distance'>
|
||||
<Text>{game.venue}</Text>
|
||||
<Text>·</Text>
|
||||
<Text>{game.veuneType}</Text>
|
||||
<Text>·</Text>
|
||||
<Text>{game.distance}</Text>
|
||||
</View>
|
||||
{/* organizer avatar、applications、level requirements、play type */}
|
||||
<View className='recommend-games-list-item-addon'>
|
||||
<Avatar className='recommend-games-list-item-addon-avatar' src={game.avatar} />
|
||||
<View className='recommend-games-list-item-addon-message'>
|
||||
<View className='recommend-games-list-item-addon-message-applications'>
|
||||
<Text>报名人数 {game.checkedApplications}/{game.applications}</Text>
|
||||
</View>
|
||||
<View className='recommend-games-list-item-addon-message-level-requirements'>
|
||||
<Text>{game.levelRequirements}</Text>
|
||||
</View>
|
||||
<View className='recommend-games-list-item-addon-message-play-type'>
|
||||
<Text>{game.playType}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
{/* sticky bottom action bar */}
|
||||
<View className="sticky-bottom-bar">
|
||||
<View className="sticky-bottom-bar-share-and-comment">
|
||||
<View className='sticky-bottom-bar-share' onClick={handleShare}>
|
||||
<Image className='sticky-bottom-bar-share-icon' src={img.ICON_DETAIL_SHARE} />
|
||||
<Text className='sticky-bottom-bar-share-text'>分享</Text>
|
||||
</View>
|
||||
<View className='sticky-bottom-bar-share-and-comment-separator' />
|
||||
<View className='sticky-bottom-bar-comment' onClick={() => { Taro.showToast({ title: 'To be continued', icon: 'none' }) }}>
|
||||
<Image className='sticky-bottom-bar-comment-icon' src={img.ICON_DETAIL_COMMENT_DARK} />
|
||||
<Text className='sticky-bottom-bar-comment-text'>32</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className="sticky-bottom-bar-join-game" onClick={handleJoinGame}>
|
||||
<Text>🎾</Text>
|
||||
<Text>立即加入</Text>
|
||||
<View className='game-price'>
|
||||
<Text>¥ 65</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* share popup */}
|
||||
<SharePopup ref={sharePopupRef} id={id} from={from} />
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default Index
|
||||
@@ -1,3 +0,0 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '首页'
|
||||
})
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
.index-page {
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 16px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.user-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.user-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
|
||||
.username {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.user-level {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.join-date {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
:global {
|
||||
.nut-cell {
|
||||
background: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 8px;
|
||||
border: none;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.nut-cell__title {
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.nut-cell__value {
|
||||
font-weight: bold;
|
||||
color: #007bff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-section {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
.custom-button {
|
||||
border-radius: 12px;
|
||||
font-weight: 500;
|
||||
height: 48px;
|
||||
border: none;
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
|
||||
&.primary-btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
&.success-btn {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
}
|
||||
|
||||
&.warning-btn {
|
||||
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
// 保留 NutUI 按钮样式(备用)
|
||||
:global {
|
||||
.nut-button {
|
||||
border-radius: 12px;
|
||||
font-weight: 500;
|
||||
height: 48px;
|
||||
border: none;
|
||||
|
||||
&--primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
&--success {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-section {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
|
||||
.loading-text {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
:global {
|
||||
.nut-progress {
|
||||
.nut-progress-outer {
|
||||
background: #f0f0f0;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.nut-progress-inner {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips-section {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.tips-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tips-content {
|
||||
.tip-item {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.index-page {
|
||||
padding: 16px;
|
||||
|
||||
.page-header {
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-card,
|
||||
.stats-section,
|
||||
.action-section,
|
||||
.loading-section,
|
||||
.tips-section {
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,81 @@
|
||||
import { Popup } from "@nutui/nutui-react-taro";
|
||||
import Range from "../../components/Range";
|
||||
import Bubble, { BubbleOption } from "../../components/Bubble";
|
||||
import Bubble from "../../components/Bubble";
|
||||
import styles from "./filterPopup.module.scss";
|
||||
import TitleComponent from "src/components/Title";
|
||||
import TitleComponent from "@/components/Title";
|
||||
import { Button } from "@nutui/nutui-react-taro";
|
||||
import { Image } from "@tarojs/components";
|
||||
import img from "../../config/images";
|
||||
import { useListStore } from "src/store/listStore";
|
||||
import { FilterPopupProps } from "../../../types/list/types";
|
||||
// 场地
|
||||
import CourtType from "@/components/CourtType";
|
||||
// 玩法
|
||||
import GamePlayType from "@/components/GamePlayType";
|
||||
import { useDictionaryActions } from "@/store/dictionaryStore";
|
||||
import { useMemo } from "react";
|
||||
|
||||
const timeOptions: BubbleOption[] = [
|
||||
{ id: 1, label: "晨间 6:00-10:00", value: "morning" },
|
||||
{ id: 2, label: "上午 10:00-12:00", value: "forenoon" },
|
||||
{ id: 3, label: "中午 12:00-14:00", value: "noon" },
|
||||
{ id: 4, label: "下午 14:00-18:00", value: "afternoon" },
|
||||
{ id: 5, label: "晚上 18:00-22:00", value: "evening" },
|
||||
{ id: 6, label: "夜间 22:00-24:00", value: "night" },
|
||||
];
|
||||
const FilterPopup = (props: FilterPopupProps) => {
|
||||
const {
|
||||
loading,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
onChange,
|
||||
filterOptions,
|
||||
onClear,
|
||||
visible,
|
||||
onClose,
|
||||
statusNavbarHeigh,
|
||||
} = props;
|
||||
|
||||
const locationOptions: BubbleOption[] = [
|
||||
{ id: 1, label: "室内", value: "1" },
|
||||
{ id: 2, label: "室外", value: "2" },
|
||||
{ id: 3, label: "半室外", value: "3" },
|
||||
];
|
||||
const store = useListStore() || {};
|
||||
const { getDictionaryValue } = useDictionaryActions() || {};
|
||||
const { timeBubbleData } = store;
|
||||
|
||||
const handleOptions = (dictionaryValue: []) => {
|
||||
return dictionaryValue?.map((item) => ({ label: item, value: item })) || [];
|
||||
};
|
||||
|
||||
const courtType = getDictionaryValue("court_type") || [];
|
||||
const locationOptions = useMemo(() => {
|
||||
return courtType ? handleOptions(courtType) : [];
|
||||
}, [courtType]);
|
||||
|
||||
const gamePlay = getDictionaryValue("game_play") || [];
|
||||
const gamePlayOptions = useMemo(() => {
|
||||
return gamePlay ? handleOptions(gamePlay) : [];
|
||||
}, [gamePlay]);
|
||||
|
||||
const handleFilterChange = (name, value) => {
|
||||
onChange({ [name]: value });
|
||||
};
|
||||
|
||||
const handleClearFilter = () => {
|
||||
onClear();
|
||||
onCancel();
|
||||
};
|
||||
|
||||
const FilterPopup = () => {
|
||||
return (
|
||||
<>
|
||||
<Popup
|
||||
visible={true}
|
||||
destroyOnClose
|
||||
position="top"
|
||||
round
|
||||
closeOnOverlayClick={false}
|
||||
onClose={() => {
|
||||
// setShowTop(false)
|
||||
}}
|
||||
visible={visible}
|
||||
onClose={onClose}
|
||||
style={{ marginTop: statusNavbarHeigh + "px" }}
|
||||
overlayStyle={{ marginTop: statusNavbarHeigh + "px" }}
|
||||
>
|
||||
<div className={styles.filterPopupWrapper}>
|
||||
{/* 时间气泡选项 */}
|
||||
<Bubble
|
||||
options={timeOptions}
|
||||
value={(value) => {}}
|
||||
onChange={(value) => {}}
|
||||
options={timeBubbleData}
|
||||
value={filterOptions?.time}
|
||||
onChange={handleFilterChange}
|
||||
layout="grid"
|
||||
size="small"
|
||||
columns={3}
|
||||
name="time"
|
||||
/>
|
||||
|
||||
{/* 范围选择 */}
|
||||
@@ -49,19 +84,58 @@ const FilterPopup = () => {
|
||||
max={5.0}
|
||||
step={0.5}
|
||||
className={styles.filterPopupRange}
|
||||
onChange={handleFilterChange}
|
||||
value={filterOptions?.ntrp}
|
||||
name="ntrp"
|
||||
/>
|
||||
|
||||
{/* 场次气泡选项 */}
|
||||
<div>
|
||||
<TitleComponent title="场地类型" />
|
||||
{/* <div>
|
||||
<TitleComponent
|
||||
title="场地类型"
|
||||
icon={<Image src={img.ICON_SITE} />}
|
||||
/>
|
||||
<Bubble
|
||||
options={locationOptions}
|
||||
value={(value) => {}}
|
||||
onChange={(value) => {}}
|
||||
value={filterOptions?.site}
|
||||
onChange={handleFilterChange}
|
||||
layout="grid"
|
||||
size="small"
|
||||
columns={3}
|
||||
name="site"
|
||||
/>
|
||||
</div> */}
|
||||
{/* CourtType */}
|
||||
<CourtType
|
||||
onChange={handleFilterChange}
|
||||
name="court_type"
|
||||
options={locationOptions}
|
||||
value={filterOptions?.site}
|
||||
/>
|
||||
{/* 玩法 */}
|
||||
<GamePlayType
|
||||
onChange={handleFilterChange}
|
||||
name="game_play"
|
||||
options={gamePlayOptions}
|
||||
value={filterOptions?.game_play}
|
||||
/>
|
||||
{/* 按钮 */}
|
||||
<div className={styles.filterPopupBtnWrapper}>
|
||||
<Button
|
||||
className={styles.btn}
|
||||
type="default"
|
||||
onClick={handleClearFilter}
|
||||
>
|
||||
清空全部
|
||||
</Button>
|
||||
<Button
|
||||
className={`${styles.btn} ${styles.confirm}`}
|
||||
type="default"
|
||||
loading={loading}
|
||||
onClick={onConfirm}
|
||||
>
|
||||
显示 332 场球局
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
|
||||
@@ -1,8 +1,35 @@
|
||||
.filterPopupWrapper {
|
||||
position: relative;
|
||||
$m18: 18px;
|
||||
padding: $m18;
|
||||
|
||||
.filterPopupRange {
|
||||
margin-top: $m18;
|
||||
margin-bottom: $m18;
|
||||
}
|
||||
|
||||
.filterPopupBtnWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
background-color: #ffffff;
|
||||
padding: 8px 0;
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
--nutui-button-border-width: 0.5px;
|
||||
--nutui-button-default-border-color: #0000000F;
|
||||
--nutui-button-border-radius: 16px;
|
||||
--nutui-button-default-height: 44px;
|
||||
--nutui-button-default-color: #000000;
|
||||
.confirm {
|
||||
--nutui-button-default-color: #ffffff;
|
||||
--nutui-button-default-background-color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '',
|
||||
enablePullDownRefresh: true,
|
||||
backgroundTextStyle: 'dark'
|
||||
backgroundTextStyle: 'dark',
|
||||
navigationStyle: 'custom',
|
||||
})
|
||||
|
||||
26
src/pages/list/index.module.scss
Normal file
26
src/pages/list/index.module.scss
Normal file
@@ -0,0 +1,26 @@
|
||||
.listPage {
|
||||
background-color: #fafafa;
|
||||
|
||||
.listTopSearchWrapper {
|
||||
padding: 0 15px;
|
||||
position: sticky;
|
||||
background: #fefefe;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.isScroll {
|
||||
border-bottom: 0.5px solid #0000000F;
|
||||
}
|
||||
|
||||
.listTopFilterWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.menuFilter {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,51 @@
|
||||
import ListItem from "../../components/ListItem";
|
||||
import List from "../../components/List";
|
||||
import Bubble from "../../components/Bubble/example";
|
||||
import Range from "../../components/Range/example";
|
||||
import Menu from "../../components/Menu/example";
|
||||
import CityFilter from "../../components/CityFilter/example";
|
||||
import Menu from "../../components/Menu";
|
||||
import CityFilter from "../../components/CityFilter";
|
||||
import SearchBar from "../../components/SearchBar";
|
||||
import FilterPopup from "./FilterPopup";
|
||||
import styles from "./index.module.scss";
|
||||
import { useEffect } from "react";
|
||||
import Taro from "@tarojs/taro";
|
||||
import {
|
||||
useTennisMatches,
|
||||
useTennisLoading,
|
||||
useTennisError,
|
||||
useTennisLastRefresh,
|
||||
useTennisActions,
|
||||
} from "../../store/listStore";
|
||||
import "./index.scss";
|
||||
import Taro, { usePageScroll, useReachBottom } from "@tarojs/taro";
|
||||
import { useListStore } from "@/store/listStore";
|
||||
import { useGlobalState } from "@/store/global";
|
||||
import { View } from "@tarojs/components";
|
||||
import CustomerNavBar from "@/components/CustomNavbar";
|
||||
import GuideBar from "@/components/GuideBar";
|
||||
import ListContainer from "@/container/listContainer";
|
||||
|
||||
const ListPage = () => {
|
||||
// 从 store 获取数据和方法
|
||||
const matches = useTennisMatches();
|
||||
const loading = useTennisLoading();
|
||||
const error = useTennisError();
|
||||
const lastRefreshTime = useTennisLastRefresh();
|
||||
const { fetchMatches, refreshMatches, clearError } = useTennisActions();
|
||||
const store = useListStore() || {};
|
||||
|
||||
const { statusNavbarHeightInfo } = useGlobalState() || {};
|
||||
const {
|
||||
isShowFilterPopup,
|
||||
error,
|
||||
matches,
|
||||
loading,
|
||||
fetchMatches,
|
||||
refreshMatches,
|
||||
updateState,
|
||||
filterCount,
|
||||
updateFilterOptions, // 更新筛选条件
|
||||
filterOptions,
|
||||
clearFilterOptions,
|
||||
distanceData,
|
||||
quickFilterData,
|
||||
distanceQuickFilter,
|
||||
isScrollTop,
|
||||
} = store;
|
||||
|
||||
usePageScroll((res) => {
|
||||
if (res?.scrollTop > 0 && !isScrollTop) {
|
||||
updateState({ isScrollTop: true });
|
||||
}
|
||||
});
|
||||
|
||||
useReachBottom(() => {
|
||||
console.log("触底了");
|
||||
// 调用 store 的加载更多方法
|
||||
// loadMoreMatches();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// 页面加载时获取数据
|
||||
@@ -59,147 +81,97 @@ const ListPage = () => {
|
||||
});
|
||||
});
|
||||
|
||||
// 错误处理
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
Taro.showToast({
|
||||
title: error,
|
||||
icon: "error",
|
||||
duration: 2000,
|
||||
});
|
||||
// 3秒后自动清除错误
|
||||
setTimeout(() => {
|
||||
clearError();
|
||||
}, 3000);
|
||||
}
|
||||
}, [error, clearError]);
|
||||
|
||||
// 格式化时间显示
|
||||
const formatRefreshTime = (timeString: string | null) => {
|
||||
if (!timeString) return "";
|
||||
const date = new Date(timeString);
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
|
||||
if (minutes < 1) return "刚刚";
|
||||
if (minutes < 60) return `${minutes}分钟前`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 24) return `${hours}小时前`;
|
||||
return date.toLocaleDateString();
|
||||
const toggleShowPopup = () => {
|
||||
updateState({ isShowFilterPopup: !isShowFilterPopup });
|
||||
};
|
||||
|
||||
// 加载状态显示
|
||||
if (loading && matches.length === 0) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "200px",
|
||||
fontSize: "14px",
|
||||
color: "#999",
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: "10px" }}>加载中...</div>
|
||||
<div style={{ fontSize: "12px", color: "#ccc" }}>
|
||||
正在获取网球比赛数据
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @description 更新筛选条件
|
||||
* @param {Record<string, any>} params 筛选项
|
||||
*/
|
||||
const handleUpdateFilterOptions = (params: Record<string, any>) => {
|
||||
updateFilterOptions(params);
|
||||
};
|
||||
|
||||
// 错误状态显示
|
||||
if (error && matches.length === 0) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "200px",
|
||||
fontSize: "14px",
|
||||
color: "#999",
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: "10px" }}>加载失败</div>
|
||||
<div style={{ marginBottom: "15px", fontSize: "12px", color: "#ccc" }}>
|
||||
{error}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => fetchMatches()}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
fontSize: "12px",
|
||||
color: "#fff",
|
||||
backgroundColor: "#007aff",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const handleSearchChange = () => {};
|
||||
|
||||
// 距离筛选
|
||||
const handleDistanceOrQuickChange = (name, value) => {
|
||||
updateState({
|
||||
distanceQuickFilter: {
|
||||
...distanceQuickFilter,
|
||||
[name]: value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SearchBar />
|
||||
{/* 综合筛选 */}
|
||||
<div>
|
||||
<FilterPopup />
|
||||
</div>
|
||||
{/* 筛选 */}
|
||||
<div>
|
||||
{/* 全城筛选 */}
|
||||
<CityFilter />
|
||||
{/* 智能排序 */}
|
||||
<Menu />
|
||||
</div>
|
||||
<>
|
||||
<CustomerNavBar />
|
||||
|
||||
|
||||
|
||||
{/* 列表内容 */}
|
||||
<List>
|
||||
{matches.map((match, index) => (
|
||||
<ListItem key={match.id || index} {...match} />
|
||||
))}
|
||||
</List>
|
||||
|
||||
{/* 空状态 */}
|
||||
{!loading && matches.length === 0 && (
|
||||
<div
|
||||
<View className={styles.listPage}>
|
||||
<View
|
||||
className={`${styles.listTopSearchWrapper} ${
|
||||
isScrollTop ? styles.isScroll : ""
|
||||
}`}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "200px",
|
||||
fontSize: "14px",
|
||||
color: "#999",
|
||||
top: statusNavbarHeightInfo?.totalHeight,
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: "10px" }}>暂无比赛数据</div>
|
||||
<button
|
||||
onClick={() => fetchMatches()}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
fontSize: "12px",
|
||||
color: "#fff",
|
||||
backgroundColor: "#007aff",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
重新加载
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<SearchBar
|
||||
handleFilterIcon={toggleShowPopup}
|
||||
isSelect={filterCount > 0}
|
||||
filterCount={filterCount}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
{/* 综合筛选 */}
|
||||
{isShowFilterPopup && (
|
||||
<div>
|
||||
<FilterPopup
|
||||
loading={loading}
|
||||
onCancel={toggleShowPopup}
|
||||
onConfirm={toggleShowPopup}
|
||||
onChange={handleUpdateFilterOptions}
|
||||
filterOptions={filterOptions}
|
||||
onClear={clearFilterOptions}
|
||||
visible={isShowFilterPopup}
|
||||
onClose={toggleShowPopup}
|
||||
statusNavbarHeigh={statusNavbarHeightInfo?.totalHeight}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* 筛选 */}
|
||||
<div className={styles.listTopFilterWrapper}>
|
||||
{/* 全城筛选 */}
|
||||
<CityFilter
|
||||
options={distanceData}
|
||||
value={distanceQuickFilter?.distance}
|
||||
wrapperClassName={styles.menuFilter}
|
||||
onChange={handleDistanceOrQuickChange}
|
||||
name="distance"
|
||||
/>
|
||||
{/* 智能排序 */}
|
||||
<Menu
|
||||
options={quickFilterData}
|
||||
value={distanceQuickFilter?.quick}
|
||||
onChange={handleDistanceOrQuickChange}
|
||||
wrapperClassName={styles.menuFilter}
|
||||
name="quick"
|
||||
/>
|
||||
</div>
|
||||
</View>
|
||||
|
||||
{/* 列表内容 */}
|
||||
<ListContainer
|
||||
data={matches}
|
||||
loading={loading}
|
||||
error={error}
|
||||
reload={refreshMatches}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<GuideBar currentPage="list" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #07C160;
|
||||
color: #000000;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
@@ -10,7 +10,7 @@ const LoginPage: React.FC = () => {
|
||||
const [show_terms_layer, set_show_terms_layer] = useState(false);
|
||||
|
||||
// 微信授权登录
|
||||
const handle_wechat_login = async () => {
|
||||
const handle_wechat_login = async (e: any) => {
|
||||
if (!agree_terms) {
|
||||
set_show_terms_layer(true);
|
||||
Taro.showToast({
|
||||
@@ -21,9 +21,20 @@ const LoginPage: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否获取到手机号
|
||||
if (!e.detail || !e.detail.code) {
|
||||
Taro.showToast({
|
||||
title: '获取手机号失败,请重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
set_is_loading(true);
|
||||
try {
|
||||
const response = await wechat_auth_login();
|
||||
// 传递手机号code给登录服务
|
||||
const response = await wechat_auth_login(e.detail.code);
|
||||
if (response.success) {
|
||||
save_login_state(response.token!, response.user_info!);
|
||||
|
||||
@@ -99,7 +110,7 @@ const LoginPage: React.FC = () => {
|
||||
<View className="background_image">
|
||||
<Image
|
||||
className="bg_img"
|
||||
src={require('../../../static/login/login_bg.png')}
|
||||
src="http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/2e00dea1-8723-42fe-ae42-84fe38e9ac3f.png"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<View className="bg_overlay"></View>
|
||||
@@ -123,7 +134,8 @@ const LoginPage: React.FC = () => {
|
||||
{/* 微信快捷登录 */}
|
||||
<Button
|
||||
className={`login_button wechat_button ${is_loading ? 'loading' : ''}`}
|
||||
onClick={handle_wechat_login}
|
||||
openType="getPhoneNumber"
|
||||
onGetPhoneNumber={handle_wechat_login}
|
||||
disabled={is_loading}
|
||||
>
|
||||
<View className="wechat_icon">
|
||||
@@ -217,7 +229,7 @@ const LoginPage: React.FC = () => {
|
||||
{agree_terms ? '已同意' : '同意并继续'}
|
||||
</Button>
|
||||
|
||||
|
||||
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
flex: 1;
|
||||
padding: 0px 24px ;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
overflow-y: auto;
|
||||
@@ -181,6 +181,7 @@
|
||||
line-height: 1.43em;
|
||||
color: #000000;
|
||||
margin-bottom: 24px;
|
||||
|
||||
}
|
||||
|
||||
// 条款详细内容
|
||||
@@ -192,6 +193,14 @@
|
||||
color: #000000;
|
||||
margin-bottom: 40px;
|
||||
white-space: pre-line;
|
||||
padding: 0px 24px ;
|
||||
|
||||
.terms_first_line,
|
||||
span.terms_first_line {
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
// 底部按钮
|
||||
|
||||
@@ -24,7 +24,7 @@ const TermsPage: React.FC = () => {
|
||||
case 'terms':
|
||||
setPageTitle('条款和条件');
|
||||
setTermsTitle('《开场的条款和条件》');
|
||||
setTermsContent(`欢迎使用本平台(以下简称"本平台")发布与参与网球活动。为保障您的权益,请您务必仔细阅读并理解以下服务条款。
|
||||
setTermsContent(`<span class="terms_first_line">欢迎使用本平台(以下简称"本平台")发布与参与网球活动。为保障您的权益,请您务必仔细阅读并理解以下服务条款。</span>
|
||||
|
||||
一、服务内容
|
||||
1. 本平台为用户提供活动发布、报名、聊天室沟通、活动提醒等服务。
|
||||
@@ -70,7 +70,7 @@ const TermsPage: React.FC = () => {
|
||||
case 'binding':
|
||||
setPageTitle('微信号绑定协议');
|
||||
setTermsTitle('《开场与微信号绑定协议》');
|
||||
setTermsContent(`欢迎使用本平台(以下简称"本平台")的微信绑定服务。为保障您的权益,请您务必仔细阅读并理解以下协议内容。
|
||||
setTermsContent(`<span class="terms_first_line">欢迎使用本平台(以下简称"本平台")的微信绑定服务。为保障您的权益,请您务必仔细阅读并理解以下协议内容。</span>
|
||||
|
||||
一、绑定服务说明
|
||||
1. 本平台提供微信账号绑定服务,用户可通过微信快捷登录方式使用平台功能。
|
||||
@@ -115,7 +115,7 @@ const TermsPage: React.FC = () => {
|
||||
case 'privacy':
|
||||
setPageTitle('隐私权政策');
|
||||
setTermsTitle('《隐私权政策》');
|
||||
setTermsContent(`本平台(以下简称"我们")非常重视用户的隐私保护,本隐私权政策说明了我们如何收集、使用、存储和保护您的个人信息。
|
||||
setTermsContent(`<span class="terms_first_line">本平台(以下简称"我们")非常重视用户的隐私保护,本隐私权政策说明了我们如何收集、使用、存储和保护您的个人信息。</span>
|
||||
|
||||
一、信息收集
|
||||
1. 注册信息:包括手机号码、微信账号、昵称、头像等基本信息。
|
||||
@@ -190,13 +190,14 @@ const TermsPage: React.FC = () => {
|
||||
</View>
|
||||
|
||||
{/* 条款详细内容 */}
|
||||
<View className="terms_content">
|
||||
{termsContent}
|
||||
</View>
|
||||
<View
|
||||
className="terms_content"
|
||||
dangerouslySetInnerHTML={{ __html: termsContent }}
|
||||
/>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
||||
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
5
src/pages/message/index.config.ts
Normal file
5
src/pages/message/index.config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '消息',
|
||||
// navigationBarBackgroundColor: '#FAFAFA',
|
||||
navigationStyle: 'custom',
|
||||
})
|
||||
80
src/pages/message/index.scss
Normal file
80
src/pages/message/index.scss
Normal file
@@ -0,0 +1,80 @@
|
||||
@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;
|
||||
|
||||
.custom-navbar {
|
||||
height: 56px; /* 通常与原生导航栏高度一致 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
// background-color: #fff;
|
||||
color: #000;
|
||||
padding-top: 44px; /* 适配状态栏 */
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
|
||||
.message-navigator {
|
||||
position: relative;
|
||||
left: 15px;
|
||||
top: -2px;
|
||||
width: 80px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
|
||||
.message-navigator-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.message-navigator-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-content {
|
||||
|
||||
.message-content-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 15px;
|
||||
box-sizing: border-box;
|
||||
gap: 12px;
|
||||
|
||||
.message-item {
|
||||
padding: 10px;
|
||||
// border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
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;
|
||||
}
|
||||
|
||||
.message-item-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.message-item-content {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/pages/message/index.tsx
Normal file
43
src/pages/message/index.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react'
|
||||
import { View, Text, ScrollView } 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 './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(''),
|
||||
}))
|
||||
|
||||
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>
|
||||
</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>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
<GuideBar currentPage='message' />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default Personal
|
||||
4
src/pages/orderCheck/index.config.ts
Normal file
4
src/pages/orderCheck/index.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '订单确认',
|
||||
navigationBarBackgroundColor: '#FAFAFA'
|
||||
})
|
||||
1
src/pages/orderCheck/index.scss
Normal file
1
src/pages/orderCheck/index.scss
Normal file
@@ -0,0 +1 @@
|
||||
@use '~@/scss/images.scss' as img;
|
||||
12
src/pages/orderCheck/index.tsx
Normal file
12
src/pages/orderCheck/index.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import { View, Text } from '@tarojs/components'
|
||||
|
||||
const OrderCheck = () => {
|
||||
return (
|
||||
<View>
|
||||
<Text>OrderCheck</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrderCheck
|
||||
5
src/pages/personal/index.config.ts
Normal file
5
src/pages/personal/index.config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '个人中心',
|
||||
// navigationBarBackgroundColor: '#FAFAFA',
|
||||
navigationStyle: 'custom',
|
||||
})
|
||||
35
src/pages/personal/index.scss
Normal file
35
src/pages/personal/index.scss
Normal file
@@ -0,0 +1,35 @@
|
||||
@use '~@/scss/images.scss' as img;
|
||||
|
||||
$--Backgrounds-Primary: '#fff';
|
||||
|
||||
.personal-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;
|
||||
|
||||
.personal-navigator {
|
||||
position: fixed;
|
||||
left: 10px;
|
||||
top: 54px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
.personal-navigator-back {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.personal-content {
|
||||
width: 100%;
|
||||
height: calc(100vh - 300px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
31
src/pages/personal/index.tsx
Normal file
31
src/pages/personal/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react'
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import Taro, { useRouter } from '@tarojs/taro'
|
||||
import GuideBar from '@/components/GuideBar'
|
||||
import img from '@/config/images'
|
||||
import './index.scss'
|
||||
|
||||
const Personal = () => {
|
||||
const { params } = useRouter()
|
||||
const { id } = params
|
||||
|
||||
const handleBack = () => {
|
||||
Taro.navigateBack()
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='personal-container'>
|
||||
{id && (
|
||||
<View className='personal-navigator' onClick={handleBack}>
|
||||
<Image className='personal-navigator-back' src={img.ICON_NAVIGATOR_BACK} />
|
||||
</View>
|
||||
)}
|
||||
<View className='personal-content'>
|
||||
<Text>Personal</Text>
|
||||
</View>
|
||||
<GuideBar currentPage='personal' />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default Personal
|
||||
@@ -3,7 +3,8 @@ import Taro from '@tarojs/taro'
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import images from '@/config/images'
|
||||
import TextareaTag from '@/components/TextareaTag'
|
||||
import CoverImageUpload, { type CoverImage } from '@/components/ImageUpload'
|
||||
// import CoverImageUpload, { type CoverImage } from '@/components/ImageUpload'
|
||||
import UploadCover, { type CoverImageValue } from '@/components/UploadCover'
|
||||
import { useDictionaryActions } from '@/store/dictionaryStore'
|
||||
import './StadiumDetail.scss'
|
||||
|
||||
@@ -18,7 +19,7 @@ export interface Stadium {
|
||||
court_surface?: string
|
||||
description?: string
|
||||
description_tag?: string[]
|
||||
venue_image_list?: CoverImage[]
|
||||
venue_image_list?: CoverImageValue[]
|
||||
}
|
||||
|
||||
interface StadiumDetailProps {
|
||||
@@ -103,7 +104,7 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
court_type: court_type[0] || '',
|
||||
court_surface: court_surface[0] || '',
|
||||
additionalInfo: '',
|
||||
venue_image_list: [] as CoverImage[],
|
||||
venue_image_list: [] as CoverImageValue[],
|
||||
description:{
|
||||
description: '',
|
||||
description_tag: []
|
||||
@@ -216,9 +217,21 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
if (item.type === 'image') {
|
||||
return (
|
||||
<SectionContainer key={item.label} title={item.label} prop={item.prop}>
|
||||
<CoverImageUpload
|
||||
images={formData[item.prop]}
|
||||
onChange={(images) => updateFormData(item.prop, images)}
|
||||
<UploadCover
|
||||
value={formData[item.prop]}
|
||||
onChange={(value) => {
|
||||
console.log(value, 'value')
|
||||
if (value instanceof Function) {
|
||||
const newValue = value(formData[item.prop])
|
||||
console.log(newValue, 'newValue')
|
||||
updateFormData(item.prop, newValue)
|
||||
} else {
|
||||
updateFormData(item.prop, value)
|
||||
}
|
||||
}}
|
||||
maxCount={9}
|
||||
source={['album', 'history', 'preset']}
|
||||
align='left'
|
||||
/>
|
||||
</SectionContainer>
|
||||
)
|
||||
@@ -230,4 +243,4 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
)
|
||||
})
|
||||
|
||||
export default StadiumDetail
|
||||
export default StadiumDetail
|
||||
@@ -8,40 +8,40 @@ import PublishForm from './publishForm'
|
||||
import { publishBallFormSchema } from '../../config/formSchema/publishBallFormSchema';
|
||||
import { PublishBallFormData } from '../../../types/publishBall';
|
||||
import PublishService from '@/services/publishService';
|
||||
import { getNextHourTime, getEndTime } from '@/utils/timeUtils';
|
||||
import { getNextHourTime, getEndTime, delay } from '@/utils';
|
||||
import images from '@/config/images'
|
||||
import styles from './index.module.scss'
|
||||
|
||||
const defaultFormData: PublishBallFormData = {
|
||||
title: '',
|
||||
image_list: ['https://static-o.oss-cn-shenzhen.aliyuncs.com/images/tpbj/tpss10.jpg'],
|
||||
title: '',
|
||||
image_list: ['https://static-o.oss-cn-shenzhen.aliyuncs.com/images/tpbj/tpss10.jpg'],
|
||||
timeRange: {
|
||||
start_time: getNextHourTime(),
|
||||
start_time: getNextHourTime(),
|
||||
end_time: getEndTime(getNextHourTime())
|
||||
},
|
||||
activityInfo: {
|
||||
activityInfo: {
|
||||
play_type: '不限',
|
||||
price: '',
|
||||
venue_id: null,
|
||||
venue_id: null,
|
||||
location_name: '',
|
||||
location: '',
|
||||
latitude: '',
|
||||
location: '',
|
||||
latitude: '',
|
||||
longitude: '',
|
||||
court_type: '',
|
||||
court_surface: '',
|
||||
venue_description_tag: [],
|
||||
venue_description: '',
|
||||
venue_image_list: [],
|
||||
court_type: '',
|
||||
court_surface: '',
|
||||
venue_description_tag: [],
|
||||
venue_description: '',
|
||||
venue_image_list: [],
|
||||
},
|
||||
players: [1, 4],
|
||||
skill_level: [1.0, 5.0],
|
||||
descriptionInfo: {
|
||||
description: '',
|
||||
description_tag: [],
|
||||
description: '',
|
||||
description_tag: [],
|
||||
},
|
||||
is_substitute_supported: true,
|
||||
is_wechat_contact: true,
|
||||
wechat_contact: '14223332214'
|
||||
is_substitute_supported: true,
|
||||
is_wechat_contact: true,
|
||||
wechat_contact: '14223332214'
|
||||
}
|
||||
|
||||
const PublishBall: React.FC = () => {
|
||||
@@ -72,7 +72,7 @@ const PublishBall: React.FC = () => {
|
||||
const [formData, setFormData] = useState<PublishBallFormData[]>([
|
||||
defaultFormData
|
||||
])
|
||||
|
||||
|
||||
// 删除确认弹窗状态
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<{
|
||||
visible: boolean;
|
||||
@@ -225,6 +225,13 @@ const PublishBall: React.FC = () => {
|
||||
title: '发布成功',
|
||||
icon: 'success'
|
||||
})
|
||||
delay(1000)
|
||||
// 如果是个人球局,则跳转到详情页,并自动分享
|
||||
// 如果是畅打,则跳转第一个球局详情页,并自动分享 @刘杰
|
||||
Taro.navigateTo({
|
||||
// @ts-expect-error: id
|
||||
url: `/pages/detail/index?id=${res.data.id || 1}&from=publish&autoShare=1`
|
||||
})
|
||||
} else {
|
||||
Taro.showToast({
|
||||
title: res.message,
|
||||
@@ -268,12 +275,16 @@ const PublishBall: React.FC = () => {
|
||||
<View className={styles['publish-ball']}>
|
||||
{/* 活动类型切换 */}
|
||||
<View className={styles['activity-type-switch']}>
|
||||
<<<<<<< HEAD
|
||||
{/* <ActivityTypeSwitch
|
||||
=======
|
||||
<ActivityTypeSwitch
|
||||
>>>>>>> d92419f3c5648a67d1b6857d66d6c92a4bece034
|
||||
value={activityType}
|
||||
onChange={handleActivityTypeChange}
|
||||
/> */}
|
||||
</View>
|
||||
|
||||
|
||||
<View className={styles['publish-ball__scroll']}>
|
||||
{
|
||||
formData.map((item, index) => (
|
||||
@@ -283,19 +294,19 @@ const PublishBall: React.FC = () => {
|
||||
<View className={styles['session-header']}>
|
||||
<View className={styles['session-title']}>
|
||||
第{index + 1}场
|
||||
<View
|
||||
className={styles['session-delete']}
|
||||
<View
|
||||
className={styles['session-delete']}
|
||||
onClick={() => showDeleteConfirm(index)}
|
||||
>
|
||||
<Image src={images.ICON_DELETE} className={styles['session-delete-icon']} />
|
||||
|
||||
|
||||
</View>
|
||||
</View>
|
||||
<View className={styles['session-actions']}>
|
||||
|
||||
|
||||
{index > 0 && (
|
||||
<View
|
||||
className={styles['session-action-btn']}
|
||||
<View
|
||||
className={styles['session-action-btn']}
|
||||
onClick={() => handleCopyPrevious(index)}
|
||||
>
|
||||
复制上一场
|
||||
@@ -304,10 +315,10 @@ const PublishBall: React.FC = () => {
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
<PublishForm
|
||||
formData={item}
|
||||
onChange={(key, value) => updateFormData(key, value, index)}
|
||||
optionsConfig={publishBallFormSchema}
|
||||
<PublishForm
|
||||
formData={item}
|
||||
onChange={(key, value) => updateFormData(key, value, index)}
|
||||
optionsConfig={publishBallFormSchema}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
@@ -323,6 +334,7 @@ const PublishBall: React.FC = () => {
|
||||
</View>
|
||||
|
||||
{/* 删除确认弹窗 */}
|
||||
<<<<<<< HEAD
|
||||
<CommonDialog
|
||||
visible={deleteConfirm.visible}
|
||||
cancelText="再想想"
|
||||
@@ -332,6 +344,30 @@ const PublishBall: React.FC = () => {
|
||||
contentTitle="确认移除该场次?"
|
||||
contentDesc="该操作不可恢复"
|
||||
/>
|
||||
=======
|
||||
{deleteConfirm.visible && (
|
||||
<View className={styles['delete-modal']}>
|
||||
<View className={styles['delete-modal__content']}>
|
||||
<Text className={styles['delete-modal__title']}>确认移除该场次?</Text>
|
||||
<Text className={styles['delete-modal__desc']}>该操作不可恢复</Text>
|
||||
<View className={styles['delete-modal__actions']}>
|
||||
<Button
|
||||
className={styles['delete-modal__btn']}
|
||||
onClick={closeDeleteConfirm}
|
||||
>
|
||||
再想想
|
||||
</Button>
|
||||
<Button
|
||||
className={styles['delete-modal__btn']}
|
||||
onClick={confirmDelete}
|
||||
>
|
||||
确认移除
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
>>>>>>> d92419f3c5648a67d1b6857d66d6c92a4bece034
|
||||
|
||||
{/* 完成按钮 */}
|
||||
<View className={styles['submit-section']}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import { ImageUpload, Range, TimeSelector, TextareaTag, NumberInterval, TitleTextarea, FormSwitch } from '../../components'
|
||||
import { ImageUpload, Range, TimeSelector, TextareaTag, NumberInterval, TitleTextarea, FormSwitch, UploadCover } from '../../components'
|
||||
import FormBasicInfo from './components/FormBasicInfo'
|
||||
import { type CoverImage } from '../../components/index.types'
|
||||
import { FormFieldConfig, FieldType } from '../../config/formSchema/publishBallFormSchema'
|
||||
@@ -22,18 +22,22 @@ const componentMap = {
|
||||
[FieldType.WECHATCONTACT]: WechatSwitch,
|
||||
}
|
||||
|
||||
const PublishForm: React.FC<{
|
||||
formData: PublishBallFormData,
|
||||
onChange: (key: keyof PublishBallFormData, value: any, index?: number) => void,
|
||||
const PublishForm: React.FC<{
|
||||
formData: PublishBallFormData,
|
||||
onChange: (key: keyof PublishBallFormData, value: any, index?: number) => void,
|
||||
optionsConfig: FormFieldConfig[] }> = ({ formData, onChange, optionsConfig }) => {
|
||||
const [coverImages, setCoverImages] = useState<CoverImage[]>([])
|
||||
|
||||
|
||||
// 字典数据相关
|
||||
const { getDictionaryValue } = useDictionaryActions()
|
||||
|
||||
// 处理封面图片变化
|
||||
const handleCoverImagesChange = (images: CoverImage[]) => {
|
||||
setCoverImages(images)
|
||||
const handleCoverImagesChange = (fn: (images: CoverImage[]) => CoverImage[]) => {
|
||||
if (fn instanceof Function) {
|
||||
setCoverImages(fn(coverImages))
|
||||
} else {
|
||||
setCoverImages(fn)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新表单数据
|
||||
@@ -70,7 +74,7 @@ const PublishForm: React.FC<{
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 如果是补充要求,从字典获取选项
|
||||
if (item.prop === 'descriptionInfo') {
|
||||
const descriptionOptions = getDictionaryOptions('publishing_requirements', [])
|
||||
@@ -79,7 +83,7 @@ const PublishForm: React.FC<{
|
||||
options: descriptionOptions
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return item
|
||||
})
|
||||
}
|
||||
@@ -121,7 +125,7 @@ const PublishForm: React.FC<{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 获取动态表单配置
|
||||
const dynamicConfig = getDynamicFormConfig()
|
||||
@@ -140,8 +144,8 @@ const PublishForm: React.FC<{
|
||||
}
|
||||
if (item.type === FieldType.UPLOADIMAGE) {
|
||||
/* 活动封面 */
|
||||
return <ImageUpload
|
||||
images={coverImages}
|
||||
return <UploadCover
|
||||
value={coverImages}
|
||||
onChange={handleCoverImagesChange}
|
||||
{...item.props}
|
||||
/>
|
||||
@@ -182,7 +186,7 @@ const PublishForm: React.FC<{
|
||||
value={formData[item.prop]}
|
||||
onChange={(value) => updateFormData(item.prop as keyof PublishBallFormData, value)}
|
||||
{...optionProps}
|
||||
placeholder={item.placeholder}
|
||||
placeholder={item.placeholder}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
9
src/pages/userInfo/myself/index.config.ts
Normal file
9
src/pages/userInfo/myself/index.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export default {
|
||||
navigationBarTitleText: '个人主页',
|
||||
navigationBarBackgroundColor: '#FFFFFF',
|
||||
navigationBarTextStyle: 'black',
|
||||
backgroundColor: '#FAFAFA',
|
||||
enablePullDownRefresh: false,
|
||||
disableScroll: false,
|
||||
navigationStyle: 'custom'
|
||||
}
|
||||
600
src/pages/userInfo/myself/index.scss
Normal file
600
src/pages/userInfo/myself/index.scss
Normal file
@@ -0,0 +1,600 @@
|
||||
// 个人页面样式
|
||||
.myself_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;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 标签和简介
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
// 球局订单和收藏功能
|
||||
.quick_actions_section {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.action_card {
|
||||
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;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 20px 0;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.action_icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.action_text {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
line-height: 1.4em;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
|
||||
.action_divider {
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 球局类型标签页
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部指示器
|
||||
.home_indicator {
|
||||
position: absolute;
|
||||
bottom: 21px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 140px;
|
||||
height: 5px;
|
||||
background: #000000;
|
||||
border-radius: 2.5px;
|
||||
z-index: 10;
|
||||
}
|
||||
377
src/pages/userInfo/myself/index.tsx
Normal file
377
src/pages/userInfo/myself/index.tsx
Normal file
@@ -0,0 +1,377 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, Image, ScrollView, Button } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import './index.scss';
|
||||
|
||||
// 用户信息接口
|
||||
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[];
|
||||
}
|
||||
|
||||
const MyselfPage: React.FC = () => {
|
||||
// 获取页面参数
|
||||
const instance = Taro.getCurrentInstance();
|
||||
const user_id = instance.router?.params?.userid;
|
||||
|
||||
// 判断是否为当前用户
|
||||
const is_current_user = !user_id;
|
||||
|
||||
// 模拟用户数据
|
||||
const [user_info] = useState<UserInfo>({
|
||||
id: '1',
|
||||
nickname: '188的王晨',
|
||||
avatar: require('../../../static/userInfo/default_avatar.svg'),
|
||||
join_date: '2025年9月加入',
|
||||
stats: {
|
||||
following: 124,
|
||||
friends: 24,
|
||||
hosted: 7,
|
||||
participated: 24
|
||||
},
|
||||
tags: ['上海黄浦', '互联网从业者', 'NTRP 4.0'],
|
||||
bio: '网球入坑两年,偏好双打,正手进攻型选手\n平时在张江、世纪公园附近活动,欢迎约球!\n不卷分数,但认真对待每一拍,每一场球都想打得开心。有时候也会带相机来拍点照片📸',
|
||||
location: '上海黄浦',
|
||||
occupation: '互联网从业者',
|
||||
ntrp_level: 'NTRP 4.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 [is_following, setIsFollowing] = useState(false);
|
||||
|
||||
// 当前激活的标签页
|
||||
const [active_tab, setActiveTab] = useState<'hosted' | 'participated'>('hosted');
|
||||
|
||||
// 处理关注/取消关注
|
||||
const handle_follow = () => {
|
||||
setIsFollowing(!is_following);
|
||||
Taro.showToast({
|
||||
title: is_following ? '已取消关注' : '关注成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
};
|
||||
|
||||
// 处理分享
|
||||
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'
|
||||
});
|
||||
};
|
||||
|
||||
// 处理收藏
|
||||
const handle_favorites = () => {
|
||||
Taro.navigateTo({
|
||||
url: '/pages/game/favorites/index'
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="myself_page">
|
||||
{/* 主要内容 */}
|
||||
<ScrollView className="main_content" scrollY>
|
||||
{/* 用户信息区域 */}
|
||||
<View className="user_info_section">
|
||||
{/* 头像和基本信息 */}
|
||||
<View className="basic_info">
|
||||
<View className="avatar_container">
|
||||
<Image className="avatar" src={user_info.avatar} />
|
||||
</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>
|
||||
|
||||
{/* 球局订单和收藏功能 */}
|
||||
<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')}
|
||||
/>
|
||||
<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')}
|
||||
/>
|
||||
<Text className="action_text">收藏</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 球局类型标签页 */}
|
||||
<View className="game_tabs_section">
|
||||
<View className="tab_container">
|
||||
<View className={`tab_item ${active_tab === 'hosted' ? 'active' : ''}`} onClick={() => setActiveTab('hosted')}>
|
||||
<Text className="tab_text">我主办的</Text>
|
||||
</View>
|
||||
<View className={`tab_item ${active_tab === 'participated' ? 'active' : ''}`} onClick={() => setActiveTab('participated')}>
|
||||
<Text className="tab_text">我参与的</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 球局列表 */}
|
||||
<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>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyselfPage;
|
||||
Reference in New Issue
Block a user