feat: add router to detail and ignore css order problem

This commit is contained in:
2025-08-26 17:58:01 +08:00
parent 2a38b88d86
commit e51015b447
16 changed files with 209 additions and 608 deletions

View File

@@ -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;
@@ -984,8 +985,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);
}
}
}

View File

@@ -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 } 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,9 +23,70 @@ function insertDotInTags(tags: string[]) {
return tags.join('-·-').split('-')
}
function handleShare() {
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
@@ -35,10 +97,13 @@ 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(() => {
@@ -63,6 +128,20 @@ 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 tags = [{
name: '🕙 急招',
icon: '',
@@ -143,7 +222,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'>
@@ -238,7 +317,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>
@@ -461,6 +540,8 @@ function Index() {
</View>
</View>
</View>
{/* share popup */}
<SharePopup ref={sharePopupRef} id={id} from={from} />
</View>
</View>
)

View File

@@ -1,3 +0,0 @@
export default definePageConfig({
navigationBarTitleText: '首页'
})

View File

@@ -1 +0,0 @@

View File

@@ -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;
}
}
}

View File

@@ -1,293 +0,0 @@
import React, { useState, useEffect } from 'react'
import { View, Text, Button } from '@tarojs/components'
import { Cell, Avatar, Progress } from '@nutui/nutui-react-taro'
import Taro from '@tarojs/taro'
// 导入API服务
import demoApi from '../../services/demoApi'
import commonApi from '../../services/commonApi'
import {
useUserStats,
useUserActions
} from '../../store/userStore'
import './index.scss'
function Index() {
// 使用Zustand store
const userStats = useUserStats()
const { incrementRequestCount, resetUserStats } = useUserActions()
// 本地状态管理
const [loading, setLoading] = useState(false)
const [userProfile, setUserProfile] = useState<any>(null)
const [interests, setInterests] = useState<string[]>([])
// 页面加载时获取数据
useEffect(() => {
initializeData()
}, [])
// 初始化数据
const initializeData = async () => {
try {
// 获取推荐的兴趣爱好
const interestsRes = await demoApi.getRecommendedInterests()
if (interestsRes.success) {
setInterests(interestsRes.data || [])
}
} catch (error) {
console.log('获取初始数据失败:', error)
}
}
// 1. 获取用户信息 API 请求
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) {
console.error('获取用户信息失败:', error)
Taro.showToast({
title: '获取失败,使用模拟数据',
icon: 'none'
})
// 模拟数据
setUserProfile({
id: '123',
nickname: '网球爱好者',
avatar: '',
gender: 'male',
interests: interests.slice(0, 3)
})
incrementRequestCount()
} finally {
setLoading(false)
}
}
// 2. 提交统计数据 API 请求
const handleSubmitStats = async () => {
console.log('提交统计数据...');
setLoading(true)
try {
const response = await commonApi.submitForm('userStats', [
{
type: 'userStats',
data: {
requestCount: userStats.requestCount,
matchesCreated: userStats.matchesCreated,
matchesJoined: userStats.matchesJoined,
lastActiveTime: userStats.lastActiveTime,
userId: userProfile?.id || 'guest'
}
}
])
if (response.success) {
incrementRequestCount()
Taro.showToast({
title: '统计数据提交成功',
icon: 'success'
})
console.log('提交结果:', response.data)
}
} catch (error) {
console.error('提交统计数据失败:', error)
incrementRequestCount() // 即使失败也计数,用于演示
Taro.showToast({
title: '网络模拟提交成功',
icon: 'success'
})
} finally {
setLoading(false)
}
}
// 3. 提交反馈 API 请求
const handleSubmitFeedback = async () => {
console.log('提交用户反馈...');
setLoading(true)
try {
const response = await demoApi.submitFeedback({
matchId: 'demo_match_' + Date.now(),
rating: 5,
recommend: 'yes',
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'
})
} finally {
setLoading(false)
}
}
// 重置所有数据
const handleResetAllData = () => {
console.log('重置所有数据...');
resetUserStats()
setUserProfile(null)
Taro.showToast({
title: '数据已重置',
icon: 'success'
})
}
return (
<View className='index-page'>
{/* 页面标题 */}
<View className='page-header'>
<Text className='page-title'>API </Text>
<Text className='page-subtitle'></Text>
</View>
{/* 用户信息卡片 */}
<View className='user-card'>
<View className='user-header'>
<Avatar
size="large"
src={userProfile?.avatar || ''}
style={{ backgroundColor: '#fa2c19' }}
>
{userProfile?.nickname?.charAt(0) || 'U'}
</Avatar>
<View className='user-info'>
<Text className='username'>
{userProfile?.nickname || '点击获取用户信息'}
</Text>
<Text className='user-level'>
: {userProfile?.gender === 'male' ? '男' : userProfile?.gender === 'female' ? '女' : '未知'}
</Text>
<Text className='join-date'>
: {userProfile?.interests?.join(', ') || '暂无'}
</Text>
</View>
</View>
</View>
{/* 统计数据 */}
<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="最后活跃时间"
extra={new Date(userStats.lastActiveTime).toLocaleTimeString()}
/>
{interests.length > 0 && (
<Cell
title="推荐兴趣"
extra={interests.slice(0, 2).join(', ')}
/>
)}
</View>
{/* API 请求按钮区域 */}
<View className='action-section'>
<Text className='section-title'>🚀 API </Text>
<View className='button-group'>
<Button
type="primary"
loading={loading}
onClick={handleGetUserProfile}
disabled={loading}
className="custom-button primary-btn"
>
{loading ? '请求中...' : '获取用户信息'}
</Button>
<Button
type="default"
loading={loading}
onClick={handleSubmitStats}
disabled={loading}
className="custom-button success-btn"
>
{loading ? '提交中...' : '提交统计数据'}
</Button>
<Button
type="default"
loading={loading}
onClick={handleSubmitFeedback}
disabled={loading}
className="custom-button warning-btn"
>
{loading ? '提交中...' : '提交用户反馈'}
</Button>
<Button
type="warn"
onClick={handleResetAllData}
disabled={loading}
className="custom-button warning-btn"
>
</Button>
</View>
</View>
{/* 实时进度显示 */}
{loading && (
<View className='loading-section'>
<Text className='loading-text'> API ...</Text>
<Progress percent={100} animated />
</View>
)}
{/* 提示信息 */}
<View className='tips-section'>
<Text className='tips-title'>💡 API </Text>
<View className='tips-content'>
<Text className='tip-item'> "获取用户信息" - API</Text>
<Text className='tip-item'> "提交统计数据" - </Text>
<Text className='tip-item'> "提交用户反馈" - </Text>
<Text className='tip-item'> API </Text>
<Text className='tip-item'> 使</Text>
</View>
</View>
</View>
)
}
export default Index

View File

@@ -6,40 +6,40 @@ import PublishForm from './publishForm'
import { 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: ['https://static-o.oss-cn-shenzhen.aliyuncs.com/images/tpbj/tpss10.jpg'],
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],
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 = () => {
@@ -47,7 +47,7 @@ const PublishBall: React.FC = () => {
const [formData, setFormData] = useState<PublishBallFormData[]>([
defaultFormData
])
// 删除确认弹窗状态
const [deleteConfirm, setDeleteConfirm] = useState<{
visible: boolean;
@@ -195,6 +195,13 @@ 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,
@@ -208,12 +215,12 @@ const PublishBall: React.FC = () => {
<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 +230,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 +251,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={publishBallFormSchema}
/>
</View>
))
@@ -269,14 +276,14 @@ const PublishBall: React.FC = () => {
<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']}
<Button
className={styles['delete-modal__btn']}
onClick={closeDeleteConfirm}
>
</Button>
<Button
className={styles['delete-modal__btn']}
<Button
className={styles['delete-modal__btn']}
onClick={confirmDelete}
>