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>
1 +/*
2 + * @Date: 2026-02-06 18:10:17
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2026-02-06 18:12:50
5 + * @FilePath: /manulife-weapp/src/api/file.js
6 + * @Description: 文件相关 API
7 + */
1 import { fn, fetch } from '@/api/fn'; 8 import { fn, fetch } from '@/api/fn';
2 9
3 const Api = { 10 const Api = {
...@@ -62,7 +69,9 @@ const Api = { ...@@ -62,7 +69,9 @@ const Api = {
62 * }; 69 * };
63 * }>} 70 * }>}
64 */ 71 */
65 -export const fileListAPI = (params) => fn(fetch.get(Api.FileList, params)); 72 +export const fileListAPI = (params) => {
73 + return fn(fetch.get(Api.FileList, params));
74 +};
66 75
67 /** 76 /**
68 * @description 本周热门资料 77 * @description 本周热门资料
...@@ -86,4 +95,6 @@ export const fileListAPI = (params) => fn(fetch.get(Api.FileList, params)); ...@@ -86,4 +95,6 @@ export const fileListAPI = (params) => fn(fetch.get(Api.FileList, params));
86 * }; 95 * };
87 * }>} 96 * }>}
88 */ 97 */
89 -export const weekHotAPI = (params) => fn(fetch.get(Api.WeekHot, params)); 98 +export const weekHotAPI = (params) => {
99 + return fn(fetch.get(Api.WeekHot, params));
100 +};
......
1 import { fn, fetch } from '@/api/fn'; 1 import { fn, fetch } from '@/api/fn';
2 -import { mockListAPI, mockDetailAPI } from './mock/product';
3 -
4 -// ⚠️ Mock 数据开关 - 设置为 true 使用 mock 数据,false 使用真实 API
5 -const USE_MOCK_DATA = false;
6 2
7 const Api = { 3 const Api = {
8 Detail: '/srv/?a=get_product&t=detail', 4 Detail: '/srv/?a=get_product&t=detail',
...@@ -50,10 +46,6 @@ const Api = { ...@@ -50,10 +46,6 @@ const Api = {
50 * }>} 46 * }>}
51 */ 47 */
52 export const detailAPI = (params) => { 48 export const detailAPI = (params) => {
53 - // 如果开启 Mock 数据,返回 mock 数据
54 - if (USE_MOCK_DATA) {
55 - return mockDetailAPI(params);
56 - }
57 return fn(fetch.get(Api.Detail, params)); 49 return fn(fetch.get(Api.Detail, params));
58 }; 50 };
59 51
...@@ -98,9 +90,5 @@ export const detailAPI = (params) => { ...@@ -98,9 +90,5 @@ export const detailAPI = (params) => {
98 * }>} 90 * }>}
99 */ 91 */
100 export const listAPI = (params) => { 92 export const listAPI = (params) => {
101 - // 如果开启 Mock 数据,返回 mock 数据
102 - if (USE_MOCK_DATA) {
103 - return mockListAPI(params);
104 - }
105 return fn(fetch.get(Api.List, params)); 93 return fn(fetch.get(Api.List, params));
106 }; 94 };
......
1 -/**
2 - * @description Mock 数据 - 产品中心
3 - * @note 用于测试滚动加载更多功能
4 - */
5 -
6 -// Mock 分类数据
7 -export const mockCategories = [
8 - { id: 1, name: '寿险' },
9 - { id: 2, name: '健康险' },
10 - { id: 3, name: '意外险' },
11 - { id: 4, name: '年金险' },
12 - { id: 5, name: '重疾险' }
13 -]
14 -
15 -// Mock 标签数据
16 -const mockTags = [
17 - { id: 1, name: '热销', bg_color: '#FEF3C7', text_color: '#92400E' },
18 - { id: 2, name: '新品', bg_color: '#DBEAFE', text_color: '#1E40AF' },
19 - { id: 3, name: '推荐', bg_color: '#D1FAE5', text_color: '#065F46' },
20 - { id: 4, name: '限时', bg_color: '#FEE2E2', text_color: '#991B1B' }
21 -]
22 -
23 -// 生成单个产品数据
24 -const generateProduct = (id) => {
25 - const recommendTypes = ['normal', 'hot']
26 - const recommend = recommendTypes[Math.floor(Math.random() * recommendTypes.length)]
27 -
28 - // 随机选择 1-2 个标签
29 - const tagCount = Math.floor(Math.random() * 2) + 1
30 - const tags = []
31 - for (let i = 0; i < tagCount; i++) {
32 - const tag = mockTags[Math.floor(Math.random() * mockTags.length)]
33 - if (!tags.find(t => t.id === tag.id)) {
34 - tags.push(tag)
35 - }
36 - }
37 -
38 - // 随机选择 1-2 个分类
39 - const categoryCount = Math.floor(Math.random() * 2) + 1
40 - const categories = []
41 - for (let i = 0; i < categoryCount; i++) {
42 - const category = mockCategories[Math.floor(Math.random() * mockCategories.length)]
43 - if (!categories.find(c => c.id === category.id)) {
44 - categories.push(category)
45 - }
46 - }
47 -
48 - return {
49 - id: id,
50 - product_name: `测试产品 ${id} - ${categories.map(c => c.name).join('+')}`,
51 - recommend: recommend,
52 - form_sn: `product_form_${id}`,
53 - created_time: '2025-12-01 12:00:00',
54 - categories: categories,
55 - tags: tags,
56 - // 使用 Lorem Picsum 随机图片服务(基于产品 ID 确保图片固定)
57 - cover_image: `https://picsum.photos/300/200?random=${id}`
58 - }
59 -}
60 -
61 -// 生成产品列表
62 -const generateProductList = (page, limit, cid = null) => {
63 - const start = page * limit
64 - const end = start + limit
65 -
66 - // 如果指定了分类,只返回该分类的产品
67 - let filteredProducts = []
68 - if (cid) {
69 - // 为每个分类生成固定数量的产品
70 - const categoryProducts = []
71 - for (let i = 1; i <= 50; i++) {
72 - const product = generateProduct(i)
73 - // 强制该产品属于指定分类
74 - product.categories = mockCategories.find(c => String(c.id) === String(cid))
75 - ? [mockCategories.find(c => String(c.id) === String(cid))]
76 - : [{ id: parseInt(cid), name: '测试分类' }]
77 - categoryProducts.push(product)
78 - }
79 - filteredProducts = categoryProducts
80 - } else {
81 - // 全部产品
82 - for (let i = 1; i <= 100; i++) {
83 - filteredProducts.push(generateProduct(i))
84 - }
85 - }
86 -
87 - const total = filteredProducts.length
88 - const list = filteredProducts.slice(start, end)
89 -
90 - return {
91 - list,
92 - total,
93 - hasMore: end < total
94 - }
95 -}
96 -
97 -/**
98 - * Mock 产品列表 API
99 - * @param {Object} params 请求参数
100 - * @param {string} params.page 页码(从 0 开始)
101 - * @param {string} params.limit 每页数量
102 - * @param {string} params.cid 分类 ID(可选)
103 - * @param {string} params.keyword 搜索关键词(可选)
104 - * @returns {Promise} 模拟 API 响应
105 - */
106 -export const mockListAPI = (params) => {
107 - return new Promise((resolve) => {
108 - // 模拟网络延迟(300-800ms)
109 - const delay = Math.floor(Math.random() * 500) + 300
110 -
111 - setTimeout(() => {
112 - const page = parseInt(params.page) || 0
113 - const limit = parseInt(params.limit) || 10
114 - const cid = params.cid || null
115 - const keyword = params.keyword || ''
116 -
117 - let result = generateProductList(page, limit, cid)
118 -
119 - // 如果有搜索关键词,过滤产品
120 - if (keyword) {
121 - result.list = result.list.filter(p =>
122 - p.product_name.includes(keyword)
123 - )
124 - // 搜索时重新计算总数
125 - result.total = result.list.length + Math.floor(Math.random() * 20)
126 - }
127 -
128 - resolve({
129 - code: 1,
130 - msg: 'success',
131 - data: {
132 - categories: mockCategories,
133 - list: result.list,
134 - total: result.total
135 - }
136 - })
137 - }, delay)
138 - })
139 -}
140 -
141 -/**
142 - * Mock 产品详情 API
143 - * @param {Object} params 请求参数
144 - * @param {string} params.i 产品 ID
145 - * @returns {Promise} 模拟 API 响应
146 - */
147 -export const mockDetailAPI = (params) => {
148 - return new Promise((resolve) => {
149 - const delay = Math.floor(Math.random() * 500) + 300
150 -
151 - setTimeout(() => {
152 - const id = parseInt(params.i) || 1
153 - const product = generateProduct(id)
154 -
155 - // 添加额外的详情字段
156 - product.product_description = `这是产品 ${id} 的详细描述。\n\n产品特点:\n1. 保障全面\n2. 灵活配置\n3. 理赔便捷`
157 - product.documents = [
158 - {
159 - file_url: 'https://example.com/file1.pdf',
160 - file_name: '产品条款.pdf',
161 - file_size: '1024000',
162 - file_size_formatted: '1.0 MB'
163 - },
164 - {
165 - file_url: 'https://example.com/file2.pdf',
166 - file_name: '产品说明.pdf',
167 - file_size: '512000',
168 - file_size_formatted: '512 KB'
169 - }
170 - ]
171 - product.status = 'active'
172 - product.created_by = 1
173 - product.updated_by = 1
174 - product.updated_time = '2025-12-01 12:00:00'
175 -
176 - resolve({
177 - code: 1,
178 - msg: 'success',
179 - data: product
180 - })
181 - }, delay)
182 - })
183 -}
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
3 * @Description: 资料列表页 - 已改造为 NutTabs 版本 3 * @Description: 资料列表页 - 已改造为 NutTabs 版本
4 --> 4 -->
5 <template> 5 <template>
6 - <view class="h-screen bg-[#F9FAFB] flex flex-col"> 6 + <view class="bg-[#F9FAFB]">
7 - <view class="bg-[#F9FAFB] z-10"> 7 + <!-- 固定在顶部的导航和搜索 -->
8 + <view class="bg-[#F9FAFB] sticky top-0 z-10">
8 <NavHeader :title="pageTitle" /> 9 <NavHeader :title="pageTitle" />
9 10
10 <view class="px-[32rpx] mt-[32rpx] mb-[24rpx]"> 11 <view class="px-[32rpx] mt-[32rpx] mb-[24rpx]">
...@@ -18,10 +19,7 @@ ...@@ -18,10 +19,7 @@
18 :show-clear="true" 19 :show-clear="true"
19 /> 20 />
20 </view> 21 </view>
21 - </view>
22 22
23 - <!-- Tabs Container -->
24 - <view class="flex-1 min-h-0 flex flex-col">
25 <!-- 动态显示 Tabs(仅在有分类时显示) --> 23 <!-- 动态显示 Tabs(仅在有分类时显示) -->
26 <nut-tabs v-if="hasCategories" v-model="activeTabId"> 24 <nut-tabs v-if="hasCategories" v-model="activeTabId">
27 <!-- 自定义标签栏 --> 25 <!-- 自定义标签栏 -->
...@@ -41,12 +39,13 @@ ...@@ -41,12 +39,13 @@
41 </view> 39 </view>
42 </template> 40 </template>
43 </nut-tabs> 41 </nut-tabs>
42 + </view>
44 43
45 - <!-- 列表容器(独立于 nut-tab-pane) --> 44 + <!-- 列表容器 - 页面级滚动 -->
46 <view 45 <view
47 v-if="listVisible" 46 v-if="listVisible"
48 :key="listRenderKey" 47 :key="listRenderKey"
49 - class="flex-1 min-h-0 overflow-y-auto px-[32rpx] pb-[calc(160rpx+env(safe-area-inset-bottom))] box-border" 48 + class="px-[32rpx] pb-[calc(160rpx+env(safe-area-inset-bottom))] box-border"
50 > 49 >
51 <view class="flex flex-col gap-[24rpx]"> 50 <view class="flex flex-col gap-[24rpx]">
52 <view v-for="(item, index) in currentList" :key="index" 51 <view v-for="(item, index) in currentList" :key="index"
...@@ -112,7 +111,6 @@ ...@@ -112,7 +111,6 @@
112 </view> 111 </view>
113 </view> 112 </view>
114 </view> 113 </view>
115 - </view>
116 </template> 114 </template>
117 115
118 <script setup> 116 <script setup>
...@@ -282,9 +280,9 @@ const fetchMaterialList = async (params = {}, isLoadMore = false) => { ...@@ -282,9 +280,9 @@ const fetchMaterialList = async (params = {}, isLoadMore = false) => {
282 loading.value = true 280 loading.value = true
283 } 281 }
284 282
285 - // console.log('[Material List] 请求参数:', params) 283 + console.log('[Material List] 请求参数:', params)
286 284
287 - // 调用接口(直接调用,不使用 fn() 包装) 285 + // 调用接口
288 const res = await fileListAPI(params) 286 const res = await fileListAPI(params)
289 287
290 if (res.code === 1 && res.data) { 288 if (res.code === 1 && res.data) {
......