发布球局
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 React from 'react'
|
||||||
import { View, Text, Image } from '@tarojs/components'
|
import { View, Text, Image } from '@tarojs/components'
|
||||||
import images from '@/config/images'
|
import images from '@/config/images'
|
||||||
|
import styles from './index.module.scss'
|
||||||
import './index.scss'
|
|
||||||
|
|
||||||
export type ActivityType = 'individual' | 'group'
|
export type ActivityType = 'individual' | 'group'
|
||||||
|
|
||||||
@@ -13,22 +12,22 @@ interface ActivityTypeSwitchProps {
|
|||||||
|
|
||||||
const ActivityTypeSwitch: React.FC<ActivityTypeSwitchProps> = ({ value, onChange }) => {
|
const ActivityTypeSwitch: React.FC<ActivityTypeSwitchProps> = ({ value, onChange }) => {
|
||||||
return (
|
return (
|
||||||
<View className='activity-type-switch'>
|
<View className={styles['activity-type-switch']}>
|
||||||
<View
|
<View
|
||||||
className={`switch-tab ${value === 'individual' ? 'active' : ''}`}
|
className={`${styles['switch-tab']} ${value === 'individual' ? styles.active : ''}`}
|
||||||
onClick={() => onChange('individual')}
|
onClick={() => onChange('individual')}
|
||||||
>
|
>
|
||||||
<View className='tab-icon'>
|
<View className={styles['tab-icon']}>
|
||||||
<Image src={images.ICON_PERSONAL} className='icon-style' />
|
<Image src={images.ICON_PERSONAL} className={styles['icon-style']} />
|
||||||
</View>
|
</View>
|
||||||
<Text className='tab-text'>个人约球</Text>
|
<Text className={styles['tab-text']}>个人约球</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
className={`switch-tab ${value === 'group' ? 'active' : ''}`}
|
className={`${styles['switch-tab']} ${value === 'group' ? styles.active : ''}`}
|
||||||
onClick={() => onChange('group')}
|
onClick={() => onChange('group')}
|
||||||
>
|
>
|
||||||
<Image src={images.ICON_CHANGDA} className='icon-style' />
|
<Image src={images.ICON_CHANGDA} className={styles['icon-style']} />
|
||||||
<Text className='tab-text'>畅打活动</Text>
|
<Text className={styles['tab-text']}>畅打活动</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
color: #1f2329;
|
color: #1f2329;
|
||||||
border: none;
|
border: none;
|
||||||
width: 154px;
|
width: 154px;
|
||||||
height: 36px;
|
height: 44px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
.common-popup__btn-confirm {
|
.common-popup__btn-confirm {
|
||||||
/* 使用按钮组件的 primary 样式 */
|
/* 使用按钮组件的 primary 样式 */
|
||||||
width: 154px;
|
width: 154px;
|
||||||
height: 36px;
|
height: 44px;
|
||||||
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||||
background: #000;
|
background: #000;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export { default, type CoverImage } from './CoverImageUpload'
|
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
width: 108px;
|
width: 108px;
|
||||||
height: 108px;
|
height: 108px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
margin-right: 12px;
|
margin-right: 6px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useMemo, useCallback } from 'react'
|
import React, { useMemo, useCallback } from 'react'
|
||||||
import { View, Text, Image, ScrollView } from '@tarojs/components'
|
import { View, Text, Image, ScrollView } from '@tarojs/components'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import './CoverImageUpload.scss'
|
import './ImageUpload.scss'
|
||||||
|
|
||||||
export interface CoverImage {
|
export interface CoverImage {
|
||||||
id: string
|
id: string
|
||||||
@@ -9,13 +9,13 @@ export interface CoverImage {
|
|||||||
tempFilePath?: string
|
tempFilePath?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CoverImageUploadProps {
|
interface ImageUploadProps {
|
||||||
images: CoverImage[]
|
images: CoverImage[]
|
||||||
onChange: (images: CoverImage[]) => void
|
onChange: (images: CoverImage[]) => void
|
||||||
maxCount?: number
|
maxCount?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const CoverImageUpload: React.FC<CoverImageUploadProps> = ({
|
const ImageUpload: React.FC<ImageUploadProps> = ({
|
||||||
images,
|
images,
|
||||||
onChange,
|
onChange,
|
||||||
maxCount = 9
|
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 React from 'react'
|
||||||
import { View, Text, Button } from '@tarojs/components'
|
import { View, Text, Button } from '@tarojs/components'
|
||||||
import './ParticipantsControl.scss'
|
import './NumberInterval.scss'
|
||||||
import { InputNumber } from '@nutui/nutui-react-taro'
|
import { InputNumber } from '@nutui/nutui-react-taro'
|
||||||
|
|
||||||
interface ParticipantsControlProps {
|
interface NumberIntervalProps {
|
||||||
minParticipants: number
|
minParticipants: number
|
||||||
maxParticipants: number
|
maxParticipants: number
|
||||||
onMinParticipantsChange: (value: number) => void
|
onMinParticipantsChange: (value: number) => void
|
||||||
onMaxParticipantsChange: (value: number) => void
|
onMaxParticipantsChange: (value: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ParticipantsControl: React.FC<ParticipantsControlProps> = ({
|
const NumberInterval: React.FC<NumberIntervalProps> = ({
|
||||||
minParticipants,
|
minParticipants,
|
||||||
maxParticipants,
|
maxParticipants,
|
||||||
onMinParticipantsChange,
|
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('')
|
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) => {
|
const markSearchText = (text: string) => {
|
||||||
return text.replace(searchValue, `<span style="color: #007AFF;">${searchValue}</span>`)
|
return text.replace(searchValue, `<span style="color: #007AFF;">${searchValue}</span>`)
|
||||||
@@ -215,9 +230,9 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
|
|||||||
</View>
|
</View>
|
||||||
<View className='stadium-item-right'>
|
<View className='stadium-item-right'>
|
||||||
<View className='stadium-name' dangerouslySetInnerHTML={{ __html: markSearchText(stadium.name) }}></View>
|
<View className='stadium-name' dangerouslySetInnerHTML={{ __html: markSearchText(stadium.name) }}></View>
|
||||||
<View className='stadium-address'>
|
<View className='stadium-address' >
|
||||||
<Text>{stadium.istance} · </Text>
|
<Text onClick={(e) => { e.stopPropagation(); handleItemLocation(stadium); }}>{stadium.istance} · </Text>
|
||||||
<Text>{stadium.address}</Text>
|
<Text onClick={(e) => { e.stopPropagation(); handleItemLocation(stadium); }}>{stadium.address}</Text>
|
||||||
<Image src={images.ICON_ARRORW_SMALL} className='stadium-map-icon' />
|
<Image src={images.ICON_ARRORW_SMALL} className='stadium-map-icon' />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -57,77 +57,79 @@
|
|||||||
|
|
||||||
// 场地类型
|
// 场地类型
|
||||||
.venue-type-section {
|
.venue-type-section {
|
||||||
border-bottom: 1px solid #e5e5e5;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
padding: 18px 20px 10px 20px;
|
padding: 18px 20px 10px 20px;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 12px;
|
|
||||||
display: block;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.option-buttons {
|
.option-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 16px;
|
||||||
padding: 0 15px;
|
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 {
|
.option-btn {
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
border: 1px solid #e0e0e0;
|
border: 1px solid #e0e0e0;
|
||||||
background: white;
|
background: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
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;
|
display: flex;
|
||||||
gap: 12px;
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
.option-btn {
|
align-items: center;
|
||||||
padding: 8px 16px;
|
height: 40px;
|
||||||
border-radius: 20px;
|
border-radius: 999px;
|
||||||
border: 1px solid #e0e0e0;
|
border: 0.5px solid rgba(0, 0, 0, 0.12);
|
||||||
background: white;
|
background: #FFF;
|
||||||
cursor: pointer;
|
font-weight: 500;
|
||||||
transition: all 0.2s;
|
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
background: #333;
|
background: #000;
|
||||||
border-color: #333;
|
border-color: #fff;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-weight: 600;
|
||||||
|
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||||
.option-text {
|
.option-text {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
@@ -141,36 +143,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 场地信息补充
|
|
||||||
.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;
|
|
||||||
color: #333;
|
|
||||||
background: white;
|
|
||||||
height: 44px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 底部按钮
|
// 底部按钮
|
||||||
.bottom-actions {
|
.bottom-actions {
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import React, { useState, useCallback } from 'react'
|
import React, { useState, useCallback } from 'react'
|
||||||
|
import Taro from '@tarojs/taro'
|
||||||
import { View, Text, Image } from '@tarojs/components'
|
import { View, Text, Image } from '@tarojs/components'
|
||||||
import images from '@/config/images'
|
import images from '@/config/images'
|
||||||
import './StadiumDetail.scss'
|
import './StadiumDetail.scss'
|
||||||
import TextareaTag from '@/components/TextareaTag'
|
import TextareaTag from '@/components/TextareaTag'
|
||||||
import CoverImageUpload, { type CoverImage } from '@/components/CoverImageUpload'
|
import CoverImageUpload, { type CoverImage } from '@/components/ImageUpload'
|
||||||
|
|
||||||
export interface Stadium {
|
export interface Stadium {
|
||||||
id?: string
|
id?: string
|
||||||
name: string
|
name: string
|
||||||
address?: string
|
address?: string
|
||||||
|
longitude?: number
|
||||||
|
latitude?: number
|
||||||
|
istance?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StadiumDetailProps {
|
interface StadiumDetailProps {
|
||||||
@@ -21,56 +25,108 @@ const stadiumInfo = [
|
|||||||
{
|
{
|
||||||
label: '场地类型',
|
label: '场地类型',
|
||||||
options: ['室内', '室外', '室外雨棚'],
|
options: ['室内', '室外', '室外雨棚'],
|
||||||
|
prop: 'venueType',
|
||||||
type: 'tags'
|
type: 'tags'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '地面材质',
|
label: '地面材质',
|
||||||
options: ['硬地', '红土', '草地'],
|
options: ['硬地', '红土', '草地'],
|
||||||
|
prop: 'groundMaterial',
|
||||||
type: 'tags'
|
type: 'tags'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '场地信息补充',
|
label: '场地信息补充',
|
||||||
options: ['1号场', '2号场', '3号场', '4号场', '有空调', '6号场'],
|
options: ['1号场', '2号场', '3号场', '4号场', '有空调', '6号场','6号场'],
|
||||||
|
prop: 'additionalInfo',
|
||||||
type: 'textareaTag'
|
type: 'textareaTag'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '场地预定截图',
|
label: '场地预定截图',
|
||||||
options: ['有其他场地信息可备注'],
|
options: ['有其他场地信息可备注'],
|
||||||
|
prop: 'imagesList',
|
||||||
type: 'image'
|
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> = ({
|
const StadiumDetail: React.FC<StadiumDetailProps> = ({
|
||||||
stadium,
|
stadium,
|
||||||
onBack,
|
|
||||||
onConfirm
|
|
||||||
}) => {
|
}) => {
|
||||||
const [venueType, setVenueType] = useState('室内')
|
const [formData, setFormData] = useState({
|
||||||
const [groundMaterial, setGroundMaterial] = useState('硬地')
|
stadiumName: stadium.name,
|
||||||
const [additionalInfo, setAdditionalInfo] = useState('')
|
stadiumAddress: stadium.address,
|
||||||
const [imagesList, setImagesList] = useState<CoverImage[]>([])
|
stadiumLongitude: stadium.longitude,
|
||||||
|
stadiumLatitude: stadium.latitude,
|
||||||
|
istance: stadium.istance,
|
||||||
|
venueType: '室内',
|
||||||
|
groundMaterial: '硬地',
|
||||||
|
additionalInfo: '',
|
||||||
|
imagesList: [] as CoverImage[]
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
const handleConfirm = () => {
|
|
||||||
onConfirm(stadium, venueType, groundMaterial, additionalInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleMapLocation = () => {
|
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 updateFormData = useCallback((prop: string, value: any) => {
|
||||||
|
setFormData(prev => ({ ...prev, [prop]: value }))
|
||||||
|
}, [])
|
||||||
|
|
||||||
const getSelectedByLabel = useCallback((label: string) => {
|
const getSelectedByLabel = useCallback((label: string) => {
|
||||||
if (label === '场地类型') return venueType
|
if (label === '场地类型') return formData.venueType
|
||||||
if (label === '地面材质') return groundMaterial
|
if (label === '地面材质') return formData.groundMaterial
|
||||||
return ''
|
return ''
|
||||||
}, [venueType, groundMaterial])
|
}, [formData.venueType, formData.groundMaterial])
|
||||||
|
|
||||||
const setSelectedByLabel = useCallback((label: string, value: string) => {
|
|
||||||
if (label === '场地类型') {
|
|
||||||
setVenueType(value)
|
|
||||||
} else if (label === '地面材质') {
|
|
||||||
setGroundMaterial(value)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
console.log(stadium,'stadiumstadium');
|
console.log(stadium,'stadiumstadium');
|
||||||
return (
|
return (
|
||||||
@@ -84,9 +140,10 @@ const StadiumDetail: React.FC<StadiumDetailProps> = ({
|
|||||||
<Image src={images.ICON_STADIUM} className='stadium-icon' />
|
<Image src={images.ICON_STADIUM} className='stadium-icon' />
|
||||||
</View>
|
</View>
|
||||||
<View className='stadium-item-right'>
|
<View className='stadium-item-right'>
|
||||||
<View className='stadium-name'>{stadium.name}</View>
|
<View className='stadium-name'>{formData.stadiumName}</View>
|
||||||
<View className='stadium-address'>
|
<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' />
|
<Image src={images.ICON_ARRORW_SMALL} className='stadium-map-icon' />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -96,54 +153,43 @@ const StadiumDetail: React.FC<StadiumDetailProps> = ({
|
|||||||
if (item.type === 'tags') {
|
if (item.type === 'tags') {
|
||||||
const selected = getSelectedByLabel(item.label)
|
const selected = getSelectedByLabel(item.label)
|
||||||
return (
|
return (
|
||||||
<View className='venue-type-section' key={item.label}>
|
<SectionContainer key={item.label} title={item.label} prop={item.prop}>
|
||||||
<Text className='section-title'>{item.label}</Text>
|
|
||||||
<View className='option-buttons'>
|
|
||||||
{item.options.map((opt) => (
|
{item.options.map((opt) => (
|
||||||
<View
|
<View
|
||||||
key={opt}
|
key={opt}
|
||||||
className={`option-btn ${selected === opt ? 'selected' : ''}`}
|
className={`option-btn ${selected === opt ? 'selected' : ''}`}
|
||||||
onClick={() => setSelectedByLabel(item.label, opt)}
|
onClick={() => updateFormData(item.prop, opt)}
|
||||||
>
|
>
|
||||||
<Text className='option-text'>{opt}</Text>
|
<Text className='option-text'>{opt}</Text>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</SectionContainer>
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.type === 'textareaTag') {
|
if (item.type === 'textareaTag') {
|
||||||
return (
|
return (
|
||||||
<View className='venue-type-section' key={item.label}>
|
<SectionContainer key={item.label} title={item.label} prop={item.prop}>
|
||||||
<Text className='section-title'>{item.label}</Text>
|
<View className='textarea-tag-container'>
|
||||||
<View className='option-buttons'>
|
|
||||||
<TextareaTag
|
<TextareaTag
|
||||||
key={item.label}
|
value={formData.additionalInfo}
|
||||||
value={additionalInfo}
|
onChange={(value) => updateFormData(item.prop, value)}
|
||||||
onChange={setAdditionalInfo}
|
|
||||||
placeholder='有其他场地信息可备注'
|
placeholder='有其他场地信息可备注'
|
||||||
options={(item.options || []).map((o) => ({ label: o, value: o }))}
|
options={(item.options || []).map((o) => ({ label: o, value: o }))}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</SectionContainer>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.type === 'image') {
|
if (item.type === 'image') {
|
||||||
return (
|
return (
|
||||||
<View className='venue-type-section' key={item.label}>
|
<SectionContainer key={item.label} title={item.label} prop={item.prop}>
|
||||||
<Text className='section-title'>{item.label}</Text>
|
|
||||||
<View className='option-buttons'>
|
|
||||||
<CoverImageUpload
|
<CoverImageUpload
|
||||||
key={item.label}
|
images={formData.imagesList}
|
||||||
images={imagesList}
|
onChange={(images) => updateFormData(item.prop, images)}
|
||||||
onChange={setImagesList}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</SectionContainer>
|
||||||
</View>
|
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
.input-wrapper {
|
.input-wrapper {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
.additional-input {
|
.additional-input {
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
.nut-checkbox{
|
.nut-checkbox{
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
|
||||||
.nut-checkbox-button{
|
.nut-checkbox-button{
|
||||||
border: 1px solid theme.$primary-border-color;
|
border: 1px solid theme.$primary-border-color;
|
||||||
color: theme.$primary-color;
|
color: theme.$primary-color;
|
||||||
@@ -43,6 +45,7 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
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'
|
import './index.scss'
|
||||||
|
|
||||||
interface TitleInputProps {
|
interface TitleInputProps {
|
||||||
@@ -18,13 +18,14 @@ const TitleInput: React.FC<TitleInputProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<View className='title-input-wrapper'>
|
<View className='title-input-wrapper'>
|
||||||
<Input
|
<TextArea
|
||||||
className='title-input'
|
className='title-input'
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
placeholderClass='title-placeholder'
|
|
||||||
value={value}
|
value={value}
|
||||||
onInput={(e) => onChange(e.detail.value)}
|
onInput={(e) => onChange(e.detail.value)}
|
||||||
maxlength={maxLength}
|
maxlength={maxLength}
|
||||||
|
autoSize={true}
|
||||||
|
placeholderClass='title-input-placeholder'
|
||||||
/>
|
/>
|
||||||
<View className='char-count'>{value.length}/{maxLength}</View>
|
<View className='char-count'>{value.length}/{maxLength}</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,35 +1,34 @@
|
|||||||
.title-input-wrapper {
|
.title-input-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-around;
|
||||||
.title-input {
|
.title-input {
|
||||||
width: 100%;
|
width: 83%;
|
||||||
height: 44px;
|
min-height: 44px;
|
||||||
padding: 0 16px;
|
padding: 12px 16px;
|
||||||
border: 1px solid #e5e5e5;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 44px;
|
line-height: 1.4;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
&:focus {
|
resize: none;
|
||||||
border-color: #007aff;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-placeholder {
|
// 使用 placeholderClass 来控制 placeholder 样式
|
||||||
color: #999;
|
.title-input-placeholder {
|
||||||
font-size: 16px;
|
color: rgba(60, 60, 67, 0.60) !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
font-weight: normal !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.char-count {
|
.char-count {
|
||||||
position: absolute;
|
|
||||||
right: 16px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #999;
|
color: #999;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
padding-top: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import ActivityTypeSwitch from './ActivityTypeSwitch'
|
import ActivityTypeSwitch from './ActivityTypeSwitch'
|
||||||
import TextareaTag from './TextareaTag'
|
import TextareaTag from './TextareaTag'
|
||||||
import FormSwitch from './FormSwitch'
|
import FormSwitch from './FormSwitch'
|
||||||
import CoverImageUpload from './CoverImageUpload'
|
import ImageUpload from './ImageUpload'
|
||||||
import FormBasicInfo from './FormBasicInfo'
|
import FormBasicInfo from './FormBasicInfo'
|
||||||
import Range from './Range'
|
import Range from './Range'
|
||||||
import ParticipantsControl from './ParticipantsControl'
|
import NumberInterval from './NumberInterval'
|
||||||
import { SelectStadium, StadiumDetail } from './SelectStadium'
|
import { SelectStadium, StadiumDetail } from './SelectStadium'
|
||||||
import TimeSelector from './TimeSelector'
|
import TimeSelector from './TimeSelector'
|
||||||
import TitleInput from './TitleInput'
|
import TitleInput from './TitleInput'
|
||||||
@@ -14,10 +14,10 @@ export {
|
|||||||
ActivityTypeSwitch,
|
ActivityTypeSwitch,
|
||||||
TextareaTag,
|
TextareaTag,
|
||||||
FormSwitch,
|
FormSwitch,
|
||||||
CoverImageUpload,
|
ImageUpload,
|
||||||
FormBasicInfo,
|
FormBasicInfo,
|
||||||
Range,
|
Range,
|
||||||
ParticipantsControl,
|
NumberInterval,
|
||||||
SelectStadium,
|
SelectStadium,
|
||||||
TimeSelector,
|
TimeSelector,
|
||||||
TitleInput,
|
TitleInput,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { getNTRPRangeText, type NTRPRange } from './NTRPSlider'
|
|
||||||
import { type TimeRange } from './TimeSelector'
|
import { type TimeRange } from './TimeSelector'
|
||||||
import { type Stadium } from './SelectStadium'
|
import { type Stadium } from './SelectStadium'
|
||||||
import { type ActivityType } from './ActivityTypeSwitch'
|
import { type ActivityType } from './ActivityTypeSwitch'
|
||||||
import { type CoverImage } from './CoverImageUpload'
|
import { type CoverImage } from './ImageUpload'
|
||||||
export type { NTRPRange, getNTRPRangeText, TimeRange, Stadium, ActivityType, CoverImage }
|
export type { TimeRange, Stadium, ActivityType, CoverImage }
|
||||||
@@ -7,7 +7,6 @@ export type EnvType = 'development' | 'test' | 'production'
|
|||||||
export interface EnvConfig {
|
export interface EnvConfig {
|
||||||
name: string
|
name: string
|
||||||
apiBaseURL: string
|
apiBaseURL: string
|
||||||
apiVersion: string
|
|
||||||
timeout: number
|
timeout: number
|
||||||
enableLog: boolean
|
enableLog: boolean
|
||||||
enableMock: boolean
|
enableMock: boolean
|
||||||
@@ -18,8 +17,7 @@ const envConfigs: Record<EnvType, EnvConfig> = {
|
|||||||
// 开发环境
|
// 开发环境
|
||||||
development: {
|
development: {
|
||||||
name: '开发环境',
|
name: '开发环境',
|
||||||
apiBaseURL: 'https://dev-api.playballtogether.com',
|
apiBaseURL: 'https://sit.light120.com',
|
||||||
apiVersion: 'v1',
|
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
enableLog: true,
|
enableLog: true,
|
||||||
enableMock: true
|
enableMock: true
|
||||||
@@ -28,8 +26,7 @@ const envConfigs: Record<EnvType, EnvConfig> = {
|
|||||||
// 测试环境
|
// 测试环境
|
||||||
test: {
|
test: {
|
||||||
name: '测试环境',
|
name: '测试环境',
|
||||||
apiBaseURL: 'https://test-api.playballtogether.com',
|
apiBaseURL: 'https://sit.light120.com',
|
||||||
apiVersion: 'v1',
|
|
||||||
timeout: 12000,
|
timeout: 12000,
|
||||||
enableLog: true,
|
enableLog: true,
|
||||||
enableMock: false
|
enableMock: false
|
||||||
@@ -38,8 +35,7 @@ const envConfigs: Record<EnvType, EnvConfig> = {
|
|||||||
// 生产环境
|
// 生产环境
|
||||||
production: {
|
production: {
|
||||||
name: '生产环境',
|
name: '生产环境',
|
||||||
apiBaseURL: 'https://api.playballtogether.com',
|
apiBaseURL: 'https://sit.light120.com',
|
||||||
apiVersion: 'v1',
|
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
enableLog: false,
|
enableLog: false,
|
||||||
enableMock: false
|
enableMock: false
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export const publishBallFormSchema: FormFieldConfig[] = [
|
|||||||
placeholder: '好的标题更吸引人哦',
|
placeholder: '好的标题更吸引人哦',
|
||||||
required: true,
|
required: true,
|
||||||
props: {
|
props: {
|
||||||
maxLength: 20
|
maxLength: 80
|
||||||
},
|
},
|
||||||
rules: [
|
rules: [
|
||||||
{ required: true, message: '请输入活动标题' },
|
{ required: true, message: '请输入活动标题' },
|
||||||
|
|||||||
@@ -13,4 +13,8 @@ export default {
|
|||||||
ICON_STADIUM: require('@/static/publishBall/icon-stadium.svg'),
|
ICON_STADIUM: require('@/static/publishBall/icon-stadium.svg'),
|
||||||
ICON_ARRORW_SMALL: require('@/static/publishBall/icon-arrow-small.svg'),
|
ICON_ARRORW_SMALL: require('@/static/publishBall/icon-arrow-small.svg'),
|
||||||
ICON_MAP_SEARCH: require('@/static/publishBall/icon-map-search.svg'),
|
ICON_MAP_SEARCH: require('@/static/publishBall/icon-map-search.svg'),
|
||||||
|
ICON_HEART_CIRCLE: require('@/static/publishBall/icon-heartcircle.png'),
|
||||||
|
ICON_ADD: require('@/static/publishBall/icon-add.svg'),
|
||||||
|
ICON_COPY: require('@/static/publishBall/icon-arrow-right.svg'),
|
||||||
|
ICON_DELETE: require('@/static/publishBall/icon-delete.svg')
|
||||||
}
|
}
|
||||||
1
src/pages/index/index.module.scss
Normal file
1
src/pages/index/index.module.scss
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -8,12 +8,28 @@
|
|||||||
&__scroll {
|
&__scroll {
|
||||||
height: calc(100vh - 120px);
|
height: calc(100vh - 120px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 4px 16px 0 16px;
|
padding: 4px 16px 72px 16px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
padding-bottom: 72px;
|
}
|
||||||
|
&__add{
|
||||||
|
margin-top: 2px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px dashed rgba(22, 24, 35, 0.12);
|
||||||
|
display: flex;
|
||||||
|
width: 343px;
|
||||||
|
height: 60px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
color: rgba(60, 60, 67, 0.50);
|
||||||
|
|
||||||
|
&-icon{
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.activity-type-switch{
|
.activity-type-switch{
|
||||||
padding: 4px 16px 0 16px;
|
padding: 4px 16px 0 16px;
|
||||||
@@ -22,6 +38,60 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 场次标题行
|
||||||
|
.session-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 22px 4px;
|
||||||
|
|
||||||
|
.session-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: theme.$primary-color;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.session-delete {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
&-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-action-btn {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 0.5px solid rgba(0, 0, 0, 0.16);
|
||||||
|
background: #000;
|
||||||
|
box-shadow: 0 8px 64px 0 rgba(0, 0, 0, 0.10);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 8px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
.action-icon {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 标题区域 - 独立白色块
|
// 标题区域 - 独立白色块
|
||||||
@@ -162,6 +232,75 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除确认弹窗
|
||||||
|
.delete-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10000;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
background: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
margin: 0 32px;
|
||||||
|
max-width: 320px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
display: block;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: theme.$primary-color;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__desc {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(60, 60, 67, 0.6);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.delete-modal__btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
color: rgba(60, 60, 67, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
background: #FF3B30;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 旋转动画
|
// 旋转动画
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { View, Text, Input, Button, Image, ScrollView, Picker } from '@tarojs/components'
|
import { View, Text, Button, Image } from '@tarojs/components'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import ActivityTypeSwitch, { type ActivityType } from '../../components/ActivityTypeSwitch'
|
import ActivityTypeSwitch, { type ActivityType } from '../../components/ActivityTypeSwitch'
|
||||||
import PublishForm from './publishForm'
|
import PublishForm from './publishForm'
|
||||||
import { publishBallFormSchema } from '../../config/formSchema/publishBallFormSchema';
|
import { publishBallFormSchema } from '../../config/formSchema/publishBallFormSchema';
|
||||||
import { PublishBallFormData } from '../../../types/publishBall';
|
import { PublishBallFormData } from '../../../types/publishBall';
|
||||||
|
import PublishService from '@/services/publishService';
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
|
import images from '@/config/images'
|
||||||
|
|
||||||
const PublishBall: React.FC = () => {
|
const PublishBall: React.FC = () => {
|
||||||
const [formData, setFormData] = useState<PublishBallFormData>({
|
const [activityType, setActivityType] = useState<ActivityType>('individual')
|
||||||
activityType: 'individual', // 默认值
|
const [formData, setFormData] = useState<PublishBallFormData[]>([
|
||||||
|
{
|
||||||
title: '',
|
title: '',
|
||||||
timeRange: {
|
timeRange: {
|
||||||
startDate: '2025-11-23',
|
startDate: '2025-11-23',
|
||||||
@@ -25,40 +27,116 @@ const PublishBall: React.FC = () => {
|
|||||||
ntpLevel: [2.0, 4.0],
|
ntpLevel: [2.0, 4.0],
|
||||||
additionalRequirements: '',
|
additionalRequirements: '',
|
||||||
autoDegrade: false
|
autoDegrade: false
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 删除确认弹窗状态
|
||||||
|
const [deleteConfirm, setDeleteConfirm] = useState<{
|
||||||
|
visible: boolean;
|
||||||
|
index: number;
|
||||||
|
}>({
|
||||||
|
visible: false,
|
||||||
|
index: -1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 更新表单数据
|
// 更新表单数据
|
||||||
const updateFormData = (key: keyof PublishBallFormData, value: any) => {
|
const updateFormData = (key: keyof PublishBallFormData, value: any, index: number) => {
|
||||||
setFormData(prev => ({
|
setFormData(prev => {
|
||||||
...prev,
|
const newData = [...prev]
|
||||||
[key]: value
|
newData[index] = { ...newData[index], [key]: value }
|
||||||
}))
|
return newData
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 处理活动类型变化
|
// 处理活动类型变化
|
||||||
const handleActivityTypeChange = (type: ActivityType) => {
|
const handleActivityTypeChange = (type: ActivityType) => {
|
||||||
updateFormData('activityType', type)
|
setActivityType(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
setFormData(prev => [...prev, {
|
||||||
|
title: '',
|
||||||
|
timeRange: {
|
||||||
|
startDate: '2025-11-23',
|
||||||
|
startTime: '08:00',
|
||||||
|
endTime: '10:00'
|
||||||
|
},
|
||||||
|
fee: '',
|
||||||
|
location: '',
|
||||||
|
gameplay: '',
|
||||||
|
minParticipants: 1,
|
||||||
|
maxParticipants: 4,
|
||||||
|
ntpLevel: [2.0, 4.0],
|
||||||
|
additionalRequirements: '',
|
||||||
|
autoDegrade: false
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制上一场数据
|
||||||
|
const handleCopyPrevious = (index: number) => {
|
||||||
|
if (index > 0) {
|
||||||
|
setFormData(prev => {
|
||||||
|
const newData = [...prev]
|
||||||
|
newData[index] = { ...newData[index - 1] }
|
||||||
|
return newData
|
||||||
|
})
|
||||||
|
Taro.showToast({
|
||||||
|
title: '已复制上一场数据',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除确认弹窗
|
||||||
|
const showDeleteConfirm = (index: number) => {
|
||||||
|
setDeleteConfirm({
|
||||||
|
visible: true,
|
||||||
|
index
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭删除确认弹窗
|
||||||
|
const closeDeleteConfirm = () => {
|
||||||
|
setDeleteConfirm({
|
||||||
|
visible: false,
|
||||||
|
index: -1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认删除
|
||||||
|
const confirmDelete = () => {
|
||||||
|
if (deleteConfirm.index >= 0) {
|
||||||
|
setFormData(prev => prev.filter((_, index) => index !== deleteConfirm.index))
|
||||||
|
closeDeleteConfirm()
|
||||||
|
Taro.showToast({
|
||||||
|
title: '已删除该场次',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 提交表单
|
// 提交表单
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
// 基础验证
|
// 基础验证
|
||||||
if (!formData.title.trim()) {
|
|
||||||
Taro.showToast({
|
|
||||||
title: '请输入活动标题',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO: 实现提交逻辑
|
|
||||||
|
|
||||||
|
// TODO: 实现提交逻辑
|
||||||
|
const res = await PublishService.createPersonal({
|
||||||
|
"title": "周末网球约球",
|
||||||
|
"venue_id": 1,
|
||||||
|
"creator_id": 1,
|
||||||
|
"game_date": "2024-06-15",
|
||||||
|
"start_time": "14:00",
|
||||||
|
"end_time": "16:00",
|
||||||
|
"max_participants": 4,
|
||||||
|
"current_participants": 2,
|
||||||
|
"ntrp_level": "2.0-4.0",
|
||||||
|
"play_style": "单打",
|
||||||
|
"description": "周末约球,欢迎参加",
|
||||||
|
})
|
||||||
|
console.log(res);
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '发布成功',
|
title: '发布成功',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
@@ -70,15 +148,82 @@ const PublishBall: React.FC = () => {
|
|||||||
{/* 活动类型切换 */}
|
{/* 活动类型切换 */}
|
||||||
<View className={styles['activity-type-switch']}>
|
<View className={styles['activity-type-switch']}>
|
||||||
<ActivityTypeSwitch
|
<ActivityTypeSwitch
|
||||||
value={formData.activityType}
|
value={activityType}
|
||||||
onChange={handleActivityTypeChange}
|
onChange={handleActivityTypeChange}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className={styles['publish-ball__scroll']}>
|
<View className={styles['publish-ball__scroll']}>
|
||||||
<PublishForm formData={formData} onChange={updateFormData} optionsConfig={publishBallFormSchema} />
|
{
|
||||||
|
formData.map((item, index) => (
|
||||||
|
<View key={index}>
|
||||||
|
{/* 场次标题行 */}
|
||||||
|
{activityType === 'group' && index > 0 && (
|
||||||
|
<View className={styles['session-header']}>
|
||||||
|
<View className={styles['session-title']}>
|
||||||
|
第{index + 1}场
|
||||||
|
<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']}
|
||||||
|
onClick={() => handleCopyPrevious(index)}
|
||||||
|
>
|
||||||
|
复制上一场
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<PublishForm
|
||||||
|
formData={item}
|
||||||
|
onChange={(key, value) => updateFormData(key, value, index)}
|
||||||
|
optionsConfig={publishBallFormSchema}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
{
|
||||||
|
activityType === 'group' && (
|
||||||
|
<View className={styles['publish-ball__add']} onClick={handleAdd}>
|
||||||
|
<Image src={images.ICON_ADD} className={styles['publish-ball__add-icon']} />
|
||||||
|
再添加一场
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* 删除确认弹窗 */}
|
||||||
|
{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>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 完成按钮 */}
|
{/* 完成按钮 */}
|
||||||
<View className={styles['submit-section']}>
|
<View className={styles['submit-section']}>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState } from 'react'
|
|||||||
import { View, Text } from '@tarojs/components'
|
import { View, Text } from '@tarojs/components'
|
||||||
|
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import { CoverImageUpload, Range, TimeSelector, TextareaTag, SelectStadium, ParticipantsControl, TitleInput, FormBasicInfo, FormSwitch } from '../../components'
|
import { ImageUpload, Range, TimeSelector, TextareaTag, SelectStadium, NumberInterval, TitleInput, FormBasicInfo, FormSwitch } from '../../components'
|
||||||
import { type Stadium, type CoverImage } from '../../components/index.types'
|
import { type Stadium, type CoverImage } from '../../components/index.types'
|
||||||
import { FormFieldConfig, FieldType } from '../../config/formSchema/publishBallFormSchema'
|
import { FormFieldConfig, FieldType } from '../../config/formSchema/publishBallFormSchema'
|
||||||
import { PublishBallFormData } from '../../../types/publishBall';
|
import { PublishBallFormData } from '../../../types/publishBall';
|
||||||
@@ -15,15 +15,15 @@ const componentMap = {
|
|||||||
[FieldType.TIMEINTERVAL]: TimeSelector,
|
[FieldType.TIMEINTERVAL]: TimeSelector,
|
||||||
[FieldType.RANGE]: Range,
|
[FieldType.RANGE]: Range,
|
||||||
[FieldType.TEXTAREATAG]: TextareaTag,
|
[FieldType.TEXTAREATAG]: TextareaTag,
|
||||||
[FieldType.NUMBERINTERVAL]: ParticipantsControl,
|
[FieldType.NUMBERINTERVAL]: NumberInterval,
|
||||||
[FieldType.UPLOADIMAGE]: CoverImageUpload,
|
[FieldType.UPLOADIMAGE]: ImageUpload,
|
||||||
[FieldType.ACTIVITYINFO]: FormBasicInfo,
|
[FieldType.ACTIVITYINFO]: FormBasicInfo,
|
||||||
[FieldType.CHECKBOX]: FormSwitch,
|
[FieldType.CHECKBOX]: FormSwitch,
|
||||||
}
|
}
|
||||||
|
|
||||||
const PublishForm: React.FC<{
|
const PublishForm: React.FC<{
|
||||||
formData: PublishBallFormData,
|
formData: PublishBallFormData,
|
||||||
onChange: (key: keyof PublishBallFormData, value: any) => void,
|
onChange: (key: keyof PublishBallFormData, value: any, index?: number) => void,
|
||||||
optionsConfig: FormFieldConfig[] }> = ({ formData, onChange, optionsConfig }) => {
|
optionsConfig: FormFieldConfig[] }> = ({ formData, onChange, optionsConfig }) => {
|
||||||
const [coverImages, setCoverImages] = useState<CoverImage[]>([])
|
const [coverImages, setCoverImages] = useState<CoverImage[]>([])
|
||||||
const [showStadiumSelector, setShowStadiumSelector] = useState(false)
|
const [showStadiumSelector, setShowStadiumSelector] = useState(false)
|
||||||
@@ -102,7 +102,7 @@ const PublishForm: React.FC<{
|
|||||||
console.log(optionProps, item.label, formData[item.key]);
|
console.log(optionProps, item.label, formData[item.key]);
|
||||||
if (item.type === FieldType.UPLOADIMAGE) {
|
if (item.type === FieldType.UPLOADIMAGE) {
|
||||||
/* 活动封面 */
|
/* 活动封面 */
|
||||||
return <CoverImageUpload
|
return <ImageUpload
|
||||||
images={coverImages}
|
images={coverImages}
|
||||||
onChange={handleCoverImagesChange}
|
onChange={handleCoverImagesChange}
|
||||||
{...item.props}
|
{...item.props}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class HttpService {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// 使用环境配置
|
// 使用环境配置
|
||||||
this.baseURL = `${envConfig.apiBaseURL}/api/${envConfig.apiVersion}`
|
this.baseURL = `${envConfig.apiBaseURL}/api/`
|
||||||
this.timeout = envConfig.timeout
|
this.timeout = envConfig.timeout
|
||||||
this.enableLog = envConfig.enableLog
|
this.enableLog = envConfig.enableLog
|
||||||
|
|
||||||
@@ -77,7 +77,6 @@ class HttpService {
|
|||||||
private buildHeaders(config: RequestConfig): Record<string, string> {
|
private buildHeaders(config: RequestConfig): Record<string, string> {
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Environment': envConfig.name, // 添加环境标识
|
|
||||||
...config.headers
|
...config.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +210,7 @@ class HttpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log(this.buildHeaders(config), 1111);
|
||||||
const requestConfig = {
|
const requestConfig = {
|
||||||
url: fullUrl,
|
url: fullUrl,
|
||||||
method: method,
|
method: method,
|
||||||
|
|||||||
38
src/services/publishService.ts
Normal file
38
src/services/publishService.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import httpService from './httpService'
|
||||||
|
import type { ApiResponse } from './httpService'
|
||||||
|
|
||||||
|
// 用户接口
|
||||||
|
export interface PublishBallData {
|
||||||
|
title: string,
|
||||||
|
venue_id: number,
|
||||||
|
creator_id: number,
|
||||||
|
game_date: string,
|
||||||
|
start_time: string,
|
||||||
|
end_time: string,
|
||||||
|
max_participants: number,
|
||||||
|
current_participants: number,
|
||||||
|
ntrp_level: string,
|
||||||
|
play_style: string,
|
||||||
|
description: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应接口
|
||||||
|
export interface Response {
|
||||||
|
code: string
|
||||||
|
message: string
|
||||||
|
data: any
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发布球局类
|
||||||
|
class PublishService {
|
||||||
|
// 用户登录
|
||||||
|
async createPersonal(data: PublishBallData): Promise<ApiResponse<Response>> {
|
||||||
|
return httpService.post('/games/create', data, {
|
||||||
|
showLoading: true,
|
||||||
|
loadingText: '发布中...'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出认证服务实例
|
||||||
|
export default new PublishService()
|
||||||
5
src/static/publishBall/icon-delete.svg
Normal file
5
src/static/publishBall/icon-delete.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.26661 7.23309C7.26661 6.90172 6.99798 6.63309 6.66661 6.63309C6.33524 6.63309 6.06661 6.90172 6.06661 7.23309V11.8998C6.06661 12.2311 6.33524 12.4998 6.66661 12.4998C6.99798 12.4998 7.26661 12.2311 7.26661 11.8998V7.23309Z" fill="black"/>
|
||||||
|
<path d="M9.33331 6.63309C9.66468 6.63309 9.93331 6.90172 9.93331 7.23309V11.8998C9.93331 12.2311 9.66468 12.4998 9.33331 12.4998C9.00194 12.4998 8.73331 12.2311 8.73331 11.8998V7.23309C8.73331 6.90172 9.00194 6.63309 9.33331 6.63309Z" fill="black"/>
|
||||||
|
<path d="M4.97805 3.13887C5.08566 2.53447 5.6112 2.09424 6.2251 2.09424H9.77486C10.3888 2.09424 10.9143 2.53447 11.0219 3.13887L11.1979 4.12753L13.5 4.12753C13.8313 4.12753 14.1 4.39616 14.1 4.72753C14.1 5.0589 13.8313 5.32753 13.5 5.32753H12.7317L12.2463 13.3242C12.1844 14.3446 11.3388 15.1404 10.3165 15.1404H5.68341C4.66115 15.1404 3.81556 14.3446 3.75363 13.3242L3.26828 5.32753H2.49996C2.16859 5.32753 1.89996 5.0589 1.89996 4.72753C1.89996 4.39616 2.16859 4.12753 2.49996 4.12753H4.80202L4.97805 3.13887ZM9.97907 4.12753L9.8405 3.34922C9.83483 3.31741 9.80717 3.29424 9.77486 3.29424H6.2251C6.19279 3.29424 6.16513 3.31741 6.15947 3.34922L6.0209 4.12753L9.97907 4.12753ZM4.47083 5.33309L4.95142 13.2515C4.97491 13.6386 5.29565 13.9404 5.68341 13.9404H10.3165C10.7043 13.9404 11.025 13.6386 11.0485 13.2515L11.5291 5.33309H4.47083Z" fill="black"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/static/publishBall/icon-heartcircle.png
Normal file
BIN
src/static/publishBall/icon-heartcircle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
Reference in New Issue
Block a user