You need to sign in or sign up before continuing.
hookehuyr

fix(material): 修复滚动加载无法触发的问题

移除 h-screen flex-col 布局,改用页面级滚动
- NavHeader 和搜索栏使用 sticky top-0 固定
- 列表容器使用页面级滚动(移除 overflow-y-auto)
- 删除 mock 数据相关代码

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
/*
* @Date: 2026-02-06 18:10:17
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2026-02-06 18:12:50
* @FilePath: /manulife-weapp/src/api/file.js
* @Description: 文件相关 API
*/
import { fn, fetch } from '@/api/fn';
const Api = {
......@@ -62,7 +69,9 @@ const Api = {
* };
* }>}
*/
export const fileListAPI = (params) => fn(fetch.get(Api.FileList, params));
export const fileListAPI = (params) => {
return fn(fetch.get(Api.FileList, params));
};
/**
* @description 本周热门资料
......@@ -86,4 +95,6 @@ export const fileListAPI = (params) => fn(fetch.get(Api.FileList, params));
* };
* }>}
*/
export const weekHotAPI = (params) => fn(fetch.get(Api.WeekHot, params));
export const weekHotAPI = (params) => {
return fn(fetch.get(Api.WeekHot, params));
};
......
import { fn, fetch } from '@/api/fn';
import { mockListAPI, mockDetailAPI } from './mock/product';
// ⚠️ Mock 数据开关 - 设置为 true 使用 mock 数据,false 使用真实 API
const USE_MOCK_DATA = false;
const Api = {
Detail: '/srv/?a=get_product&t=detail',
......@@ -50,10 +46,6 @@ const Api = {
* }>}
*/
export const detailAPI = (params) => {
// 如果开启 Mock 数据,返回 mock 数据
if (USE_MOCK_DATA) {
return mockDetailAPI(params);
}
return fn(fetch.get(Api.Detail, params));
};
......@@ -98,9 +90,5 @@ export const detailAPI = (params) => {
* }>}
*/
export const listAPI = (params) => {
// 如果开启 Mock 数据,返回 mock 数据
if (USE_MOCK_DATA) {
return mockListAPI(params);
}
return fn(fetch.get(Api.List, params));
};
......
/**
* @description Mock 数据 - 产品中心
* @note 用于测试滚动加载更多功能
*/
// Mock 分类数据
export const mockCategories = [
{ id: 1, name: '寿险' },
{ id: 2, name: '健康险' },
{ id: 3, name: '意外险' },
{ id: 4, name: '年金险' },
{ id: 5, name: '重疾险' }
]
// Mock 标签数据
const mockTags = [
{ id: 1, name: '热销', bg_color: '#FEF3C7', text_color: '#92400E' },
{ id: 2, name: '新品', bg_color: '#DBEAFE', text_color: '#1E40AF' },
{ id: 3, name: '推荐', bg_color: '#D1FAE5', text_color: '#065F46' },
{ id: 4, name: '限时', bg_color: '#FEE2E2', text_color: '#991B1B' }
]
// 生成单个产品数据
const generateProduct = (id) => {
const recommendTypes = ['normal', 'hot']
const recommend = recommendTypes[Math.floor(Math.random() * recommendTypes.length)]
// 随机选择 1-2 个标签
const tagCount = Math.floor(Math.random() * 2) + 1
const tags = []
for (let i = 0; i < tagCount; i++) {
const tag = mockTags[Math.floor(Math.random() * mockTags.length)]
if (!tags.find(t => t.id === tag.id)) {
tags.push(tag)
}
}
// 随机选择 1-2 个分类
const categoryCount = Math.floor(Math.random() * 2) + 1
const categories = []
for (let i = 0; i < categoryCount; i++) {
const category = mockCategories[Math.floor(Math.random() * mockCategories.length)]
if (!categories.find(c => c.id === category.id)) {
categories.push(category)
}
}
return {
id: id,
product_name: `测试产品 ${id} - ${categories.map(c => c.name).join('+')}`,
recommend: recommend,
form_sn: `product_form_${id}`,
created_time: '2025-12-01 12:00:00',
categories: categories,
tags: tags,
// 使用 Lorem Picsum 随机图片服务(基于产品 ID 确保图片固定)
cover_image: `https://picsum.photos/300/200?random=${id}`
}
}
// 生成产品列表
const generateProductList = (page, limit, cid = null) => {
const start = page * limit
const end = start + limit
// 如果指定了分类,只返回该分类的产品
let filteredProducts = []
if (cid) {
// 为每个分类生成固定数量的产品
const categoryProducts = []
for (let i = 1; i <= 50; i++) {
const product = generateProduct(i)
// 强制该产品属于指定分类
product.categories = mockCategories.find(c => String(c.id) === String(cid))
? [mockCategories.find(c => String(c.id) === String(cid))]
: [{ id: parseInt(cid), name: '测试分类' }]
categoryProducts.push(product)
}
filteredProducts = categoryProducts
} else {
// 全部产品
for (let i = 1; i <= 100; i++) {
filteredProducts.push(generateProduct(i))
}
}
const total = filteredProducts.length
const list = filteredProducts.slice(start, end)
return {
list,
total,
hasMore: end < total
}
}
/**
* Mock 产品列表 API
* @param {Object} params 请求参数
* @param {string} params.page 页码(从 0 开始)
* @param {string} params.limit 每页数量
* @param {string} params.cid 分类 ID(可选)
* @param {string} params.keyword 搜索关键词(可选)
* @returns {Promise} 模拟 API 响应
*/
export const mockListAPI = (params) => {
return new Promise((resolve) => {
// 模拟网络延迟(300-800ms)
const delay = Math.floor(Math.random() * 500) + 300
setTimeout(() => {
const page = parseInt(params.page) || 0
const limit = parseInt(params.limit) || 10
const cid = params.cid || null
const keyword = params.keyword || ''
let result = generateProductList(page, limit, cid)
// 如果有搜索关键词,过滤产品
if (keyword) {
result.list = result.list.filter(p =>
p.product_name.includes(keyword)
)
// 搜索时重新计算总数
result.total = result.list.length + Math.floor(Math.random() * 20)
}
resolve({
code: 1,
msg: 'success',
data: {
categories: mockCategories,
list: result.list,
total: result.total
}
})
}, delay)
})
}
/**
* Mock 产品详情 API
* @param {Object} params 请求参数
* @param {string} params.i 产品 ID
* @returns {Promise} 模拟 API 响应
*/
export const mockDetailAPI = (params) => {
return new Promise((resolve) => {
const delay = Math.floor(Math.random() * 500) + 300
setTimeout(() => {
const id = parseInt(params.i) || 1
const product = generateProduct(id)
// 添加额外的详情字段
product.product_description = `这是产品 ${id} 的详细描述。\n\n产品特点:\n1. 保障全面\n2. 灵活配置\n3. 理赔便捷`
product.documents = [
{
file_url: 'https://example.com/file1.pdf',
file_name: '产品条款.pdf',
file_size: '1024000',
file_size_formatted: '1.0 MB'
},
{
file_url: 'https://example.com/file2.pdf',
file_name: '产品说明.pdf',
file_size: '512000',
file_size_formatted: '512 KB'
}
]
product.status = 'active'
product.created_by = 1
product.updated_by = 1
product.updated_time = '2025-12-01 12:00:00'
resolve({
code: 1,
msg: 'success',
data: product
})
}, delay)
})
}
......@@ -3,8 +3,9 @@
* @Description: 资料列表页 - 已改造为 NutTabs 版本
-->
<template>
<view class="h-screen bg-[#F9FAFB] flex flex-col">
<view class="bg-[#F9FAFB] z-10">
<view class="bg-[#F9FAFB]">
<!-- 固定在顶部的导航和搜索 -->
<view class="bg-[#F9FAFB] sticky top-0 z-10">
<NavHeader :title="pageTitle" />
<view class="px-[32rpx] mt-[32rpx] mb-[24rpx]">
......@@ -18,10 +19,7 @@
:show-clear="true"
/>
</view>
</view>
<!-- Tabs Container -->
<view class="flex-1 min-h-0 flex flex-col">
<!-- 动态显示 Tabs(仅在有分类时显示) -->
<nut-tabs v-if="hasCategories" v-model="activeTabId">
<!-- 自定义标签栏 -->
......@@ -41,12 +39,13 @@
</view>
</template>
</nut-tabs>
</view>
<!-- 列表容器(独立于 nut-tab-pane) -->
<!-- 列表容器 - 页面级滚动 -->
<view
v-if="listVisible"
:key="listRenderKey"
class="flex-1 min-h-0 overflow-y-auto px-[32rpx] pb-[calc(160rpx+env(safe-area-inset-bottom))] box-border"
class="px-[32rpx] pb-[calc(160rpx+env(safe-area-inset-bottom))] box-border"
>
<view class="flex flex-col gap-[24rpx]">
<view v-for="(item, index) in currentList" :key="index"
......@@ -112,7 +111,6 @@
</view>
</view>
</view>
</view>
</template>
<script setup>
......@@ -282,9 +280,9 @@ const fetchMaterialList = async (params = {}, isLoadMore = false) => {
loading.value = true
}
// console.log('[Material List] 请求参数:', params)
console.log('[Material List] 请求参数:', params)
// 调用接口(直接调用,不使用 fn() 包装)
// 调用接口
const res = await fileListAPI(params)
if (res.code === 1 && res.data) {
......