Files
mini-programs/src/publish_pages/publishBall/index.tsx

496 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 images from '@/config/images'
import styles from './index.module.scss'
import dayjs from 'dayjs'
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: [1, 1],
skill_level: [1.0, 5.0],
descriptionInfo: {
description: '',
description_tag: [],
},
is_substitute_supported: true,
is_wechat_contact: true,
wechat_contact: '14223332214'
}
const PublishBall: React.FC = () => {
const [activityType, setActivityType] = useState<ActivityType>('individual')
const [isSubmitDisabled, setIsSubmitDisabled] = useState(false)
// 获取页面参数并设置导航标题
const [optionsConfig, setOptionsConfig] = useState<FormFieldConfig[]>(publishBallFormSchema)
const [formData, setFormData] = useState<PublishBallFormData[]>([
defaultFormData
])
const [checked, setChecked] = useState(true)
// 删除确认弹窗状态
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 handleActivityTypeChange = (type: ActivityType) => {
if (type === 'group') {
setFormData([defaultFormData])
} else {
setFormData([defaultFormData])
}
}
// 检查相邻两组数据是否相同
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, image_list, 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, ...rest } = formData[0];
const options = {
...rest,
...activityInfo,
...descriptionInfo,
...timeRange,
max_players: players[1],
current_players: players[0],
skill_level_min: skill_level[0],
skill_level_max: skill_level[1],
image_list: image_list.map(item => item.url)
}
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.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;
return {
...rest,
...activityInfo,
...descriptionInfo,
...timeRange,
max_players: players[1],
current_players: players[0],
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.data?.[0].id || 1}&from=publish&autoShare=1`
})
} else {
Taro.showToast({
title: res.message,
icon: 'none'
})
}
}
}
const initFormData = () => {
const currentInstance = Taro.getCurrentInstance()
const params = currentInstance.router?.params
if (params?.type) {
const type = params.type as ActivityType
if (type === 'individual' || type === 'group') {
setActivityType(type)
if (type === 'group') {
const newFormSchema = publishBallFormSchema.reduce((acc, item) => {
if (item.prop === 'is_wechat_contact') {
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)
setFormData([defaultFormData])
}
// 根据type设置导航标题
if (type === 'group') {
Taro.setNavigationBarTitle({
title: '发布畅打活动'
})
} else {
Taro.setNavigationBarTitle({
title: '发布'
})
}
}
handleActivityTypeChange(type)
}
}
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 className={styles['publish-ball']}>
{/* 活动类型切换 */}
<View className={styles['activity-type-switch']}>
{/* <ActivityTypeSwitch
value={activityType}
onChange={handleActivityTypeChange}
/> */}
</View>
<View className={styles['publish-ball__scroll']}>
{
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']}></Text>
</Text>
)
}
{
activityType === 'group' && (
<View className={styles['submit-tip']}>
<Checkbox
className={styles['submit-checkbox']}
checked={checked}
onChange={onCheckedChange}
/>
</View>
)
}
</View>
</View>
)
}
export default withAuth(PublishBall)