修改导航

This commit is contained in:
筱野
2025-09-21 22:00:30 +08:00
parent b42a5d610c
commit 64aa4ab2e3
10 changed files with 263 additions and 40 deletions

View File

@@ -0,0 +1,49 @@
.customNavbar {
position: fixed;
top: 0;
left: 0;
z-index: 999;
width: 100%;
background-color: #FAFAFA;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
.navbarContent {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
padding: 0 16px;
}
.leftSection {
display: flex;
align-items: center;
min-width: 60px;
}
.centerSection {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.rightSection {
display: flex;
align-items: center;
min-width: 60px;
}
.title {
font-size: 18px;
font-weight: 600;
color: #333;
text-align: center;
}
.backIcon {
width: 24px;
height: 24px;
cursor: pointer;
}

View File

@@ -0,0 +1,105 @@
import React from 'react'
import { View, Text, Image } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useGlobalState } from '@/store/global'
import styles from './index.module.scss'
import img from '@/config/images'
interface GeneralNavbarProps {
title?: string
titleStyle?: React.CSSProperties
titleClassName?: string
leftContent?: React.ReactNode
backgroundColor?: string
showBack?: boolean
showLeft?: boolean
onBack?: () => void
className?: string
}
const GeneralNavbar: React.FC<GeneralNavbarProps> = ({
title = '',
titleStyle,
titleClassName = '',
leftContent,
backgroundColor = '#FAFAFA',
showBack = true,
showLeft = true,
onBack,
className = ''
}) => {
const { statusNavbarHeightInfo } = useGlobalState()
const { statusBarHeight, navBarHeight } = statusNavbarHeightInfo
const handleBack = () => {
if (onBack) {
onBack()
} else {
Taro.navigateBack()
}
}
const renderLeftContent = () => {
if (!showLeft) {
return null
}
if (leftContent) {
return leftContent
}
if (showBack) {
return (
<Image
src={img.ICON_LIST_SEARCH_BACK}
className={styles.backIcon}
onClick={handleBack}
/>
)
}
return null
}
const renderTitle = () => {
if (!title) {
return null
}
return (
<Text
className={`${styles.title} ${titleClassName}`}
style={titleStyle}
>
{title}
</Text>
)
}
return (
<View
className={`${styles.customNavbar} ${className}`}
style={{
height: `${navBarHeight}px`,
paddingTop: `${statusBarHeight}px`,
backgroundColor
}}
>
<View className={styles.navbarContent}>
<View className={styles.leftSection}>
{renderLeftContent()}
</View>
<View className={styles.centerSection}>
{renderTitle()}
</View>
<View className={styles.rightSection}>
{/* 右侧占位,保持标题居中 */}
</View>
</View>
</View>
)
}
export default GeneralNavbar

View File

@@ -44,14 +44,7 @@ const PublishMenu: React.FC<PublishMenuProps> = () => {
}) })
} }
const handlePasteAndRecognize = (text: string) => {
console.log('识别的文本:', text)
// TODO: 实现文本识别逻辑
Taro.showToast({
title: '文本识别功能开发中',
icon: 'none'
})
}
return ( return (
<View className={styles.publishMenu}> <View className={styles.publishMenu}>
@@ -129,7 +122,6 @@ const PublishMenu: React.FC<PublishMenuProps> = () => {
visible={aiImportVisible} visible={aiImportVisible}
onClose={handleAiImportClose} onClose={handleAiImportClose}
onManualPublish={handleManualPublish} onManualPublish={handleManualPublish}
onPasteAndRecognize={handlePasteAndRecognize}
/> />
</View> </View>
) )

View File

@@ -66,8 +66,11 @@ const TimeSelector: React.FC<TimeSelectorProps> = ({
<View className='time-content' onClick={() => openPicker('start')}> <View className='time-content' onClick={() => openPicker('start')}>
<Text className='time-label'></Text> <Text className='time-label'></Text>
<view className='time-text-wrapper'> <view className='time-text-wrapper'>
{value.start_time && (<>
<Text className='time-text'>{getDate(value.start_time)}</Text> <Text className='time-text'>{getDate(value.start_time)}</Text>
<Text className='time-text time-am'>{getTime(value.start_time)}</Text> <Text className='time-text time-am'>{getTime(value.start_time)}</Text>
</>)}
{!value.start_time && (<Text className='time-text'></Text>)}
</view> </view>
</View> </View>
</View> </View>
@@ -80,8 +83,9 @@ const TimeSelector: React.FC<TimeSelectorProps> = ({
<View className='time-content' onClick={() => openPicker('end')}> <View className='time-content' onClick={() => openPicker('end')}>
<Text className='time-label'></Text> <Text className='time-label'></Text>
<view className='time-text-wrapper'> <view className='time-text-wrapper'>
{showEndTime && (<Text className='time-text'>{getDate(value.end_time)}</Text>)} {value.end_time && (<>{showEndTime && (<Text className='time-text'>{getDate(value.end_time)}</Text>)}
<Text className='time-text time-am'>{getTime(value.end_time)}</Text> <Text className='time-text time-am'>{getTime(value.end_time)}</Text></>)}
{!value.end_time && (<Text className='time-text'></Text>)}
</view> </view>
</View> </View>
</View> </View>

View File

@@ -20,6 +20,7 @@ import RefundPopup from "./refundPopup";
import GameManagePopup from './GameManagePopup'; import GameManagePopup from './GameManagePopup';
import FollowUserCard from './FollowUserCard/index'; import FollowUserCard from './FollowUserCard/index';
import Comments from "./Comments"; import Comments from "./Comments";
import GeneralNavbar from "./GeneralNavbar";
export { export {
ActivityTypeSwitch, ActivityTypeSwitch,
@@ -45,4 +46,5 @@ export {
GameManagePopup, GameManagePopup,
FollowUserCard, FollowUserCard,
Comments, Comments,
GeneralNavbar,
}; };

View File

@@ -12,19 +12,19 @@ export interface AiImportPopupProps {
visible: boolean visible: boolean
onClose: () => void onClose: () => void
onManualPublish?: () => void onManualPublish?: () => void
onPasteAndRecognize?: (text: string) => void
} }
const AiImportPopup: React.FC<AiImportPopupProps> = ({ const AiImportPopup: React.FC<AiImportPopupProps> = ({
visible, visible,
onClose, onClose,
onManualPublish, onManualPublish,
onPasteAndRecognize
}) => { }) => {
const [text, setText] = useState('') const [text, setText] = useState('')
const [uploadFailCount, setUploadFailCount] = useState(0) const [uploadFailCount, setUploadFailCount] = useState(0)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [uploadLoading, setUploadLoading] = useState(false) const [uploadLoading, setUploadLoading] = useState(false)
const [keyboardHeight, setKeyboardHeight] = useState(0)
const [isKeyboardVisible, setIsKeyboardVisible] = useState(false)
const maxFailCount = 3 const maxFailCount = 3
// 获取 actions在组件顶层调用 Hook // 获取 actions在组件顶层调用 Hook
@@ -33,12 +33,34 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
const textIdentification = async (text: string) => { const textIdentification = async (text: string) => {
setLoading(true) setLoading(true)
const res = await publishService.extract_tennis_activity({text}) const res = await publishService.extract_tennis_activity({text})
console.log(res)
const { data } = res const { data } = res
if (data && data?.length > 0) {
navigateToPublishBall(data) navigateToPublishBall(data)
} else {
Taro.showToast({
title: '未识别到球局信息',
icon: 'error'
})
setUploadFailCount(prev => prev + 1)
}
setLoading(false) setLoading(false)
} }
const initAiPopup = () => {
setText('')
setUploadFailCount(0)
setLoading(false)
setUploadLoading(false)
setKeyboardHeight(0)
setIsKeyboardVisible(false)
}
const handlePasteAndRecognize = async () => {
if (text) {
textIdentification(text)
} else {
getClipboardData()
}
}
const getClipboardData = async () => { const getClipboardData = async () => {
try { try {
const res = await Taro.getClipboardData() const res = await Taro.getClipboardData()
@@ -75,6 +97,8 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
const navigateToPublishBall = (data: any) => { const navigateToPublishBall = (data: any) => {
if (Array.isArray(data) && data.length > 0) { if (Array.isArray(data) && data.length > 0) {
setPublishData(data) setPublishData(data)
initAiPopup()
onClose()
Taro.navigateTo({ Taro.navigateTo({
url: '/publish_pages/publishBall/index?type=ai' url: '/publish_pages/publishBall/index?type=ai'
}) })
@@ -85,6 +109,32 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
setText(e.detail.value) setText(e.detail.value)
} }
// 监听键盘高度变化,保持弹窗贴合底部
useEffect(() => {
Taro.onKeyboardHeightChange?.((res: any) => {
const height = Number(res?.height || 0)
if (height > 0) {
setIsKeyboardVisible(true)
setKeyboardHeight(height)
} else {
setIsKeyboardVisible(false)
setKeyboardHeight(0)
}
})
return () => {
// Taro 里 onKeyboardHeightChange 返回的不是取消函数,这里通过置零兜底
setIsKeyboardVisible(false)
setKeyboardHeight(0)
// 微信小程序环境可调用 offKeyboardHeightChange如存在则尝试注销
// @ts-ignore
if (typeof Taro.offKeyboardHeightChange === 'function') {
// @ts-ignore
Taro.offKeyboardHeightChange()
}
}
}, [])
const handleImageRecognition = async () => { const handleImageRecognition = async () => {
try { try {
const res = await Taro.chooseMedia({ const res = await Taro.chooseMedia({
@@ -104,7 +154,16 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
setUploadLoading(true) setUploadLoading(true)
const publishData = await publishService.extract_tennis_activity_from_image({image_url: ossPath}) const publishData = await publishService.extract_tennis_activity_from_image({image_url: ossPath})
const { data } = publishData const { data } = publishData
if (data && data?.length > 0) {
navigateToPublishBall(data) navigateToPublishBall(data)
} else {
Taro.showToast({
title: '未识别到球局信息',
icon: 'error'
})
setUploadFailCount(prev => prev + 1)
setUploadLoading(false)
}
setLoading(false) setLoading(false)
} }
} }
@@ -138,6 +197,7 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
closeable={false} closeable={false}
onClose={onClose} onClose={onClose}
className={styles.aiImportPopup} className={styles.aiImportPopup}
style={{ paddingBottom: isKeyboardVisible ? `${keyboardHeight}px` : undefined }}
> >
<View className={styles.popupContent}> <View className={styles.popupContent}>
{/* 头部 */} {/* 头部 */}
@@ -157,11 +217,15 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
className={styles.textArea} className={styles.textArea}
value={text} value={text}
onInput={handleTextChange} onInput={handleTextChange}
onFocus={() => setIsKeyboardVisible(true)}
onBlur={() => setIsKeyboardVisible(false)}
placeholder="在此「粘贴识别」或输入文本,智能拆分球局时间、费用、地点和其他信息,并帮你智能生成球局标题" placeholder="在此「粘贴识别」或输入文本,智能拆分球局时间、费用、地点和其他信息,并帮你智能生成球局标题"
maxlength={100} maxlength={100}
showConfirmBar={false} showConfirmBar={false}
placeholderClass={styles.textArea_placeholder} placeholderClass={styles.textArea_placeholder}
autoHeight autoHeight
// 关闭系统自动上推,改为手动根据键盘高度加内边距
adjustPosition={false}
/> />
<View className={styles.charCount}> <View className={styles.charCount}>
<Text className={styles.charCountText}>{text.length}/100</Text> <Text className={styles.charCountText}>{text.length}/100</Text>
@@ -186,7 +250,7 @@ const AiImportPopup: React.FC<AiImportPopupProps> = ({
<Text className={styles.manualButtonText}></Text> <Text className={styles.manualButtonText}></Text>
</View> </View>
)} )}
<View className={styles.pasteButton} onClick={getClipboardData}> <View className={styles.pasteButton} onClick={handlePasteAndRecognize}>
{ {
loading ? ( loading ? (
<View className={styles.loadingContainer}> <View className={styles.loadingContainer}>

View File

@@ -1,4 +1,3 @@
export default definePageConfig({ export default definePageConfig({
navigationBarTitleText: '发布', navigationStyle: 'custom'
navigationBarBackgroundColor: '#FAFAFA'
}) })

View File

@@ -1,14 +1,15 @@
@use '~@/scss/themeColor.scss' as theme; @use '~@/scss/themeColor.scss' as theme;
.publish-ball { .publish-ball {
padding-top: 0;
min-height: 100vh; min-height: 100vh;
background: theme.$page-background-color; background: theme.$page-background-color;
box-sizing: border-box;
position: relative; position: relative;
&__scroll { &__scroll {
height: calc(100vh - 120px); height: calc(100vh - 120px);
overflow: auto; overflow: auto;
padding: 4px 16px 72px 16px; padding: 4px 16px 20px 16px;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -255,3 +256,7 @@
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
.publish-ball-navbar{
box-shadow: none!important;
}

View File

@@ -10,6 +10,8 @@ import { FormFieldConfig, publishBallFormSchema } from '../../config/formSchema/
import { PublishBallFormData } from '../../../types/publishBall'; import { PublishBallFormData } from '../../../types/publishBall';
import PublishService from '@/services/publishService'; import PublishService from '@/services/publishService';
import { getNextHourTime, getEndTime, delay } from '@/utils'; import { getNextHourTime, getEndTime, delay } from '@/utils';
import { useGlobalState } from "@/store/global"
import GeneralNavbar from "@/components/GeneralNavbar"
import images from '@/config/images' import images from '@/config/images'
import { useUserInfo } from '@/store/userStore' import { useUserInfo } from '@/store/userStore'
import styles from './index.module.scss' import styles from './index.module.scss'
@@ -57,12 +59,13 @@ const PublishBall: React.FC = () => {
const [isSubmitDisabled, setIsSubmitDisabled] = useState(false) const [isSubmitDisabled, setIsSubmitDisabled] = useState(false)
const userInfo = useUserInfo(); const userInfo = useUserInfo();
const publishAiData = usePublishBallData() const publishAiData = usePublishBallData()
const { statusNavbarHeightInfo } = useGlobalState();
// 获取页面参数并设置导航标题 // 获取页面参数并设置导航标题
const [optionsConfig, setOptionsConfig] = useState<FormFieldConfig[]>(publishBallFormSchema) const [optionsConfig, setOptionsConfig] = useState<FormFieldConfig[]>(publishBallFormSchema)
console.log(userInfo, 'userInfo'); console.log(userInfo, 'userInfo');
const [formData, setFormData] = useState<PublishBallFormData[]>([defaultFormData]) const [formData, setFormData] = useState<PublishBallFormData[]>([defaultFormData])
const [checked, setChecked] = useState(true) const [checked, setChecked] = useState(true)
const [titleBar, setTitleBar] = useState('发布')
// 删除确认弹窗状态 // 删除确认弹窗状态
const [deleteConfirm, setDeleteConfirm] = useState<{ const [deleteConfirm, setDeleteConfirm] = useState<{
visible: boolean; visible: boolean;
@@ -412,13 +415,9 @@ const PublishBall: React.FC = () => {
}, [] as FormFieldConfig[]) }, [] as FormFieldConfig[])
setOptionsConfig(newFormSchema) setOptionsConfig(newFormSchema)
setFormData([defaultFormData]) setFormData([defaultFormData])
Taro.setNavigationBarTitle({ setTitleBar('发布畅打活动')
title: '发布畅打活动'
})
} else { } else {
Taro.setNavigationBarTitle({ setTitleBar('发布')
title: '发布'
})
setFormData([{...defaultFormData, wechat: { ...defaultFormData.wechat, default_wechat_contact: userPhone } }]) setFormData([{...defaultFormData, wechat: { ...defaultFormData.wechat, default_wechat_contact: userPhone } }])
} }
} else if (type === 'ai') { } else if (type === 'ai') {
@@ -435,9 +434,7 @@ const PublishBall: React.FC = () => {
} else { } else {
setFormData([defaultFormData]) setFormData([defaultFormData])
} }
Taro.setNavigationBarTitle({ setTitleBar('发布畅打活动')
title: '发布畅打活动'
})
} }
} }
} }
@@ -459,7 +456,9 @@ const PublishBall: React.FC = () => {
}, []) }, [])
return ( return (
<View className={styles['publish-ball']}> <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']}> <View className={styles['activity-type-switch']}>
{/* <ActivityTypeSwitch {/* <ActivityTypeSwitch
@@ -468,7 +467,7 @@ const PublishBall: React.FC = () => {
/> */} /> */}
</View> </View>
<View className={styles['publish-ball__scroll']}> <View className={styles['publish-ball__scroll']} style={{ height: `calc(100vh - ${statusNavbarHeightInfo.totalHeight+120}px)` }}>
{ {
formData.map((item, index) => ( formData.map((item, index) => (
<View key={index}> <View key={index}>
@@ -553,6 +552,7 @@ const PublishBall: React.FC = () => {
} }
</View> </View>
</View> </View>
</View>
) )
} }

View File

@@ -27,6 +27,9 @@ export interface PublishBallFormData {
description_tag: string[] // 备注标签 description_tag: string[] // 备注标签
} }
is_substitute_supported: boolean // 是否支持替补 is_substitute_supported: boolean // 是否支持替补
wechat:{
is_wechat_contact: boolean // 是否需要微信联系 is_wechat_contact: boolean // 是否需要微信联系
default_wechat_contact: string // 默认微信联系
wechat_contact: string // 微信联系 wechat_contact: string // 微信联系
} }
}