Files
mini-programs/src/components/ListCard/index.tsx
张成 7833c2f552 1
2026-02-11 23:23:36 +08:00

277 lines
8.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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";
import { navigateTo } from "@/utils/navigation";
import images from "@/config/images";
import "./index.scss";
import { OSS_BASE } from "@/config/api";
const ListCard: React.FC<ListCardProps> = ({
id,
title,
original_start_time,
start_time,
end_time,
location,
distance_km,
current_players,
max_players,
skill_level_min,
skill_level_max,
play_type,
image_list = [],
court_type,
key,
participants, // 参与者图片
venue_image_list, // 场馆图片
venue_description,
game_type, // 球局类型
}) => {
// 参与者要前三个数据
const participantsImageList = participants?.slice(0, 3) || [];
// 场地 要第一个数据
// const venueImageList = venue_image_list?.slice(0, 1) || [];
const venueImage = venue_image_list?.[0]?.url || "";
// 是否显示畅打球局
const isShowSmoothPlayingGame = game_type === "畅打球局";
const renderItemImage = (src: string) => {
return (
<Image
src={src}
className="image"
mode="aspectFill"
lazyLoad
defaultSource={`${OSS_BASE}/front/ball/images/publish-empty-card.png`}
/>
);
};
const handleViewDetail = () => {
navigateTo({
url: `/game_pages/detail/index?id=${id}&from=list`,
});
};
// 处理地点截断,确保固定信息始终显示
const displayLocation = useMemo(() => {
if (!location) return null; // 直接返回 null不渲染
// 获取屏幕宽度,用于计算实际容器宽度
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;
if (image_list?.length === 1) {
return (
<View className="single-image">
<View className="image-container">
{renderItemImage(image_list?.[0])}
</View>
</View>
);
}
if (image_list?.length === 2) {
return (
<View className="double-image">
<View className="image-container">
{renderItemImage(image_list?.[1])}
</View>
<View className="image-container">
{renderItemImage(image_list?.[0])}
</View>
</View>
);
}
// 3张或更多图片
return (
<View className="triple-image">
<View className="image-container">
{renderItemImage(image_list?.[0])}
</View>
<View className="image-container">
{renderItemImage(image_list?.[1])}
</View>
<View className="image-container">
{renderItemImage(image_list?.[2])}
</View>
</View>
);
};
return (
<View className="listCard" key={key}>
<View className="listItem" onClick={handleViewDetail}>
{/* 左侧内容区域 */}
<View className="content">
{/* 标题 */}
{title && (
<View className="titleWrapper">
<Text className="title">{title}</Text>
<Image
src={img.ICON_LIST_RIGHT_ARROW}
className="title-right-arrow"
mode="aspectFit"
/>
</View>
)}
{/* 时间信息 */}
<View className="date-time">
<Text>{formatGameTime(original_start_time || start_time)}</Text>
{/* 时长 如 2小时 */}
{end_time && (
<Text>
{" "}
{calculateDuration(original_start_time || start_time, end_time)}
</Text>
)}
</View>
{/* 地点,室内外,距离 */}
<View className="location">
{displayLocation && (
<Text className="location-text location-position">
{displayLocation}
</Text>
)}
<Text className="location-text location-time-distance">
{court_type && `${court_type}`}
{distance_km && `${distance_km}km`}
</Text>
</View>
{/* 底部信息行:头像组、报名人数、技能等级、比赛类型 */}
<View className="bottom-info">
{participantsImageList && participantsImageList.length > 0 && (
<View className="left-section">
<View className="avatar-group">
{participantsImageList.map((item, index) => (
<View key={index} className="avatar">
<Image
className="avatar-image"
src={item?.user?.avatar_url}
mode="aspectFill"
/>
</View>
))}
</View>
</View>
)}
<View className="tags">
<View className="tag">
<Text className="tag-text">
{current_players}/
<Text className="tag-text-max">{max_players}</Text>
</Text>
</View>
<View className="tag ntprTag">
<Image src={images.ICON_LIST_NTPR} className="ntprIcon" />
<Text className="tag-text">
{Number(skill_level_min)?.toFixed(1)} -{" "}
{Number(skill_level_max)?.toFixed(1)}
</Text>
{/* 分割线 */}
<View className="typeLine" />
<Text className="tag-text">{play_type}</Text>
</View>
{/* {play_type && (
<View className="tag">
<Text className="tag-text">{play_type}</Text>
</View>
)} */}
</View>
</View>
</View>
{/* 右侧图片区域 */}
<View className="image-section">{renderImages()}</View>
</View>
{/* 畅打球局 */}
{isShowSmoothPlayingGame && (
<View className="smoothPlayingGame">
<View className="smoothWrapper">
<Image
className="iconListPlayingGame"
src={img.ICON_LIST_CHANGDA_QIuju}
mode="widthFix"
/>
{/* <Text className="smoothTitle">{game_type}</Text> */}
</View>
{venue_description && <View className="line" />}
{venue_description && (
<View className="localAreaContainer">
<View className="localAreaTitle">:</View>
<View className="localAreaWrapper">
<Image className="localArea" src={venueImage} />
<Text className="localAreaText">{venue_description}</Text>
</View>
</View>
)}
</View>
)}
</View>
);
};
export default ListCard;