完成数据分页加载

This commit is contained in:
kron
2025-06-18 12:32:08 +08:00
parent 7413a8fdee
commit a040a60987
5 changed files with 230 additions and 145 deletions

View File

@@ -113,11 +113,12 @@ export const startRoomAPI = (number) => {
return request("POST", "/user/room/start", { number }); return request("POST", "/user/room/start", { number });
}; };
export const getPractiseResultListAPI = (page = 1, page_size = 10) => { export const getPractiseResultListAPI = async (page = 1, page_size = 15) => {
return request( const reuslt = await request(
"GET", "GET",
`/user/practice/list?page=${page}&page_size=${page_size}` `/user/practice/list?page=${page}&page_size=${page_size}`
); );
return reuslt.list;
}; };
export const matchGameAPI = (match, gameType, teamSize) => { export const matchGameAPI = (match, gameType, teamSize) => {

View File

@@ -15,6 +15,10 @@ defineProps({
type: Function, type: Function,
default: null, default: null,
}, },
overflow: {
type: String,
default: "auto",
},
}); });
const isIos = ref(true); const isIos = ref(true);
onMounted(() => { onMounted(() => {
@@ -29,7 +33,7 @@ onMounted(() => {
<Header :title="title" :onBack="onBack" /> <Header :title="title" :onBack="onBack" />
<view <view
class="content" class="content"
:style="{ height: `calc(100vh - ${isIos ? 105 : 95}px)` }" :style="{ height: `calc(100vh - ${isIos ? 98 : 95}px)`, overflow }"
> >
<slot></slot> <slot></slot>
</view> </view>
@@ -40,11 +44,9 @@ onMounted(() => {
.content { .content {
width: 100vw; width: 100vw;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding-bottom: 10px;
} }
</style> </style>

View File

@@ -36,7 +36,7 @@ onMounted(() => {
</script> </script>
<template> <template>
<view class="container" :style="{ paddingTop: isIos ? '35px' : '25px' }"> <view class="container" :style="{ paddingTop: isIos ? '38px' : '25px' }">
<view class="back-btn" @click="onClick"> <view class="back-btn" @click="onClick">
<image src="../static/back.png" mode="widthFix" /> <image src="../static/back.png" mode="widthFix" />
</view> </view>

View File

@@ -0,0 +1,85 @@
<script setup>
import { ref, watch } from "vue";
const props = defineProps({
show: {
type: Boolean,
default: false,
},
onLoading: {
type: Function,
default: async (page) => [],
},
pageSize: {
type: Number,
default: 10,
},
});
const refreshing = ref(false);
const loading = ref(false);
const noMore = ref(false);
const page = ref(1);
const refresherrefresh = async () => {
if (refreshing.value) return;
try {
refreshing.value = true;
page.value = 1;
const list = await props.onLoading(page.value);
if (list.length < props.pageSize) noMore.value = true;
} finally {
refreshing.value = false;
}
};
const scrolltolower = async () => {
if (loading.value) return;
try {
loading.value = true;
page.value += 1;
const list = await props.onLoading(page.value);
if (list.length < props.pageSize) noMore.value = true;
} finally {
loading.value = false;
}
};
watch(
() => props.show,
async (newVal) => {
if (newVal) await props.onLoading(1);
},
{
immediate: true,
}
);
</script>
<template>
<scroll-view
class="scroll-list"
scroll-y
refresher-default-style="white"
:refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="refresherrefresh"
@scrolltolower="scrolltolower"
:style="{
display: show ? 'flex' : 'none',
}"
>
<slot></slot>
<text class="tips" v-if="loading">加载中...</text>
<text class="tips" v-if="noMore">我是有底线的</text>
</scroll-view>
</template>
<style scoped>
.scroll-list {
width: 100%;
height: 100%;
}
.tips {
color: #fff9;
display: block;
text-align: center;
font-size: 12px;
margin: 10px;
}
</style>

View File

@@ -3,6 +3,7 @@ import { onMounted } from "vue";
import Container from "@/components/Container.vue"; import Container from "@/components/Container.vue";
import Avatar from "@/components/Avatar.vue"; import Avatar from "@/components/Avatar.vue";
import BowData from "@/components/BowData.vue"; import BowData from "@/components/BowData.vue";
import ScrollList from "@/components/ScrollList.vue";
import { import {
getBattleListAPI, getBattleListAPI,
getPractiseResultListAPI, getPractiseResultListAPI,
@@ -12,32 +13,12 @@ import {
import { ref } from "vue"; import { ref } from "vue";
const selectedIndex = ref(0); const selectedIndex = ref(0);
const matchPage = ref(1);
const matchList = ref([]); const matchList = ref([]);
const battlePage = ref(1);
const battleList = ref([]); const battleList = ref([]);
const practiseList = ref([]); const practiseList = ref([]);
const showBowData = ref(false); const showBowData = ref(false);
const arrows = ref([]); const arrows = ref([]);
const handleSelect = async (index) => {
selectedIndex.value = index;
if (index === 0 && matchList.value.length === 0) {
const result = await getBattleListAPI(matchPage.value, 2);
matchList.value = result;
matchPage.value += 1;
}
if (index === 1 && battleList.value.length === 0) {
const result = await getBattleListAPI(battlePage.value, 1);
battleList.value = result;
battlePage.value += 1;
}
if (index === 2 && practiseList.value.length === 0) {
const result = await getPractiseResultListAPI();
practiseList.value = result.list;
}
};
const toMatchDetail = (id) => { const toMatchDetail = (id) => {
uni.navigateTo({ uni.navigateTo({
url: `/pages/match-detail?id=${id}`, url: `/pages/match-detail?id=${id}`,
@@ -48,146 +29,157 @@ const getPractiseDetail = async (id) => {
arrows.value = JSON.parse(result.UserPracticeRound.arrows); arrows.value = JSON.parse(result.UserPracticeRound.arrows);
showBowData.value = true; showBowData.value = true;
}; };
onMounted(async () => { const onMatchLoading = async (page) => {
const result = await getBattleListAPI(matchPage.value, 2); const result = await getBattleListAPI(page, 2);
matchList.value = result; if (page === 1) {
matchPage.value += 1; matchList.value = result;
}); } else {
matchList.value = matchList.value.concat(result);
}
return result.length;
};
const onBattleLoading = async (page) => {
const result = await getBattleListAPI(page, 1);
if (page === 1) {
battleList.value = result;
} else {
battleList.value = battleList.value.concat(result);
}
return result.length;
};
const onPractiseLoading = async (page) => {
const result = await getPractiseResultListAPI(page);
if (page === 1) {
practiseList.value = result;
} else {
practiseList.value = practiseList.value.concat(result);
}
return result.length;
};
</script> </script>
<template> <template>
<Container title="我的成长脚印"> <Container title="我的成长脚印" overflow="hidden">
<view class="container"> <view class="tabs">
<view class="tabs"> <view
<view v-for="(rankType, index) in ['排位赛', '好友约战', '射馆练习']"
v-for="(rankType, index) in ['排位赛', '好友约战', '射馆练习']" :key="index"
:key="index" :style="{
:style="{ color: index === selectedIndex ? '#000' : '#fff',
color: index === selectedIndex ? '#000' : '#fff', backgroundColor: index === selectedIndex ? '#FFD947' : 'transparent',
backgroundColor: }"
index === selectedIndex ? '#FFD947' : 'transparent', @tap="() => (selectedIndex = index)"
}" >
@tap="handleSelect(index)" {{ rankType }}
>
{{ rankType }}
</view>
</view> </view>
<view class="contents"> </view>
<view class="contents">
<ScrollList :show="selectedIndex === 0" :onLoading="onMatchLoading">
<view <view
:style="{ v-for="(item, index) in matchList"
display: selectedIndex === 0 ? 'flex' : 'none', :key="index"
}" @click="() => toMatchDetail(item.battleId)"
> >
<view <view class="contest-header">
v-for="(item, index) in matchList" <text>{{ item.name }}</text>
:key="index" <text>{{ item.createdAt }}</text>
@click="() => toMatchDetail(item.battleId)" <image src="../static/back.png" mode="widthFix" />
>
<view class="contest-header">
<text>{{ item.name }}</text>
<text>{{ item.createdAt }}</text>
<image src="../static/back.png" mode="widthFix" />
</view>
<view v-if="item.mode === 1" class="contest-team">
<block v-if="item.bluePlayers[0]">
<view class="player">
<Avatar frame :src="item.bluePlayers[0].avatar" />
<text>{{ item.bluePlayers[0].name }}</text>
<image
v-if="item.winner === 1"
src="../static/winner-badge.png"
mode="widthFix"
/>
</view>
</block>
<block v-if="item.redPlayers[0]">
<view class="player">
<Avatar frame :src="item.redPlayers[0].avatar" />
<text>{{ item.redPlayers[0].name }}</text>
<image
v-if="item.winner === 0"
src="../static/winner-badge.png"
mode="widthFix"
/>
</view>
</block>
</view>
<view v-if="item.mode === 2" class="contest-multi">
<view
class="player"
v-for="(p, index2) in item.players"
:key="index2"
>
<Avatar :rank="index2 + 1" :src="p.avatar" />
<text>{{ p.name }}</text>
</view>
</view>
</view> </view>
</view> <view v-if="item.mode === 1" class="contest-team">
<view <block v-if="item.bluePlayers[0]">
:style="{ <view class="player">
display: selectedIndex === 1 ? 'flex' : 'none', <Avatar frame :src="item.bluePlayers[0].avatar" />
}" <text>{{ item.bluePlayers[0].name }}</text>
>
<view
v-for="(item, index) in battleList"
:key="index"
@click="() => toMatchDetail(item.battleId)"
>
<view class="contest-header">
<text>{{ item.name }}</text>
<text>{{ item.createdAt }}</text>
<image src="../static/back.png" mode="widthFix" />
</view>
<view v-if="item.mode === 1" class="contest-team">
<view
class="player"
v-for="(p, index2) in item.players"
:key="index2"
>
<Avatar frame :src="p.avatar" />
<text>{{ p.name }}</text>
<image <image
v-if="index2 === 0" v-if="item.winner === 1"
src="../static/winner-badge.png" src="../static/winner-badge.png"
mode="widthFix" mode="widthFix"
/> />
</view> </view>
</view> </block>
<view v-if="item.mode === 2" class="contest-multi"> <block v-if="item.redPlayers[0]">
<view <view class="player">
class="player" <Avatar frame :src="item.redPlayers[0].avatar" />
v-for="(p, index2) in item.players" <text>{{ item.redPlayers[0].name }}</text>
:key="index2" <image
> v-if="item.winner === 0"
<Avatar :rank="index2 + 1" :src="p.avatar" /> src="../static/winner-badge.png"
<text>{{ p.name }}</text> mode="widthFix"
/>
</view> </view>
</block>
</view>
<view v-if="item.mode === 2" class="contest-multi">
<view
class="player"
v-for="(p, index2) in item.players"
:key="index2"
>
<Avatar :rank="index2 + 1" :src="p.avatar" />
<text>{{ p.name }}</text>
</view> </view>
</view> </view>
</view> </view>
</ScrollList>
<ScrollList :show="selectedIndex === 1" :onLoading="onBattleLoading">
<view <view
:style="{ v-for="(item, index) in battleList"
display: selectedIndex === 2 ? 'flex' : 'none', :key="index"
}" @click="() => toMatchDetail(item.battleId)"
> >
<view <view class="contest-header">
v-for="(item, index) in practiseList" <text>{{ item.name }}</text>
:key="index" <text>{{ item.createdAt }}</text>
class="practice-record"
@click="() => getPractiseDetail(item.id)"
>
<text>单组练习 {{ item.createdAt }}</text>
<image src="../static/back.png" mode="widthFix" /> <image src="../static/back.png" mode="widthFix" />
</view> </view>
<view v-if="item.mode === 1" class="contest-team">
<view
class="player"
v-for="(p, index2) in item.players"
:key="index2"
>
<Avatar frame :src="p.avatar" />
<text>{{ p.name }}</text>
<image
v-if="index2 === 0"
src="../static/winner-badge.png"
mode="widthFix"
/>
</view>
</view>
<view v-if="item.mode === 2" class="contest-multi">
<view
class="player"
v-for="(p, index2) in item.players"
:key="index2"
>
<Avatar :rank="index2 + 1" :src="p.avatar" />
<text>{{ p.name }}</text>
</view>
</view>
</view> </view>
</view> </ScrollList>
<BowData <ScrollList
:arrows="arrows" :show="selectedIndex === 2"
:show="showBowData" :onLoading="onPractiseLoading"
:onClose="() => (showBowData = false)" :pageSize="15"
/> >
<view
v-for="(item, index) in practiseList"
:key="index"
class="practice-record"
@click="() => getPractiseDetail(item.id)"
>
<text>单组练习 {{ item.createdAt }}</text>
<image src="../static/back.png" mode="widthFix" />
</view>
</ScrollList>
</view> </view>
<BowData
:arrows="arrows"
:show="showBowData"
:onClose="() => (showBowData = false)"
/>
</Container> </Container>
</template> </template>
@@ -209,8 +201,13 @@ onMounted(async () => {
text-align: center; text-align: center;
border-radius: 20px; border-radius: 20px;
} }
.contents > view { .contents {
width: 100%; width: 100%;
height: calc(100% - 50px);
}
.contents > scroll-view {
width: 100%;
height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }