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 && (
)
}
// 场馆信息
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)