列表联调
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
}
|
||||
.arrow {
|
||||
width: 10px;
|
||||
height: 20px;
|
||||
position: relative;
|
||||
}
|
||||
.arrow.left {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
}
|
||||
|
||||
.location-position {
|
||||
max-width: 66%;
|
||||
max-width: 50%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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]}
|
||||
|
||||
Reference in New Issue
Block a user