From c096d265abf5069ce74f8ccb894444904636424c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AD=B1=E9=87=8E?= Date: Thu, 18 Sep 2025 21:39:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=99=BA=E8=83=BD=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/images.js | 3 + .../AiImportPopup/AiImportPopup.tsx | 101 +++++--- .../AiImportPopup/index.module.scss | 52 ++-- .../SelectStadium/SelectStadium.tsx | 10 +- .../publishBall/footballRules/index.config.ts | 8 + .../publishBall/footballRules/index.scss | 245 ++++++++++++++++++ .../publishBall/footballRules/index.tsx | 40 +++ src/publish_pages/publishBall/index.tsx | 92 ++++--- src/publish_pages/publishBall/publishForm.tsx | 4 +- src/services/publishService.ts | 12 + src/store/publishBallStore.ts | 37 +++ 11 files changed, 508 insertions(+), 96 deletions(-) create mode 100644 src/publish_pages/publishBall/footballRules/index.config.ts create mode 100644 src/publish_pages/publishBall/footballRules/index.scss create mode 100644 src/publish_pages/publishBall/footballRules/index.tsx create mode 100644 src/store/publishBallStore.ts diff --git a/src/config/images.js b/src/config/images.js index 4e1ac7f..87333d5 100644 --- a/src/config/images.js +++ b/src/config/images.js @@ -61,4 +61,7 @@ export default { ICON_ARROW_RIGHT_BLACK: require('@/static/publishBall/icon-arrow-right-black.svg'), ICON_EXAMINATION: require('@/static/userInfo/examination.svg'), ICON_ARROW_GREEN: require('@/static/userInfo/arrow-green.svg'), + ICON_COPY: require('@/static/publishBall/icon-copy.svg'), + ICON_UPLOAD_IMG: require('@/static/publishBall/icon-upload-img.svg'), + ICON_UPLOAD_SUCCESS: require('@/static/publishBall/icon-upload-success.svg'), } diff --git a/src/publish_pages/publishBall/components/AiImportPopup/AiImportPopup.tsx b/src/publish_pages/publishBall/components/AiImportPopup/AiImportPopup.tsx index 68b54a0..9f7c6e0 100644 --- a/src/publish_pages/publishBall/components/AiImportPopup/AiImportPopup.tsx +++ b/src/publish_pages/publishBall/components/AiImportPopup/AiImportPopup.tsx @@ -1,8 +1,11 @@ import React, { useState, useEffect } from 'react' import { View, Text, Textarea, Image } from '@tarojs/components' import Taro from '@tarojs/taro' -import { Popup, Toast } from '@nutui/nutui-react-taro' +import { ConfigProvider, Loading, Popup, Toast } from '@nutui/nutui-react-taro' import styles from './index.module.scss' +import uploadFiles from '@/services/uploadFiles' +import publishService from '@/services/publishService' +import { usePublishBallActions } from '@/store/publishBallStore' import images from '@/config/images' export interface AiImportPopupProps { @@ -20,14 +23,21 @@ const AiImportPopup: React.FC = ({ }) => { const [text, setText] = useState('') const [uploadFailCount, setUploadFailCount] = useState(0) + const [loading, setLoading] = useState(false) + const [uploadLoading, setUploadLoading] = useState(false) const maxFailCount = 3 - // 当弹窗显示时,尝试获取剪切板内容 - useEffect(() => { - if (visible) { - getClipboardData() - } - }, [visible]) + // 获取 actions(在组件顶层调用 Hook) + const { setPublishData } = usePublishBallActions() + + const textIdentification = async (text: string) => { + setLoading(true) + const res = await publishService.extract_tennis_activity({text}) + console.log(res) + const {data} = res + navigateToPublishBall(data) + setLoading(false) + } const getClipboardData = async () => { try { @@ -36,9 +46,10 @@ const AiImportPopup: React.FC = ({ setText(res.data) Toast.show('toast', { content: '有场读取了你的剪切板信息', - duration: 90, + duration: 2, wordBreak:'break-word' }) + textIdentification(res.data) // Taro.showToast({ // title: '已读取你的剪切板信息', // icon: 'success', @@ -48,7 +59,7 @@ const AiImportPopup: React.FC = ({ Taro.showToast({ title: '剪切板为空,请手动输入', icon: 'none', - duration: 2000 + duration: 2 }) } } catch (error) { @@ -56,11 +67,20 @@ const AiImportPopup: React.FC = ({ Taro.showToast({ title: '读取剪切板失败,请手动输入', icon: 'error', - duration: 2000 + duration: 2 }) } } + const navigateToPublishBall = (data: any) => { + if (Array.isArray(data) && data.length > 0) { + setPublishData(data) + Taro.navigateTo({ + url: '/publish_pages/publishBall/pages/publishBall?type=ai' + }) + } + } + const handleTextChange = (e: any) => { setText(e.detail.value) } @@ -76,12 +96,17 @@ const AiImportPopup: React.FC = ({ if (res.tempFiles && res.tempFiles.length > 0) { // 这里可以调用图片识别API - console.log('选择的图片:', res.tempFiles[0]) - // TODO: 实现图片识别逻辑 - Taro.showToast({ - title: '图片识别功能开发中', - icon: 'none' - }) + setUploadLoading(false) + setLoading(true) + const res_upload = await uploadFiles.upload_oss_img(res.tempFiles[0].tempFilePath) + const {ossPath} = res_upload; + if (ossPath) { + setUploadLoading(true) + const publishData = await publishService.extract_tennis_activity_from_image({image_url: ossPath}) + const {data} = publishData + navigateToPublishBall(data) + setLoading(false) + } } } catch (error) { console.error('选择图片失败:', error) @@ -93,20 +118,6 @@ const AiImportPopup: React.FC = ({ } } - const handlePasteAndRecognize = () => { - if (!text.trim()) { - Taro.showToast({ - title: '请输入球局信息', - icon: 'none' - }) - return - } - - if (onPasteAndRecognize) { - onPasteAndRecognize(text) - } - onClose() - } const handleManualPublish = () => { if (onManualPublish) { @@ -144,9 +155,10 @@ const AiImportPopup: React.FC = ({ className={styles.textArea} value={text} onInput={handleTextChange} - placeholder="请输入球局信息..." + placeholder="在此「粘贴识别」或输入文本,智能拆分球局时间、费用、地点和其他信息,并帮你智能生成球局标题" maxlength={100} showConfirmBar={false} + placeholderClass={styles.textArea_placeholder} autoHeight /> @@ -156,10 +168,12 @@ const AiImportPopup: React.FC = ({ {/* 图片识别按钮 */} - - + + { + uploadLoading ? () : () + } 图片识别 - 支持订场截图/小红书笔记截图等图片 + {uploadLoading ? '已上传 1 张图片' : '支持订场截图/小红书笔记截图等图片'} @@ -170,9 +184,22 @@ const AiImportPopup: React.FC = ({ 手动发布球局 )} - - - 粘贴并识别 + + { + loading ? ( + + + + + 识别中 + + ) : ( + <> + + 粘贴并识别 + + ) + } diff --git a/src/publish_pages/publishBall/components/AiImportPopup/index.module.scss b/src/publish_pages/publishBall/components/AiImportPopup/index.module.scss index 77f20df..912bbf2 100644 --- a/src/publish_pages/publishBall/components/AiImportPopup/index.module.scss +++ b/src/publish_pages/publishBall/components/AiImportPopup/index.module.scss @@ -57,7 +57,7 @@ .textArea { width: 100%; - min-height: 80px; + min-height: 120px; padding: 12px; border: 1px solid #e5e6eb; border-radius: 8px; @@ -67,9 +67,10 @@ box-sizing: border-box; resize: none; - &:focus { - border-color: #165dff; - outline: none; + .textArea_placeholder{ + color: rgba(60, 60, 67, 0.30); + font-size: 14px; + line-height: 24px; } } @@ -90,17 +91,15 @@ .imageRecognitionButton { display: flex; + height: 40px; + padding: 2px 16px; + justify-content: center; align-items: center; - gap: 8px; - padding: 12px; - background: #f8f9fa; - border-radius: 8px; - cursor: pointer; - transition: background-color 0.2s; - - &:active { - background: #e9ecef; - } + gap: 6px; + align-self: stretch; + border-radius: 999px; + border: 0.5px solid rgba(0, 0, 0, 0.16); + background: #FFF; .cameraIcon { width: 16px; @@ -115,10 +114,15 @@ .imageRecognitionDesc { font-size: 12px; - color: #8a8a8a; - margin-left: auto; + color: rgba(0, 0, 0, 0.35); } } + .uploadLoadingContainer{ + border-radius: 999px; + border: 0.5px solid rgba(0, 0, 0, 0.16); + background: rgba(52, 199, 89, 0.10); + display: flex; + } } .bottomButtons { @@ -161,21 +165,25 @@ gap: 6px; cursor: pointer; transition: all 0.2s; - + .loadingContainer{ + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + } &:active { background: #333; } .clipboardIcon { - width: 16px; - height: 16px; - filter: invert(1); + width: 20px; + height: 20px; } .pasteButtonText { - font-size: 14px; + font-size: 16px; color: #fff; - font-weight: 500; + font-weight: 600; } } } diff --git a/src/publish_pages/publishBall/components/SelectStadium/SelectStadium.tsx b/src/publish_pages/publishBall/components/SelectStadium/SelectStadium.tsx index a03cf46..1c1258a 100644 --- a/src/publish_pages/publishBall/components/SelectStadium/SelectStadium.tsx +++ b/src/publish_pages/publishBall/components/SelectStadium/SelectStadium.tsx @@ -3,7 +3,7 @@ import { View, Text, Input, ScrollView, Image } from '@tarojs/components' import Taro from '@tarojs/taro' import { Loading } from '@nutui/nutui-react-taro' import StadiumDetail, { StadiumDetailRef } from './StadiumDetail' -import { CommonPopup } from '@/components' +import { CommonPopup } from '../../../../components' import { getLocation } from '@/utils/locationUtils' import PublishService from '@/services/publishService' import images from '@/config/images' @@ -135,6 +135,12 @@ const SelectStadium: React.FC = ({ setSearchValue('') } + const handleDetailCancel = () => { + setShowDetail(false) + setSelectedStadium(null) + setSearchValue('') + } + const handleItemLocation = (stadium: Stadium) => { if (stadium.latitude && stadium.longitude) { Taro.openLocation({ @@ -169,7 +175,7 @@ const SelectStadium: React.FC = ({ cancelText="返回" confirmText="确认" className="select-stadium-popup" - onCancel={handleCancel} + onCancel={handleDetailCancel} onConfirm={handleConfirm} position="bottom" round diff --git a/src/publish_pages/publishBall/footballRules/index.config.ts b/src/publish_pages/publishBall/footballRules/index.config.ts new file mode 100644 index 0000000..ed04f1a --- /dev/null +++ b/src/publish_pages/publishBall/footballRules/index.config.ts @@ -0,0 +1,8 @@ +export default definePageConfig({ + navigationBarTitleText: '约球规则', + navigationBarBackgroundColor: '#ffffff', + navigationBarTextStyle: 'black', + backgroundColor: '#f5f5f5', + enablePullDownRefresh: false, + disableScroll: false +}) \ No newline at end of file diff --git a/src/publish_pages/publishBall/footballRules/index.scss b/src/publish_pages/publishBall/footballRules/index.scss new file mode 100644 index 0000000..35a33c7 --- /dev/null +++ b/src/publish_pages/publishBall/footballRules/index.scss @@ -0,0 +1,245 @@ +// 条款页面样式 +.terms_page { + width: 100%; + height: 100vh; + position: relative; + overflow: hidden; + display: flex; + flex-direction: column; + background: #FAFAFA; + box-sizing: border-box; + +} + + +// 状态栏样式 +.status_bar { + position: absolute; + top: 21px; + left: 0; + right: 0; + height: 33px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 16px; + z-index: 10; + + .time { + color: #000000; + font-family: 'SF Pro'; + font-weight: 590; + font-size: 17px; + line-height: 22px; + } + + .status_icons { + display: flex; + align-items: center; + gap: 7px; + + .signal_icon, + .wifi_icon, + .battery_icon { + width: 20px; + height: 12px; + background: #000000; + border-radius: 2px; + opacity: 0.8; + } + + .signal_icon { + width: 19px; + height: 12px; + } + + .wifi_icon { + width: 17px; + height: 12px; + } + + .battery_icon { + width: 27px; + height: 13px; + border: 1px solid rgba(0, 0, 0, 0.35); + background: #000000; + border-radius: 4px; + position: relative; + + &::after { + content: ''; + position: absolute; + right: -3px; + top: 4px; + width: 1px; + height: 4px; + background: rgba(0, 0, 0, 0.4); + border-radius: 0 1px 1px 0; + } + } + } +} + +// 导航栏样式 +.navigation_bar { + position: absolute; + top: 54px; + left: 0; + right: 0; + height: 44px; + background: #FFFFFF; + border-radius: 44px 44px 0 0; + z-index: 10; + display: flex; + align-items: center; + padding: 0 10px; + + .nav_content { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + + .back_button { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + + .back_icon { + width: 8px; + height: 16px; + background: #000000; + position: relative; + + &::before { + content: ''; + position: absolute; + top: 50%; + left: 0; + width: 100%; + height: 2px; + background: #000000; + transform: translateY(-50%) rotate(45deg); + } + + &::after { + content: ''; + position: absolute; + top: 50%; + left: 0; + width: 100%; + height: 2px; + background: #000000; + transform: translateY(-50%) rotate(-45deg); + } + } + } + + .page_title { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 18px; + color: #000000; + text-align: center; + } + + .nav_placeholder { + width: 32px; + } + } +} + +// 主要内容区域 +.main_content { + position: relative; + z-index: 5; + flex: 1; + + box-sizing: border-box; + + overflow-y: auto; + + // 条款标题 + .terms_title { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 20px; + line-height: 1.6em; + text-align: center; + color: #000000; + margin-bottom: 24px; + } + + // 条款简介 + .terms_intro { + font-family: 'PingFang SC'; + font-weight: 600; + font-size: 14px; + line-height: 1.43em; + color: #000000; + margin-bottom: 24px; + + } + + // 条款详细内容 + .terms_content { + font-family: 'PingFang SC'; + font-weight: 400; + font-size: 14px; + line-height: 1.43em; + color: #000000; + margin-bottom: 40px; + white-space: pre-line; + padding: 0px 24px; + + .terms_first_line, + span.terms_first_line { + font-weight: 500; + display: block; + margin-bottom: 16px; + } + } + + // 底部按钮 + .bottom_actions { + margin-bottom: 40px; + + .agree_button { + width: 100%; + height: 52px; + background: #07C160; + border: none; + border-radius: 16px; + color: #FFFFFF; + font-size: 16px; + font-weight: 600; + font-family: 'PingFang SC'; + cursor: pointer; + transition: all 0.3s ease; + + &::after { + border: none; + } + + &:active { + opacity: 0.8; + } + } + } +} + +// 底部指示器 +.home_indicator { + position: absolute; + bottom: 21px; + left: 50%; + transform: translateX(-50%); + width: 140px; + height: 5px; + background: #000000; + border-radius: 2.5px; + z-index: 10; +} \ No newline at end of file diff --git a/src/publish_pages/publishBall/footballRules/index.tsx b/src/publish_pages/publishBall/footballRules/index.tsx new file mode 100644 index 0000000..330320f --- /dev/null +++ b/src/publish_pages/publishBall/footballRules/index.tsx @@ -0,0 +1,40 @@ +import React, { useEffect } from 'react'; +import { View, ScrollView } from '@tarojs/components'; +import './index.scss'; + +const footballRules: React.FC = () => { + // 获取页面参数 + + const [termsContent, setTermsContent] = React.useState(''); + +useEffect(() => { + setTermsContent(`欢迎使用本平台(以下简称"本平台")的微信绑定服务。为保障您的权益,请您务必仔细阅读并理解以下协议内容。 + +一、绑定服务说明 +1. 本平台提供微信账号绑定服务,用户可通过微信快捷登录方式使用平台功能。 +2. 绑定微信账号后,用户可使用微信登录、微信支付、微信分享等功能。 +3. 本平台承诺保护用户微信账号信息安全,不会泄露给第三方。`) +}, []) + return ( + + {/* 主要内容 */} + + {/* 条款标题 */} + + 约球规则 + + + {/* 条款详细内容 */} + + + + + + + ); +}; + +export default footballRules; \ No newline at end of file diff --git a/src/publish_pages/publishBall/index.tsx b/src/publish_pages/publishBall/index.tsx index 8b0a5e6..681d9a5 100644 --- a/src/publish_pages/publishBall/index.tsx +++ b/src/publish_pages/publishBall/index.tsx @@ -11,8 +11,10 @@ import { PublishBallFormData } from '../../../types/publishBall'; import PublishService from '@/services/publishService'; import { getNextHourTime, getEndTime, delay } from '@/utils'; import images from '@/config/images' +import { useUserInfo } from '@/store/userStore' import styles from './index.module.scss' import dayjs from 'dayjs' +import { usePublishBallData, usePublishBallActions } from '@/store/publishBallStore' const defaultFormData: PublishBallFormData = { title: '', @@ -43,19 +45,21 @@ const defaultFormData: PublishBallFormData = { }, is_substitute_supported: true, is_wechat_contact: true, - wechat_contact: '14223332214' + wechat_contact: '' } const PublishBall: React.FC = () => { const [activityType, setActivityType] = useState('individual') const [isSubmitDisabled, setIsSubmitDisabled] = useState(false) + const userInfo = useUserInfo(); + const publishAiData = usePublishBallData() + const { clearPublishData } = usePublishBallActions() + // 获取页面参数并设置导航标题 const [optionsConfig, setOptionsConfig] = useState(publishBallFormSchema) - const [formData, setFormData] = useState([ - defaultFormData - ]) + console.log(userInfo, 'userInfo'); + const [formData, setFormData] = useState([defaultFormData]) const [checked, setChecked] = useState(true) - // 删除确认弹窗状态 const [deleteConfirm, setDeleteConfirm] = useState<{ visible: boolean; @@ -77,14 +81,6 @@ const PublishBall: React.FC = () => { } - // 处理活动类型变化 - const handleActivityTypeChange = (type: ActivityType) => { - if (type === 'group') { - setFormData([defaultFormData]) - } else { - setFormData([defaultFormData]) - } - } // 检查相邻两组数据是否相同 const checkAdjacentDataSame = (formDataArray: PublishBallFormData[]) => { @@ -165,17 +161,17 @@ const PublishBall: React.FC = () => { } const validateFormData = (formData: PublishBallFormData, isOnSubmit: boolean = false) => { - const { activityInfo, image_list, title, timeRange } = formData; + 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 (!image_list?.length) { + // if (!isOnSubmit) { + // Taro.showToast({ + // title: `请上传活动封面`, + // icon: 'none' + // }) + // } + // return false + // } if (!title) { if (!isOnSubmit) { Taro.showToast({ @@ -277,8 +273,8 @@ const PublishBall: React.FC = () => { // 如果是个人球局,则跳转到详情页,并自动分享 // 如果是畅打,则跳转第一个球局详情页,并自动分享 @刘杰 Taro.navigateTo({ - // @ts-expect-error: id - url: `/game_pages/detail/index?id=${res.data.id || 1}&from=publish&autoShare=1` + // @ts-expect-error: id + url: `/pages/detail/index?id=${(res as any).data?.id || 1}&from=publish&autoShare=1` }) } else { Taro.showToast({ @@ -323,8 +319,8 @@ const PublishBall: React.FC = () => { // 如果是个人球局,则跳转到详情页,并自动分享 // 如果是畅打,则跳转第一个球局详情页,并自动分享 @刘杰 Taro.navigateTo({ - // @ts-expect-error: id - url: `/game_pages/detail/index?id=${res.data?.[0].id || 1}&from=publish&autoShare=1` + // @ts-expect-error: id + url: `/pages/detail/index?id=${(res as any).data?.[0]?.id || 1}&from=publish&autoShare=1` }) } else { Taro.showToast({ @@ -362,19 +358,48 @@ const PublishBall: React.FC = () => { }, [] as FormFieldConfig[]) setOptionsConfig(newFormSchema) setFormData([defaultFormData]) - } - // 根据type设置导航标题 - if (type === 'group') { Taro.setNavigationBarTitle({ title: '发布畅打活动' }) } else { - Taro.setNavigationBarTitle({ + Taro.setNavigationBarTitle({ title: '发布' }) + const userPhone = (userInfo as any)?.phone || '' + setFormData([{...defaultFormData, wechat_contact: userPhone }]) + } + } else if (type === 'ai') { + // 从 Store 注入 AI 生成的表单 JSON + const mergeWithDefault = (data: PublishBallFormData): PublishBallFormData => { + return { + ...defaultFormData, + ...data, + timeRange: { + ...defaultFormData.timeRange, + ...(data?.timeRange || {}), + }, + activityInfo: { + ...defaultFormData.activityInfo, + ...(data?.activityInfo || {}), + }, + descriptionInfo: { + ...defaultFormData.descriptionInfo, + ...(data?.descriptionInfo || {}), + }, + } + } + + if (publishAiData) { + if (Array.isArray(publishAiData)) { + const merged = publishAiData.map(item => mergeWithDefault(item)) + setFormData(merged.length ? merged : [defaultFormData]) + } else { + setFormData([mergeWithDefault(publishAiData)]) + } + } else { + setFormData([defaultFormData]) } } - handleActivityTypeChange(type) } } const onCheckedChange = (checked: boolean) => { @@ -394,6 +419,7 @@ const PublishBall: React.FC = () => { initFormData() }, []) + console.log(formData, 'formDataformDataformData'); return ( {/* 活动类型切换 */} @@ -471,7 +497,7 @@ const PublishBall: React.FC = () => { activityType === 'individual' && ( 点击确定发布约球,即表示已经同意条款 - 《约球规则》 + Taro.navigateTo({url: '/pages/publishBall/footballRules/index'})}>《约球规则》 ) } diff --git a/src/publish_pages/publishBall/publishForm.tsx b/src/publish_pages/publishBall/publishForm.tsx index a68a5eb..5c59563 100644 --- a/src/publish_pages/publishBall/publishForm.tsx +++ b/src/publish_pages/publishBall/publishForm.tsx @@ -1,13 +1,13 @@ import React, { useState, useEffect } from 'react' import { View, Text } from '@tarojs/components' -import { ImageUpload, Range, TimeSelector, TextareaTag, NumberInterval, TitleTextarea, FormSwitch, UploadCover } from '@/components' +import { ImageUpload, Range, TimeSelector, TextareaTag, NumberInterval, TitleTextarea, FormSwitch, UploadCover } from '../../components' import FormBasicInfo from './components/FormBasicInfo' import { type CoverImage } from '../../components/index.types' import { FormFieldConfig, FieldType } from '../../config/formSchema/publishBallFormSchema' import { PublishBallFormData } from '../../../types/publishBall'; import WechatSwitch from './components/WechatSwitch/WechatSwitch' import styles from './index.module.scss' -import { useDictionaryActions } from '@/store/dictionaryStore' +import { useDictionaryActions } from '../../store/dictionaryStore' // 组件映射器 const componentMap = { diff --git a/src/services/publishService.ts b/src/services/publishService.ts index 8e4674e..36731bc 100644 --- a/src/services/publishService.ts +++ b/src/services/publishService.ts @@ -142,6 +142,18 @@ class PublishService { showToast: false, }) } + async extract_tennis_activity(req: {text: string}): Promise { + return httpService.post('/ai/extract_tennis_activity', req, { + showLoading: false, + showToast: false, + }) + } + async extract_tennis_activity_from_image(req: {image_url: string}): Promise { + return httpService.post('/ai/extract_tennis_activity_from_image', req, { + showLoading: false, + showToast: false, + }) + } async getPictures(req) { const { type, tag, otherReq = {} } = req if (type === 'history') { diff --git a/src/store/publishBallStore.ts b/src/store/publishBallStore.ts new file mode 100644 index 0000000..0f01836 --- /dev/null +++ b/src/store/publishBallStore.ts @@ -0,0 +1,37 @@ +import { create } from "zustand"; +import { PublishBallFormData } from "../../types/publishBall"; + +interface PublishBallState { + // 待注入到发布页面的表单数据(支持单场或多场) + publishData: PublishBallFormData | PublishBallFormData[] | null; + + // 赋值/覆盖整份数据 + setPublishData: ( + data: PublishBallFormData | PublishBallFormData[] | null + ) => void; + + // 读取当前数据 + getPublishData: () => PublishBallFormData | PublishBallFormData[] | null; + + // 清空 + clearPublishData: () => void; +} + +export const usePublishBallStore = create()((set, get) => ({ + publishData: null, + setPublishData: (data) => set({ publishData: data }), + getPublishData: () => get().publishData, + clearPublishData: () => set({ publishData: null }), +})); + +// 便捷 hooks +export const usePublishBallData = () => + usePublishBallStore((state) => state.publishData); + +export const usePublishBallActions = () => + usePublishBallStore((state) => ({ + setPublishData: state.setPublishData, + clearPublishData: state.clearPublishData, + })); + +