feat: P3 功能增强完成

- 下拉刷新:index / strategies / me / detail 四个页面
- 上拉加载:detail 交易记录分页加载
- 空状态优化:index / strategies / detail 空状态提示
- pages.json 开启 enablePullDownRefresh

功能细节:
- onPullDownRefresh / onReachBottom 生命周期
- 交易记录分页逻辑(logPage / logHasMore / logLoading)
- 加载状态提示
This commit is contained in:
claw_bot 2026-03-24 07:53:14 +00:00
parent 365c461ea4
commit 762665bfd5
7 changed files with 278 additions and 18 deletions

View File

@ -1,27 +1,35 @@
{
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "资产"
"navigationBarTitleText": "资产",
"enablePullDownRefresh": true,
"backgroundTextStyle": "dark"
}
},
{
"path": "pages/strategies/strategies",
"style": {
"navigationBarTitleText": "策略库"
"navigationBarTitleText": "策略库",
"enablePullDownRefresh": true,
"backgroundTextStyle": "dark"
}
},
{
"path": "pages/me/me",
"style": {
"navigationBarTitleText": "我的"
"navigationBarTitleText": "我的",
"enablePullDownRefresh": true,
"backgroundTextStyle": "dark"
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": ""
"navigationBarTitleText": "",
"enablePullDownRefresh": true,
"backgroundTextStyle": "dark"
}
},
{
@ -75,4 +83,4 @@
"^u-(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue"
}
}
}
}

View File

