Merge branch 'master' of https://gitee.com/ballminiprogramwe/mini-programs
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0.10);
|
||||
|
||||
.detail-navigator-back {
|
||||
border-right: 1px solid #444;
|
||||
@@ -123,6 +124,7 @@
|
||||
&-tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
|
||||
&-tag {
|
||||
@@ -545,6 +547,7 @@
|
||||
|
||||
&-scroll {
|
||||
flex: 0 0 auto;
|
||||
width: calc(100% - 116px);
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
@@ -813,7 +816,7 @@
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(33, 178, 0, 0.20);
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
padding: 12px 15px;
|
||||
padding: 12px 0 12px 15px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-title {
|
||||
@@ -923,6 +926,52 @@
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
background: #FFF;
|
||||
|
||||
.sticky-bottom-bar-share {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
&-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 16px; /* 160% */
|
||||
}
|
||||
}
|
||||
|
||||
&-separator {
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background: rgba(0, 0, 0, 0.10);
|
||||
}
|
||||
|
||||
.sticky-bottom-bar-comment {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
&-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 16px; /* 160% */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-join-game {
|
||||
@@ -938,8 +987,48 @@
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
background: #FFF;
|
||||
|
||||
&-price {
|
||||
font-family: "PoetsenOne";
|
||||
font-size: 28px;
|
||||
font-weight: 400;
|
||||
line-height: 24px; /* 114.286% */
|
||||
letter-spacing: -0.56px;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.share-popup-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 20px 16px env(safe-area-inset-bottom);
|
||||
box-sizing: border-box;
|
||||
// padding-bottom: env(safe-area-inset-bottom);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
|
||||
& > view {
|
||||
width: 100px;
|
||||
height: 64px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
& > image {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
& > text {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import React, { useState, useEffect, useRef, useImperativeHandle, forwardRef } from 'react'
|
||||
import { View, Text, Button, Swiper, SwiperItem, Image, Map, ScrollView } from '@tarojs/components'
|
||||
import { Cell, Avatar, Progress, Popover } from '@nutui/nutui-react-taro'
|
||||
import Taro, { useRouter } from '@tarojs/taro'
|
||||
import Taro, { useRouter, useShareAppMessage, useShareTimeline, useDidShow } from '@tarojs/taro'
|
||||
// 导入API服务
|
||||
import DetailService from '../../services/detailService'
|
||||
import {
|
||||
@@ -9,8 +9,9 @@ import {
|
||||
useUserActions
|
||||
} from '../../store/userStore'
|
||||
import img from '../../config/images'
|
||||
import { getTextColorOnImage } from '../../utils/processImage'
|
||||
import { getTextColorOnImage } from '../../utils'
|
||||
import './index.scss'
|
||||
import { CommonPopup } from '@/components'
|
||||
|
||||
const images = [
|
||||
'http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/1a35ebbf-2361-44da-b338-7608561d0b31.png',
|
||||
@@ -22,6 +23,71 @@ function insertDotInTags(tags: string[]) {
|
||||
return tags.join('-·-').split('-')
|
||||
}
|
||||
|
||||
const SharePopup = forwardRef(({ id, from }: { id: string, from: string }, ref) => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
show: () => {
|
||||
setVisible(true)
|
||||
}
|
||||
}))
|
||||
|
||||
function handleShareToWechat() {
|
||||
useShareAppMessage(() => {
|
||||
return {
|
||||
title: '分享',
|
||||
path: `/pages/detail/index?id=${id}&from=${from}`,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleShareToWechatMoments() {
|
||||
useShareTimeline(() => {
|
||||
return {
|
||||
title: '分享',
|
||||
path: `/pages/detail/index?id=${id}&from=${from}`,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleSaveToLocal() {
|
||||
Taro.saveImageToPhotosAlbum({
|
||||
filePath: images[0],
|
||||
success: () => {
|
||||
Taro.showToast({ title: '保存成功', icon: 'success' })
|
||||
},
|
||||
fail: () => {
|
||||
Taro.showToast({ title: '保存失败', icon: 'none' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<CommonPopup
|
||||
title="分享"
|
||||
visible={visible}
|
||||
onClose={() => { setVisible(false) }}
|
||||
hideFooter
|
||||
style={{ minHeight: '100px' }}
|
||||
>
|
||||
<View catchMove className='share-popup-content'>
|
||||
<View onClick={handleShareToWechat}>
|
||||
<Image src={img.ICON_DETAIL_SHARE} />
|
||||
<Text>分享到微信</Text>
|
||||
</View>
|
||||
<View onClick={handleShareToWechatMoments}>
|
||||
<Image src={img.ICON_DETAIL_SHARE} />
|
||||
<Text>分享朋友圈</Text>
|
||||
</View>
|
||||
<View onClick={handleSaveToLocal}>
|
||||
<Image src={img.ICON_DETAIL_SHARE} />
|
||||
<Text>保存到本地</Text>
|
||||
</View>
|
||||
</View>
|
||||
</CommonPopup>
|
||||
)
|
||||
})
|
||||
|
||||
function Index() {
|
||||
// 使用Zustand store
|
||||
// const userStats = useUserStats()
|
||||
@@ -31,16 +97,24 @@ function Index() {
|
||||
const [colors, setColors] = useState<string []>([])
|
||||
const [detail, setDetail] = useState<any>(null)
|
||||
const { params } = useRouter()
|
||||
const { id, share } = params
|
||||
const { id, autoShare, from } = params
|
||||
|
||||
console.log('from', from)
|
||||
|
||||
// 本地状态管理
|
||||
const [loading, setLoading] = useState(false)
|
||||
const sharePopupRef = useRef<any>(null)
|
||||
|
||||
// 页面加载时获取数据
|
||||
useEffect(() => {
|
||||
// useEffect(() => {
|
||||
// fetchDetail()
|
||||
// calcBgMainColors()
|
||||
// }, [])
|
||||
|
||||
useDidShow(() => {
|
||||
fetchDetail()
|
||||
calcBgMainColors()
|
||||
}, [])
|
||||
})
|
||||
|
||||
const fetchDetail = async () => {
|
||||
const res = await DetailService.getDetail(Number(id))
|
||||
@@ -59,6 +133,26 @@ function Index() {
|
||||
setColors(textcolors)
|
||||
}
|
||||
|
||||
function handleShare() {
|
||||
sharePopupRef.current.show()
|
||||
}
|
||||
|
||||
const openMap = () => {
|
||||
Taro.openLocation({
|
||||
latitude: detail?.latitude, // 纬度(必填)
|
||||
longitude: detail?.longitude, // 经度(必填)
|
||||
name: '上海体育场', // 位置名(可选)
|
||||
address: '上海市徐汇区肇嘉浜路128号', // 地址详情(可选)
|
||||
scale: 15, // 地图缩放级别(1-28)
|
||||
})
|
||||
}
|
||||
|
||||
const handleJoinGame = () => {
|
||||
Taro.navigateTo({
|
||||
url: `/pages/orderCheck/index?id=${id}`,
|
||||
})
|
||||
}
|
||||
|
||||
const tags = [{
|
||||
name: '🕙 急招',
|
||||
icon: '',
|
||||
@@ -139,7 +233,7 @@ function Index() {
|
||||
{/* custom navbar */}
|
||||
<view className="custom-navbar">
|
||||
<View className='detail-navigator'>
|
||||
<View className='detail-navigator-back'>
|
||||
<View className='detail-navigator-back' onClick={() => { Taro.navigateBack() }}>
|
||||
<Image className='detail-navigator-back-icon' src={img.ICON_ARROW_LEFT} />
|
||||
</View>
|
||||
<View className='detail-navigator-icon'>
|
||||
@@ -234,7 +328,7 @@ function Index() {
|
||||
{/* location message */}
|
||||
<View className='location-message-text'>
|
||||
{/* venue name and distance */}
|
||||
<View className='location-message-text-name-distance'>
|
||||
<View className='location-message-text-name-distance' onClick={openMap}>
|
||||
<Text>上海体育场</Text>
|
||||
<Text>·</Text>
|
||||
<Text>1.2km</Text>
|
||||
@@ -325,7 +419,7 @@ function Index() {
|
||||
</View>
|
||||
{/* participants list */}
|
||||
<ScrollView className='participants-list-scroll' scrollX>
|
||||
<View className='participants-list-scroll-content' style={{ width: `${(participants.length + 1) * 108 + (participants.length) * 8 - 30}px` }}>
|
||||
<View className='participants-list-scroll-content' style={{ width: `${participants.length * 103 + (participants.length - 1) * 8}px` }}>
|
||||
{participants.map((participant) => (
|
||||
<View key={participant.id} className='participants-list-item'>
|
||||
{/* <Avatar className='participants-list-item-avatar' src={participant.user.avatar_url} /> */}
|
||||
@@ -420,7 +514,7 @@ function Index() {
|
||||
<Avatar className='recommend-games-list-item-addon-avatar' src={game.avatar} />
|
||||
<View className='recommend-games-list-item-addon-message'>
|
||||
<View className='recommend-games-list-item-addon-message-applications'>
|
||||
<Text>报名人数 {game.checkedApplications} / {game.applications}</Text>
|
||||
<Text>报名人数 {game.checkedApplications}/{game.applications}</Text>
|
||||
</View>
|
||||
<View className='recommend-games-list-item-addon-message-level-requirements'>
|
||||
<Text>{game.levelRequirements}</Text>
|
||||
@@ -439,10 +533,17 @@ function Index() {
|
||||
{/* sticky bottom action bar */}
|
||||
<View className="sticky-bottom-bar">
|
||||
<View className="sticky-bottom-bar-share-and-comment">
|
||||
<View></View>
|
||||
<View></View>
|
||||
<View className='sticky-bottom-bar-share' onClick={handleShare}>
|
||||
<Image className='sticky-bottom-bar-share-icon' src={img.ICON_DETAIL_SHARE} />
|
||||
<Text className='sticky-bottom-bar-share-text'>分享</Text>
|
||||
</View>
|
||||
<View className='sticky-bottom-bar-share-and-comment-separator' />
|
||||
<View className='sticky-bottom-bar-comment' onClick={() => { Taro.showToast({ title: 'To be continued', icon: 'none' }) }}>
|
||||
<Image className='sticky-bottom-bar-comment-icon' src={img.ICON_DETAIL_COMMENT_DARK} />
|
||||
<Text className='sticky-bottom-bar-comment-text'>32</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className="sticky-bottom-bar-join-game">
|
||||
<View className="sticky-bottom-bar-join-game" onClick={handleJoinGame}>
|
||||
<Text>🎾</Text>
|
||||
<Text>立即加入</Text>
|
||||
<View className='game-price'>
|
||||
@@ -450,6 +551,8 @@ function Index() {
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{/* share popup */}
|
||||
<SharePopup ref={sharePopupRef} id={id} from={from} />
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
.index-page {
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 16px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.user-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.user-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
|
||||
.username {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.user-level {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.join-date {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
:global {
|
||||
.nut-cell {
|
||||
background: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 8px;
|
||||
border: none;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.nut-cell__title {
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.nut-cell__value {
|
||||
font-weight: bold;
|
||||
color: #007bff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-section {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
.custom-button {
|
||||
border-radius: 12px;
|
||||
font-weight: 500;
|
||||
height: 48px;
|
||||
border: none;
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
|
||||
&.primary-btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
&.success-btn {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
}
|
||||
|
||||
&.warning-btn {
|
||||
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
// 保留 NutUI 按钮样式(备用)
|
||||
:global {
|
||||
.nut-button {
|
||||
border-radius: 12px;
|
||||
font-weight: 500;
|
||||
height: 48px;
|
||||
border: none;
|
||||
|
||||
&--primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
&--success {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-section {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
|
||||
.loading-text {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
:global {
|
||||
.nut-progress {
|
||||
.nut-progress-outer {
|
||||
background: #f0f0f0;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.nut-progress-inner {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips-section {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.tips-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tips-content {
|
||||
.tip-item {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.index-page {
|
||||
padding: 16px;
|
||||
|
||||
.page-header {
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-card,
|
||||
.stats-section,
|
||||
.action-section,
|
||||
.loading-section,
|
||||
.tips-section {
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,9 @@ import Taro from '@tarojs/taro'
|
||||
// 导入API服务
|
||||
import demoApi from '../../services/demoApi'
|
||||
import commonApi from '../../services/commonApi'
|
||||
import {
|
||||
useUserStats,
|
||||
import PublishMenu from '../../components/PublishMenu'
|
||||
import {
|
||||
useUserStats,
|
||||
useUserActions
|
||||
} from '../../store/userStore'
|
||||
import './index.scss'
|
||||
@@ -15,7 +16,7 @@ function Index() {
|
||||
// 使用Zustand store
|
||||
const userStats = useUserStats()
|
||||
const { incrementRequestCount, resetUserStats } = useUserActions()
|
||||
|
||||
|
||||
// 本地状态管理
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [userProfile, setUserProfile] = useState<any>(null)
|
||||
@@ -43,19 +44,19 @@ function Index() {
|
||||
const handleGetUserProfile = async () => {
|
||||
console.log('获取用户信息...');
|
||||
setLoading(true)
|
||||
|
||||
|
||||
try {
|
||||
const response = await demoApi.getUserProfile()
|
||||
|
||||
|
||||
if (response.success) {
|
||||
setUserProfile(response.data)
|
||||
incrementRequestCount()
|
||||
|
||||
|
||||
Taro.showToast({
|
||||
title: '获取用户信息成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
|
||||
console.log('用户信息:', response.data)
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -64,7 +65,7 @@ function Index() {
|
||||
title: '获取失败,使用模拟数据',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
|
||||
// 模拟数据
|
||||
setUserProfile({
|
||||
id: '123',
|
||||
@@ -83,7 +84,7 @@ function Index() {
|
||||
const handleSubmitStats = async () => {
|
||||
console.log('提交统计数据...');
|
||||
setLoading(true)
|
||||
|
||||
|
||||
try {
|
||||
const response = await commonApi.submitForm('userStats', [
|
||||
{
|
||||
@@ -97,21 +98,21 @@ function Index() {
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
if (response.success) {
|
||||
incrementRequestCount()
|
||||
|
||||
|
||||
Taro.showToast({
|
||||
title: '统计数据提交成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
|
||||
console.log('提交结果:', response.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交统计数据失败:', error)
|
||||
incrementRequestCount() // 即使失败也计数,用于演示
|
||||
|
||||
|
||||
Taro.showToast({
|
||||
title: '网络模拟提交成功',
|
||||
icon: 'success'
|
||||
@@ -125,7 +126,7 @@ function Index() {
|
||||
const handleSubmitFeedback = async () => {
|
||||
console.log('提交用户反馈...');
|
||||
setLoading(true)
|
||||
|
||||
|
||||
try {
|
||||
const response = await demoApi.submitFeedback({
|
||||
matchId: 'demo_match_' + Date.now(),
|
||||
@@ -134,21 +135,21 @@ function Index() {
|
||||
aspects: ['场地环境', '服务质量', '价格合理'],
|
||||
comments: `用户反馈 - 请求次数: ${userStats.requestCount + 1},体验良好!`
|
||||
})
|
||||
|
||||
|
||||
if (response.success) {
|
||||
incrementRequestCount()
|
||||
|
||||
|
||||
Taro.showToast({
|
||||
title: '反馈提交成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
|
||||
console.log('反馈结果:', response.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交反馈失败:', error)
|
||||
incrementRequestCount() // 即使失败也计数,用于演示
|
||||
|
||||
|
||||
Taro.showToast({
|
||||
title: '网络模拟提交成功',
|
||||
icon: 'success'
|
||||
@@ -163,7 +164,7 @@ function Index() {
|
||||
console.log('重置所有数据...');
|
||||
resetUserStats()
|
||||
setUserProfile(null)
|
||||
|
||||
|
||||
Taro.showToast({
|
||||
title: '数据已重置',
|
||||
icon: 'success'
|
||||
@@ -181,9 +182,9 @@ function Index() {
|
||||
{/* 用户信息卡片 */}
|
||||
<View className='user-card'>
|
||||
<View className='user-header'>
|
||||
<Avatar
|
||||
size="large"
|
||||
src={userProfile?.avatar || ''}
|
||||
<Avatar
|
||||
size="large"
|
||||
src={userProfile?.avatar || ''}
|
||||
style={{ backgroundColor: '#fa2c19' }}
|
||||
>
|
||||
{userProfile?.nickname?.charAt(0) || 'U'}
|
||||
@@ -205,17 +206,17 @@ function Index() {
|
||||
{/* 统计数据 */}
|
||||
<View className='stats-section'>
|
||||
<Text className='section-title'>📊 API 请求统计</Text>
|
||||
|
||||
|
||||
<Cell title="API 请求次数" extra={userStats.requestCount} />
|
||||
<Cell title="创建的比赛" extra={userStats.matchesCreated} />
|
||||
<Cell title="参加的比赛" extra={userStats.matchesJoined} />
|
||||
<Cell
|
||||
title="最后活跃时间"
|
||||
<Cell
|
||||
title="最后活跃时间"
|
||||
extra={new Date(userStats.lastActiveTime).toLocaleTimeString()}
|
||||
/>
|
||||
{interests.length > 0 && (
|
||||
<Cell
|
||||
title="推荐兴趣"
|
||||
<Cell
|
||||
title="推荐兴趣"
|
||||
extra={interests.slice(0, 2).join(', ')}
|
||||
/>
|
||||
)}
|
||||
@@ -224,10 +225,10 @@ function Index() {
|
||||
{/* API 请求按钮区域 */}
|
||||
<View className='action-section'>
|
||||
<Text className='section-title'>🚀 API 请求功能</Text>
|
||||
|
||||
|
||||
<View className='button-group'>
|
||||
<Button
|
||||
type="primary"
|
||||
<Button
|
||||
type="primary"
|
||||
loading={loading}
|
||||
onClick={handleGetUserProfile}
|
||||
disabled={loading}
|
||||
@@ -236,8 +237,8 @@ function Index() {
|
||||
{loading ? '请求中...' : '获取用户信息'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="default"
|
||||
<Button
|
||||
type="default"
|
||||
loading={loading}
|
||||
onClick={handleSubmitStats}
|
||||
disabled={loading}
|
||||
@@ -246,8 +247,8 @@ function Index() {
|
||||
{loading ? '提交中...' : '提交统计数据'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="default"
|
||||
<Button
|
||||
type="default"
|
||||
loading={loading}
|
||||
onClick={handleSubmitFeedback}
|
||||
disabled={loading}
|
||||
@@ -256,8 +257,8 @@ function Index() {
|
||||
{loading ? '提交中...' : '提交用户反馈'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="warn"
|
||||
<Button
|
||||
type="warn"
|
||||
onClick={handleResetAllData}
|
||||
disabled={loading}
|
||||
className="custom-button warning-btn"
|
||||
@@ -286,6 +287,13 @@ function Index() {
|
||||
<Text className='tip-item'>• 请求失败时会自动使用模拟数据</Text>
|
||||
</View>
|
||||
</View>
|
||||
<PublishMenu
|
||||
onPersonalPublish={() => {
|
||||
Taro.navigateTo({
|
||||
url: '/pages/publishBall/index'
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import { FilterPopupProps } from "../../../types/list/types";
|
||||
import CourtType from "@/components/CourtType";
|
||||
// 玩法
|
||||
import GamePlayType from "@/components/GamePlayType";
|
||||
import { useDictionaryActions } from "@/store/dictionaryStore";
|
||||
import { useMemo } from "react";
|
||||
|
||||
const FilterPopup = (props: FilterPopupProps) => {
|
||||
const {
|
||||
@@ -27,7 +29,22 @@ const FilterPopup = (props: FilterPopupProps) => {
|
||||
} = props;
|
||||
|
||||
const store = useListStore() || {};
|
||||
const { timeBubbleData, locationOptions, gamePlayOptions } = store;
|
||||
const { getDictionaryValue } = useDictionaryActions() || {};
|
||||
const { timeBubbleData } = store;
|
||||
|
||||
const handleOptions = (dictionaryValue: []) => {
|
||||
return dictionaryValue?.map((item) => ({ label: item, value: item })) || [];
|
||||
};
|
||||
|
||||
const courtType = getDictionaryValue("court_type") || [];
|
||||
const locationOptions = useMemo(() => {
|
||||
return courtType ? handleOptions(courtType) : [];
|
||||
}, [courtType]);
|
||||
|
||||
const gamePlay = getDictionaryValue("game_play") || [];
|
||||
const gamePlayOptions = useMemo(() => {
|
||||
return gamePlay ? handleOptions(gamePlay) : [];
|
||||
}, [gamePlay]);
|
||||
|
||||
const handleFilterChange = (name, value) => {
|
||||
onChange({ [name]: value });
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
.listPage {
|
||||
background-color: #fafafa;
|
||||
background-color: #fefefe;
|
||||
|
||||
.listTopSearchWrapper {
|
||||
padding: 0 15px;
|
||||
// position: sticky;
|
||||
// background: #fefefe;
|
||||
// z-index: 999;
|
||||
}
|
||||
|
||||
// .isScroll {
|
||||
// border-bottom: 0.5px solid #0000000F;
|
||||
// }
|
||||
|
||||
.listTopFilterWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.listContentWrapper {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.menuFilter {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
import ListCard from "../../components/ListCard";
|
||||
import ListCardSkeleton from "../../components/ListCardSkeleton";
|
||||
import List from "../../components/List";
|
||||
import Menu from "../../components/Menu";
|
||||
import CityFilter from "../../components/CityFilter";
|
||||
import SearchBar from "../../components/SearchBar";
|
||||
import FilterPopup from "./FilterPopup";
|
||||
import styles from "./index.module.scss";
|
||||
import { useEffect } from "react";
|
||||
import Taro, { useReachBottom } from "@tarojs/taro";
|
||||
import Taro, { usePageScroll, useReachBottom } from "@tarojs/taro";
|
||||
import { useListStore } from "@/store/listStore";
|
||||
import {useGlobalState} from '@/store/global'
|
||||
import { useGlobalState } from "@/store/global";
|
||||
import { View } from "@tarojs/components";
|
||||
import CustomerNavBar from "@/components/CustomNavbar";
|
||||
import CustomerNavBar from "@/container/listCustomNavbar";
|
||||
import InputCustomerBar from "@/container/inputCustomerNavbar";
|
||||
import GuideBar from "@/components/GuideBar";
|
||||
import ListContainer from "@/container/listContainer";
|
||||
import DistanceQuickFilter from "@/components/DistanceQuickFilter";
|
||||
import img from "@/config/images";
|
||||
|
||||
const ListPage = () => {
|
||||
// 从 store 获取数据和方法
|
||||
const store = useListStore() || {};
|
||||
|
||||
const {statusNavbarHeightInfo } = useGlobalState() || {}
|
||||
// console.log("===store===", store);
|
||||
// console.log('===statusNavbarHeightInfo', statusNavbarHeightInfo)
|
||||
|
||||
const { statusNavbarHeightInfo } = useGlobalState() || {};
|
||||
const { totalHeight } = statusNavbarHeightInfo || {};
|
||||
const {
|
||||
isShowFilterPopup,
|
||||
error,
|
||||
matches,
|
||||
recommendList,
|
||||
loading,
|
||||
fetchMatches,
|
||||
refreshMatches,
|
||||
clearError,
|
||||
updateState,
|
||||
filterCount,
|
||||
updateFilterOptions, // 更新筛选条件
|
||||
@@ -36,8 +37,22 @@ const ListPage = () => {
|
||||
distanceData,
|
||||
quickFilterData,
|
||||
distanceQuickFilter,
|
||||
isScrollTop,
|
||||
searchValue,
|
||||
isShowInputCustomerNavBar,
|
||||
} = store;
|
||||
|
||||
usePageScroll((res) => {
|
||||
// if (res?.scrollTop > 0 && !isScrollTop) {
|
||||
// updateState({ isScrollTop: true });
|
||||
// }
|
||||
if (res?.scrollTop >= totalHeight && !isScrollTop) {
|
||||
updateState({ isShowInputCustomerNavBar: true });
|
||||
} else {
|
||||
updateState({ isShowInputCustomerNavBar: false });
|
||||
}
|
||||
});
|
||||
|
||||
useReachBottom(() => {
|
||||
console.log("触底了");
|
||||
// 调用 store 的加载更多方法
|
||||
@@ -47,7 +62,7 @@ const ListPage = () => {
|
||||
useEffect(() => {
|
||||
// 页面加载时获取数据
|
||||
fetchMatches();
|
||||
}, [fetchMatches]);
|
||||
}, []);
|
||||
|
||||
// 下拉刷新处理函数 - 使用Taro生命周期钩子
|
||||
Taro.usePullDownRefresh(() => {
|
||||
@@ -78,78 +93,6 @@ const ListPage = () => {
|
||||
});
|
||||
});
|
||||
|
||||
// 错误处理
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
Taro.showToast({
|
||||
title: error,
|
||||
icon: "error",
|
||||
duration: 2000,
|
||||
});
|
||||
// 3秒后自动清除错误
|
||||
setTimeout(() => {
|
||||
clearError();
|
||||
}, 3000);
|
||||
}
|
||||
}, [error, clearError]);
|
||||
|
||||
// 加载状态显示
|
||||
if (loading && matches.length === 0) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "200px",
|
||||
fontSize: "14px",
|
||||
color: "#999",
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: "10px" }}>加载中...</div>
|
||||
<div style={{ fontSize: "12px", color: "#ccc" }}>
|
||||
正在获取网球比赛数据
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 错误状态显示
|
||||
if (error && matches.length === 0) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "200px",
|
||||
fontSize: "14px",
|
||||
color: "#999",
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: "10px" }}>加载失败</div>
|
||||
<div style={{ marginBottom: "15px", fontSize: "12px", color: "#ccc" }}>
|
||||
{error}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => fetchMatches()}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
fontSize: "12px",
|
||||
color: "#fff",
|
||||
backgroundColor: "#007aff",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const toggleShowPopup = () => {
|
||||
updateState({ isShowFilterPopup: !isShowFilterPopup });
|
||||
};
|
||||
@@ -174,21 +117,40 @@ const ListPage = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleSearchClick = () => {
|
||||
Taro.navigateTo({
|
||||
url: "/pages/search/index",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomerNavBar />
|
||||
{!isShowInputCustomerNavBar ? (
|
||||
<CustomerNavBar />
|
||||
) : (
|
||||
<InputCustomerBar icon={img.ICON_LIST_INPUT_LOGO} />
|
||||
)}
|
||||
|
||||
<View className={styles.listPage}>
|
||||
<View className={styles.listTopSearchWrapper}>
|
||||
<View
|
||||
className={`${styles.listTopSearchWrapper} ${
|
||||
isScrollTop ? styles.isScroll : ""
|
||||
}`}
|
||||
// style={{
|
||||
// top: statusNavbarHeightInfo?.totalHeight,
|
||||
// }}
|
||||
>
|
||||
<SearchBar
|
||||
handleFilterIcon={toggleShowPopup}
|
||||
isSelect={filterCount > 0}
|
||||
filterCount={filterCount}
|
||||
onChange={handleSearchChange}
|
||||
value={searchValue}
|
||||
onInputClick={handleSearchClick}
|
||||
/>
|
||||
{/* 综合筛选 */}
|
||||
{isShowFilterPopup && (
|
||||
<div>
|
||||
<View>
|
||||
<FilterPopup
|
||||
loading={loading}
|
||||
onCancel={toggleShowPopup}
|
||||
@@ -200,47 +162,33 @@ const ListPage = () => {
|
||||
onClose={toggleShowPopup}
|
||||
statusNavbarHeigh={statusNavbarHeightInfo?.totalHeight}
|
||||
/>
|
||||
</div>
|
||||
</View>
|
||||
)}
|
||||
{/* 筛选 */}
|
||||
<div className={styles.listTopFilterWrapper}>
|
||||
{/* 全城筛选 */}
|
||||
<CityFilter
|
||||
options={distanceData}
|
||||
value={distanceQuickFilter?.distance}
|
||||
wrapperClassName={styles.menuFilter}
|
||||
onChange={handleDistanceOrQuickChange}
|
||||
name="distance"
|
||||
/>
|
||||
{/* 智能排序 */}
|
||||
<Menu
|
||||
options={quickFilterData}
|
||||
value={distanceQuickFilter?.quick}
|
||||
onChange={handleDistanceOrQuickChange}
|
||||
wrapperClassName={styles.menuFilter}
|
||||
name="quick"
|
||||
/>
|
||||
</div>
|
||||
</View>
|
||||
{/* 筛选 */}
|
||||
<View className={styles.listTopFilterWrapper}>
|
||||
<DistanceQuickFilter
|
||||
cityOptions={distanceData}
|
||||
quickOptions={quickFilterData}
|
||||
onChange={handleDistanceOrQuickChange}
|
||||
cityName="distance"
|
||||
quickName="quick"
|
||||
cityValue={distanceQuickFilter?.distance}
|
||||
quickValue={distanceQuickFilter?.quick}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View className={styles.listContentWrapper}>
|
||||
{/* 列表内容 */}
|
||||
<List>
|
||||
{!loading &&
|
||||
matches.length > 0 &&
|
||||
matches.map((match, index) => (
|
||||
<ListCard key={match.id || index} {...match} />
|
||||
))}
|
||||
</List>
|
||||
|
||||
{/* 空状态 */}
|
||||
{loading &&
|
||||
matches.length === 0 &&
|
||||
new Array(10).fill(0).map(() => {
|
||||
return <ListCardSkeleton />;
|
||||
})}
|
||||
</View>
|
||||
{/* 列表内容 */}
|
||||
<ListContainer
|
||||
data={matches}
|
||||
recommendList={recommendList}
|
||||
loading={loading}
|
||||
error={error}
|
||||
reload={refreshMatches}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<GuideBar currentPage="list" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -110,7 +110,7 @@ const LoginPage: React.FC = () => {
|
||||
<View className="background_image">
|
||||
<Image
|
||||
className="bg_img"
|
||||
src={require('../../../static/login/login_bg.png')}
|
||||
src="http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/2e00dea1-8723-42fe-ae42-84fe38e9ac3f.png"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<View className="bg_overlay"></View>
|
||||
@@ -229,7 +229,7 @@ const LoginPage: React.FC = () => {
|
||||
{agree_terms ? '已同意' : '同意并继续'}
|
||||
</Button>
|
||||
|
||||
|
||||
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -31,15 +31,15 @@ const VerificationPage: React.FC = () => {
|
||||
|
||||
try {
|
||||
console.log('开始发送验证码,手机号:', phone);
|
||||
|
||||
|
||||
// 调用发送短信接口
|
||||
const result = await send_sms_code(phone);
|
||||
|
||||
|
||||
console.log('发送验证码结果:', result);
|
||||
|
||||
if (result.success) {
|
||||
console.log('验证码发送成功,开始倒计时');
|
||||
|
||||
|
||||
Taro.showToast({
|
||||
title: '验证码已发送',
|
||||
icon: 'success',
|
||||
@@ -49,7 +49,7 @@ const VerificationPage: React.FC = () => {
|
||||
// 开始倒计时
|
||||
setCanSendCode(false);
|
||||
setCountdown(60);
|
||||
|
||||
|
||||
console.log('设置状态: can_send_code = false, countdown = 60');
|
||||
|
||||
// 发送验证码成功后,让验证码输入框获得焦点并调用系统键盘
|
||||
@@ -81,7 +81,7 @@ const VerificationPage: React.FC = () => {
|
||||
// 倒计时效果
|
||||
useEffect(() => {
|
||||
console.log('倒计时 useEffect 触发,countdown:', countdown);
|
||||
|
||||
|
||||
if (countdown > 0) {
|
||||
const timer = setTimeout(() => {
|
||||
console.log('倒计时减少,从', countdown, '到', countdown - 1);
|
||||
@@ -124,7 +124,7 @@ const VerificationPage: React.FC = () => {
|
||||
|
||||
setTimeout(() => {
|
||||
Taro.redirectTo({
|
||||
url: '/pages/index/index'
|
||||
url: '/pages/list/index'
|
||||
});
|
||||
}, 200);
|
||||
} else {
|
||||
@@ -257,4 +257,4 @@ const VerificationPage: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default VerificationPage;
|
||||
export default VerificationPage;
|
||||
@@ -1,5 +0,0 @@
|
||||
// import MapPlugin from "src/components/MapDisplay/mapPlugin";
|
||||
import MapDisplay from "src/components/MapDisplay";
|
||||
export default function MapDisplayPage() {
|
||||
return <MapDisplay />
|
||||
}
|
||||
5
src/pages/message/index.config.ts
Normal file
5
src/pages/message/index.config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '消息',
|
||||
// navigationBarBackgroundColor: '#FAFAFA',
|
||||
navigationStyle: 'custom',
|
||||
})
|
||||
80
src/pages/message/index.scss
Normal file
80
src/pages/message/index.scss
Normal file
@@ -0,0 +1,80 @@
|
||||
@use '~@/scss/images.scss' as img;
|
||||
|
||||
$--Backgrounds-Primary: '#fff';
|
||||
|
||||
.message-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: radial-gradient(227.15% 100% at 50% 0%, #EEFFDC 0%, #FFF 36.58%), var(--Backgrounds-Primary, #FFF);
|
||||
// padding-top: 100px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.custom-navbar {
|
||||
height: 56px; /* 通常与原生导航栏高度一致 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
// background-color: #fff;
|
||||
color: #000;
|
||||
padding-top: 44px; /* 适配状态栏 */
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
|
||||
.message-navigator {
|
||||
position: relative;
|
||||
left: 15px;
|
||||
top: -2px;
|
||||
width: 80px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
|
||||
.message-navigator-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.message-navigator-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-content {
|
||||
|
||||
.message-content-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 15px;
|
||||
box-sizing: border-box;
|
||||
gap: 12px;
|
||||
|
||||
.message-item {
|
||||
padding: 10px;
|
||||
// border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2);
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.message-item-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.message-item-content {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/pages/message/index.tsx
Normal file
43
src/pages/message/index.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react'
|
||||
import { View, Text, ScrollView } from '@tarojs/components'
|
||||
import { Avatar } from '@nutui/nutui-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import GuideBar from '@/components/GuideBar'
|
||||
// import img from '@/config/images'
|
||||
import './index.scss'
|
||||
|
||||
const Personal = () => {
|
||||
const messageList = Array(10).fill(0).map((_, index) => ({
|
||||
id: index + 1,
|
||||
title: `消息${index + 1}消息${index + 1}消息${index + 1}消息${index + 1}`,
|
||||
content: Array(Math.round(Math.random() * 40)).fill(0).map((_, index) => `消息${index + 1}`).join(''),
|
||||
}))
|
||||
|
||||
return (
|
||||
<View className='message-container'>
|
||||
<View className='custom-navbar'>
|
||||
<View className='message-navigator'>
|
||||
<Avatar className='message-navigator-avatar' src="https://img.yzcdn.cn/vant/cat.jpeg" />
|
||||
<Text className='message-navigator-title'>消息</Text>
|
||||
</View>
|
||||
</View>
|
||||
<ScrollView scrollY className='message-content'>
|
||||
<View className='message-content-list'>
|
||||
{messageList.map((item) => (
|
||||
<View className='message-item' key={item.id}>
|
||||
<View className='message-item-title'>
|
||||
<Text>{item.title}</Text>
|
||||
</View>
|
||||
<View className='message-item-content'>
|
||||
<Text>{item.content}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
<GuideBar currentPage='message' />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default Personal
|
||||
4
src/pages/orderCheck/index.config.ts
Normal file
4
src/pages/orderCheck/index.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '订单确认',
|
||||
navigationBarBackgroundColor: '#FAFAFA'
|
||||
})
|
||||
1
src/pages/orderCheck/index.scss
Normal file
1
src/pages/orderCheck/index.scss
Normal file
@@ -0,0 +1 @@
|
||||
@use '~@/scss/images.scss' as img;
|
||||
31
src/pages/orderCheck/index.tsx
Normal file
31
src/pages/orderCheck/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react'
|
||||
import { View, Text, Button } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { delay } from '@/utils'
|
||||
|
||||
const OrderCheck = () => {
|
||||
const handlePay = async () => {
|
||||
Taro.showLoading({
|
||||
title: '支付中...',
|
||||
mask: true
|
||||
})
|
||||
await delay(2000)
|
||||
Taro.hideLoading()
|
||||
Taro.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
})
|
||||
await delay(1000)
|
||||
Taro.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<Text>OrderCheck</Text>
|
||||
<Button onClick={handlePay}>支付</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrderCheck
|
||||
5
src/pages/personal/index.config.ts
Normal file
5
src/pages/personal/index.config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '个人中心',
|
||||
// navigationBarBackgroundColor: '#FAFAFA',
|
||||
navigationStyle: 'custom',
|
||||
})
|
||||
35
src/pages/personal/index.scss
Normal file
35
src/pages/personal/index.scss
Normal file
@@ -0,0 +1,35 @@
|
||||
@use '~@/scss/images.scss' as img;
|
||||
|
||||
$--Backgrounds-Primary: '#fff';
|
||||
|
||||
.personal-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: radial-gradient(227.15% 100% at 50% 0%, #EEFFDC 0%, #FFF 36.58%), var(--Backgrounds-Primary, #FFF);
|
||||
padding-top: 100px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.personal-navigator {
|
||||
position: fixed;
|
||||
left: 10px;
|
||||
top: 54px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
.personal-navigator-back {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.personal-content {
|
||||
width: 100%;
|
||||
height: calc(100vh - 300px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
31
src/pages/personal/index.tsx
Normal file
31
src/pages/personal/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react'
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import Taro, { useRouter } from '@tarojs/taro'
|
||||
import GuideBar from '@/components/GuideBar'
|
||||
import img from '@/config/images'
|
||||
import './index.scss'
|
||||
|
||||
const Personal = () => {
|
||||
const { params } = useRouter()
|
||||
const { id } = params
|
||||
|
||||
const handleBack = () => {
|
||||
Taro.navigateBack()
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='personal-container'>
|
||||
{id && (
|
||||
<View className='personal-navigator' onClick={handleBack}>
|
||||
<Image className='personal-navigator-back' src={img.ICON_NAVIGATOR_BACK} />
|
||||
</View>
|
||||
)}
|
||||
<View className='personal-content'>
|
||||
<Text>Personal</Text>
|
||||
</View>
|
||||
<GuideBar currentPage='personal' />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default Personal
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react'
|
||||
import { View, Text, Input, Image, Picker } from '@tarojs/components'
|
||||
import { View, Text, Input, Image } from '@tarojs/components'
|
||||
import PopupGameplay from '../PopupGameplay'
|
||||
import img from '@/config/images';
|
||||
import { FormFieldConfig } from '@/config/formSchema/publishBallFormSchema';
|
||||
@@ -65,10 +65,52 @@ const FormBasicInfo: React.FC<FormBasicInfoProps> = ({
|
||||
})
|
||||
setShowStadiumSelector(false)
|
||||
}
|
||||
|
||||
const handleChange = useCallback((key: string, costValue: any) => {
|
||||
// 价格输入限制:¥0.00–9999.99
|
||||
console.log(costValue, 'valuevalue');
|
||||
|
||||
const handleChange = useCallback((key: string, value: any) => {
|
||||
onChange({...value, [key]: value})
|
||||
}, [onChange])
|
||||
if (key === children[0]?.prop) {
|
||||
// 允许清空
|
||||
if (costValue === '') {
|
||||
onChange({...value, [key]: ''});
|
||||
return;
|
||||
}
|
||||
|
||||
// 只允许数字和一个小数点
|
||||
const filteredValue = costValue.replace(/[^\d.]/g, '');
|
||||
|
||||
// 确保只有一个小数点
|
||||
const parts = filteredValue.split('.');
|
||||
if (parts.length > 2) {
|
||||
return; // 不更新,保持原值
|
||||
}
|
||||
|
||||
// 限制小数点后最多2位
|
||||
if (parts.length === 2 && parts[1].length > 2) {
|
||||
return; // 不更新,保持原值
|
||||
}
|
||||
|
||||
const numValue = parseFloat(filteredValue);
|
||||
if (isNaN(numValue)) {
|
||||
onChange({...value, [key]: ''});
|
||||
return;
|
||||
}
|
||||
if (numValue < 0) {
|
||||
onChange({...value, [key]: '0'});
|
||||
return;
|
||||
}
|
||||
if (numValue > 9999.99) {
|
||||
onChange({...value, [key]: '9999.99'});
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用过滤后的值
|
||||
onChange({...value, [key]: filteredValue});
|
||||
return;
|
||||
}
|
||||
onChange({...value, [key]: costValue})
|
||||
}, [onChange, children])
|
||||
|
||||
useEffect(() => {
|
||||
if (children.length > 2) {
|
||||
@@ -76,6 +118,10 @@ const FormBasicInfo: React.FC<FormBasicInfoProps> = ({
|
||||
setPlayGame(options)
|
||||
}
|
||||
}, [children])
|
||||
|
||||
useEffect(() => {
|
||||
console.log(value, 'valuevalue');
|
||||
}, [value])
|
||||
const renderChildren = () => {
|
||||
return children.map((child: any, index: number) => {
|
||||
return <View className='form-item'>
|
||||
@@ -91,6 +137,7 @@ const FormBasicInfo: React.FC<FormBasicInfoProps> = ({
|
||||
placeholder='请输入'
|
||||
placeholderClass='title-placeholder'
|
||||
type='digit'
|
||||
maxlength={7}
|
||||
value={value[child.prop]}
|
||||
onInput={(e) => handleChange(child.prop, e.detail.value)}
|
||||
/>
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface Stadium {
|
||||
id?: string
|
||||
name: string
|
||||
address?: string
|
||||
istance?: string
|
||||
distance_km?: number | null | undefined
|
||||
longitude?: number
|
||||
latitude?: number
|
||||
}
|
||||
@@ -78,6 +78,15 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
|
||||
setShowDetail(true)
|
||||
}
|
||||
|
||||
const calculateDistance = (stadium: Stadium) => {
|
||||
const distance_km = stadium.distance_km
|
||||
if (!distance_km) return ''
|
||||
if (distance_km && distance_km > 1) {
|
||||
return distance_km.toFixed(1) + 'km'
|
||||
}
|
||||
return (distance_km * 1000).toFixed(0) + 'm'
|
||||
}
|
||||
|
||||
|
||||
// 处理搜索框输入
|
||||
const handleSearchInput = (e: any) => {
|
||||
@@ -253,7 +262,7 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
|
||||
handleItemLocation(stadium)
|
||||
}}
|
||||
>
|
||||
{stadium.istance} ·
|
||||
{calculateDistance(stadium)} ·
|
||||
</Text>
|
||||
<Text
|
||||
className='stadium-address-text'
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface Stadium {
|
||||
address?: string
|
||||
longitude?: number
|
||||
latitude?: number
|
||||
istance?: string
|
||||
distance_km?: number | null
|
||||
court_type?: string
|
||||
court_surface?: string
|
||||
description?: string
|
||||
@@ -100,7 +100,7 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
address: stadium.address,
|
||||
latitude: stadium.longitude,
|
||||
longitude: stadium.latitude,
|
||||
istance: stadium.istance,
|
||||
istance: stadium.distance_km,
|
||||
court_type: court_type[0] || '',
|
||||
court_surface: court_surface[0] || '',
|
||||
additionalInfo: '',
|
||||
@@ -117,6 +117,13 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
setFormData: (data: any) => setFormData(data)
|
||||
}), [formData, stadium])
|
||||
|
||||
const calculateDistance = (distance_km: number | null) => {
|
||||
if (!distance_km) return ''
|
||||
if (distance_km && distance_km > 1) {
|
||||
return distance_km.toFixed(1) + 'km'
|
||||
}
|
||||
return (distance_km * 1000).toFixed(0) + 'm'
|
||||
}
|
||||
|
||||
|
||||
const handleMapLocation = () => {
|
||||
@@ -128,7 +135,8 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
name: res.name,
|
||||
address: res.address,
|
||||
latitude: res.longitude,
|
||||
longitude: res.latitude
|
||||
longitude: res.latitude,
|
||||
istance: null
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
@@ -166,7 +174,7 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
<View className='stadium-item-right'>
|
||||
<View className='stadium-name'>{formData.name}</View>
|
||||
<View className='stadium-address'>
|
||||
<Text>{formData.istance} · </Text>
|
||||
<Text>{calculateDistance(formData.istance || null)} · </Text>
|
||||
<Text>{formData.address}</Text>
|
||||
<Image src={images.ICON_ARRORW_SMALL} className='stadium-map-icon' />
|
||||
</View>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react'
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { View, Text, Input } from '@tarojs/components'
|
||||
import { Checkbox } from '@nutui/nutui-react-taro'
|
||||
import styles from './index.module.scss'
|
||||
interface FormSwitchProps {
|
||||
@@ -10,7 +10,14 @@ interface FormSwitchProps {
|
||||
}
|
||||
|
||||
const FormSwitch: React.FC<FormSwitchProps> = ({ value, onChange, subTitle, wechatId }) => {
|
||||
|
||||
const [editWechat, setEditWechat] = useState(false)
|
||||
const editWechatId = () => {
|
||||
|
||||
}
|
||||
const setWechatId = useCallback((e: any) => {
|
||||
const value = e.target.value
|
||||
onChange(value)
|
||||
}, [])
|
||||
return (
|
||||
<>
|
||||
<View className={styles['wechat-contact-section']}>
|
||||
@@ -28,7 +35,14 @@ const FormSwitch: React.FC<FormSwitchProps> = ({ value, onChange, subTitle, wech
|
||||
wechatId && (
|
||||
<View className={styles['wechat-contact-id']}>
|
||||
<Text className={styles['wechat-contact-text']}>微信号: {wechatId.replace(/(\d{3})(\d{4})(\d{4})/, '$1 $2 $3')}</Text>
|
||||
<View className={styles['wechat-contact-edit']}>修改</View>
|
||||
<View className={styles['wechat-contact-edit']} onClick={editWechatId}>修改</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
{
|
||||
editWechat && (
|
||||
<View className={styles['wechat-contact-edit']}>
|
||||
<Input value={wechatId} onInput={setWechatId} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: rgba(60, 60, 67, 0.50);
|
||||
|
||||
font-size: 14px;
|
||||
|
||||
&-icon{
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@@ -183,6 +184,9 @@
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
background: #000;
|
||||
box-shadow: 0 8px 64px 0 rgba(0, 0, 0, 0.10);
|
||||
&.submit-btn-disabled {
|
||||
color: rgba(255, 255, 255, 0.30);
|
||||
}
|
||||
}
|
||||
|
||||
.submit-tip {
|
||||
@@ -191,12 +195,21 @@
|
||||
color: #999;
|
||||
line-height: 1.4;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 12px 0;
|
||||
justify-content: center;
|
||||
padding: 12px 0;
|
||||
align-items: center;
|
||||
.link {
|
||||
color: #007AFF;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-checkbox {
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
:global(.nut-icon-Checked){
|
||||
background: rgba(22, 24, 35, 0.75)!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载状态遮罩保持原样
|
||||
@@ -230,74 +243,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 删除确认弹窗
|
||||
.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,53 +1,59 @@
|
||||
import React, { useState } from 'react'
|
||||
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 ActivityTypeSwitch, { type ActivityType } from '../../components/ActivityTypeSwitch'
|
||||
import { type ActivityType } from '../../components/ActivityTypeSwitch'
|
||||
import CommonDialog from '../../components/CommonDialog'
|
||||
import PublishForm from './publishForm'
|
||||
import { publishBallFormSchema } from '../../config/formSchema/publishBallFormSchema';
|
||||
import { FormFieldConfig, publishBallFormSchema } from '../../config/formSchema/publishBallFormSchema';
|
||||
import { PublishBallFormData } from '../../../types/publishBall';
|
||||
import PublishService from '@/services/publishService';
|
||||
import { getNextHourTime, getEndTime } from '@/utils/timeUtils';
|
||||
import { getNextHourTime, getEndTime, delay } from '@/utils';
|
||||
import images from '@/config/images'
|
||||
import styles from './index.module.scss'
|
||||
|
||||
const defaultFormData: PublishBallFormData = {
|
||||
title: '',
|
||||
image_list: ['https://static-o.oss-cn-shenzhen.aliyuncs.com/images/tpbj/tpss10.jpg'],
|
||||
title: '',
|
||||
image_list: [],
|
||||
timeRange: {
|
||||
start_time: getNextHourTime(),
|
||||
start_time: getNextHourTime(),
|
||||
end_time: getEndTime(getNextHourTime())
|
||||
},
|
||||
activityInfo: {
|
||||
activityInfo: {
|
||||
play_type: '不限',
|
||||
price: '',
|
||||
venue_id: null,
|
||||
venue_id: null,
|
||||
location_name: '',
|
||||
location: '',
|
||||
latitude: '',
|
||||
location: '',
|
||||
latitude: '',
|
||||
longitude: '',
|
||||
court_type: '',
|
||||
court_surface: '',
|
||||
venue_description_tag: [],
|
||||
venue_description: '',
|
||||
venue_image_list: [],
|
||||
court_type: '',
|
||||
court_surface: '',
|
||||
venue_description_tag: [],
|
||||
venue_description: '',
|
||||
venue_image_list: [],
|
||||
},
|
||||
players: [1, 4],
|
||||
players: [1, 1],
|
||||
skill_level: [1.0, 5.0],
|
||||
descriptionInfo: {
|
||||
description: '',
|
||||
description_tag: [],
|
||||
description: '',
|
||||
description_tag: [],
|
||||
},
|
||||
is_substitute_supported: true,
|
||||
is_wechat_contact: true,
|
||||
wechat_contact: '14223332214'
|
||||
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;
|
||||
@@ -69,21 +75,49 @@ const PublishBall: React.FC = () => {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 处理活动类型变化
|
||||
const handleActivityTypeChange = (type: ActivityType) => {
|
||||
setActivityType(type)
|
||||
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: '',
|
||||
start_time: newStartTime,
|
||||
end_time: getEndTime(newStartTime)
|
||||
timeRange: {
|
||||
start_time: newStartTime,
|
||||
end_time: getEndTime(newStartTime)
|
||||
}
|
||||
}])
|
||||
}
|
||||
|
||||
|
||||
// 复制上一场数据
|
||||
const handleCopyPrevious = (index: number) => {
|
||||
@@ -94,7 +128,7 @@ const PublishBall: React.FC = () => {
|
||||
return newData
|
||||
})
|
||||
Taro.showToast({
|
||||
title: '已复制上一场数据',
|
||||
title: '复制上一场填入',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
@@ -128,42 +162,59 @@ const PublishBall: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const validateFormData = (formData: PublishBallFormData) => {
|
||||
const validateFormData = (formData: PublishBallFormData, isOnSubmit: boolean = false) => {
|
||||
const { activityInfo, image_list, title } = formData;
|
||||
const { play_type, price, location_name } = activityInfo;
|
||||
if (!image_list.length) {
|
||||
Taro.showToast({
|
||||
title: `请上传活动封面`,
|
||||
icon: 'none'
|
||||
})
|
||||
if (!image_list?.length) {
|
||||
if (!isOnSubmit) {
|
||||
Taro.showToast({
|
||||
title: `请上传活动封面`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (!title) {
|
||||
Taro.showToast({
|
||||
title: `请输入活动标题`,
|
||||
icon: 'none'
|
||||
})
|
||||
if (!isOnSubmit) {
|
||||
Taro.showToast({
|
||||
title: `请输入活动标题`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (!price) {
|
||||
Taro.showToast({
|
||||
title: `请输入费用`,
|
||||
icon: 'none'
|
||||
})
|
||||
if (!price || (typeof price === 'number' && price <= 0) || (typeof price === 'string' && !price.trim())) {
|
||||
if (!isOnSubmit) {
|
||||
Taro.showToast({
|
||||
title: `请输入费用`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (!play_type) {
|
||||
Taro.showToast({
|
||||
title: `请选择玩法类型`,
|
||||
icon: 'none'
|
||||
})
|
||||
if (!play_type || !play_type.trim()) {
|
||||
if (!isOnSubmit) {
|
||||
Taro.showToast({
|
||||
title: `请选择玩法类型`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (!location_name) {
|
||||
Taro.showToast({
|
||||
title: `请选择场地`,
|
||||
icon: 'none'
|
||||
})
|
||||
if (!location_name || !location_name.trim()) {
|
||||
if (!isOnSubmit) {
|
||||
Taro.showToast({
|
||||
title: `请选择场地`,
|
||||
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
|
||||
@@ -178,7 +229,7 @@ const PublishBall: React.FC = () => {
|
||||
if (!isValid) {
|
||||
return
|
||||
}
|
||||
const { activityInfo, descriptionInfo, timeRange, players, skill_level, ...rest } = formData[0];
|
||||
const { activityInfo, descriptionInfo, timeRange, players, skill_level,image_list, ...rest } = formData[0];
|
||||
const options = {
|
||||
...rest,
|
||||
...activityInfo,
|
||||
@@ -187,7 +238,8 @@ const PublishBall: React.FC = () => {
|
||||
max_players: players[1],
|
||||
current_players: players[0],
|
||||
skill_level_min: skill_level[0],
|
||||
skill_level_max: skill_level[1]
|
||||
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) {
|
||||
@@ -195,6 +247,59 @@ const PublishBall: React.FC = () => {
|
||||
title: '发布成功',
|
||||
icon: 'success'
|
||||
})
|
||||
delay(1000)
|
||||
// 如果是个人球局,则跳转到详情页,并自动分享
|
||||
// 如果是畅打,则跳转第一个球局详情页,并自动分享 @刘杰
|
||||
Taro.navigateTo({
|
||||
// @ts-expect-error: id
|
||||
url: `/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: `/pages/detail/index?id=${res.data?.[0].id || 1}&from=publish&autoShare=1`
|
||||
})
|
||||
} else {
|
||||
Taro.showToast({
|
||||
title: res.message,
|
||||
@@ -204,16 +309,75 @@ const PublishBall: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{/* <ActivityTypeSwitch
|
||||
value={activityType}
|
||||
onChange={handleActivityTypeChange}
|
||||
/>
|
||||
/> */}
|
||||
</View>
|
||||
|
||||
|
||||
<View className={styles['publish-ball__scroll']}>
|
||||
{
|
||||
formData.map((item, index) => (
|
||||
@@ -223,19 +387,19 @@ const PublishBall: React.FC = () => {
|
||||
<View className={styles['session-header']}>
|
||||
<View className={styles['session-title']}>
|
||||
第{index + 1}场
|
||||
<View
|
||||
className={styles['session-delete']}
|
||||
<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']}
|
||||
<View
|
||||
className={styles['session-action-btn']}
|
||||
onClick={() => handleCopyPrevious(index)}
|
||||
>
|
||||
复制上一场
|
||||
@@ -244,10 +408,10 @@ const PublishBall: React.FC = () => {
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
<PublishForm
|
||||
formData={item}
|
||||
onChange={(key, value) => updateFormData(key, value, index)}
|
||||
optionsConfig={publishBallFormSchema}
|
||||
<PublishForm
|
||||
formData={item}
|
||||
onChange={(key, value) => updateFormData(key, value, index)}
|
||||
optionsConfig={optionsConfig}
|
||||
/>
|
||||
</View>
|
||||
))
|
||||
@@ -263,38 +427,40 @@ const PublishBall: React.FC = () => {
|
||||
</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>
|
||||
)}
|
||||
|
||||
<CommonDialog
|
||||
visible={deleteConfirm.visible}
|
||||
cancelText="再想想"
|
||||
confirmText="确认移除"
|
||||
onCancel={closeDeleteConfirm}
|
||||
onConfirm={confirmDelete}
|
||||
contentTitle="确认移除该场次?"
|
||||
contentDesc="该操作不可恢复"
|
||||
/>
|
||||
{/* 完成按钮 */}
|
||||
<View className={styles['submit-section']}>
|
||||
<Button className={styles['submit-btn']} onClick={handleSubmit}>
|
||||
<Button className={`${styles['submit-btn']} ${isSubmitDisabled ? styles['submit-btn-disabled'] : ''}`} onClick={handleSubmit}>
|
||||
发布
|
||||
</Button>
|
||||
<Text className={styles['submit-tip']}>
|
||||
点击确定发布约球,即表示已经同意条款
|
||||
<Text className={styles['link']}>《约球规则》</Text>
|
||||
</Text>
|
||||
{
|
||||
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>
|
||||
)
|
||||
|
||||
@@ -31,13 +31,16 @@ const PublishForm: React.FC<{
|
||||
// 字典数据相关
|
||||
const { getDictionaryValue } = useDictionaryActions()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setCoverImages(formData.image_list)
|
||||
}, [formData.image_list])
|
||||
|
||||
// 处理封面图片变化
|
||||
const handleCoverImagesChange = (fn: (images: CoverImage[]) => CoverImage[]) => {
|
||||
if (fn instanceof Function) {
|
||||
setCoverImages(fn(coverImages))
|
||||
} else {
|
||||
setCoverImages(fn)
|
||||
}
|
||||
const newImages = fn instanceof Function ? fn(coverImages) : fn
|
||||
setCoverImages(newImages)
|
||||
onChange('image_list', newImages)
|
||||
}
|
||||
|
||||
// 更新表单数据
|
||||
@@ -88,9 +91,44 @@ const PublishForm: React.FC<{
|
||||
})
|
||||
}
|
||||
|
||||
const getNTRPText = (ntrp: [number, number]) => {
|
||||
const [min, max] = ntrp
|
||||
if (min === 1.0 && max === 5.0) {
|
||||
return '不限'
|
||||
}
|
||||
if (min === 5.0 && max === 5.0) {
|
||||
return '5.0 及以上'
|
||||
}
|
||||
if (min === 1.0 && max === 1.0) {
|
||||
return `${min.toFixed(1)}`
|
||||
}
|
||||
if (min > 1.0 && max === 5.0) {
|
||||
return `${min.toFixed(1)} 以上`
|
||||
}
|
||||
if (min === 1.0 && max < 5.0) {
|
||||
return `${max.toFixed(1)} 以下`
|
||||
}
|
||||
if (min > 1.0 && max < 5.0) {
|
||||
return `${min.toFixed(1)} - ${max.toFixed(1)}之间`
|
||||
}
|
||||
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
const getPlayersText = (players: [number, number]) => {
|
||||
const [min, max] = players
|
||||
return `最少${min}人,最多${max}人`
|
||||
}
|
||||
|
||||
const renderSummary = (item: FormFieldConfig) => {
|
||||
if (item.props?.showSummary) {
|
||||
return <Text className={styles['section-summary']}>{item.props?.summary}</Text>
|
||||
if (item.prop === 'skill_level') {
|
||||
return <Text className={styles['section-summary']}>{getNTRPText(formData.skill_level)}</Text>
|
||||
}
|
||||
if (item.prop === 'players') {
|
||||
return <Text className={styles['section-summary']}>{getPlayersText(formData.players)}</Text>
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '首页'
|
||||
navigationBarTitleText: ''
|
||||
})
|
||||
120
src/pages/search/index.scss
Normal file
120
src/pages/search/index.scss
Normal file
@@ -0,0 +1,120 @@
|
||||
.listSearchContainer {
|
||||
padding: 0 15px;
|
||||
|
||||
.icon16 {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.topSearch {
|
||||
padding: 10px 16px 5px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 44px;
|
||||
box-sizing: border-box;
|
||||
gap: 10px;
|
||||
border-radius: 44px;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||
background: #FFF;
|
||||
box-shadow: 0 4px 48px 0 rgba(0, 0, 0, 0.08);
|
||||
|
||||
.nut-input {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.searchRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.searchLine {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
border-radius: 20px;
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.searchText {
|
||||
color: #000000;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 20px
|
||||
}
|
||||
}
|
||||
|
||||
.searchIcon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.historySearchTitleWrapper {
|
||||
display: flex;
|
||||
padding: 12px 15px;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
align-self: stretch;
|
||||
|
||||
.historySearchTitle,
|
||||
.historySearchClear {
|
||||
color: #000;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.historySearchClear {
|
||||
color: #9a9a9a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.historySearchList {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.historySearchItem {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
display: flex;
|
||||
height: 28px;
|
||||
padding: 4px 12px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
border-radius: 999px;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.06);
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
}
|
||||
|
||||
.searchSuggestion {
|
||||
padding: 6px 0;
|
||||
|
||||
.searchSuggestionItem {
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.searchSuggestionItemLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: rgba(60, 60, 67, 0.60);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
200
src/pages/search/index.tsx
Normal file
200
src/pages/search/index.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
import CustomerNavbarBack from "@/components/CustomerNavbarBack";
|
||||
import { View, Image, Text } from "@tarojs/components";
|
||||
import { Input } from "@nutui/nutui-react-taro";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { useListState } from "@/store/listStore";
|
||||
import img from "@/config/images";
|
||||
import "./index.scss";
|
||||
import Taro from "@tarojs/taro";
|
||||
|
||||
const ListSearch = () => {
|
||||
const {
|
||||
searchValue,
|
||||
updateState,
|
||||
getSearchHistory,
|
||||
searchHistory = [],
|
||||
clearHistory,
|
||||
searchSuggestion,
|
||||
suggestionList,
|
||||
isShowSuggestion,
|
||||
} = useListState() || {};
|
||||
|
||||
const ref = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
getSearchHistory();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref?.current) {
|
||||
ref.current.focus();
|
||||
}
|
||||
}, [ref.current]);
|
||||
|
||||
const regex = useMemo(() => {
|
||||
return new RegExp(searchValue, "gi");
|
||||
}, [searchValue]);
|
||||
|
||||
/**
|
||||
* @description 输入
|
||||
* @param value
|
||||
*/
|
||||
const handleChange = (value: string) => {
|
||||
updateState({ searchValue: value });
|
||||
if (value) {
|
||||
searchSuggestion(value);
|
||||
} else {
|
||||
updateState({
|
||||
isShowSuggestion: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 点击清空输入内容
|
||||
*/
|
||||
const handleClear = () => {
|
||||
updateState({ searchValue: "" });
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 点击历史搜索
|
||||
* @param value
|
||||
*/
|
||||
const handleHistoryClick = (value: string) => {
|
||||
updateState({ searchValue: value });
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 清空历史搜索
|
||||
*/
|
||||
const handleClearHistory = () => {
|
||||
clearHistory();
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 点击联想词
|
||||
*/
|
||||
const handleSuggestionSearch = (val: string) => {
|
||||
updateState({
|
||||
searchValue: val,
|
||||
isShowSuggestion: false,
|
||||
});
|
||||
handleSearch();
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 点击搜索
|
||||
*/
|
||||
const handleSearch = () => {
|
||||
Taro.navigateTo({
|
||||
url: `/pages/searchResult/index`,
|
||||
});
|
||||
};
|
||||
|
||||
// 是否显示清空图标
|
||||
const isShowClearIcon = searchValue && searchValue?.length > 0;
|
||||
|
||||
// 是否显示搜索历史
|
||||
const isShowHistory =
|
||||
!isShowClearIcon && searchHistory && searchHistory?.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className="listSearchContainer">
|
||||
{/* 搜索 */}
|
||||
<View className="topSearch">
|
||||
<Image className="searchIcon" src={img.ICON_LIST_SEARCH_SEARCH} />
|
||||
<Input
|
||||
placeholder="搜索上海的球局和场地"
|
||||
value={searchValue}
|
||||
defaultValue={searchValue}
|
||||
onChange={handleChange}
|
||||
onClear={handleClear}
|
||||
autoFocus
|
||||
clearable={false}
|
||||
ref={ref}
|
||||
/>
|
||||
<View className="searchRight">
|
||||
{isShowClearIcon && (
|
||||
<Image
|
||||
className="clearIcon icon16"
|
||||
src={img.ICON_LIST_SEARCH_CLEAR}
|
||||
onClick={handleClear}
|
||||
/>
|
||||
)}
|
||||
<View className="searchLine" />
|
||||
<Text className="searchText" onClick={handleSearch}>
|
||||
搜索
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
{/* 联想词 */}
|
||||
{isShowSuggestion && (
|
||||
<View className="searchSuggestion">
|
||||
{(suggestionList || [])?.map((item) => {
|
||||
// 替换匹配文本为高亮版本
|
||||
const highlightedText = item.replace(regex, (match) => {
|
||||
// 如果匹配不到,则返回原文本
|
||||
if (!match) return match;
|
||||
return `<Text class="highlight">${match}</Text>`;
|
||||
});
|
||||
return (
|
||||
<View
|
||||
className="searchSuggestionItem"
|
||||
onClick={() => handleSuggestionSearch(item)}
|
||||
>
|
||||
<View className="searchSuggestionItemLeft">
|
||||
<Image
|
||||
className="icon16"
|
||||
src={img.ICON_LIST_SEARCH_SEARCH}
|
||||
/>
|
||||
<Text
|
||||
dangerouslySetInnerHTML={{ __html: highlightedText }}
|
||||
></Text>
|
||||
</View>
|
||||
<Image
|
||||
className="icon16"
|
||||
src={img.ICON_LIST_SEARCH_SUGGESTION}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
{/* 历史搜索 */}
|
||||
{!isShowClearIcon && (
|
||||
<View className="historySearch">
|
||||
<View className="historySearchTitleWrapper">
|
||||
<View className="historySearchTitle">历史搜索</View>
|
||||
<View className="historySearchClear" onClick={handleClearHistory}>
|
||||
<Text>清空</Text>
|
||||
<Image
|
||||
className="clearIcon icon16"
|
||||
src={img.ICON_LIST_SEARCH_CLEAR_HISTORY}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{isShowHistory && (
|
||||
<View className="historySearchList">
|
||||
{(searchHistory || [])?.map((item) => {
|
||||
return (
|
||||
<Text
|
||||
className="historySearchItem"
|
||||
onClick={() => handleHistoryClick(item)}
|
||||
>
|
||||
{item}
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ListSearch;
|
||||
4
src/pages/searchResult/index.config.ts
Normal file
4
src/pages/searchResult/index.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '搜索结果',
|
||||
// navigationStyle: 'custom',
|
||||
})
|
||||
18
src/pages/searchResult/index.scss
Normal file
18
src/pages/searchResult/index.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
.searchResultPage {
|
||||
position: relative;
|
||||
|
||||
.searchResultFilterWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 5px 0px 10px 0px;
|
||||
position: sticky;
|
||||
top: -1px;
|
||||
background-color: #fefefe;
|
||||
z-index: 123;
|
||||
}
|
||||
|
||||
.menuFilter {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
71
src/pages/searchResult/index.tsx
Normal file
71
src/pages/searchResult/index.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { View } from "@tarojs/components";
|
||||
import { useListState } from "@/store/listStore";
|
||||
import { useGlobalState } from "@/store/global";
|
||||
import ListContainer from "@/container/listContainer";
|
||||
|
||||
import "./index.scss";
|
||||
import DistanceQuickFilter from "@/components/DistanceQuickFilter";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const SearchResult = () => {
|
||||
const {
|
||||
distanceData,
|
||||
quickFilterData,
|
||||
distanceQuickFilter,
|
||||
updateState,
|
||||
matches,
|
||||
recommendList,
|
||||
loading,
|
||||
error,
|
||||
refreshMatches,
|
||||
fetchMatches,
|
||||
} = useListState() || {};
|
||||
|
||||
const { statusNavbarHeightInfo } = useGlobalState() || {};
|
||||
const { totalHeight } = statusNavbarHeightInfo || {}
|
||||
|
||||
useEffect(() => {
|
||||
// 页面加载时获取数据
|
||||
fetchMatches();
|
||||
}, []);
|
||||
|
||||
// 距离筛选
|
||||
const handleDistanceOrQuickChange = (name, value) => {
|
||||
updateState({
|
||||
distanceQuickFilter: {
|
||||
...distanceQuickFilter,
|
||||
[name]: value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="searchResultPage">
|
||||
{/* 筛选 */}
|
||||
<View className='searchResultFilterWrapper' style={{
|
||||
// top: `${totalHeight}px`
|
||||
}}>
|
||||
<DistanceQuickFilter
|
||||
cityOptions={distanceData}
|
||||
quickOptions={quickFilterData}
|
||||
onChange={handleDistanceOrQuickChange}
|
||||
cityName="distance"
|
||||
quickName="quick"
|
||||
cityValue={distanceQuickFilter?.distance}
|
||||
quickValue={distanceQuickFilter?.quick}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 列表内容 */}
|
||||
<ListContainer
|
||||
data={matches}
|
||||
recommendList={recommendList}
|
||||
loading={loading}
|
||||
error={error}
|
||||
reload={refreshMatches}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchResult;
|
||||
Reference in New Issue
Block a user