我的鲜花

This commit is contained in:
haomingming 2026-03-06 10:44:49 +08:00
parent 1e99b3a082
commit b833a00d65
3 changed files with 380 additions and 62 deletions

View File

@ -1686,7 +1686,7 @@
"list": [
{
"name": "",
"path": "/pages_app/idcardAuth/bankCardList",
"path": "/pages_app/myFlower/myFlower",
"query": ""
}
]

View File

@ -16,6 +16,7 @@
/>
</view>
<view class="fixed-top-block">
<view class="stats-bar">
<view class="stat">
<up-image :src="flowerImg" width="36rpx" height="36rpx" ></up-image>
@ -33,6 +34,7 @@
<text class="col time">时间</text>
<text class="col qty">数量</text>
</view>
</view>
<!-- 表体/列表或空状态 -->
<scroll-view
@ -46,6 +48,7 @@
lower-threshold="100"
:scroll-top="scrollTop"
>
<view class="list-content">
<view v-if="records.length === 0" class="empty-wrap">
<up-image :src="emptyImg" width="176rpx" height="204rpx" ></up-image>
<text class="empty-text">您暂未收到鲜花</text>
@ -67,12 +70,13 @@
<button @click="testLoadMore" size="mini" type="primary">测试加载更多</button>
<text class="debug-info">当前页: {{ page }}, 加载中: {{ loading }}, 无更多: {{ noMore }}</text>
</view> -->
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ref, onMounted, nextTick } from 'vue';
import flowerImg from "@/static/flowers.png"
import moneyImg from "@/static/mind_totle_money.png"
import emptyImg from "@/static/icon_empty.png"
@ -103,6 +107,25 @@ const noMore = ref(false);
const page = ref(1);
const pageSize = ref(10);
const scrollTop = ref(0);
const autoLoading = ref(false);
const formatDateToYDM = (value) => {
if (!value) return '-';
const raw = String(value).trim();
const directMatch = raw.match(/^(\d{4})[-/.](\d{1,2})[-/.](\d{1,2})/);
if (directMatch) {
const [, year, month, day] = directMatch;
return `${year}-${day.padStart(2, '0')}-${month.padStart(2, '0')}`;
}
const parsedDate = new Date(raw);
if (Number.isNaN(parsedDate.getTime())) return '-';
const year = parsedDate.getFullYear();
const month = String(parsedDate.getMonth() + 1).padStart(2, '0');
const day = String(parsedDate.getDate()).padStart(2, '0');
return `${year}-${day}-${month}`;
};
@ -116,6 +139,12 @@ const goBack = () => {
const getFlowerList = () => {
return new Promise((resolve, reject) => {
console.log('[myFlower] getFlowerList:start', {
page: page.value,
loading: loading.value,
refreshing: refreshing.value,
noMore: noMore.value
});
// loadingonLoadMore
if (page.value === 1) {
loading.value = true;
@ -123,20 +152,29 @@ const getFlowerList = () => {
console.log('正在获取第', page.value, '页数据...');
api.getFlowerList({page: page.value}).then(res => {
api.getFlowerList({
page: page.value,
}).then(res => {
console.log('接口返回数据:', res);
if (res.code === 200 && res.data) {
const { flower_data, total_amount, total_num } = res.data;
console.log('[myFlower] getFlowerList:response', {
reqPage: page.value,
pageNum: flower_data?.pageNum,
pages: flower_data?.pages,
isLastPage: flower_data?.isLastPage,
listLength: flower_data?.list?.length || 0
});
//
stat.value.totalCount = total_num || 0;
stat.value.totalAmount = total_amount || 0;
//
if (flower_data && flower_data.list) {
if (flower_data && Array.isArray(flower_data.list)) {
const newRecords = flower_data.list.map(item => ({
name: item.patient_name || '未知',
time: item.create_date || '',
name: item.patient_name || '账号已注销',
time: formatDateToYDM(item.create_date),
amount: item.num || 0,
//
original: item
@ -151,12 +189,28 @@ const getFlowerList = () => {
console.log('第', page.value, '页数据,追加到列表,新增', newRecords.length, '条');
}
//
//
if (typeof flower_data.isLastPage === 'boolean') {
noMore.value = flower_data.isLastPage;
console.log('是否最后一页:', flower_data.isLastPage, ',总页数:', flower_data.pages);
} else if (Number(flower_data.pages) > 0) {
noMore.value = page.value >= Number(flower_data.pages);
} else {
//
noMore.value = newRecords.length === 0;
}
console.log('是否最后一页:', noMore.value, ',总页数:', flower_data.pages);
}
console.log('[myFlower] getFlowerList:end', {
page: page.value,
records: records.value.length,
noMore: noMore.value
});
resolve(res);
} else {
console.log('[myFlower] getFlowerList:invalid', {
code: res?.code,
msg: res?.msg
});
reject(new Error('接口返回错误'));
}
}).catch(err => {
@ -172,18 +226,11 @@ const getFlowerList = () => {
});
});
};
onMounted(() => {
getFlowerList();
});
const onRefresh = async () => {
if (refreshing.value) return;
if (refreshing.value || loading.value) return;
refreshing.value = true;
try {
page.value = 1;
noMore.value = false;
records.value = []; //
getFlowerList(); //
await loadFirstTwoPages();
uni.showToast({ title: '刷新成功', icon: 'none' });
} catch (error) {
console.error('刷新失败:', error);
@ -193,39 +240,138 @@ const onRefresh = async () => {
const onLoadMore = async () => {
console.log('触发上拉加载更多事件');
console.log('[myFlower] onLoadMore:enter', {
page: page.value,
loading: loading.value,
refreshing: refreshing.value,
noMore: noMore.value
});
//
if (loading.value) {
if (loading.value || refreshing.value) {
console.log('正在加载中,忽略重复请求');
console.log('[myFlower] onLoadMore:blockByState');
return;
}
if (noMore.value) {
console.log('已经没有更多数据了');
console.log('[myFlower] onLoadMore:blockByNoMore');
return;
}
console.log('开始加载下一页,当前页码:', page.value);
loading.value = true;
const currentPage = page.value;
try {
page.value += 1;
page.value = currentPage + 1;
console.log('[myFlower] onLoadMore:request', {
fromPage: currentPage,
toPage: page.value
});
await getFlowerList(); //
console.log('加载完成,当前页码:', page.value);
console.log('[myFlower] onLoadMore:done', {
page: page.value,
records: records.value.length,
noMore: noMore.value
});
} catch (error) {
console.error('加载更多失败:', error);
page.value -= 1; //
page.value = currentPage; //
console.log('[myFlower] onLoadMore:rollback', { page: page.value });
uni.showToast({ title: '加载失败', icon: 'error' });
} finally {
loading.value = false;
}
};
const getListLayoutInfo = () => {
return new Promise((resolve) => {
const query = uni.createSelectorQuery();
query.select('.table-body').boundingClientRect();
query.select('.list-content').boundingClientRect();
query.exec((res = []) => {
const bodyHeight = Number(res?.[0]?.height || 0);
const contentHeight = Number(res?.[1]?.height || 0);
resolve({ bodyHeight, contentHeight });
});
});
};
const ensureListCanScroll = async () => {
if (autoLoading.value || loading.value || refreshing.value || noMore.value) return;
autoLoading.value = true;
try {
while (!noMore.value) {
await nextTick();
await new Promise((resolve) => setTimeout(resolve, 50));
const { bodyHeight, contentHeight } = await getListLayoutInfo();
if (!bodyHeight || !contentHeight) break;
if (contentHeight > bodyHeight) break;
await onLoadMore();
if (loading.value) break;
}
} finally {
autoLoading.value = false;
}
};
const loadFirstTwoPages = async () => {
console.log('[myFlower] loadFirstTwoPages:start');
page.value = 1;
noMore.value = false;
records.value = [];
console.log('[myFlower] loadFirstTwoPages:reset', {
page: page.value,
records: records.value.length,
noMore: noMore.value
});
await getFlowerList();
// 1 Promise resolve finally loading
loading.value = false;
console.log('[myFlower] loadFirstTwoPages:afterPage1', {
page: page.value,
records: records.value.length,
noMore: noMore.value
});
if (!noMore.value) {
console.log('[myFlower] loadFirstTwoPages:triggerPage2');
const firstPage = page.value;
try {
page.value = firstPage + 1;
console.log('[myFlower] loadFirstTwoPages:requestPage2', {
fromPage: firstPage,
toPage: page.value,
refreshing: refreshing.value
});
await getFlowerList();
loading.value = false;
} catch (error) {
page.value = firstPage;
console.log('[myFlower] loadFirstTwoPages:page2FailedRollback', { page: page.value });
throw error;
}
} else {
console.log('[myFlower] loadFirstTwoPages:skipPage2(noMore=true)');
}
console.log('[myFlower] loadFirstTwoPages:end', {
page: page.value,
records: records.value.length,
noMore: noMore.value
});
};
//
const testLoadMore = () => {
console.log('手动测试加载更多');
onLoadMore();
};
onMounted(async () => {
await loadFirstTwoPages();
});
</script>
<style lang="scss" scoped>
@ -237,13 +383,27 @@ $card: #ffffff;
.flower-page {
background: $bg;
--nav-offset: calc(var(--status-bar-height) + 44px);
--stats-height: 86rpx;
--header-height: 74rpx;
/* 导航栏下方固定区域总高度:统计栏 + 表头 */
--fixed-block-height: calc(var(--stats-height) + var(--header-height));
}
.fixed-top-block {
position: fixed;
top: var(--nav-offset);
left: 0;
right: 0;
z-index: 20;
}
.stats-bar {
display: flex;
margin-top: calc(var(--status-bar-height) + 44px);
align-items: center;
padding: 24rpx 30rpx;
height: var(--stats-height);
padding: 0 30rpx;
box-sizing: border-box;
background: #fff;
border-bottom: 2rpx solid #eee;
@ -260,20 +420,25 @@ $card: #ffffff;
color: #fff;
display: flex;
justify-content: space-between;
padding: 22rpx 30rpx;
align-items: center;
height: var(--header-height);
padding: 0 30rpx;
box-sizing: border-box;
font-size: 30rpx;
position: sticky;
top: 180rpx; /* nav 高度 */
z-index: 5;
.col { width: 33%; }
.name { text-align: left; }
.time { text-align: center; }
.qty { text-align: right; }
.qty { text-align: center; }
}
.table-body {
height: calc(100vh - 180rpx - 88rpx - 70rpx);
position: fixed;
top: calc(var(--nav-offset) + var(--fixed-block-height));
left: 0;
right: 0;
bottom: 0;
box-sizing: border-box;
/* 充满剩余高度,避免滚动穿透 */
}
@ -289,7 +454,7 @@ $card: #ffffff;
.cell { width: 33%; }
.name { text-align: left; }
.time { text-align: center; color: #666; }
.qty { text-align: right; }
.qty { text-align: center; }
.qty.plus { color: #2dbd85; }
.qty.minus { color: #e34d4d; }
}

View File

@ -0,0 +1,153 @@
<template>
<view class="page">
<view class="header">
<text class="title">scroll-view 上拉加载 Demo</text>
<text class="sub">下拉刷新 + 触底加载更多</text>
</view>
<scroll-view
class="list-scroll"
scroll-y
lower-threshold="80"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
>
<view v-for="item in list" :key="item.id" class="list-item">
<text class="item-title">{{ item.title }}</text>
<text class="item-time">{{ item.time }}</text>
</view>
<view v-if="loading" class="tip">加载中...</view>
<view v-else-if="noMore && list.length > 0" class="tip">没有更多数据了</view>
<view v-else-if="list.length === 0" class="tip">暂无数据</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const page = ref(1);
const pageSize = ref(10);
const list = ref([]);
const loading = ref(false);
const refreshing = ref(false);
const noMore = ref(false);
const totalMockCount = 37;
const fullData = Array.from({ length: totalMockCount }, (_, i) => {
const index = i + 1;
return {
id: index,
title: `${index} 条数据`,
time: `2026-03-${String((index % 28) + 1).padStart(2, '0')}`
};
});
const getPageData = (pageNum, size) => {
return new Promise((resolve) => {
setTimeout(() => {
const start = (pageNum - 1) * size;
const end = start + size;
resolve(fullData.slice(start, end));
}, 350);
});
};
const fetchList = async (isRefresh = false) => {
if (loading.value) return;
loading.value = true;
try {
if (isRefresh) {
page.value = 1;
noMore.value = false;
}
const rows = await getPageData(page.value, pageSize.value);
if (isRefresh) {
list.value = rows;
} else {
list.value = [...list.value, ...rows];
}
noMore.value = rows.length < pageSize.value;
} finally {
loading.value = false;
refreshing.value = false;
}
};
const onRefresh = async () => {
if (refreshing.value) return;
refreshing.value = true;
await fetchList(true);
};
const onLoadMore = async () => {
if (loading.value || refreshing.value || noMore.value) return;
page.value += 1;
await fetchList(false);
};
onMounted(() => {
fetchList(true);
});
</script>
<style scoped>
.page {
height: 100vh;
background: #f7f8fa;
}
.header {
padding: 30rpx;
background: #ffffff;
border-bottom: 1rpx solid #ededed;
}
.title {
display: block;
font-size: 34rpx;
color: #222;
font-weight: 600;
}
.sub {
display: block;
margin-top: 8rpx;
font-size: 24rpx;
color: #888;
}
.list-scroll {
height: calc(100vh - 130rpx);
}
.list-item {
margin: 20rpx 24rpx 0;
padding: 24rpx;
background: #ffffff;
border-radius: 12rpx;
}
.item-title {
display: block;
font-size: 30rpx;
color: #222;
}
.item-time {
display: block;
margin-top: 10rpx;
font-size: 24rpx;
color: #999;
}
.tip {
padding: 28rpx 0 40rpx;
text-align: center;
color: #9aa0a6;
font-size: 24rpx;
}
</style>