diff --git a/src/components/ListCard/index.scss b/src/components/ListCard/index.scss index c28b995..e7f0b56 100644 --- a/src/components/ListCard/index.scss +++ b/src/components/ListCard/index.scss @@ -49,13 +49,20 @@ font-size: 12px; line-height: 18px; color: #3C3C4399; + overflow: hidden; } .location-position { - max-width: 50%; + flex: 1; + min-width: 0; // 允许缩小 white-space: nowrap; overflow: hidden; - text-overflow: ellipsis; + // 不使用 CSS 的省略号,由 JS 控制 +} + +.location-time-distance { + flex-shrink: 0; // 固定信息不压缩,始终显示 + white-space: nowrap; } .location-text { diff --git a/src/components/ListCard/index.tsx b/src/components/ListCard/index.tsx index 6f7b205..012636f 100644 --- a/src/components/ListCard/index.tsx +++ b/src/components/ListCard/index.tsx @@ -1,5 +1,6 @@ import { View, Text, Image } from "@tarojs/components"; import Taro from "@tarojs/taro"; +import { useMemo } from "react"; import img from "../../config/images"; import { ListCardProps } from "../../../types/list/types"; import { formatGameTime, calculateDuration } from "@/utils/timeUtils"; @@ -54,6 +55,59 @@ const ListCard: React.FC = ({ }); }; + // 处理地点截断,确保固定信息始终显示 + const displayLocation = useMemo(() => { + if (!location) return ''; + + // 获取屏幕宽度,用于计算实际容器宽度 + const systemInfo = Taro.getSystemInfoSync(); + const screenWidth = systemInfo.windowWidth || 375; + // 容器宽度 = 屏幕宽度 - 左右 padding - 图片区域宽度 + const containerWidthPx = screenWidth - 130; + + // 计算固定信息宽度 + const extraInfo = `${court_type ? `・${court_type}` : ''}${distance_km ? `・${distance_km}km` : ''}`; + + // 估算字符宽度(基于 12px 字体) + const getTextWidth = (text: string) => { + let width = 0; + for (let char of text) { + if (/[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]/.test(char)) { + width += 12; // 中文字符 12px + } else { + width += 6; // 英文字符和数字 6px + } + } + return width; + }; + + const extraWidth = getTextWidth(extraInfo); + const locationWidth = getTextWidth(location); + + // 可用宽度 = 容器宽度 - 固定信息宽度 - 省略号宽度(18px) + const availableWidth = containerWidthPx - extraWidth - 18; + + // 如果地点宽度小于可用宽度,不需要截断 + if (locationWidth <= availableWidth) { + return location; + } + + // 需要截断地点 + let maxChars = 0; + let currentWidth = 0; + for (let i = 0; i < location.length; i++) { + const char = location[i]; + const charWidth = /[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]/.test(char) ? 12 : 6; + if (currentWidth + charWidth > availableWidth) { + break; + } + currentWidth += charWidth; + maxChars++; + } + + return location.slice(0, maxChars) + '...'; + }, [location, court_type, distance_km]); + // 根据图片数量决定展示样式 const renderImages = () => { if (image_list?.length === 0) return null; @@ -128,9 +182,9 @@ const ListCard: React.FC = ({ {/* 地点,室内外,距离 */} - {location && ( + {displayLocation && ( - {location} + {displayLocation} )} diff --git a/src/components/PublishMenu/index.module.scss b/src/components/PublishMenu/index.module.scss index a003d6f..00e6b68 100644 --- a/src/components/PublishMenu/index.module.scss +++ b/src/components/PublishMenu/index.module.scss @@ -206,13 +206,12 @@ box-sizing: border-box; font-weight: bold; line-height: 1; transform: rotate(45deg); - + transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1); } &.rotated { .closeIcon{ transform: rotate(90deg); } - } } diff --git a/src/container/listCustomNavbar/index.tsx b/src/container/listCustomNavbar/index.tsx index d982524..345d81a 100644 --- a/src/container/listCustomNavbar/index.tsx +++ b/src/container/listCustomNavbar/index.tsx @@ -19,6 +19,7 @@ interface IProps { leftIconClick?: () => void; }; onCityPickerVisibleChange?: (visible: boolean) => void; // 城市选择器显示/隐藏回调 + onScrollToTop?: () => void; // 滚动到顶部回调 } function CityPicker(props) { @@ -50,7 +51,7 @@ function CityPicker(props) { } const ListHeader = (props: IProps) => { - const { config, onCityPickerVisibleChange } = props; + const { config, onCityPickerVisibleChange, onScrollToTop } = props; const { showInput = false, inputLeftIcon, leftIconClick } = config || {}; const { getLocationLoading, statusNavbarHeightInfo } = useGlobalState(); const { gamesNum, searchValue, cities, area, updateArea, getMatchesData, fetchGetGamesCount, refreshBothLists } = useListState(); @@ -89,10 +90,16 @@ const ListHeader = (props: IProps) => { const handleLogoClick = () => { // 如果当前在列表页,点击后页面回到顶部 if (getCurrentFullPath() === "/game_pages/list/index") { - Taro.pageScrollTo({ - scrollTop: 0, - duration: 300, - }); + // 使用父组件传递的滚动方法(适配 ScrollView) + if (onScrollToTop) { + onScrollToTop(); + } else { + // 降级方案:使用页面滚动(兼容旧版本) + Taro.pageScrollTo({ + scrollTop: 0, + duration: 300, + }); + } } Taro.redirectTo({ url: "game_pages/list/index", // 列表页 diff --git a/src/game_pages/list/index.tsx b/src/game_pages/list/index.tsx index e7a9c07..e8dadef 100644 --- a/src/game_pages/list/index.tsx +++ b/src/game_pages/list/index.tsx @@ -63,6 +63,7 @@ const ListPage = () => { // 滚动相关状态 const scrollContextRef = useRef(null); + const scrollViewRef = useRef(null); // ScrollView 的 ref const scrollTimeoutRef = useRef(null); const lastScrollTopRef = useRef(0); const scrollDirectionRef = useRef<'up' | 'down' | null>(null); @@ -70,6 +71,7 @@ const ListPage = () => { const loadingMoreRef = useRef(false); // 防止重复加载更多 const scrollStartPositionRef = useRef(0); // 记录开始滚动的位置 const [showSearchBar, setShowSearchBar] = useState(true); // 控制搜索框显示/隐藏(筛选始终显示) + const [scrollTop, setScrollTop] = useState(0); // 控制 ScrollView 滚动位置 // 动态控制 GuideBar 的 z-index const [guideBarZIndex, setGuideBarZIndex] = useState<'low' | 'high'>('high'); @@ -92,6 +94,12 @@ const ListPage = () => { setIsCityPickerVisible(visible); }, []); + // 滚动到顶部的方法 + const scrollToTop = useCallback(() => { + // 使用一个唯一值触发 scrollTop 更新,确保每次都能滚动到顶部 + setScrollTop(prev => prev === 0 ? 0.1 : 0); + }, []); + // 监听所有弹窗和菜单的状态,动态调整 GuideBar 的 z-index useEffect(() => { if (isPublishMenuVisible) { @@ -432,6 +440,7 @@ const ListPage = () => { showInput: isShowInputCustomerNavBar, }} onCityPickerVisibleChange={handleCityPickerVisibleChange} + onScrollToTop={scrollToTop} /> {area_city !== "上海" ? ( renderCityQrcode() @@ -485,7 +494,9 @@ const ListPage = () => { {/* 可滚动的列表内容 */}