@ -257,9 +257,16 @@
<view class="section-container">
<view class="section-header">
<text class="section-title">最近交易记录</text>
<text class="section-count" v-if="logTotal > 0"> {{ logTotal }} </text>
</view>
<view class="timeline-box">
<!-- 空状态 -->
<view v-if="logs.length === 0 && !loading" class="empty-state-sm">
<uni-icons type="list" size="48" color="#D1D5DB"></uni-icons>
<text class="empty-text-sm">暂无交易记录</text>
</view>
<view v-else class="timeline-box">
<view class="timeline-item" v-for="(log, k) in logs" :key="k">
<view class="tl-left">
<text class="tl-date">{{ log.date }}</text>
@ -281,6 +288,13 @@
<text class="tl-desc mt-1">{{ log.type === 'buy' ? '买入' : '卖出' }} 操作</text>
</view>
</view>
<!-- 加载更多提示 -->
<view class="load-more" v-if="logs.length > 0">
<text class="load-more-text" v-if="logLoading">加载中...</text>
<text class="load-more-text" v-else-if="!logHasMore">没有更多了</text>
<text class="load-more-text" v-else>上拉加载更多</text>
</view>
</view>
</view>
@ -551,6 +565,7 @@
<script setup lang="ts">
import { ref, onMounted, getCurrentInstance, nextTick } from 'vue';
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
import { api } from '@/utils/api';
import type { PositionItem, TransactionItem, NavHistoryItem, NavStatistics } from '@/types';
@ -582,6 +597,13 @@ const portfolioData = ref<any>({
const positions = ref<PositionItem[]>([]);
const logs = ref<TransactionItem[]>([]);
//
const logPage = ref<number>(0);
const logPageSize = ref<number>(10);
const logTotal = ref<number>(0);
const logHasMore = ref<boolean>(true);
const logLoading = ref<boolean>(false);
const navLoading = ref<boolean>(false);
const navPeriod = ref<string>('30d');
const navHistory = ref<NavHistoryItem[]>([]);
@ -915,19 +937,35 @@ const fetchPortfolioData = async () => {
}
};
const fetchTransactions = async () => {
const fetchTransactions = async (loadMore: boolean = false) => {
try {
if (!portfolioId.value) return;
if (loadMore && logLoading.value) return;
if (loadMore && !logHasMore.value) return;
logLoading.value = true;
const offset = loadMore ? (logPage.value + 1) * logPageSize.value : 0;
const response = await api.assets.getTransactions({
portfolioId: portfolioId.value,
limit: 10,
offset: 0
limit: logPageSize.value,
offset: offset
});
if (response.code === 200) {
logs.value = response.data.items || [];
console.log('交易记录获取成功:', response.data);
const items = response.data.items || [];
if (loadMore) {
logs.value = [...logs.value, ...items];
logPage.value++;
} else {
logs.value = items;
logPage.value = 0;
}
logTotal.value = response.data.total || 0;
logHasMore.value = logs.value.length < logTotal.value;
}
} catch (error) {
console.error('获取交易记录失败:', error);
@ -936,6 +974,8 @@ const fetchTransactions = async () => {
message: '加载交易记录失败',
icon: 'error'
});
} finally {
logLoading.value = false;
}
};
@ -994,6 +1034,23 @@ onMounted(async () => {
loading.value = false;
});
//
onPullDownRefresh(async () => {
await Promise.all([
fetchPortfolioData(),
fetchTransactions(),
fetchNavHistory()
]);
uni.stopPullDownRefresh();
});
//
onReachBottom(async () => {
if (logHasMore.value && !logLoading.value) {
await fetchTransactions(true);
}
});
const goStrategyConfig = () => {
if (portfolioData.value.strategy?.id) {
uni.navigateTo({ url: `/pages/strategies/edit/edit?id=${portfolioData.value.strategy.id}` });
@ -1717,4 +1774,40 @@ const deletePortfolio = async () => {
font-size: 28rpx;
color: #1F2937;
}
/* 空状态小 */
.empty-state-sm {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 40rpx;
background-color: #FFFFFF;
border-radius: 24rpx;
}
.empty-text-sm {
font-size: 26rpx;
color: #9CA3AF;
margin-top: 20rpx;
}
/* 加载更多 */
.load-more {
display: flex;
justify-content: center;
padding: 32rpx 0;
}
.load-more-text {
font-size: 24rpx;
color: #9CA3AF;
}
/* 标题栏附加信息 */
.section-count {
font-size: 24rpx;
color: #9CA3AF;
margin-left: 16rpx;
}
</style>

View File

@ -85,6 +85,19 @@
</view>
</view>
<!-- 空状态 -->
<view v-else-if="holdings.length === 0" class="empty-state">
<view class="empty-icon">
<uni-icons type="folder" size="64" color="#D1D5DB"></uni-icons>
</view>
<text class="empty-title">暂无组合记录</text>
<text class="empty-desc">点击上方"新建组合"开始记账</text>
<view class="empty-btn" @click="goConfig">
<uni-icons type="plus" size="16" color="#064E3B"></uni-icons>
<text class="empty-btn-text">新建第一个组合</text>
</view>
</view>
<!-- 真实内容持仓卡片 -->
<view
v-else
@ -137,12 +150,13 @@
<script setup lang="ts">
import { ref } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import { onShow, onPullDownRefresh } from '@dcloudio/uni-app';
import { api } from '@/utils/api';
import { getCurrencySymbol } from '@/utils/currency';
import type { PortfolioListItem, TotalAssetsResponse } from '@/types';
const loading = ref<boolean>(true);
const refreshing = ref<boolean>(false);
const assetData = ref<TotalAssetsResponse>({
totalValue: 0,
@ -236,6 +250,22 @@ onShow(async () => {
loading.value = false;
isFetching = false;
});
//
onPullDownRefresh(async () => {
refreshing.value = true;
await Promise.all([fetchAssetData(), fetchHoldingsData()]);
refreshing.value = false;
uni.stopPullDownRefresh();
});
//
const handleRefresh = async (): Promise<void> => {
loading.value = true;
await Promise.all([fetchAssetData(), fetchHoldingsData()]);
loading.value = false;
uni.showToast({ title: '刷新成功', icon: 'success', duration: 1500 });
};
</script>
<style scoped>
@ -671,4 +701,53 @@ onShow(async () => {
font-weight: 600;
font-family: 'DIN Alternate', sans-serif;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 40rpx;
background-color: #FFFFFF;
border-radius: 32rpx;
margin: 20rpx 0;
}
.empty-icon {
margin-bottom: 32rpx;
}
.empty-title {
font-size: 32rpx;
font-weight: 700;
color: #374151;
margin-bottom: 16rpx;
}
.empty-desc {
font-size: 26rpx;
color: #9CA3AF;
margin-bottom: 40rpx;
}
.empty-btn {
display: flex;
align-items: center;
gap: 12rpx;
background-color: #ECFDF5;
padding: 20rpx 40rpx;
border-radius: 100rpx;
border: 2rpx solid #10B981;
}
.empty-btn:active {
background-color: #D1FAE5;
}
.empty-btn-text {
font-size: 28rpx;
font-weight: 600;
color: #064E3B;
}
</style>

View File

@ -80,6 +80,7 @@
<script setup lang="ts">
import { ref, onMounted, getCurrentInstance } from 'vue';
import { onPullDownRefresh } from '@dcloudio/uni-app';
import type { UserInfoResponse, UserStatsResponse, ApiResponse } from '@/types';
//
@ -151,6 +152,15 @@ onMounted(async () => {
]);
loading.value = false;
});
//
onPullDownRefresh(async () => {
await Promise.all([
fetchUserInfo(),
fetchUserStats()
]);
uni.stopPullDownRefresh();
});
</script>
<style scoped>

View File

@ -31,6 +31,19 @@
<view class="skeleton-text skeleton-btn"></view>
</view>
</view>
<!-- 空状态 -->
<view v-else-if="strategies.length === 0" class="empty-state">
<view class="empty-icon">
<uni-icons type="bars" size="64" color="#D1D5DB"></uni-icons>
</view>
<text class="empty-title">暂无策略</text>
<text class="empty-desc">点击右上角"+"创建你的第一个策略</text>
<view class="empty-btn" @click="goToAdd">
<uni-icons type="plus" size="16" color="#064E3B"></uni-icons>
<text class="empty-btn-text">创建策略</text>
</view>
</view>
<!-- 真实内容 -->
<view
@ -78,7 +91,7 @@
<script setup lang="ts">
import { ref, getCurrentInstance } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import { onShow, onPullDownRefresh } from '@dcloudio/uni-app';
import type { StrategyListItemDto, ApiResponse } from '@/types';
interface StrategyCardItem {
@ -135,6 +148,12 @@ onShow(async () => {
isFetching = false;
});
//
onPullDownRefresh(async () => {
await fetchStrategies();
uni.stopPullDownRefresh();
});
const goToAdd = (): void => {
uni.navigateTo({ url: '/pages/strategies/edit/edit' });
};
@ -327,4 +346,54 @@ const handleAction = (item: StrategyCardItem): void => {
.gap-3 { gap: 24rpx; }
.gap-2 { gap: 16rpx; }
.mb-4 { margin-bottom: 32rpx; }
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 40rpx;
background-color: #FFFFFF;
border-radius: 32rpx;
box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.06);
border: 1rpx solid #E5E7EB;
}
.empty-icon {
margin-bottom: 32rpx;
}
.empty-title {
font-size: 32rpx;
font-weight: 700;
color: #374151;
margin-bottom: 16rpx;
}
.empty-desc {
font-size: 26rpx;
color: #9CA3AF;
margin-bottom: 40rpx;
}
.empty-btn {
display: flex;
align-items: center;
gap: 12rpx;
background-color: #ECFDF5;
padding: 20rpx 40rpx;
border-radius: 100rpx;
border: 2rpx solid #10B981;
}
.empty-btn:active {
background-color: #D1FAE5;
}
.empty-btn-text {
font-size: 28rpx;
font-weight: 600;
color: #064E3B;
}
</style>

View File

@ -47,10 +47,10 @@
- [x] config.vue: 替换 4 处 u-input 为原生 input
- [x] detail.vue: 替换 1 处 u-input 为原生 input
### P3 - 功能增强
- [ ] 下拉刷新
- [ ] 上拉加载更多
- [ ] 空状态优化
### P3 - 功能增强 ✅ 完成
- [x] 下拉刷新index / strategies / me / detail
- [x] 上拉加载detail 交易记录分页)
- [x] 空状态优化index / strategies / detail
### P4 - TypeScript 迁移 🚀 进行中
详细计划见 `TYPESCRIPT_MIGRATION.md`

View File

@ -12,6 +12,7 @@
"lib": ["ES2019", "DOM"],
"skipLibCheck": true,
"noEmit": true,
"ignoreDeprecations": "6.0",
"baseUrl": ".",
"paths": {
"@/*": ["./*"],