6 Commits

Author SHA1 Message Date
李瑞
49f53d60ed 处理列表请求 2026-02-06 22:37:35 +08:00
李瑞
4c75368fe8 优化banner逻辑 2026-02-01 23:37:31 +08:00
张成
8abf6e6f2b 1 2026-02-01 18:46:30 +08:00
李瑞
0c83aab053 Merge branch 'feature/juguohong/20260101' 2026-02-01 16:12:48 +08:00
李瑞
1cbec87f77 增加banneer占位图 2026-02-01 16:12:04 +08:00
李瑞
d7c24ca8b3 显示空状态 2026-01-01 15:37:27 +08:00
14 changed files with 211 additions and 35 deletions

View File

@@ -57,6 +57,7 @@ export default defineAppConfig({
"ntrp-evaluate/index", // NTRP评估页
"enable_notification/index", // 开启消息通知
"emptyState/index", // 空状态页面
"bannerDetail/index", // Banner 图片详情页
],
},
],

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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} />

View File

@@ -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",
// },

View File

@@ -63,6 +63,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
area,
cityQrCode,
districts,
fetchMatches,
gamesNum, // 新增:获取球局数量
} = store;
@@ -77,6 +78,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
pageOption,
isShowNoData,
} = listPageState || {};
console.log('===matches', matches)
const scrollContextRef = useRef(null);
const scrollViewRef = useRef(null);
@@ -92,6 +94,8 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
// 记录上一次加载数据时的城市,用于检测城市变化
const lastLoadedAreaRef = useRef<[string, string] | null>(null);
const prevIsActiveRef = useRef(isActive);
// 首次加载标记:避免切回 tab 时使用 isRefresh 导致智能排序顺序抖动
const hasLoadedOnceRef = useRef(false);
// 处理距离筛选显示/隐藏
const handleDistanceFilterVisibleChange = useCallback(
@@ -230,9 +234,16 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
// 只有当页面激活时才加载位置和列表数据
if (isActive) {
getLocation().catch((error) => {
console.error('获取位置信息失败:', error);
});
const firstLoad = !hasLoadedOnceRef.current;
getLocation(firstLoad)
.then(() => {
if (firstLoad) {
hasLoadedOnceRef.current = true;
}
})
.catch((error) => {
console.error('获取位置信息失败:', error);
});
}
}, [isActive]);
@@ -359,7 +370,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
};
}, []);
const getLocation = async () => {
const getLocation = async (useRefresh = true) => {
const location = await getCurrentLocationInfo();
updateState({ location });
if (location && location.latitude && location.longitude) {
@@ -370,7 +381,7 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
}
}
// 先调用列表接口
await getMatchesData();
await fetchMatches({}, useRefresh);
// 列表接口完成后,再调用数量接口
await fetchGetGamesCount();
// 初始数据加载完成后,记录当前城市
@@ -385,7 +396,9 @@ const ListPageContent: React.FC<ListPageContentProps> = ({
};
const handleRefresh = async () => {
if (refreshing) {
return;
}
setRefreshing(true);
try {

View File

@@ -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);
}

View File

@@ -0,0 +1,8 @@
export default definePageConfig({
navigationBarTitleText: '',
navigationStyle: 'custom',
navigationBarBackgroundColor: '#FFFFFF',
backgroundColor: '#FFFFFF',
});

View 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;
}

View 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;

View File

@@ -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()

View File

@@ -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 [listResSettled, integrateResSettled] = await Promise.allSettled([
getGamesList({
...searchParams,
order: searchParams.order || "distance",
}),
getGamesIntegrateList({
...searchParams,
order: "",
seachOption: {
...searchParams.seachOption,
isRefresh: true,
},
}),
]);
// 调用智能排序列表接口
const integrateParams = {
...searchParams,
order: "",
seachOption: {
...searchParams.seachOption,
isRefresh: true,
},
};
const integrateRes = await getGamesIntegrateList(integrateParams);
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({

View File

@@ -76,7 +76,7 @@
}
.btn {
width: 78px;
width: 90px;
height: 24px;
border: 1px solid rgba(0, 0, 0, 0.06);
display: flex;

View File

@@ -64,7 +64,7 @@
}
.btn {
width: 78px;
width: 90px;
height: 24px;
border: 1px solid rgba(0, 0, 0, 0.06);
display: flex;