发布球局
This commit is contained in:
49
src/components/ActivityTypeSwitch/index.module.scss
Normal file
49
src/components/ActivityTypeSwitch/index.module.scss
Normal file
@@ -0,0 +1,49 @@
|
||||
.activity-type-switch {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding: 0 4px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.switch-tab {
|
||||
flex: 1;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #e5e5e5;
|
||||
color: #1890ff;
|
||||
opacity: 0.3;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.switch-tab.active {
|
||||
background: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0px 4px 48px 0px rgba(0, 0, 0, 0.08);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.icon-style {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
@use '~@/scss/themeColor.scss' as theme;
|
||||
|
||||
.activity-type-switch {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding: 0 4px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
overflow: hidden;
|
||||
.switch-tab {
|
||||
flex: 1;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #e5e5e5;
|
||||
color: theme.$primary-color;
|
||||
opacity: 0.3;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
.icon-style {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
&.active {
|
||||
background: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0px 4px 48px 0px rgba(0, 0, 0, 0.08);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react'
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import images from '@/config/images'
|
||||
|
||||
import './index.scss'
|
||||
import styles from './index.module.scss'
|
||||
|
||||
export type ActivityType = 'individual' | 'group'
|
||||
|
||||
@@ -13,22 +12,22 @@ interface ActivityTypeSwitchProps {
|
||||
|
||||
const ActivityTypeSwitch: React.FC<ActivityTypeSwitchProps> = ({ value, onChange }) => {
|
||||
return (
|
||||
<View className='activity-type-switch'>
|
||||
<View className={styles['activity-type-switch']}>
|
||||
<View
|
||||
className={`switch-tab ${value === 'individual' ? 'active' : ''}`}
|
||||
className={`${styles['switch-tab']} ${value === 'individual' ? styles.active : ''}`}
|
||||
onClick={() => onChange('individual')}
|
||||
>
|
||||
<View className='tab-icon'>
|
||||
<Image src={images.ICON_PERSONAL} className='icon-style' />
|
||||
<View className={styles['tab-icon']}>
|
||||
<Image src={images.ICON_PERSONAL} className={styles['icon-style']} />
|
||||
</View>
|
||||
<Text className='tab-text'>个人约球</Text>
|
||||
<Text className={styles['tab-text']}>个人约球</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`switch-tab ${value === 'group' ? 'active' : ''}`}
|
||||
className={`${styles['switch-tab']} ${value === 'group' ? styles.active : ''}`}
|
||||
onClick={() => onChange('group')}
|
||||
>
|
||||
<Image src={images.ICON_CHANGDA} className='icon-style' />
|
||||
<Text className='tab-text'>畅打活动</Text>
|
||||
<Image src={images.ICON_CHANGDA} className={styles['icon-style']} />
|
||||
<Text className={styles['tab-text']}>畅打活动</Text>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
color: #1f2329;
|
||||
border: none;
|
||||
width: 154px;
|
||||
height: 36px;
|
||||
height: 44px;
|
||||
border-radius: 12px;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||
background: #fff;
|
||||
@@ -53,7 +53,7 @@
|
||||
.common-popup__btn-confirm {
|
||||
/* 使用按钮组件的 primary 样式 */
|
||||
width: 154px;
|
||||
height: 36px;
|
||||
height: 44px;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||
background: #000;
|
||||
border-radius: 12px;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default, type CoverImage } from './CoverImageUpload'
|
||||
@@ -25,7 +25,7 @@
|
||||
width: 108px;
|
||||
height: 108px;
|
||||
border-radius: 12px;
|
||||
margin-right: 12px;
|
||||
margin-right: 6px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useMemo, useCallback } from 'react'
|
||||
import { View, Text, Image, ScrollView } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import './CoverImageUpload.scss'
|
||||
import './ImageUpload.scss'
|
||||
|
||||
export interface CoverImage {
|
||||
id: string
|
||||
@@ -9,13 +9,13 @@ export interface CoverImage {
|
||||
tempFilePath?: string
|
||||
}
|
||||
|
||||
interface CoverImageUploadProps {
|
||||
interface ImageUploadProps {
|
||||
images: CoverImage[]
|
||||
onChange: (images: CoverImage[]) => void
|
||||
maxCount?: number
|
||||
}
|
||||
|
||||
const CoverImageUpload: React.FC<CoverImageUploadProps> = ({
|
||||
const ImageUpload: React.FC<ImageUploadProps> = ({
|
||||
images,
|
||||
onChange,
|
||||
maxCount = 9
|
||||
@@ -88,4 +88,4 @@ const CoverImageUpload: React.FC<CoverImageUploadProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default CoverImageUpload
|
||||
export default ImageUpload
|
||||
1
src/components/ImageUpload/index.ts
Normal file
1
src/components/ImageUpload/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default, type CoverImage } from './ImageUpload'
|
||||
@@ -1,149 +0,0 @@
|
||||
.ntrp-slider {
|
||||
// 区域标题 - 灰色背景
|
||||
.section-title-wrapper {
|
||||
margin-bottom: 16px;
|
||||
padding: 0 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.section-summary {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
// NTRP控制区域 - 白色块
|
||||
.ntrp-control-section {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.ntrp-slider-container {
|
||||
.ntrp-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.ntrp-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.ntrp-slider-track {
|
||||
position: relative;
|
||||
height: 40px;
|
||||
margin: 0 12px;
|
||||
|
||||
.slider-bg {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.slider-range {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
height: 4px;
|
||||
background: #333;
|
||||
border-radius: 2px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.slider-thumb {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: #333;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&.active {
|
||||
transform: translate(-50%, -50%) scale(1.2);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.thumb-value {
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: #333;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
&.active .thumb-value {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 暗色模式适配
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.ntrp-slider {
|
||||
.section-title-wrapper {
|
||||
.section-title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.section-summary {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.ntrp-control-section {
|
||||
background: #2d2d2d;
|
||||
|
||||
.ntrp-labels .ntrp-label {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.slider-bg {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.slider-range {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.slider-thumb {
|
||||
background: #fff;
|
||||
border-color: #2d2d2d;
|
||||
|
||||
.thumb-value {
|
||||
background: #fff;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import './NTRPSlider.scss'
|
||||
|
||||
export interface NTRPRange {
|
||||
min: number
|
||||
max: number
|
||||
}
|
||||
|
||||
// 获取NTRP显示文本的工具函数
|
||||
export const getNTRPRangeText = (range: NTRPRange): string => {
|
||||
if (range.min === 2.0 && range.max === 4.0) {
|
||||
return '不限'
|
||||
}
|
||||
return `${range.min} - ${range.max}`
|
||||
}
|
||||
|
||||
interface NTRPSliderProps {
|
||||
value: NTRPRange
|
||||
onChange: (range: NTRPRange) => void
|
||||
title?: string
|
||||
showTitle?: boolean
|
||||
}
|
||||
|
||||
const NTRPSlider: React.FC<NTRPSliderProps> = ({
|
||||
value = {
|
||||
min: 1.0,
|
||||
max: 5.0
|
||||
},
|
||||
onChange,
|
||||
title = 'NTRP水平要求',
|
||||
showTitle = false
|
||||
}) => {
|
||||
const [activeThumb, setActiveThumb] = useState<'min' | 'max' | null>(null)
|
||||
|
||||
// 计算滑动条位置百分比
|
||||
const getSliderPercentage = useCallback((level: number) => {
|
||||
return ((level - 2.0) / 2.0) * 100
|
||||
}, [])
|
||||
|
||||
// 获取当前NTRP显示文本
|
||||
const currentRangeText = getNTRPRangeText(value)
|
||||
|
||||
const handleSliderTouchStart = useCallback((thumb: 'min' | 'max') => {
|
||||
setActiveThumb(thumb)
|
||||
}, [])
|
||||
|
||||
const handleSliderTouchMove = useCallback((e: any) => {
|
||||
if (!activeThumb) return
|
||||
|
||||
e.preventDefault()
|
||||
const query = Taro.createSelectorQuery()
|
||||
query.select('.ntrp-slider-track').boundingClientRect((rect: any) => {
|
||||
if (rect && !Array.isArray(rect)) {
|
||||
const touch = e.touches[0]
|
||||
const relativeX = touch.clientX - rect.left
|
||||
const percentage = Math.max(0, Math.min(1, relativeX / rect.width))
|
||||
const level = Number((2.0 + percentage * 2.0).toFixed(1))
|
||||
|
||||
if (activeThumb === 'min') {
|
||||
const newMin = Math.min(level, value.max - 0.1)
|
||||
onChange({ min: newMin, max: value.max })
|
||||
} else {
|
||||
const newMax = Math.max(level, value.min + 0.1)
|
||||
onChange({ min: value.min, max: newMax })
|
||||
}
|
||||
}
|
||||
}).exec()
|
||||
}, [activeThumb, value, onChange])
|
||||
|
||||
const handleSliderTouchEnd = useCallback(() => {
|
||||
setActiveThumb(null)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View className='ntrp-slider'>
|
||||
{showTitle && (
|
||||
<View className='section-title-wrapper'>
|
||||
<Text className='section-title'>{title}</Text>
|
||||
<Text className='section-summary'>{currentRangeText}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View className='ntrp-control-section'>
|
||||
<View className='ntrp-slider-container'>
|
||||
<View className='ntrp-labels'>
|
||||
<Text className='ntrp-label'>2.0及以下</Text>
|
||||
<Text className='ntrp-label'>4.0及以上</Text>
|
||||
</View>
|
||||
|
||||
<View
|
||||
className='ntrp-slider-track'
|
||||
onTouchMove={handleSliderTouchMove}
|
||||
onTouchEnd={handleSliderTouchEnd}
|
||||
>
|
||||
{/* 背景轨道 */}
|
||||
<View className='slider-bg'></View>
|
||||
|
||||
{/* 选中区间 */}
|
||||
<View
|
||||
className='slider-range'
|
||||
style={{
|
||||
left: `${getSliderPercentage(value.min)}%`,
|
||||
width: `${getSliderPercentage(value.max) - getSliderPercentage(value.min)}%`
|
||||
}}
|
||||
></View>
|
||||
|
||||
{/* 最小值滑块 */}
|
||||
<View
|
||||
className={`slider-thumb ${activeThumb === 'min' ? 'active' : ''}`}
|
||||
style={{ left: `${getSliderPercentage(value.min)}%` }}
|
||||
onTouchStart={() => handleSliderTouchStart('min')}
|
||||
>
|
||||
<View className='thumb-value'>{value.min}</View>
|
||||
</View>
|
||||
|
||||
{/* 最大值滑块 */}
|
||||
<View
|
||||
className={`slider-thumb ${activeThumb === 'max' ? 'active' : ''}`}
|
||||
style={{ left: `${getSliderPercentage(value.max)}%` }}
|
||||
onTouchStart={() => handleSliderTouchStart('max')}
|
||||
>
|
||||
<View className='thumb-value'>{value.max}</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default NTRPSlider
|
||||
@@ -1 +0,0 @@
|
||||
export { default, type NTRPRange, getNTRPRangeText } from './NTRPSlider'
|
||||
@@ -1,16 +1,16 @@
|
||||
import React from 'react'
|
||||
import { View, Text, Button } from '@tarojs/components'
|
||||
import './ParticipantsControl.scss'
|
||||
import './NumberInterval.scss'
|
||||
import { InputNumber } from '@nutui/nutui-react-taro'
|
||||
|
||||
interface ParticipantsControlProps {
|
||||
interface NumberIntervalProps {
|
||||
minParticipants: number
|
||||
maxParticipants: number
|
||||
onMinParticipantsChange: (value: number) => void
|
||||
onMaxParticipantsChange: (value: number) => void
|
||||
}
|
||||
|
||||
const ParticipantsControl: React.FC<ParticipantsControlProps> = ({
|
||||
const NumberInterval: React.FC<NumberIntervalProps> = ({
|
||||
minParticipants,
|
||||
maxParticipants,
|
||||
onMinParticipantsChange,
|
||||
@@ -46,4 +46,4 @@ const ParticipantsControl: React.FC<ParticipantsControlProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default ParticipantsControl
|
||||
export default NumberInterval
|
||||
1
src/components/NumberInterval/index.ts
Normal file
1
src/components/NumberInterval/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './NumberInterval'
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default } from './ParticipantsControl'
|
||||
export type { ParticipantsControlProps } from './ParticipantsControl'
|
||||
@@ -117,6 +117,21 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
|
||||
setSearchValue('')
|
||||
}
|
||||
|
||||
const handleItemLocation = (stadium: Stadium) => {
|
||||
console.log(stadium,'stadiumstadium');
|
||||
if(stadium.latitude && stadium.longitude){
|
||||
Taro.openLocation({
|
||||
latitude: stadium.latitude,
|
||||
longitude: stadium.longitude,
|
||||
name: stadium.name,
|
||||
address: stadium.address,
|
||||
success: (res) => {
|
||||
console.log(res,'resres');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const markSearchText = (text: string) => {
|
||||
return text.replace(searchValue, `<span style="color: #007AFF;">${searchValue}</span>`)
|
||||
@@ -215,9 +230,9 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
|
||||
</View>
|
||||
<View className='stadium-item-right'>
|
||||
<View className='stadium-name' dangerouslySetInnerHTML={{ __html: markSearchText(stadium.name) }}></View>
|
||||
<View className='stadium-address'>
|
||||
<Text>{stadium.istance} · </Text>
|
||||
<Text>{stadium.address}</Text>
|
||||
<View className='stadium-address' >
|
||||
<Text onClick={(e) => { e.stopPropagation(); handleItemLocation(stadium); }}>{stadium.istance} · </Text>
|
||||
<Text onClick={(e) => { e.stopPropagation(); handleItemLocation(stadium); }}>{stadium.address}</Text>
|
||||
<Image src={images.ICON_ARRORW_SMALL} className='stadium-map-icon' />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -57,121 +57,93 @@
|
||||
|
||||
// 场地类型
|
||||
.venue-type-section {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
|
||||
.section-title {
|
||||
padding: 18px 20px 10px 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.option-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 0 15px;
|
||||
.option-btn {
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid #e0e0e0;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.selected {
|
||||
background: #333;
|
||||
border-color: #333;
|
||||
|
||||
.option-text {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.option-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 地面材质
|
||||
.ground-material-section {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.option-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
.option-btn {
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid #e0e0e0;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.selected {
|
||||
background: #333;
|
||||
border-color: #333;
|
||||
|
||||
.option-text {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.option-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 场地信息补充
|
||||
.additional-info-section {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.additional-input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
background: white;
|
||||
height: 44px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
.heart-wrapper{
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.heart-icon{
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
z-index: 1;
|
||||
}
|
||||
.icon-bg{
|
||||
border-radius: 1.6px;
|
||||
width: 165px;
|
||||
height: 17px;
|
||||
flex-shrink: 0;
|
||||
border: 0.5px solid rgba(238, 255, 135, 0.00);
|
||||
opacity: 0.4;
|
||||
background: linear-gradient(258deg, rgba(220, 250, 97, 0.00) 6.85%, rgba(228, 255, 59, 0.82) 91.69%);
|
||||
backdrop-filter: blur(1.25px);
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 4px;
|
||||
}
|
||||
.heart-text{
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.90);
|
||||
z-index: 2;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #999;
|
||||
.option-buttons {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 0 15px;
|
||||
.textarea-tag-container{
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
background: #FFF;
|
||||
box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.option-btn {
|
||||
border-radius: 20px;
|
||||
border: 1px solid #e0e0e0;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
border-radius: 999px;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.12);
|
||||
background: #FFF;
|
||||
font-weight: 500;
|
||||
&.selected {
|
||||
background: #000;
|
||||
border-color: #fff;
|
||||
border-radius: 999px;
|
||||
font-weight: 600;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||
.option-text {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.option-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 底部按钮
|
||||
.bottom-actions {
|
||||
background: white;
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import images from '@/config/images'
|
||||
import './StadiumDetail.scss'
|
||||
import TextareaTag from '@/components/TextareaTag'
|
||||
import CoverImageUpload, { type CoverImage } from '@/components/CoverImageUpload'
|
||||
import CoverImageUpload, { type CoverImage } from '@/components/ImageUpload'
|
||||
|
||||
export interface Stadium {
|
||||
id?: string
|
||||
name: string
|
||||
address?: string
|
||||
longitude?: number
|
||||
latitude?: number
|
||||
istance?: string
|
||||
}
|
||||
|
||||
interface StadiumDetailProps {
|
||||
@@ -21,57 +25,109 @@ const stadiumInfo = [
|
||||
{
|
||||
label: '场地类型',
|
||||
options: ['室内', '室外', '室外雨棚'],
|
||||
prop: 'venueType',
|
||||
type: 'tags'
|
||||
},
|
||||
{
|
||||
label: '地面材质',
|
||||
options: ['硬地', '红土', '草地'],
|
||||
prop: 'groundMaterial',
|
||||
type: 'tags'
|
||||
},
|
||||
{
|
||||
label: '场地信息补充',
|
||||
options: ['1号场', '2号场', '3号场', '4号场', '有空调', '6号场'],
|
||||
options: ['1号场', '2号场', '3号场', '4号场', '有空调', '6号场','6号场'],
|
||||
prop: 'additionalInfo',
|
||||
type: 'textareaTag'
|
||||
},
|
||||
{
|
||||
label: '场地预定截图',
|
||||
options: ['有其他场地信息可备注'],
|
||||
prop: 'imagesList',
|
||||
type: 'image'
|
||||
}
|
||||
]
|
||||
|
||||
// 公共的标题组件
|
||||
const SectionTitle: React.FC<{ title: string,prop: string }> = ({ title, prop }) => {
|
||||
console.log(prop,'propprop');
|
||||
if (prop === 'imagesList') {
|
||||
return (
|
||||
<View className='section-title'>
|
||||
<Text>{title}</Text>
|
||||
<View className='heart-wrapper'>
|
||||
<Image src={images.ICON_HEART_CIRCLE} className='heart-icon' />
|
||||
<View className='icon-bg'></View>
|
||||
<Text className='heart-text'>添加截图,平台会优先推荐</Text>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Text className='section-title'>{title}</Text>
|
||||
)
|
||||
}
|
||||
|
||||
// 公共的容器组件
|
||||
const SectionContainer: React.FC<{ title: string; children: React.ReactNode, prop: string }> = ({ title, children, prop }) => (
|
||||
<View className='venue-type-section'>
|
||||
<SectionTitle title={title} prop={prop}/>
|
||||
<View className='option-buttons'>
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
|
||||
const StadiumDetail: React.FC<StadiumDetailProps> = ({
|
||||
stadium,
|
||||
onBack,
|
||||
onConfirm
|
||||
}) => {
|
||||
const [venueType, setVenueType] = useState('室内')
|
||||
const [groundMaterial, setGroundMaterial] = useState('硬地')
|
||||
const [additionalInfo, setAdditionalInfo] = useState('')
|
||||
const [imagesList, setImagesList] = useState<CoverImage[]>([])
|
||||
const [formData, setFormData] = useState({
|
||||
stadiumName: stadium.name,
|
||||
stadiumAddress: stadium.address,
|
||||
stadiumLongitude: stadium.longitude,
|
||||
stadiumLatitude: stadium.latitude,
|
||||
istance: stadium.istance,
|
||||
venueType: '室内',
|
||||
groundMaterial: '硬地',
|
||||
additionalInfo: '',
|
||||
imagesList: [] as CoverImage[]
|
||||
})
|
||||
|
||||
|
||||
const handleConfirm = () => {
|
||||
onConfirm(stadium, venueType, groundMaterial, additionalInfo)
|
||||
}
|
||||
|
||||
const handleMapLocation = () => {
|
||||
|
||||
Taro.chooseLocation({
|
||||
success: (res) => {
|
||||
console.log(res,'resres');
|
||||
setFormData({
|
||||
...formData,
|
||||
stadiumName: res.name,
|
||||
stadiumAddress: res.address,
|
||||
stadiumLongitude: res.longitude,
|
||||
stadiumLatitude: res.latitude
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('选择位置失败:', err)
|
||||
Taro.showToast({
|
||||
title: '位置选择失败',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getSelectedByLabel = useCallback((label: string) => {
|
||||
if (label === '场地类型') return venueType
|
||||
if (label === '地面材质') return groundMaterial
|
||||
return ''
|
||||
}, [venueType, groundMaterial])
|
||||
|
||||
const setSelectedByLabel = useCallback((label: string, value: string) => {
|
||||
if (label === '场地类型') {
|
||||
setVenueType(value)
|
||||
} else if (label === '地面材质') {
|
||||
setGroundMaterial(value)
|
||||
}
|
||||
const updateFormData = useCallback((prop: string, value: any) => {
|
||||
setFormData(prev => ({ ...prev, [prop]: value }))
|
||||
}, [])
|
||||
|
||||
const getSelectedByLabel = useCallback((label: string) => {
|
||||
if (label === '场地类型') return formData.venueType
|
||||
if (label === '地面材质') return formData.groundMaterial
|
||||
return ''
|
||||
}, [formData.venueType, formData.groundMaterial])
|
||||
|
||||
|
||||
console.log(stadium,'stadiumstadium');
|
||||
return (
|
||||
<View className='stadium-detail'>
|
||||
@@ -84,9 +140,10 @@ const StadiumDetail: React.FC<StadiumDetailProps> = ({
|
||||
<Image src={images.ICON_STADIUM} className='stadium-icon' />
|
||||
</View>
|
||||
<View className='stadium-item-right'>
|
||||
<View className='stadium-name'>{stadium.name}</View>
|
||||
<View className='stadium-name'>{formData.stadiumName}</View>
|
||||
<View className='stadium-address'>
|
||||
<Text>{stadium.address}</Text>
|
||||
<Text>{formData.istance} · </Text>
|
||||
<Text>{formData.stadiumAddress}</Text>
|
||||
<Image src={images.ICON_ARRORW_SMALL} className='stadium-map-icon' />
|
||||
</View>
|
||||
</View>
|
||||
@@ -96,54 +153,43 @@ const StadiumDetail: React.FC<StadiumDetailProps> = ({
|
||||
if (item.type === 'tags') {
|
||||
const selected = getSelectedByLabel(item.label)
|
||||
return (
|
||||
<View className='venue-type-section' key={item.label}>
|
||||
<Text className='section-title'>{item.label}</Text>
|
||||
<View className='option-buttons'>
|
||||
{item.options.map((opt) => (
|
||||
<View
|
||||
key={opt}
|
||||
className={`option-btn ${selected === opt ? 'selected' : ''}`}
|
||||
onClick={() => setSelectedByLabel(item.label, opt)}
|
||||
>
|
||||
<Text className='option-text'>{opt}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
<SectionContainer key={item.label} title={item.label} prop={item.prop}>
|
||||
{item.options.map((opt) => (
|
||||
<View
|
||||
key={opt}
|
||||
className={`option-btn ${selected === opt ? 'selected' : ''}`}
|
||||
onClick={() => updateFormData(item.prop, opt)}
|
||||
>
|
||||
<Text className='option-text'>{opt}</Text>
|
||||
</View>
|
||||
))}
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
if (item.type === 'textareaTag') {
|
||||
return (
|
||||
<View className='venue-type-section' key={item.label}>
|
||||
<Text className='section-title'>{item.label}</Text>
|
||||
<View className='option-buttons'>
|
||||
<SectionContainer key={item.label} title={item.label} prop={item.prop}>
|
||||
<View className='textarea-tag-container'>
|
||||
<TextareaTag
|
||||
key={item.label}
|
||||
value={additionalInfo}
|
||||
onChange={setAdditionalInfo}
|
||||
value={formData.additionalInfo}
|
||||
onChange={(value) => updateFormData(item.prop, value)}
|
||||
placeholder='有其他场地信息可备注'
|
||||
options={(item.options || []).map((o) => ({ label: o, value: o }))}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
if (item.type === 'image') {
|
||||
return (
|
||||
<View className='venue-type-section' key={item.label}>
|
||||
<Text className='section-title'>{item.label}</Text>
|
||||
<View className='option-buttons'>
|
||||
<SectionContainer key={item.label} title={item.label} prop={item.prop}>
|
||||
<CoverImageUpload
|
||||
key={item.label}
|
||||
images={imagesList}
|
||||
onChange={setImagesList}
|
||||
images={formData.imagesList}
|
||||
onChange={(images) => updateFormData(item.prop, images)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
border-radius: 16px;
|
||||
padding: 10px 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
.input-wrapper {
|
||||
margin-top: 8px;
|
||||
.additional-input {
|
||||
@@ -36,6 +37,7 @@
|
||||
gap: 6px;
|
||||
.nut-checkbox{
|
||||
margin-right: 0;
|
||||
|
||||
.nut-checkbox-button{
|
||||
border: 1px solid theme.$primary-border-color;
|
||||
color: theme.$primary-color;
|
||||
@@ -43,6 +45,7 @@
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
margin-right: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { View, Input } from '@tarojs/components'
|
||||
|
||||
import { View } from '@tarojs/components'
|
||||
import { TextArea } from '@nutui/nutui-react-taro'
|
||||
import './index.scss'
|
||||
|
||||
interface TitleInputProps {
|
||||
@@ -18,13 +18,14 @@ const TitleInput: React.FC<TitleInputProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<View className='title-input-wrapper'>
|
||||
<Input
|
||||
<TextArea
|
||||
className='title-input'
|
||||
placeholder={placeholder}
|
||||
placeholderClass='title-placeholder'
|
||||
value={value}
|
||||
onInput={(e) => onChange(e.detail.value)}
|
||||
maxlength={maxLength}
|
||||
autoSize={true}
|
||||
placeholderClass='title-input-placeholder'
|
||||
/>
|
||||
<View className='char-count'>{value.length}/{maxLength}</View>
|
||||
</View>
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
.title-input-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-around;
|
||||
.title-input {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
padding: 0 16px;
|
||||
border: 1px solid #e5e5e5;
|
||||
width: 83%;
|
||||
min-height: 44px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
line-height: 44px;
|
||||
line-height: 1.4;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
&:focus {
|
||||
border-color: #007aff;
|
||||
outline: none;
|
||||
}
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.title-placeholder {
|
||||
color: #999;
|
||||
font-size: 16px;
|
||||
// 使用 placeholderClass 来控制 placeholder 样式
|
||||
.title-input-placeholder {
|
||||
color: rgba(60, 60, 67, 0.60) !important;
|
||||
font-size: 16px !important;
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.char-count {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
pointer-events: none;
|
||||
padding-top: 12px;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import ActivityTypeSwitch from './ActivityTypeSwitch'
|
||||
import TextareaTag from './TextareaTag'
|
||||
import FormSwitch from './FormSwitch'
|
||||
import CoverImageUpload from './CoverImageUpload'
|
||||
import ImageUpload from './ImageUpload'
|
||||
import FormBasicInfo from './FormBasicInfo'
|
||||
import Range from './Range'
|
||||
import ParticipantsControl from './ParticipantsControl'
|
||||
import NumberInterval from './NumberInterval'
|
||||
import { SelectStadium, StadiumDetail } from './SelectStadium'
|
||||
import TimeSelector from './TimeSelector'
|
||||
import TitleInput from './TitleInput'
|
||||
@@ -14,10 +14,10 @@ export {
|
||||
ActivityTypeSwitch,
|
||||
TextareaTag,
|
||||
FormSwitch,
|
||||
CoverImageUpload,
|
||||
ImageUpload,
|
||||
FormBasicInfo,
|
||||
Range,
|
||||
ParticipantsControl,
|
||||
NumberInterval,
|
||||
SelectStadium,
|
||||
TimeSelector,
|
||||
TitleInput,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getNTRPRangeText, type NTRPRange } from './NTRPSlider'
|
||||
import { type TimeRange } from './TimeSelector'
|
||||
import { type Stadium } from './SelectStadium'
|
||||
import { type ActivityType } from './ActivityTypeSwitch'
|
||||
import { type CoverImage } from './CoverImageUpload'
|
||||
export type { NTRPRange, getNTRPRangeText, TimeRange, Stadium, ActivityType, CoverImage }
|
||||
import { type CoverImage } from './ImageUpload'
|
||||
export type { TimeRange, Stadium, ActivityType, CoverImage }
|
||||
Reference in New Issue
Block a user