Merge branch 'master' into feat/liujie
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"miniprogramRoot": "dist/",
|
||||
"projectname": "playBallTogether",
|
||||
"description": "playBallTogether",
|
||||
"appid": "wx815b533167eb7b53",
|
||||
"appid": "wx915ecf6c01bea4ec",
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"es6": true,
|
||||
|
||||
@@ -57,6 +57,7 @@ export default defineAppConfig({
|
||||
"ntrp-evaluate/index", // NTRP评估页
|
||||
"enable_notification/index", // 开启消息通知
|
||||
"emptyState/index", // 空状态页面
|
||||
"bannerDetail/index", // Banner 图片详情页
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -21,6 +21,6 @@ page {
|
||||
font-family: "Quicksand";
|
||||
// 注意:此路径来自 @/config/api.ts 中的 OSS_BASE_URL 配置
|
||||
// 如需修改,请更新配置文件中的 OSS_BASE_URL
|
||||
src: url("https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/other/57dc951f-f10e-45b7-9157-0b1e468187fd.ttf") format("truetype");
|
||||
src: url("https://youchang2026.oss-cn-shanghai.aliyuncs.com/front/ball/other/57dc951f-f10e-45b7-9157-0b1e468187fd.ttf") format("truetype");
|
||||
font-display: swap;
|
||||
}
|
||||
@@ -7,7 +7,8 @@
|
||||
border-radius: 20px 20px 0 0 !important;
|
||||
}
|
||||
.common-popup__drag-handle-container {
|
||||
position: position;
|
||||
position: relative;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.common-popup__drag-handle {
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
EvaluateCallback,
|
||||
EvaluateScene,
|
||||
} from "@/store/evaluateStore";
|
||||
import { useListState } from "@/store/listStore";
|
||||
|
||||
import { navigateTo, redirectTo, navigateBack } from "@/utils/navigation";
|
||||
import { requireLoginWithPhone } from "@/utils/helper";
|
||||
import styles from "./index.module.scss";
|
||||
@@ -24,6 +26,11 @@ const PublishMenu: React.FC<PublishMenuProps> = (props) => {
|
||||
const { onVisibleChange } = props;
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const {
|
||||
area
|
||||
} = useListState();
|
||||
|
||||
|
||||
// 使用 useEffect 监听 isVisible 变化,确保所有情况都能触发回调
|
||||
useEffect(() => {
|
||||
onVisibleChange?.(isVisible);
|
||||
@@ -59,6 +66,16 @@ const PublishMenu: React.FC<PublishMenuProps> = (props) => {
|
||||
});
|
||||
};
|
||||
const handleMenuItemClick = (type: "individual" | "group" | "ai") => {
|
||||
const [_, address] = area;
|
||||
if (address !== '上海') {
|
||||
(Taro as any).showModal({
|
||||
title: '提示',
|
||||
content: '仅上海地区开放,您可加入社群或切换城市',
|
||||
showCancel: false,
|
||||
confirmText: '知道了'
|
||||
})
|
||||
return;
|
||||
}
|
||||
if (!userInfo.ntrp_level) {
|
||||
ntrpRef.current.show({
|
||||
type: EvaluateScene.publish,
|
||||
|
||||
@@ -29,8 +29,10 @@ const TextareaTag: React.FC<TextareaTagProps> = ({
|
||||
// 处理文本输入变化
|
||||
const handleTextChange = useCallback((val: string) => {
|
||||
console.log(val,'e.detail.value')
|
||||
onChange({...value, description: val})
|
||||
}, [onChange])
|
||||
const maxAllowedLength = Math.floor(maxLength * 1.2)
|
||||
const truncatedVal = val.length > maxAllowedLength ? val.slice(0, maxAllowedLength) : val
|
||||
onChange({...value, description: truncatedVal})
|
||||
}, [onChange, maxLength, value])
|
||||
|
||||
// 处理标签选择变化
|
||||
const handleTagChange = useCallback((selectedTags: string[]) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import envConfig from './env'// API配置
|
||||
|
||||
// OSS 基础路径配置
|
||||
export const OSS_BASE_URL = 'https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball'
|
||||
export const OSS_BASE_URL = 'https://youchang2026.oss-cn-shanghai.aliyuncs.com/front/ball'
|
||||
|
||||
export const API_CONFIG = {
|
||||
// 基础URL
|
||||
|
||||
@@ -3,6 +3,7 @@ import ListCard from "@/components/ListCard";
|
||||
import ListLoadError from "@/components/ListLoadError";
|
||||
import ListCardSkeleton from "@/components/ListCardSkeleton";
|
||||
import { useReachBottom } from "@tarojs/taro";
|
||||
import Taro from "@tarojs/taro";
|
||||
import { useUserInfo, useUserActions, useLastTestResult } from "@/store/userStore";
|
||||
import { NTRPTestEntryCard } from "@/components";
|
||||
import { EvaluateScene } from "@/store/evaluateStore";
|
||||
@@ -158,6 +159,35 @@ const ListContainer = (props) => {
|
||||
[evaluateFlag, data, hasTestInLastMonth, showNumber]
|
||||
);
|
||||
|
||||
// 渲染 banner 卡片
|
||||
const renderBanner = (item, index) => {
|
||||
if (!item?.banner_image_url) return null;
|
||||
return (
|
||||
<View
|
||||
key={item.id || `banner-${index}`}
|
||||
style={{
|
||||
maxHeight: "122px",
|
||||
overflow: "hidden",
|
||||
borderRadius: "12px",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={item.banner_image_url}
|
||||
mode="widthFix"
|
||||
style={{ width: "100%", display: "block", maxHeight: "122px" }}
|
||||
onClick={() => {
|
||||
const target = item.banner_detail_url;
|
||||
if (target) {
|
||||
(Taro as any).navigateTo({
|
||||
url: `/other_pages/bannerDetail/index?img=${encodeURIComponent(target)}`,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染列表
|
||||
const renderList = () => {
|
||||
// 请求数据为空
|
||||
@@ -181,6 +211,9 @@ const ListContainer = (props) => {
|
||||
return (
|
||||
<>
|
||||
{memoizedList.map((match, index) => {
|
||||
if (match.type === "banner") {
|
||||
return renderBanner(match, index);
|
||||
}
|
||||
if (match.type === "evaluateCard") {
|
||||
return (
|
||||
<NTRPTestEntryCard key="evaluate" type={EvaluateScene.list} />
|
||||
|
||||
@@ -85,7 +85,7 @@ export default function Participants(props) {
|
||||
// id: 18,
|
||||
// nickname: "小猫开刀削面店往猫毛里面下面条",
|
||||
// avatar_url:
|
||||
// "https://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/d284060f-248b-4d58-a153-4d37c0ca77c8.jpg",
|
||||
// "https://youchang2026.oss-cn-shanghai.aliyuncs.com/front/ball/images/d284060f-248b-4d58-a153-4d37c0ca77c8.jpg",
|
||||
// phone: "18513125687",
|
||||
// ntrp_level: "1.5",
|
||||
// },
|
||||
|
||||
@@ -385,7 +385,9 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
|
||||
};
|
||||
|
||||
const handleRefresh = async () => {
|
||||
|
||||
if (refreshing) {
|
||||
return;
|
||||
}
|
||||
|
||||
setRefreshing(true);
|
||||
try {
|
||||
|
||||
@@ -64,11 +64,17 @@ const MessagePageContent: React.FC<MessagePageContentProps> = ({ isActive = true
|
||||
}
|
||||
};
|
||||
|
||||
// 只有当页面激活且未加载过数据时才加载接口
|
||||
// 当切换到消息 tab 时,调用红点信息接口
|
||||
useEffect(() => {
|
||||
if (isActive) {
|
||||
fetchReddotInfo();
|
||||
}
|
||||
}, [isActive]);
|
||||
|
||||
// 只有当页面激活且未加载过数据时才加载通知列表
|
||||
useEffect(() => {
|
||||
if (isActive && !hasLoaded) {
|
||||
getNoticeList();
|
||||
fetchReddotInfo();
|
||||
setHasLoaded(true);
|
||||
}
|
||||
}, [isActive, hasLoaded]);
|
||||
|
||||
@@ -13,6 +13,7 @@ import MessagePageContent from "./components/MessagePageContent";
|
||||
import MyselfPageContent from "./components/MyselfPageContent";
|
||||
import "./index.scss";
|
||||
import FamilyContext from "@/context";
|
||||
import { useDictionaryStore } from "@/store/dictionaryStore";
|
||||
|
||||
type TabType = "list" | "message" | "personal";
|
||||
|
||||
@@ -66,6 +67,12 @@ const MainPage: React.FC = () => {
|
||||
try {
|
||||
await fetchUserInfo();
|
||||
await checkNicknameChangeStatus();
|
||||
// 启动时预取 Banner 字典(与业务无强依赖,失败不影响主流程)
|
||||
try {
|
||||
await useDictionaryStore.getState().fetchBannerDictionary();
|
||||
} catch (e) {
|
||||
console.error("预取 Banner 字典失败:", e);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取用户信息失败:", error);
|
||||
}
|
||||
|
||||
8
src/other_pages/bannerDetail/index.config.ts
Normal file
8
src/other_pages/bannerDetail/index.config.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '',
|
||||
navigationStyle: 'custom',
|
||||
navigationBarBackgroundColor: '#FFFFFF',
|
||||
backgroundColor: '#FFFFFF',
|
||||
});
|
||||
|
||||
|
||||
16
src/other_pages/bannerDetail/index.scss
Normal file
16
src/other_pages/bannerDetail/index.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
.banner_detail_page {
|
||||
min-height: 100vh;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.banner_detail_content {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.banner_detail_image {
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
36
src/other_pages/bannerDetail/index.tsx
Normal file
36
src/other_pages/bannerDetail/index.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { View, Image } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { GeneralNavbar } from '@/components';
|
||||
import './index.scss';
|
||||
|
||||
function Index() {
|
||||
const [imgUrl, setImgUrl] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const instance = (Taro as any).getCurrentInstance?.();
|
||||
const params = instance?.router?.params || {};
|
||||
const url = params?.img ? decodeURIComponent(params.img) : '';
|
||||
setImgUrl(url);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View className="banner_detail_page">
|
||||
<GeneralNavbar title="" showBack={true} />
|
||||
<View className="banner_detail_content">
|
||||
{imgUrl ? (
|
||||
<Image
|
||||
className="banner_detail_image"
|
||||
src={imgUrl}
|
||||
mode="widthFix"
|
||||
showMenuByLongpress
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default Index;
|
||||
|
||||
|
||||
@@ -71,14 +71,12 @@ const CommentReply = () => {
|
||||
|
||||
setCommentList(mappedList);
|
||||
|
||||
// 获取未读评论ID并标记已读
|
||||
const unreadIds = res.data.rows
|
||||
.filter((item: any) => item.is_read === 0)
|
||||
.map((item: any) => item.id);
|
||||
// 获取所有评论ID列表并标记已读(传入所有ID,包括已读和未读)
|
||||
const allCommentIds = res.data.rows.map((item: any) => item.id);
|
||||
|
||||
if (unreadIds.length > 0) {
|
||||
// 使用统一接口标记已读
|
||||
messageService.markAsRead('comment', unreadIds).catch(e => {
|
||||
if (allCommentIds.length > 0) {
|
||||
// 使用统一接口标记已读,传入所有评论ID
|
||||
messageService.markAsRead('comment', allCommentIds).catch(e => {
|
||||
console.error("标记评论已读失败:", e);
|
||||
});
|
||||
}
|
||||
@@ -217,6 +215,15 @@ const CommentReply = () => {
|
||||
}));
|
||||
|
||||
setCommentList(mappedList);
|
||||
|
||||
// 获取所有评论ID列表并标记已读(传入所有ID)
|
||||
const allCommentIds = res.data.rows.map((item: any) => item.id);
|
||||
|
||||
if (allCommentIds.length > 0) {
|
||||
messageService.markAsRead('comment', allCommentIds).catch(e => {
|
||||
console.error("标记评论已读失败:", e);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Taro.showToast({
|
||||
|
||||
@@ -56,14 +56,12 @@ const NewFollow = () => {
|
||||
|
||||
setFollowList(mappedList);
|
||||
|
||||
// 获取未读关注ID并标记已读
|
||||
const unreadFanIds = res.list
|
||||
.filter((item: any) => item.is_read === 0)
|
||||
.map((item: any) => item.id);
|
||||
// 获取所有关注者ID列表并标记已读(传入所有ID,包括已读和未读)
|
||||
const allFanIds = res.list.map((item: any) => item.id);
|
||||
|
||||
if (unreadFanIds.length > 0) {
|
||||
// 使用统一接口标记已读
|
||||
messageService.markAsRead('follow', unreadFanIds).catch(e => {
|
||||
if (allFanIds.length > 0) {
|
||||
// 使用统一接口标记已读,传入所有关注者ID
|
||||
messageService.markAsRead('follow', allFanIds).catch(e => {
|
||||
console.error("标记关注已读失败:", e);
|
||||
});
|
||||
}
|
||||
@@ -164,6 +162,15 @@ const NewFollow = () => {
|
||||
}));
|
||||
|
||||
setFollowList(mappedList);
|
||||
|
||||
// 获取所有关注者ID列表并标记已读(传入所有ID)
|
||||
const allFanIds = res.list.map((item: any) => item.id);
|
||||
|
||||
if (allFanIds.length > 0) {
|
||||
messageService.markAsRead('follow', allFanIds).catch(e => {
|
||||
console.error("标记关注已读失败:", e);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 如果没有数据,设置为空数组以显示空状态
|
||||
setFollowList([]);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
|
||||
box-sizing: border-box;
|
||||
// 搜索区域
|
||||
.search-section {
|
||||
background: #f5f5f5;
|
||||
|
||||
@@ -106,8 +106,15 @@ const SelectStadium: React.FC<SelectStadiumProps> = ({
|
||||
})
|
||||
setShowDetail(true)
|
||||
},
|
||||
fail: (err) => {
|
||||
fail: (err: { errMsg: string }) => {
|
||||
console.error('选择位置失败:', err)
|
||||
const { errMsg } = err || {};
|
||||
if (!errMsg.includes('fail cancel')) {
|
||||
Taro.showToast({
|
||||
title: errMsg,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useCallback, forwardRef, useImperativeHandle, useEffect } from 'react'
|
||||
import React, { useState, useCallback, forwardRef, useImperativeHandle } from 'react'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { View, Text, Image, ScrollView } from '@tarojs/components'
|
||||
import images from '@/config/images'
|
||||
@@ -70,6 +70,7 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
onAnyInput
|
||||
}, ref) => {
|
||||
const [openPicker, setOpenPicker] = useState(false);
|
||||
const [scrollTop, setScrollTop] = useState(0);
|
||||
const { getDictionaryValue } = useDictionaryActions()
|
||||
const court_type = getDictionaryValue('court_type') || []
|
||||
const court_surface = getDictionaryValue('court_surface') || []
|
||||
@@ -145,12 +146,15 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
istance: null
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
fail: (err: { errMsg: string }) => {
|
||||
console.error('选择位置失败:', err)
|
||||
Taro.showToast({
|
||||
title: '位置选择失败',
|
||||
icon: 'error'
|
||||
})
|
||||
const { errMsg } = err || {};
|
||||
if (!errMsg.includes('fail cancel')) {
|
||||
Taro.showToast({
|
||||
title: errMsg,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -167,14 +171,27 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
|
||||
|
||||
|
||||
const changeTextarea = (value) => {
|
||||
if (value) {
|
||||
// 先滚动到底部
|
||||
setScrollTop(scrollTop ? scrollTop + 1 : 9999);
|
||||
// 使用 setTimeout 确保滚动后再更新 openPicker
|
||||
}
|
||||
}
|
||||
|
||||
const changePicker = (value) => {
|
||||
setOpenPicker(value)
|
||||
setOpenPicker(value);
|
||||
}
|
||||
|
||||
console.log(stadium,'stadiumstadium');
|
||||
return (
|
||||
<View className='stadium-detail'>
|
||||
<ScrollView className='stadium-detail-scroll' refresherBackground="#FAFAFA" scrollY={!openPicker}>
|
||||
<ScrollView
|
||||
className='stadium-detail-scroll'
|
||||
refresherBackground="#FAFAFA"
|
||||
scrollY={!openPicker}
|
||||
scrollTop={scrollTop}
|
||||
>
|
||||
{/* 已选球场 */}
|
||||
<View
|
||||
className={`stadium-item`}
|
||||
@@ -217,9 +234,12 @@ const StadiumDetail = forwardRef<StadiumDetailRef, StadiumDetailProps>(({
|
||||
<View className='textarea-tag-container'>
|
||||
<TextareaTag
|
||||
value={formData[item.prop]}
|
||||
onChange={(value) => updateFormData(item.prop, value)}
|
||||
onBlur={() => changePicker(false)}
|
||||
onFocus={() => changePicker(true)}
|
||||
onChange={(value) => {
|
||||
changeTextarea(true)
|
||||
updateFormData(item.prop, value)
|
||||
}}
|
||||
// onBlur={() => changeTextarea(false)}
|
||||
onFocus={() => changeTextarea(true)}
|
||||
placeholder='有其他场地信息可备注'
|
||||
options={(item.options || []).map((o) => ({ label: o, value: o }))}
|
||||
/>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom'
|
||||
})
|
||||
navigationStyle: 'custom',
|
||||
// 禁止原生页面滚动,改用内部自定义滚动容器,避免顶部/底部回弹后中间无法继续滚动的问题
|
||||
disableScroll: true,
|
||||
})
|
||||
|
||||
@@ -85,6 +85,7 @@ const PublishBall: React.FC = () => {
|
||||
defaultFormData,
|
||||
]);
|
||||
const [checked, setChecked] = useState(true);
|
||||
const [publishLoading, setPublishLoading] = useState(false);
|
||||
const [titleBar, setTitleBar] = useState("发布球局");
|
||||
// 控制是否响应全局键盘(由具体输入框 focus/blur 控制)
|
||||
const [shouldReactToKeyboard, setShouldReactToKeyboard] = useState(false);
|
||||
@@ -372,9 +373,10 @@ const PublishBall: React.FC = () => {
|
||||
const { republish } = params || {};
|
||||
if (activityType === "individual") {
|
||||
const isValid = validateFormData(formData[0]);
|
||||
if (!isValid) {
|
||||
if (!isValid || publishLoading) {
|
||||
return;
|
||||
}
|
||||
setPublishLoading(true);
|
||||
const {
|
||||
activityInfo,
|
||||
descriptionInfo,
|
||||
@@ -435,13 +437,15 @@ const PublishBall: React.FC = () => {
|
||||
title: res.message,
|
||||
icon: "none",
|
||||
});
|
||||
setPublishLoading(false);
|
||||
}
|
||||
}
|
||||
if (activityType === "group") {
|
||||
const isValid = formData.every((item) => validateFormData(item));
|
||||
if (!isValid) {
|
||||
if (!isValid || publishLoading) {
|
||||
return;
|
||||
}
|
||||
setPublishLoading(true);
|
||||
if (checkAdjacentDataSame(formData)) {
|
||||
Taro.showToast({
|
||||
title: "信息不可与前序场完全一致",
|
||||
@@ -505,6 +509,7 @@ const PublishBall: React.FC = () => {
|
||||
title: res.message,
|
||||
icon: "none",
|
||||
});
|
||||
setPublishLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -14,6 +14,13 @@ interface DictionaryState {
|
||||
fetchDictionary: () => Promise<void>
|
||||
getDictionaryValue: (key: string, defaultValue?: any) => any
|
||||
clearDictionary: () => void
|
||||
// banner 字典(单独管理,保持原始值)
|
||||
bannerDict: {
|
||||
bannerListImage: string
|
||||
bannerDetailImage: string
|
||||
bannerListIndex: string
|
||||
} | null
|
||||
fetchBannerDictionary: () => Promise<void>
|
||||
}
|
||||
|
||||
// 创建字典Store
|
||||
@@ -22,6 +29,7 @@ export const useDictionaryStore = create<DictionaryState>()((set, get) => ({
|
||||
dictionaryData: {},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
bannerDict: null,
|
||||
|
||||
// 获取字典数据
|
||||
fetchDictionary: async () => {
|
||||
@@ -56,6 +64,27 @@ export const useDictionaryStore = create<DictionaryState>()((set, get) => ({
|
||||
}
|
||||
},
|
||||
|
||||
// 获取 Banner 字典(启动时或手动调用)
|
||||
fetchBannerDictionary: async () => {
|
||||
try {
|
||||
const keys = 'bannerListImage,bannerDetailImage,bannerListIndex';
|
||||
const response = await commonApi.getDictionaryManyKey(keys)
|
||||
if (response.code === 0 && response.data) {
|
||||
const data = response.data || {};
|
||||
set({
|
||||
bannerDict: {
|
||||
bannerListImage: data.bannerListImage || '',
|
||||
bannerDetailImage: data.bannerDetailImage || '',
|
||||
bannerListIndex: (data.bannerListIndex ?? '').toString(),
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// 保持静默,避免影响启动流程
|
||||
console.error('获取 Banner 字典失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取字典值
|
||||
getDictionaryValue: (key: string, defaultValue?: any) => {
|
||||
const { dictionaryData } = get()
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
getCityQrCode,
|
||||
getDistricts,
|
||||
} from "../services/listApi";
|
||||
// 不再在这里请求 banner 字典,统一由 dictionaryStore 启动时获取
|
||||
import { useDictionaryStore } from "./dictionaryStore";
|
||||
import {
|
||||
ListActions,
|
||||
IFilterOptions,
|
||||
@@ -18,6 +20,26 @@ import {
|
||||
IPayload,
|
||||
} from "../../types/list/types";
|
||||
|
||||
// 将 banner 按索引插入到列表的工具方法(0基;长度不足则插末尾;先移除已存在的 banner)
|
||||
function insertBannersToRows(rows: any[], dictData: any) {
|
||||
if (!Array.isArray(rows) || !dictData) return rows;
|
||||
const img = (dictData?.bannerListImage || "").trim();
|
||||
const indexRaw = (dictData?.bannerListIndex || "").toString().trim();
|
||||
if (!img) return rows;
|
||||
const parsed = parseInt(indexRaw, 10);
|
||||
const normalized = Number.isFinite(parsed) ? parsed : 0;
|
||||
// 先移除已有的 banner,确保列表中仅一条 banner
|
||||
const resultRows = rows?.filter((item) => item?.type !== "banner") || [];
|
||||
const target = Math.max(0, Math.min(normalized, resultRows.length));
|
||||
resultRows.splice(target, 0, {
|
||||
type: "banner",
|
||||
id: `banner-${target}`,
|
||||
banner_image_url: img,
|
||||
banner_detail_url: (dictData?.bannerDetailImage || "").trim(),
|
||||
} as any);
|
||||
return resultRows;
|
||||
}
|
||||
|
||||
function translateCityData(dataTree) {
|
||||
return dataTree.map((item) => {
|
||||
const { children, ...rest } = item;
|
||||
@@ -250,10 +272,14 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
const currentPageState = state.isSearchResult ? state.searchPageState : state.listPageState;
|
||||
const currentData = currentPageState?.data || [];
|
||||
const newData = isAppend ? [...currentData, ...(data || [])] : (data || []);
|
||||
// 从字典缓存获取 banner,并将其插入到最终列表指定位置(全局索引)
|
||||
const dictData = useDictionaryStore.getState().bannerDict;
|
||||
const processedData = dictData ? insertBannersToRows(newData, dictData) : newData;
|
||||
state.updateCurrentPageState({
|
||||
data: newData,
|
||||
data: processedData,
|
||||
isHasMoreData,
|
||||
isShowNoData: newData?.length === 0,
|
||||
// 使用插入后的最终数据判断是否显示空状态,避免有 banner 时仍显示空
|
||||
isShowNoData: processedData?.length === 0,
|
||||
});
|
||||
|
||||
set({
|
||||
@@ -296,7 +322,9 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
}
|
||||
}
|
||||
|
||||
const resData = (await fetchFn(reqParams)) || {};
|
||||
let resData: any = {};
|
||||
resData = (await fetchFn(reqParams)) || {};
|
||||
|
||||
const { data = {}, code } = resData;
|
||||
if (code !== 0) {
|
||||
setListData({
|
||||
@@ -308,7 +336,9 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
});
|
||||
return Promise.reject(new Error('获取数据失败'));
|
||||
}
|
||||
const { count, rows } = data;
|
||||
const { count } = data;
|
||||
let { rows } = data as any;
|
||||
|
||||
setListData({
|
||||
error: '',
|
||||
data: rows || [],
|
||||
@@ -319,7 +349,7 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
return Promise.resolve();
|
||||
} catch (error) {
|
||||
setListData({
|
||||
error: "-1",
|
||||
error: "",
|
||||
data: [],
|
||||
loading: false,
|
||||
count: 0,
|
||||
@@ -345,23 +375,24 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
try {
|
||||
const searchParams = getSearchParams() || {};
|
||||
|
||||
// 调用常规列表接口
|
||||
const listParams = {
|
||||
...searchParams,
|
||||
order: searchParams.order || "distance",
|
||||
};
|
||||
const listRes = await getGamesList(listParams);
|
||||
|
||||
// 调用智能排序列表接口
|
||||
const integrateParams = {
|
||||
...searchParams,
|
||||
order: "",
|
||||
seachOption: {
|
||||
...searchParams.seachOption,
|
||||
isRefresh: true,
|
||||
},
|
||||
};
|
||||
const integrateRes = await getGamesIntegrateList(integrateParams);
|
||||
// 并发请求:常规列表、智能排序列表
|
||||
const [listResSettled, integrateResSettled] = await Promise.allSettled([
|
||||
getGamesList({
|
||||
...searchParams,
|
||||
order: searchParams.order || "distance",
|
||||
}),
|
||||
getGamesIntegrateList({
|
||||
...searchParams,
|
||||
order: "",
|
||||
seachOption: {
|
||||
...searchParams.seachOption,
|
||||
isRefresh: true,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const listRes = listResSettled.status === "fulfilled" ? listResSettled.value : null;
|
||||
const integrateRes = integrateResSettled.status === "fulfilled" ? integrateResSettled.value : null;
|
||||
|
||||
// 根据当前排序方式更新对应的数据
|
||||
const currentPageState = state.isSearchResult ? state.searchPageState : state.listPageState;
|
||||
@@ -369,7 +400,8 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
const isIntegrate = distanceQuickFilter?.order === "0";
|
||||
|
||||
if (listRes?.code === 0 && listRes?.data) {
|
||||
const { count, rows } = listRes.data;
|
||||
const { count } = listRes.data;
|
||||
let { rows } = listRes.data as any;
|
||||
if (!isIntegrate) {
|
||||
// 如果当前是常规排序,更新常规列表数据
|
||||
setListData({
|
||||
@@ -383,7 +415,8 @@ export const useListStore = create<TennisStore>()((set, get) => ({
|
||||
}
|
||||
|
||||
if (integrateRes?.code === 0 && integrateRes?.data) {
|
||||
const { count, rows, recommendList } = integrateRes.data;
|
||||
const { count } = integrateRes.data;
|
||||
let { rows, recommendList } = integrateRes.data as any;
|
||||
if (isIntegrate) {
|
||||
// 如果当前是智能排序,更新智能排序列表数据
|
||||
setListData({
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 78px;
|
||||
width: 90px;
|
||||
height: 24px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
display: flex;
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 78px;
|
||||
width: 90px;
|
||||
height: 24px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user