列表联调

This commit is contained in:
李瑞
2025-09-07 18:54:36 +08:00
parent 2d0d728969
commit 6feb7057af
28 changed files with 1225 additions and 740 deletions

View File

@@ -30,6 +30,7 @@
}
.arrow {
width: 10px;
height: 20px;
position: relative;
}
.arrow.left {

View File

@@ -35,18 +35,18 @@
border-bottom-right-radius: 30px;
}
.nut-menu-container-item {
color: rgba(60, 60, 67, 0.60);
font-size: 14px;
font-weight: 600;
line-height: 20px;
}
// .nut-menu-container-item {
// color: rgba(60, 60, 67, 0.6);
// font-size: 14px;
// font-weight: 600;
// line-height: 20px;
// }
.nut-menu-container-item.active {
flex-direction: row-reverse;
justify-content: space-between;
color: #000;
}
// .nut-menu-container-item.active {
// flex-direction: row-reverse;
// justify-content: space-between;
// color: #000;
// }
.positionWrap {
display: flex;
@@ -88,4 +88,40 @@
width: 20px;
height: 20px;
}
}
.quickOptionsWrapper {
width: 100%;
display: flex;
flex-direction: column;
.quickItem {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0;
color: rgba(60, 60, 67, 0.6);
font-size: 14px;
font-weight: 600;
line-height: 20px;
&.active {
color: #000;
}
}
}
}
.distanceQuickFilterWrap_0 .nut-menu-title-0 {
background-color: #000;
color: #fff;
&.active {
color: #ffffff;
}
}
.distanceQuickFilterWrap_1 .nut-menu-title-1 {
background-color: #000;
color: #fff;
&.active {
color: #ffffff;
}
}

View File

