576 lines
18 KiB
TypeScript
576 lines
18 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
||
import { View, Text, Button, Image } from '@tarojs/components'
|
||
import { Checkbox } from '@nutui/nutui-react-taro'
|
||
import Taro from '@tarojs/taro'
|
||
import { type ActivityType } from '../../components/ActivityTypeSwitch'
|
||
import CommonDialog from '../../components/CommonDialog'
|
||
import { withAuth } from '@/components'
|
||
import PublishForm from './publishForm'
|
||
import { FormFieldConfig, publishBallFormSchema } from '../../config/formSchema/publishBallFormSchema';
|
||
import { PublishBallFormData } from '../../../types/publishBall';
|
||
import PublishService from '@/services/publishService';
|
||
import { getNextHourTime, getEndTime, delay } from '@/utils';
|
||
import { useGlobalState } from "@/store/global"
|
||
import GeneralNavbar from "@/components/GeneralNavbar"
|
||
import images from '@/config/images'
|
||
import { useUserInfo } from '@/store/userStore'
|
||
import styles from './index.module.scss'
|
||
import dayjs from 'dayjs'
|
||
import { usePublishBallData } from '@/store/publishBallStore'
|
||
|
||
const defaultFormData: PublishBallFormData = {
|
||
title: '',
|
||
image_list: [],
|
||
timeRange: {
|
||
start_time: getNextHourTime(),
|
||
end_time: getEndTime(getNextHourTime())
|
||
},
|
||
activityInfo: {
|
||
play_type: '不限',
|
||
price: '',
|
||
venue_id: null,
|
||
location_name: '',
|
||
location: '',
|
||
latitude: '',
|
||
longitude: '',
|
||
court_type: '',
|
||
court_surface: '',
|
||
venue_description_tag: [],
|
||
venue_description: '',
|
||
venue_image_list: [],
|
||
},
|
||
players: {
|
||
min: 1,
|
||
max: 1,
|
||
organizer_joined: true
|
||
},
|
||
skill_level: [1.0, 5.0],
|
||
descriptionInfo: {
|
||
description: '',
|
||
description_tag: [],
|
||
},
|
||
is_substitute_supported: true,
|
||
wechat: {
|
||
is_wechat_contact: true,
|
||
wechat_contact: '',
|
||
default_wechat_contact: ''
|
||
}
|
||
|
||
}
|
||
|
||
const PublishBall: React.FC = () => {
|
||
const [activityType, setActivityType] = useState<ActivityType>('individual')
|
||
const [isSubmitDisabled, setIsSubmitDisabled] = useState(false)
|
||
const userInfo = useUserInfo();
|
||
const publishAiData = usePublishBallData()
|
||
const { statusNavbarHeightInfo } = useGlobalState();
|
||
// 获取页面参数并设置导航标题
|
||
const [optionsConfig, setOptionsConfig] = useState<FormFieldConfig[]>(publishBallFormSchema)
|
||
console.log(userInfo, 'userInfo');
|
||
const [formData, setFormData] = useState<PublishBallFormData[]>([defaultFormData])
|
||
const [checked, setChecked] = useState(true)
|
||
const [titleBar, setTitleBar] = useState('发布')
|
||
// 删除确认弹窗状态
|
||
const [deleteConfirm, setDeleteConfirm] = useState<{
|
||
visible: boolean;
|
||
index: number;
|
||
}>({
|
||
visible: false,
|
||
index: -1
|
||
})
|
||
|
||
// 更新表单数据
|
||
const updateFormData = (key: keyof PublishBallFormData, value: any, index: number) => {
|
||
console.log(key, value, index, 'key, value, index');
|
||
setFormData(prev => {
|
||
const newData = [...prev]
|
||
newData[index] = { ...newData[index], [key]: value }
|
||
console.log(newData, 'newData');
|
||
return newData
|
||
})
|
||
}
|
||
|
||
|
||
|
||
// 检查相邻两组数据是否相同
|
||
const checkAdjacentDataSame = (formDataArray: PublishBallFormData[]) => {
|
||
if (formDataArray.length < 2) return false
|
||
|
||
const lastIndex = formDataArray.length - 1
|
||
const secondLastIndex = formDataArray.length - 2
|
||
|
||
const lastData = formDataArray[lastIndex]
|
||
const secondLastData = formDataArray[secondLastIndex]
|
||
|
||
// 比较关键字段是否相同
|
||
return (JSON.stringify(lastData) === JSON.stringify(secondLastData))
|
||
}
|
||
|
||
const handleAdd = () => {
|
||
// 检查最后两组数据是否相同
|
||
if (checkAdjacentDataSame(formData)) {
|
||
Taro.showToast({
|
||
title: '信息不可与前序场完全一致',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const newStartTime = getNextHourTime()
|
||
setFormData(prev => [...prev, {
|
||
...defaultFormData,
|
||
title: '',
|
||
timeRange: {
|
||
start_time: newStartTime,
|
||
end_time: getEndTime(newStartTime)
|
||
}
|
||
}])
|
||
}
|
||
|
||
|
||
// 复制上一场数据
|
||
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 validateFormData = (formData: PublishBallFormData, isOnSubmit: boolean = false) => {
|
||
const { activityInfo, title, timeRange } = formData;
|
||
const { play_type, price, location_name } = activityInfo;
|
||
// if (!image_list?.length) {
|
||
// if (!isOnSubmit) {
|
||
// Taro.showToast({
|
||
// title: `请上传活动封面`,
|
||
// icon: 'none'
|
||
// })
|
||
// }
|
||
// return false
|
||
// }
|
||
if (!title) {
|
||
if (!isOnSubmit) {
|
||
Taro.showToast({
|
||
title: `请输入活动标题`,
|
||
icon: 'none'
|
||
})
|
||
}
|
||
return false
|
||
}
|
||
if (!price || (typeof price === 'number' && price <= 0) || (typeof price === 'string' && !price.trim())) {
|
||
if (!isOnSubmit) {
|
||
Taro.showToast({
|
||
title: `请输入费用`,
|
||
icon: 'none'
|
||
})
|
||
}
|
||
return false
|
||
}
|
||
if (!play_type || !play_type.trim()) {
|
||
if (!isOnSubmit) {
|
||
Taro.showToast({
|
||
title: `请选择玩法类型`,
|
||
icon: 'none'
|
||
})
|
||
}
|
||
return false
|
||
}
|
||
if (!location_name || !location_name.trim()) {
|
||
if (!isOnSubmit) {
|
||
Taro.showToast({
|
||
title: `请选择场地`,
|
||
icon: 'none'
|
||
})
|
||
}
|
||
return false
|
||
}
|
||
// 时间范围校验:结束时间需晚于开始时间,且至少间隔30分钟(支持跨天)
|
||
if (timeRange?.start_time && timeRange?.end_time) {
|
||
const start = dayjs(timeRange.start_time)
|
||
const end = dayjs(timeRange.end_time)
|
||
if (!end.isAfter(start)) {
|
||
if (!isOnSubmit) {
|
||
Taro.showToast({
|
||
title: `结束时间需晚于开始时间`,
|
||
icon: 'none'
|
||
})
|
||
}
|
||
return false
|
||
}
|
||
if (end.isBefore(start.add(30, 'minute'))) {
|
||
if (!isOnSubmit) {
|
||
Taro.showToast({
|
||
title: `时间间隔至少30分钟`,
|
||
icon: 'none'
|
||
})
|
||
}
|
||
return false
|
||
}
|
||
}
|
||
|
||
return true
|
||
}
|
||
const validateOnSubmit = () => {
|
||
const isValid = activityType === 'individual' ? validateFormData(formData[0], true) : formData.every(item => validateFormData(item, true))
|
||
if (!isValid) {
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
// 提交表单
|
||
const handleSubmit = async () => {
|
||
// 基础验证
|
||
console.log(formData, 'formData');
|
||
if (activityType === 'individual') {
|
||
const isValid = validateFormData(formData[0])
|
||
if (!isValid) {
|
||
return
|
||
}
|
||
const { activityInfo, descriptionInfo, timeRange, players, skill_level, image_list, wechat, ...rest } = formData[0];
|
||
const { min, max, organizer_joined } = players;
|
||
const options = {
|
||
...rest,
|
||
...activityInfo,
|
||
...descriptionInfo,
|
||
...timeRange,
|
||
max_players: max,
|
||
min_players: min,
|
||
organizer_joined,
|
||
skill_level_min: skill_level[0],
|
||
skill_level_max: skill_level[1],
|
||
image_list: image_list.map(item => item.url),
|
||
is_wechat_contact: wechat.is_wechat_contact,
|
||
wechat_contact: wechat.wechat_contact || wechat.default_wechat_contact,
|
||
}
|
||
const res = await PublishService.createPersonal(options);
|
||
if (res.code === 0 && res.data) {
|
||
Taro.showToast({
|
||
title: '发布成功',
|
||
icon: 'success'
|
||
})
|
||
delay(1000)
|
||
// 如果是个人球局,则跳转到详情页,并自动分享
|
||
// 如果是畅打,则跳转第一个球局详情页,并自动分享 @刘杰
|
||
Taro.navigateTo({
|
||
// @ts-expect-error: id
|
||
url: `/game_pages/detail/index?id=${(res as any).data?.id || 1}&from=publish&autoShare=1`
|
||
})
|
||
} else {
|
||
Taro.showToast({
|
||
title: res.message,
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
if (activityType === 'group') {
|
||
const isValid = formData.every(item => validateFormData(item))
|
||
if (!isValid) {
|
||
return
|
||
}
|
||
if (checkAdjacentDataSame(formData)) {
|
||
Taro.showToast({
|
||
title: '信息不可与前序场完全一致',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const options = formData.map((item) => {
|
||
const { activityInfo, descriptionInfo, timeRange, players, skill_level, ...rest } = item;
|
||
const { min, max, organizer_joined } = players;
|
||
return {
|
||
...rest,
|
||
...activityInfo,
|
||
...descriptionInfo,
|
||
...timeRange,
|
||
max_players: max,
|
||
min_players: min,
|
||
organizer_joined,
|
||
skill_level_min: skill_level[0],
|
||
skill_level_max: skill_level[1],
|
||
image_list: item.image_list.map(img => img.url)
|
||
}
|
||
})
|
||
const res = await PublishService.create_play_pmoothlys({rows: options});
|
||
if (res.code === 0 && res.data) {
|
||
Taro.showToast({
|
||
title: '发布成功',
|
||
icon: 'success'
|
||
})
|
||
delay(1000)
|
||
// 如果是个人球局,则跳转到详情页,并自动分享
|
||
// 如果是畅打,则跳转第一个球局详情页,并自动分享 @刘杰
|
||
Taro.navigateTo({
|
||
// @ts-expect-error: id
|
||
url: `/game_pages/detail/index?id=${(res as any).data?.[0]?.id || 1}&from=publish&autoShare=1`
|
||
})
|
||
} else {
|
||
Taro.showToast({
|
||
title: res.message,
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
const mergeWithDefault = (data: any): PublishBallFormData => {
|
||
const userPhone = (userInfo as any)?.phone || ''
|
||
const { start_time, end_time, play_type, price,
|
||
description, description_tag, max_players, min_players, skill_level_max, skill_level_min,
|
||
venueDtl
|
||
} = data;
|
||
let activityInfo = {};
|
||
if (venueDtl) {
|
||
const { latitude, longitude,venue_type, surface_type, facilities, name, id } = venueDtl;
|
||
activityInfo = {
|
||
latitude,
|
||
longitude,
|
||
court_type: venue_type,
|
||
court_surface: surface_type,
|
||
venue_description: facilities,
|
||
location_name: name,
|
||
venue_id: id
|
||
}
|
||
}
|
||
return {
|
||
...defaultFormData,
|
||
...data,
|
||
timeRange: {
|
||
...defaultFormData.timeRange,
|
||
start_time,
|
||
end_time,
|
||
},
|
||
activityInfo: {
|
||
...defaultFormData.activityInfo,
|
||
...(play_type ? { play_type } : {}),
|
||
...((price) ? { price } : {}),
|
||
...activityInfo
|
||
},
|
||
descriptionInfo: {
|
||
...defaultFormData.descriptionInfo,
|
||
...(description ? { description } : {}),
|
||
...(description_tag ? { description_tag } : {}),
|
||
},
|
||
...(skill_level_max && skill_level_min ? { skill_level: [skill_level_min, skill_level_max] } : {}),
|
||
...(max_players && min_players ? { players: { min: min_players, max: max_players, organizer_joined: true } } : {}),
|
||
wechat: { ...defaultFormData.wechat, default_wechat_contact: userPhone }
|
||
}
|
||
}
|
||
|
||
|
||
const formatConfig = () => {
|
||
const newFormSchema = publishBallFormSchema.reduce((acc, item) => {
|
||
if (item.prop === 'wechat') {
|
||
return acc
|
||
}
|
||
if (item.prop === 'image_list') {
|
||
if (item.props) {
|
||
item.props.source = ['album', 'history']
|
||
}
|
||
}
|
||
if (item.prop === 'players') {
|
||
if (item.props) {
|
||
item.props.max = 100
|
||
}
|
||
}
|
||
acc.push(item)
|
||
return acc
|
||
}, [] as FormFieldConfig[])
|
||
setOptionsConfig(newFormSchema)
|
||
}
|
||
const initFormData = () => {
|
||
const currentInstance = Taro.getCurrentInstance()
|
||
const params = currentInstance.router?.params
|
||
const userPhone = (userInfo as any)?.phone || ''
|
||
if (params?.type) {
|
||
const type = params.type as ActivityType
|
||
if (type === 'individual' || type === 'group') {
|
||
setActivityType(type)
|
||
if (type === 'group') {
|
||
formatConfig()
|
||
setFormData([defaultFormData])
|
||
setTitleBar('发布畅打活动')
|
||
} else {
|
||
setTitleBar('发布')
|
||
setFormData([{...defaultFormData, wechat: { ...defaultFormData.wechat, default_wechat_contact: userPhone } }])
|
||
}
|
||
} else if (type === 'ai') {
|
||
// 从 Store 注入 AI 生成的表单 JSON
|
||
|
||
|
||
if (publishAiData && Array.isArray(publishAiData) && publishAiData.length > 0) {
|
||
Taro.showToast({
|
||
title: '智能识别成功,请完善剩余信息',
|
||
icon: 'none'
|
||
})
|
||
const merged = publishAiData.map(item => mergeWithDefault(item))
|
||
setFormData(merged.length ? merged : [defaultFormData])
|
||
if (merged.length === 1) {
|
||
setTitleBar('发布')
|
||
setActivityType('individual')
|
||
} else {
|
||
formatConfig()
|
||
setTitleBar('发布畅打活动')
|
||
setActivityType('group')
|
||
}
|
||
} else {
|
||
setFormData([defaultFormData])
|
||
setTitleBar('发布')
|
||
setActivityType('individual')
|
||
}
|
||
}
|
||
}
|
||
}
|
||
const onCheckedChange = (checked: boolean) => {
|
||
setChecked(checked)
|
||
}
|
||
useEffect(() => {
|
||
const isValid = validateOnSubmit()
|
||
if (!isValid) {
|
||
setIsSubmitDisabled(true)
|
||
} else {
|
||
setIsSubmitDisabled(false)
|
||
}
|
||
console.log(formData, 'formData');
|
||
}, [formData])
|
||
|
||
useEffect(() => {
|
||
initFormData()
|
||
}, [])
|
||
|
||
return (
|
||
<View>
|
||
<GeneralNavbar title={titleBar} backgroundColor="#FAFAFA" className={styles['publish-ball-navbar']} />
|
||
<View className={styles['publish-ball']} style={{ paddingTop: `${statusNavbarHeightInfo.totalHeight}px` }}>
|
||
{/* 活动类型切换 */}
|
||
<View className={styles['activity-type-switch']}>
|
||
{/* <ActivityTypeSwitch
|
||
value={activityType}
|
||
onChange={handleActivityTypeChange}
|
||
/> */}
|
||
</View>
|
||
|
||
<View className={styles['publish-ball__scroll']} style={{ height: `calc(100vh - ${statusNavbarHeightInfo.totalHeight+120}px)` }}>
|
||
{
|
||
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={optionsConfig}
|
||
/>
|
||
</View>
|
||
))
|
||
}
|
||
{
|
||
activityType === 'group' && (
|
||
<View className={styles['publish-ball__add']} onClick={handleAdd}>
|
||
<Image src={images.ICON_ADD} className={styles['publish-ball__add-icon']} />
|
||
再添加一场
|
||
</View>
|
||
)
|
||
}
|
||
</View>
|
||
|
||
{/* 删除确认弹窗 */}
|
||
<CommonDialog
|
||
visible={deleteConfirm.visible}
|
||
cancelText="再想想"
|
||
confirmText="确认移除"
|
||
onCancel={closeDeleteConfirm}
|
||
onConfirm={confirmDelete}
|
||
contentTitle="确认移除该场次?"
|
||
contentDesc="该操作不可恢复"
|
||
/>
|
||
{/* 完成按钮 */}
|
||
<View className={styles['submit-section']}>
|
||
<Button className={`${styles['submit-btn']} ${isSubmitDisabled ? styles['submit-btn-disabled'] : ''}`} onClick={handleSubmit}>
|
||
发布
|
||
</Button>
|
||
{
|
||
activityType === 'individual' && (
|
||
<Text className={styles['submit-tip']}>
|
||
点击确定发布约球,即表示已经同意条款
|
||
<Text className={styles['link']} onClick={() => Taro.navigateTo({url: '/publish_pages/publishBall/footballRules/index'})}>《约球规则》</Text>
|
||
</Text>
|
||
)
|
||
}
|
||
{
|
||
activityType === 'group' && (
|
||
<View className={styles['submit-tip']}>
|
||
<Checkbox
|
||
className={styles['submit-checkbox']}
|
||
checked={checked}
|
||
onChange={onCheckedChange}
|
||
/>
|
||
已认证 徐汇爱打球官方球场,请严格遵守签约协议
|
||
</View>
|
||
)
|
||
}
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
export default withAuth(PublishBall)
|