import React, { useState, useEffect, useRef, useImperativeHandle, forwardRef } from 'react' import { View, Text, Image, Map, ScrollView } from '@tarojs/components' import { Avatar, Popover, ImagePreview } 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 { 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' import './index.scss' dayjs.locale('zh-cn') // 将·作为连接符插入到标签文本之间 function insertDotInTags(tags: string[]) { return tags.join('-·-').split('-') } function GameTags(props) { const { detail } = props const tags = [{ name: '🕙 急招', icon: '', }, { name: '🔥 本周热门', icon: '', }, { name: '🎉 新活动', icon: '', }, { name: '官方组织', icon: '', }] return ( {/* network image mock */} {tags.map((tag, index) => ( {tag.icon && } {tag.name} ))} ) } type CourselItemType = { url: string width: number height: number } function Coursel(props) { const { detail } = props const [list, setList] = useState([]) const [listWidth, setListWidth] = useState(0) const { image_list } = detail async function getImagesMsg (imageList) { const latest_list: CourselItemType[] = [] const sys_info = await Taro.getSystemInfo() console.log(sys_info, 'info') const max_width = sys_info.screenWidth - 30 const max_height = 240 const current_aspect_ratio = max_width / max_height let container_width = 0 for (const imageUrl of imageList) { const { width, height } = await Taro.getImageInfo({ src: imageUrl }) if (width && height) { const aspect_ratio = width / height const latest_w_h = { width, height } if (aspect_ratio < current_aspect_ratio) { latest_w_h.width = max_height * aspect_ratio latest_w_h.height = max_height } else { latest_w_h.width = max_width latest_w_h.height = max_width / aspect_ratio } container_width += latest_w_h.width + 12 latest_list.push({ url: imageUrl, width: latest_w_h.width, height: latest_w_h.height, }) } } setList(latest_list) setListWidth(container_width) } useEffect(() => { getImagesMsg(image_list || []) }, [image_list]) return ( { list.map((item, index) => { return ( ) }) } ) } // 分享弹窗 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=share`, // } // }) // } // 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' }) // }, // }) // } return ( { setVisible(false) }} hideFooter style={{ minHeight: '100px' }} > 分享卡片 ) }) // 底部操作栏 function StickyButton(props) { const { handleShare, handleJoinGame, detail } = props const userInfo = useUserInfo() const { id } = userInfo const { publisher_id, match_status, price } = detail || {} const role = Number(publisher_id) === id ? 'ownner' : 'visitor' return ( 分享 { Taro.showToast({ title: 'To be continued', icon: 'none' }) }}> 32 🎾 立即加入 ¥ {price} ) } // 球局信息 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 ( {/* Date and Weather */} {/* Calendar and Date time */} {/* Calendar */} {startMonth}月 {startDay} {/* Date time */} {startDate} {gameRange} ({game_length}小时) {/* Weather */} {/* Weather icon */} {/* Weather text and temperature */} 28℃ - 32℃ {/* Place */} {/* venue location message */} {/* location icon */} {/* location message */} {/* venue name and distance */} {location_name || '-'} · {distance.toFixed(1)}km {/* venue address */} {location || '-'} {/* venue map */} {longitude && latitude && ( {}} // hide business msg showLocation theme='dark' /> )} ) } // 场馆信息 function VenueInfo(props) { const { detail } = props const [visible, setVisible] = useState(false) const { venue_description, venue_description_tag = [], venue_image_list = [] } = detail function showScreenShot() { setVisible(true) } function onClose() { setVisible(false) } function previewImage(current_url) { Taro.previewImage({ current: current_url, urls: venue_image_list.map(c => c.url), }) } return ( {/* venue detail title and venue ordered status */} 场馆详情 {venue_image_list?.length > 0 ? <> · 已订场 : '' } {/* venue detail content */} {/* venue detail tags */} {insertDotInTags(venue_description_tag).map((tag, index) => ( {tag} ))} {/* venue remarks */} {venue_description} 预定截图 {venue_image_list.map(item => { return ( ) })} ) } function genNTRPRequirementText(min, max) { if (min && max) { return `${min} - ${max} 之间` } else if (max) { return `${max} 以上` } return '没有要求' } // 玩法要求 function GamePlayAndRequirement(props) { const { detail: { skill_level_min, skill_level_max, play_type, game_type } } = props const requirements = [ { title: 'NTRP水平要求', desc: genNTRPRequirementText(skill_level_min, skill_level_max), }, { title: '活动玩法', desc: play_type || '-', }, { title: '人员构成', desc: game_type || '-', } ] return ( {/* title */} 玩法要求 {/* requirements */} {requirements.map((item, index) => ( {item.title} {item.desc} ))} ) } // 参与者 function Participants(props) { const { detail = {} } = props const participants = detail.participants || [] const organizer_id = Number(detail.publisher_id) return ( 参与者 · 剩余空位 3 {/* application */} { Taro.showToast({ title: 'To be continued', icon: 'none' }) }}> 申请加入 {/* participants list */} {participants.map((participant) => { const { user: { avatar_url, nickname, level, id: participant_user_id } } = participant const role = participant_user_id === organizer_id ? '组织者' : '参与者' return ( {nickname || '未知'} {level || '未知'} {role} ) })} ) } function SupplementalNotes(props) { const { detail: { description, description_tag = [] } } = props return ( 补充说明 {/* supplemental notes tags */} {insertDotInTags(description_tag).map((tag, index) => ( {tag} ))} {/* supplemental notes content */} {description} ) } function OrganizerInfo(props) { 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 ( {/* orgnizer title */} 组织者 {/* organizer avatar and name */} Light 已组织 8 次 NTRP 3.5 关注 {/* recommend games by organizer */} TA的更多活动 {recommendGames.map((game, index) => ( {/* game title */} {game.title} {/* game time and range */} {game.time} {game.timeLength} {/* game location、vunue、distance */} {game.venue} · {game.veuneType} · {game.distance} {/* organizer avatar、applications、level requirements、play type */} 报名人数 {game.checkedApplications}/{game.applications} {game.levelRequirements} {game.playType} ))} ) } function Index() { const [detail, setDetail] = useState({}) const { params } = useRouter() const [currentLocation, setCurrentLocation] = useState<[number, number]>([0, 0]) const { id, from } = params const { fetchUserInfo, updateUserInfo } = useUserActions() const sharePopupRef = useRef(null) useDidShow(async () => { await updateLocation() await fetchUserInfo() await fetchDetail() }) const updateLocation = async () => { try { const location = await getCurrentLocation() setCurrentLocation([location.latitude, location.longitude]) await updateUserInfo({ latitude: location.latitude, longitude: location.longitude }) } catch (error) { console.error('用户位置更新失败', error) } } const fetchDetail = async () => { const res = await DetailService.getDetail(Number(id)) if (res.code === 0) { setDetail(res.data) } } function handleShare() { sharePopupRef.current.show() } const handleJoinGame = () => { Taro.navigateTo({ url: `/pages/orderCheck/index?gameId=${id}`, }) } function handleBack() { const pages = Taro.getCurrentPages() if (pages.length <= 1) { Taro.redirectTo({ url: '/pages/list/index', }) } else { Taro.navigateBack() } } console.log('detail', detail) const backgroundImage = detail?.image_list?.[0] ? { backgroundImage: `url(${detail?.image_list?.[0]})` } : {} return ( {/* custom navbar */} {/* swiper */} {/* content */} {/* avatar and tags */} {/* title */} {detail.title} {/* Date and Place and weather */} {/* detail */} {/* gameplay requirements */} {/* participants */} {/* supplemental notes */} {/* organizer and recommend games by organizer */} {/* sticky bottom action bar */} {/* share popup */} ) } export default withAuth(Index)