@@ -1,6 +1,6 @@
import React, { useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { Menu, Button } from "@nutui/nutui-react-taro";
import { Image } from "@tarojs/components";
import { Image, View, Text } from "@tarojs/components";
import img from "@/config/images";
import Bubble from "../Bubble";
import "./index.scss";
@@ -15,23 +15,46 @@ const Demo3 = (props) => {
cityValue,
quickValue,
} = props;
const cityRef = useRef(null);
const quickRef = useRef(null);
const [changePosition, setChangePosition] = useState<number[]>([]);
const itemRef = useRef(null);
// 全城筛选显示的标题
const cityTitle = cityOptions.find((item) => item.value === cityValue)?.label;
const handleChange = (name: string, value: string) => {
// 快捷筛选显示的标题
const quickTitle = quickOptions.find(
(item) => item.value === quickValue
)?.label;
// className
const filterWrapperClassName = changePosition.reduce((pre, cur) => {
return `${pre} distanceQuickFilterWrap_${cur}`;
}, "");
const handleChange = (
name: string,
value: string | number,
index: number
) => {
setChangePosition((preState) => {
const newData = new Set([...preState, index]);
return Array.from(newData);
});
onChange && onChange(name, value);
(itemRef.current as any)?.toggle(false);
};
// const cityTitle = cityOptions.find((item) => item.value === cityValue)?.label;
// 控制隐藏
index === 0 && (cityRef.current as any)?.toggle(false);
index === 1 && (quickRef.current as any)?.toggle(false);
};
return (
<Menu
className="distanceQuickFilterWrap"
className={`distanceQuickFilterWrap ${filterWrapperClassName}`}
>
<Menu.Item
title={cityValue}
ref={itemRef}
title={cityTitle}
ref={cityRef}
icon={<Image src={img.ICON_MENU_ITEM_SELECTED} />}
>
<div className="positionWrap">
@@ -42,7 +65,7 @@ const Demo3 = (props) => {
<Bubble
options={cityOptions}
value={cityValue}
onChange={handleChange}
onChange={(name, value) => handleChange(name, value, 0)}
layout="grid"
size="small"
columns={4}
@@ -52,30 +75,35 @@ const Demo3 = (props) => {
</div>
</Menu.Item>
<Menu.Item
options={quickOptions}
title={quickTitle}
ref={quickRef}
defaultValue={quickValue}
onChange={(value) => handleChange(quickName, value)}
icon={<Image className="itemIcon" src={img.ICON_MENU_ITEM_SELECTED} />}
/>
{/* <Menu.Item title="筛选" ref={itemRef}>
<div
style={{
width: '50%',
lineHeight: '28px',
padding: '0 30px',
}}
>
自定义内容
</div>
<Button
size="small"
onClick={() => {
;(itemRef.current as any)?.toggle(false)
}}
>
确认
</Button>
</Menu.Item> */}
// options={quickOptions}
// onChange={(value) => handleChange(quickName, value, 1)}
// icon={<Image className="itemIcon" src={img.ICON_MENU_ITEM_SELECTED} />}
>
<View className="quickOptionsWrapper">
{quickOptions.map((item) => {
const active = quickValue === item?.value;
return (
<View
className={`quickItem ${active && "active"}`}
onClick={() => handleChange(quickName, item.value, 1)}
>
<View>{item?.label}</View>
{active && (
<View>
<Image
className="itemIcon"
src={img.ICON_MENU_ITEM_SELECTED}
/>
</View>
)}
</View>
);
})}
</View>
</Menu.Item>
</Menu>
);
};

View File

@@ -5,12 +5,23 @@ import styles from "./index.module.scss";
import { Button } from "@nutui/nutui-react-taro";
import { useListStore } from "src/store/listStore";
import { BubbleOption, FilterPopupProps } from "../../../types/list/types";
import CalendarCard from "@/components/CalendarCard/index";
import dateRangeUtils from '@/utils/dateRange'
// 场地
import CourtType from "@/components/CourtType";
// 玩法
import GamePlayType from "@/components/GamePlayType";
import { useDictionaryActions } from "@/store/dictionaryStore";
import { useMemo } from "react";
import { View } from "@tarojs/components";
const dateTrabseformMap = {
'1': dateRangeUtils.getThisWeekend,
'2': dateRangeUtils.getNextWeekRange,
'3': dateRangeUtils.getNextMonthRange
}
const FilterPopup = (props: FilterPopupProps) => {
const {
@@ -24,34 +35,75 @@ const FilterPopup = (props: FilterPopupProps) => {
onClose,
statusNavbarHeigh,
} = props;
const store = useListStore() || {};
const { getDictionaryValue } = useDictionaryActions() || {};
const { timeBubbleData } = store;
const { timeBubbleData, gamesNum, dateRangeOptions } = store;
/**
* @description 处理字典选项
* @param dictionaryValue 字典选项
* @returns 选项列表
*/
const handleOptions = (dictionaryValue: []) => {
return dictionaryValue?.map((item) => ({ label: item, value: item })) || [];
};
/**
* @description 场地类型选项
*/
const courtType = getDictionaryValue("court_type") || [];
const locationOptions: BubbleOption[] = useMemo(() => {
return courtType ? handleOptions(courtType) : [];
}, [courtType]);
/**
* @description 玩法选项
*/
const gamePlay = getDictionaryValue("game_play") || [];
const gamePlayOptions = useMemo(() => {
return gamePlay ? handleOptions(gamePlay) : [];
}, [gamePlay]);
/**
* @description 筛选选项改变
* @param name 选项名称
* @param value 选项值
*/
const handleFilterChange = (name, value) => {
onChange({ [name]: value });
};
/**
* @description 清空筛选
*/
const handleClearFilter = () => {
onClear();
onCancel();
};
/**
* @description 日期选择
* @param date 日期
*/
const handleDateRangeChange = (date: Date) => {
onChange({
'dateRange': [date, date],
'dateRangeQuick': '',
})
}
/**
* @description 点击 本周末 一周内 一月内
*/
const handleDateRangeQuickClick = (name, value) => {
const date = dateTrabseformMap?.[value]()
onChange({
'dateRange': [date?.start, date?.end],
[name]: value,
})
}
return (
<>
<Popup
@@ -62,17 +114,38 @@ const FilterPopup = (props: FilterPopupProps) => {
onClose={onClose}
style={{ marginTop: statusNavbarHeigh + "px" }}
overlayStyle={{ marginTop: statusNavbarHeigh + "px" }}
zIndex={1001}
>
<div className={styles.filterPopupWrapper}>
{/* 日历 */}
<View>
{/* 快捷选日期 */}
<View>
<Bubble
options={dateRangeOptions}
value={filterOptions?.dateRangeQuick}
onChange={handleDateRangeQuickClick}
layout="grid"
size="small"
columns={3}
name="dateRangeQuick"
/>
</View>
<CalendarCard
value={filterOptions?.dateRange?.[0]}
// minDate={}
// maxDate={}
onChange={handleDateRangeChange} />
</View>
{/* 时间气泡选项 */}
<Bubble
options={timeBubbleData}
value={filterOptions?.time}
value={filterOptions?.timeSlot}
onChange={handleFilterChange}
layout="grid"
size="small"
columns={3}
name="time"
name="timeSlot"
/>
{/* 范围选择 */}
@@ -131,7 +204,7 @@ const FilterPopup = (props: FilterPopupProps) => {
loading={loading}
onClick={onConfirm}
>
332
{gamesNum}
</Button>
</div>
</div>

View File

@@ -51,7 +51,7 @@
}
.location-position {
max-width: 66%;
max-width: 50%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

View File

@@ -7,18 +7,23 @@ import "./index.scss";
const ListCard: React.FC<ListCardProps> = ({
id,
title,
dateTime,
venue_description,
start_time,
location,
distance_km,
registeredCount,
maxCount,
skillLevel,
current_players,
max_players,
skill_level_min,
skill_level_max,
play_type,
images = [],
image_list = [],
court_type,
key
}) => {
const renderItemImage = (src: string) => {
return <Image src={src} className="image" mode="aspectFill" />;
return <Image src={src} className="image" mode="aspectFill"
lazyLoad
defaultSource='https://images.unsplash.com/photo-1554068865-24cecd4e34b8?w=200&h=200&fit=crop&crop=center'
/>;
};
const handleViewDetail = () => {
@@ -30,21 +35,21 @@ const ListCard: React.FC<ListCardProps> = ({
// 根据图片数量决定展示样式
const renderImages = () => {
if (images?.length === 0) return null;
if (image_list?.length === 0) return null;
if (images?.length === 1) {
if (image_list?.length === 1) {
return (
<View className="single-image">
<View className="image-container">{renderItemImage(images[0])}</View>
<View className="image-container">{renderItemImage(image_list?.[0])}</View>
</View>
);
}
if (images?.length === 2) {
if (image_list?.length === 2) {
return (
<View className="double-image">
<View className="image-container">{renderItemImage(images[0])}</View>
<View className="image-container">{renderItemImage(images[1])}</View>
<View className="image-container">{renderItemImage(image_list?.[0])}</View>
<View className="image-container">{renderItemImage(image_list?.[1])}</View>
</View>
);
}
@@ -52,14 +57,14 @@ const ListCard: React.FC<ListCardProps> = ({
// 3张或更多图片
return (
<View className="triple-image">
<View className="image-container">{renderItemImage(images?.[0])}</View>
<View className="image-container">{renderItemImage(images?.[1])}</View>
<View className="image-container">{renderItemImage(images?.[2])}</View>
<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">
<View className="listCard" key={key}>
<View className="listItem" onClick={handleViewDetail}>
{/* 左侧内容区域 */}
<View className="content">
@@ -75,17 +80,17 @@ const ListCard: React.FC<ListCardProps> = ({
{/* 时间信息 */}
<View className="date-time">
<Text>{dateTime}</Text>
<Text>{start_time}</Text>
</View>
{/* 地点,室内外,距离 */}
<View className="location">
{venue_description &&
<Text className="location-text location-position">{venue_description}</Text>}
{location &&
<Text className="location-text location-position">{location}</Text>}
<Text className="location-text location-time-distance">
{court_type && `${court_type}`}
{distance_km && `${distance_km}`}
{distance_km && `${distance_km}km`}
</Text>
</View>
@@ -93,7 +98,7 @@ const ListCard: React.FC<ListCardProps> = ({
<View className="bottom-info">
<View className="left-section">
<View className="avatar-group">
{Array.from({ length: Math.min(registeredCount, 3) }).map(
{Array.from({ length: 3 }).map(
(_, index) => (
<View key={index} className="avatar">
<Image
@@ -110,12 +115,12 @@ const ListCard: React.FC<ListCardProps> = ({
<View className="tags">
<View className="tag">
<Text className="tag-text">
{registeredCount}/
<Text className="tag-text-max">{maxCount}</Text>
{current_players}/
<Text className="tag-text-max">{max_players}</Text>
</Text>
</View>
<View className="tag">
<Text className="tag-text">{skill_level_max} zh {skill_level_max}</Text>
<Text className="tag-text">{skill_level_min} {skill_level_max}</Text>
</View>
{play_type && <View className="tag">
<Text className="tag-text">{play_type}</Text>

View File

@@ -2,7 +2,13 @@ import { Image, View, Text, Button } from "@tarojs/components";
import styles from "./index.module.scss";
import img from "@/config/images";
const ListLoadError = ({ reload }: { reload: () => void }) => {
interface IProps {
reload?: () => void;
text?: string;
}
const ListLoadError = (props: IProps) => {
const { reload, text } = props;
const handleReload = () => {
reload && typeof reload === "function" && reload();
};
@@ -13,11 +19,13 @@ const ListLoadError = ({ reload }: { reload: () => void }) => {
className={styles.listLoadErrorImg}
src={img.ICON_LIST_LOAD_ERROR}
/>
<Text className={styles.listLoadErrorText}></Text>
<Button className={styles.listLoadErrorBtn} onClick={handleReload}>
<Image src={img?.ICON_LIST_RELOAD} className={styles.reloadIcon} />
</Button>
{text && <Text className={styles.listLoadErrorText}>{text}</Text>}
{reload && (
<Button className={styles.listLoadErrorBtn} onClick={handleReload}>
<Image src={img?.ICON_LIST_RELOAD} className={styles.reloadIcon} />
</Button>
)}
</View>
);
};

View File

@@ -35,6 +35,11 @@ const NtrpRange: React.FC<RangeProps> = ({
}, [JSON.stringify(value || [])]);
const handleChange = (val: [number, number]) => {
setCurrentValue(val);
// onChange?.(name, val);
};
const handleEndChange = (val: [number, number]) => {
setCurrentValue(val);
onChange?.(name, val);
};
@@ -76,7 +81,7 @@ const NtrpRange: React.FC<RangeProps> = ({
max={max}
step={step}
value={currentValue}
onEnd={handleChange}
onEnd={handleEndChange}
onChange={handleChange}
disabled={disabled}
defaultValue={[min, max]}