hookehuyr

feat: 产品模块接口集成完成

- 首页热卖产品集成产品列表API (listAPI, recommend=hot)
  - 动态标签渲染,使用API返回的bg_color和text_color
  - 移除硬编码数据,改用API动态返回
- 产品详情页集成详情API (detailAPI)
  - 产品特色改为产品描述,使用rich-text渲染富文本
  - 附件大小使用file_size_formatted字段显示格式化大小
  - 移除收藏功能
  - 修复nut-loading不兼容问题,改用CSS加载动画
- 更新API集成日志和规范文档状态
- 添加CHANGELOG记录

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
...@@ -17,6 +17,7 @@ declare module 'vue' { ...@@ -17,6 +17,7 @@ declare module 'vue' {
17 NutAvatar: typeof import('@nutui/nutui-taro')['Avatar'] 17 NutAvatar: typeof import('@nutui/nutui-taro')['Avatar']
18 NutButton: typeof import('@nutui/nutui-taro')['Button'] 18 NutButton: typeof import('@nutui/nutui-taro')['Button']
19 NutInput: typeof import('@nutui/nutui-taro')['Input'] 19 NutInput: typeof import('@nutui/nutui-taro')['Input']
20 + NutLoading: typeof import('@nutui/nutui-taro')['Loading']
20 NutPicker: typeof import('@nutui/nutui-taro')['Picker'] 21 NutPicker: typeof import('@nutui/nutui-taro')['Picker']
21 NutPopup: typeof import('@nutui/nutui-taro')['Popup'] 22 NutPopup: typeof import('@nutui/nutui-taro')['Popup']
22 NutRadio: typeof import('@nutui/nutui-taro')['Radio'] 23 NutRadio: typeof import('@nutui/nutui-taro')['Radio']
......
...@@ -5,6 +5,55 @@ ...@@ -5,6 +5,55 @@
5 5
6 --- 6 ---
7 7
8 +## [2026-02-03] - 产品模块接口集成完成
9 +
10 +### 新增
11 +- 首页热卖产品模块集成产品列表 API
12 + - 使用 `listAPI` 接口,参数 `recommend=hot` 获取热卖产品
13 + - 移除硬编码产品数据,改用 API 动态返回的产品列表
14 + - 实现动态标签样式,根据 API 返回的 `bg_color``text_color` 字段设置标签背景色和文字颜色
15 + - 影响文件:src/pages/index/index.vue
16 +
17 +### 修改
18 +- 产品详情页集成详情 API
19 + - 使用 `detailAPI` 接口获取产品详情数据
20 + - "产品特色" 改为 "产品描述",使用 `<rich-text>` 组件渲染富文本内容(`product_description` 字段)
21 + - 附件大小直接使用 `file_size_formatted` 字段显示格式化后的文件大小
22 + - 移除收藏功能(产品模块无此功能)
23 + - 移除 `nut-loading` 组件(Taro 不支持),改用纯 CSS 实现的加载动画
24 + - 影响文件:src/pages/product-detail/index.vue
25 +
26 +### 修复
27 +- 修复首页热卖产品模块重复调用 API 的问题
28 +- 修复产品详情页 `nut-loading` 组件不兼容问题,使用 CSS `animate-spin` 实现加载动画
29 +
30 +### 文档
31 +- 更新 API 集成日志 `docs/api-integration-log.md`
32 + - 添加产品模块记录,包含产品列表和产品详情 2 个接口
33 + - 更新进度统计:16 个接口(11 个已完成,3 个已废弃,2 个开发中)
34 + - 更新模块快速索引,添加产品模块
35 +- 更新 API 规范文档状态
36 + - `docs/api-specs/get_product/list.md` 状态改为 `done`
37 + - `docs/api-specs/get_product/detail.md` 状态改为 `done`
38 +
39 +---
40 +
41 +**详细信息**
42 +- **影响文件**:
43 + - `src/pages/index/index.vue`(热卖产品模块 API 集成)
44 + - `src/pages/product-detail/index.vue`(产品详情 API 集成)
45 + - `docs/api-integration-log.md`(API 集成日志更新)
46 + - `docs/api-specs/get_product/list.md`(状态更新)
47 + - `docs/api-specs/get_product/detail.md`(状态更新)
48 +- **技术栈**: Vue 3, Taro 4, Composition API
49 +- **测试状态**: ✅ 已通过
50 +- **备注**:
51 + - 热卖产品接口参数:`recommend=hot`,无需传 limit、page、cid 参数
52 + - 产品标签支持自定义背景色和文字颜色
53 + - 富文本使用 Taro 的 `<rich-text>` 组件渲染
54 +
55 +---
56 +
8 ## [2026-02-03] - 修复反馈列表无法滚动 57 ## [2026-02-03] - 修复反馈列表无法滚动
9 58
10 ### 修复 59 ### 修复
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
4 4
5 ## 📊 总体进度 5 ## 📊 总体进度
6 6
7 -- **总接口数**: 14 7 +- **总接口数**: 16
8 -- **已完成**: 9 (64.3%) 8 +- **已完成**: 11 (68.8%)
9 - **联调中**: 0 (0%) 9 - **联调中**: 0 (0%)
10 -- **已废弃**: 3 (21.4%) 10 +- **已废弃**: 3 (18.8%)
11 -- **后端开发中**: 2 (14.3%) 11 +- **后端开发中**: 2 (12.5%)
12 - **有阻塞**: 0 12 - **有阻塞**: 0
13 13
14 --- 14 ---
...@@ -402,6 +402,95 @@ ...@@ -402,6 +402,95 @@
402 402
403 --- 403 ---
404 404
405 +### 产品模块
406 +
407 +#### 接口 1: 产品列表
408 +
409 +**接口信息**
410 +- **接口名称**: `listAPI`
411 +- **接口路径**: `/srv/?a=get_product&t=list`
412 +- **请求方法**: GET
413 +- **负责页面**: `src/pages/index/index.vue` (首页热卖产品模块)
414 +- **负责人**: 后端团队
415 +
416 +**接口文档更新记录**
417 +
418 +| 日期 | 版本 | 变更内容 | 变更原因 | 文档链接 |
419 +|------|------|---------|---------|---------|
420 +| 2026-02-03 | v1.0 | 初始版本 | - | [查看](docs/api-specs/get_product/list.md) |
421 +
422 +**页面调试情况**
423 +
424 +| 日期 | 调试页面 | 问题记录 | 解决方案 | 状态 |
425 +|------|---------|---------|---------|------|
426 +| 2026-02-03 | `src/pages/index/index.vue` | 联调完成 | 热卖产品列表、动态标签均正常 | ✅ 已完成 |
427 +
428 +**接口状态**: ✅ 已完成
429 +
430 +**备注**:
431 +- **调用参数**: `recommend: 'hot'` (热卖产品)
432 +- **首页场景**: 不传 limit、page、cid 参数,使用默认值
433 +- **返回数据结构**:
434 + - `data.list[]` - 产品列表
435 + - `data.categories[]` - 分类列表
436 + - `data.total` - 产品总数
437 +- **产品字段**:
438 + - `id` - 产品ID
439 + - `product_name` - 产品名称
440 + - `recommend` - 推荐位 (normal-普通, hot-热卖)
441 + - `tags[]` - 产品标签数组(包含 id、name、bg_color、text_color)
442 + - `cover_image` - 产品封面图
443 + - `categories[]` - 产品所属分类
444 +- **标签渲染**: 使用 API 返回的 `bg_color``text_color` 动态设置标签样式
445 +- **实现位置**: `src/pages/index/index.vue:250-262`, `src/pages/index/index.vue:52-92`
446 +
447 +---
448 +
449 +#### 接口 2: 产品详情
450 +
451 +**接口信息**
452 +- **接口名称**: `detailAPI`
453 +- **接口路径**: `/srv/?a=get_product&t=detail`
454 +- **请求方法**: GET
455 +- **负责页面**: `src/pages/product-detail/index.vue`
456 +- **负责人**: 后端团队
457 +
458 +**接口文档更新记录**
459 +
460 +| 日期 | 版本 | 变更内容 | 变更原因 | 文档链接 |
461 +|------|------|---------|---------|---------|
462 +| 2026-02-03 | v1.0 | 初始版本 | - | [查看](docs/api-specs/get_product/detail.md) |
463 +
464 +**页面调试情况**
465 +
466 +| 日期 | 调试页面 | 问题记录 | 解决方案 | 状态 |
467 +|------|---------|---------|---------|------|
468 +| 2026-02-03 | `src/pages/product-detail/index.vue` | nut-loading 组件报错 | 改用纯 CSS 加载动画 | ✅ 已解决 |
469 +| 2026-02-03 | `src/pages/product-detail/index.vue` | 联调完成 | 产品详情、富文本描述、附件列表均正常 | ✅ 已完成 |
470 +
471 +**接口状态**: ✅ 已完成
472 +
473 +**备注**:
474 +- **调用参数**: `i: productId` (产品ID)
475 +- **返回数据结构**:
476 + - `product_name` - 产品名称
477 + - `product_description` - 产品描述(富文本HTML)
478 + - `cover_image` - 产品封面图
479 + - `recommend` - 推荐位
480 + - `tags[]` - 产品标签数组(包含 id、name、bg_color、text_color)
481 + - `documents[]` - 附件列表
482 + - `categories[]` - 产品所属分类
483 +- **富文本渲染**: 使用 `<rich-text>` 组件渲染 `product_description` 字段
484 +- **附件显示**:
485 + - 文件名: `doc.file_name`
486 + - 文件大小: `doc.file_size_formatted` (已格式化,如 "72.61 KB")
487 + - 文件URL: `doc.file_url`
488 + - 点击预览: 通过 `useFileOperation` composable 打开文档
489 +- **移除功能**: 原有的收藏功能已移除(按钮和代码)
490 +- **实现位置**: `src/pages/product-detail/index.vue:135-162`, `src/pages/product-detail/index.vue:56-92`
491 +
492 +---
493 +
405 ### 消息模块 494 ### 消息模块
406 495
407 #### 接口 1: 我的消息列表 496 #### 接口 1: 我的消息列表
...@@ -524,19 +613,19 @@ ...@@ -524,19 +613,19 @@
524 613
525 ### 本周进度 (2026-01-27 ~ 2026-02-03) 614 ### 本周进度 (2026-01-27 ~ 2026-02-03)
526 615
527 -- **新增接口**: 14 616 +- **新增接口**: 16
528 -- **完成联调**: 9 617 +- **完成联调**: 11
529 - **已废弃**: 3 618 - **已废弃**: 3
530 - **联调中**: 0 619 - **联调中**: 0
531 - **后端开发中**: 2 620 - **后端开发中**: 2
532 -- **发现问题**: 5 621 +- **发现问题**: 6
533 -- **解决问题**: 5 622 +- **解决问题**: 6
534 623
535 ### 历史进度 624 ### 历史进度
536 625
537 | 周 | 完成数 | 新增数 | 废弃数 | 问题数 | 626 | 周 | 完成数 | 新增数 | 废弃数 | 问题数 |
538 |----|--------|--------|--------|--------| 627 |----|--------|--------|--------|--------|
539 -| 2026-01-27 ~ 2026-02-03 | 7 | 14 | 3 | 0 | 628 +| 2026-01-27 ~ 2026-02-03 | 9 | 16 | 3 | 0 |
540 629
541 --- 630 ---
542 631
...@@ -545,17 +634,16 @@ ...@@ -545,17 +634,16 @@
545 ### 按状态查看 634 ### 按状态查看
546 - [✅ 已完成](#用户中心模块) - 6个接口 635 - [✅ 已完成](#用户中心模块) - 6个接口
547 - [✅ 已完成](#意见反馈模块) - 2个接口 636 - [✅ 已完成](#意见反馈模块) - 2个接口
637 +- [✅ 已完成](#产品模块) - 2个接口
548 - [❌ 已废弃](#通用模块) - 3个接口 638 - [❌ 已废弃](#通用模块) - 3个接口
549 - [⏳ 后端开发中](#消息模块) - 2个接口 639 - [⏳ 后端开发中](#消息模块) - 2个接口
550 -- [⏳ 后端开发中](#首页模块) - 1个接口
551 640
552 ### 按模块查看 641 ### 按模块查看
553 - [用户中心](#用户中心模块) - ✅ 6个已完成 642 - [用户中心](#用户中心模块) - ✅ 6个已完成
554 - [通用](#通用模块) - ❌ 3个已废弃 643 - [通用](#通用模块) - ❌ 3个已废弃
555 -- [意见反馈](#意见反馈模块) - ⏳ 2个后端开发中 644 +- [意见反馈](#意见反馈模块) - ✅ 2个已完成
645 +- [产品](#产品模块) - ✅ 2个已完成
556 - [消息](#消息模块) - ⏳ 2个后端开发中 646 - [消息](#消息模块) - ⏳ 2个后端开发中
557 -- [首页](#首页模块) - ⏳ 1个后端开发中
558 -- [产品详情](#产品模块) - ⏳ 未开始
559 - [知识库](#知识库模块) - ⏳ 未开始 647 - [知识库](#知识库模块) - ⏳ 未开始
560 - [家办](#家办模块) - ⏳ 未开始 648 - [家办](#家办模块) - ⏳ 未开始
561 - [签单](#签单模块) - ⏳ 未开始 649 - [签单](#签单模块) - ⏳ 未开始
...@@ -592,14 +680,17 @@ ...@@ -592,14 +680,17 @@
592 680
593 --- 681 ---
594 682
595 -**最后更新时间**: 2026-02-03 22:00 683 +**最后更新时间**: 2026-02-03 23:30
596 -**文档版本**: v1.6 684 +**文档版本**: v1.7
597 **更新内容**: 685 **更新内容**:
598 -- 意见反馈模块联调完成:2个接口 686 +- 产品模块联调完成:2个接口
599 - - 意见反馈列表(listAPI):✅ 已完成 687 + - 产品列表(listAPI):✅ 已完成
600 - - 提交意见反馈(addAPI):✅ 已完成 688 + - 首页热卖产品模块,使用 recommend=hot 参数
689 + - 动态标签渲染(使用 API 返回的 bg_color 和 text_color)
690 + - 产品详情(detailAPI):✅ 已完成
691 + - 产品详情页,使用富文本渲染 product_description
692 + - 附件列表显示(使用 file_size_formatted 字段)
693 + - 移除收藏功能
601 - 修复问题: 694 - 修复问题:
602 - - 生命周期钩子导入错误(useShow → useDidShow、onMounted 从 Vue 导入) 695 + - 产品详情页 nut-loading 组件报错(改用纯 CSS 加载动画)
603 - - 图片显示错误(images 改为数组格式处理) 696 +- 更新总体进度:16个接口(11个已完成,3个已废弃,2个后端开发中)
604 - - NutUI Loading 组件报错(改用自定义 CSS spinner)
605 -- 更新总体进度:14个接口(9个已完成,3个已废弃,2个后端开发中)
......
1 +# 产品详情
2 +
3 +## OpenAPI Specification
4 +
5 +```yaml
6 +openapi: 3.0.1
7 +info:
8 + title: ''
9 + version: 1.0.0
10 +paths:
11 + /srv/:
12 + get:
13 + summary: 产品详情
14 + deprecated: false
15 + description: ''
16 + tags:
17 + - 产品
18 + parameters:
19 + - name: f
20 + in: query
21 + description: ''
22 + required: true
23 + example: manulife
24 + schema:
25 + type: string
26 + - name: a
27 + in: query
28 + description: ''
29 + required: true
30 + example: get_product
31 + schema:
32 + type: string
33 + - name: t
34 + in: query
35 + description: ''
36 + required: true
37 + example: detail
38 + schema:
39 + type: string
40 + - name: client_id
41 + in: query
42 + description: 主体id
43 + required: false
44 + example: '30901'
45 + schema:
46 + type: string
47 + - name: i
48 + in: query
49 + description: 产品id
50 + required: true
51 + example: '2769848'
52 + schema:
53 + type: string
54 + responses:
55 + '200':
56 + description: ''
57 + content:
58 + application/json:
59 + schema:
60 + type: object
61 + properties:
62 + code:
63 + type: integer
64 + msg:
65 + type: integer
66 + data:
67 + type: object
68 + properties:
69 + id:
70 + type: integer
71 + title: 产品id
72 + product_name:
73 + type: string
74 + title: 产品名
75 + recommend:
76 + type: string
77 + title: '推荐位: normal-普通, hot-热卖'
78 + status:
79 + type: string
80 + created_by:
81 + type: integer
82 + created_time:
83 + type: string
84 + updated_by:
85 + type: integer
86 + updated_time:
87 + type: string
88 + form_sn:
89 + type: string
90 + title: 关联表单sn
91 + product_description:
92 + type: string
93 + title: 产品描述
94 + categories:
95 + type: array
96 + items:
97 + type: object
98 + properties:
99 + id:
100 + type: string
101 + title: 分类id
102 + name:
103 + type: string
104 + title: 分类名称
105 + x-apifox-orders:
106 + - id
107 + - name
108 + title: 产品所属分类
109 + tags:
110 + type: array
111 + items:
112 + type: object
113 + properties:
114 + id:
115 + type: string
116 + title: 标签id
117 + name:
118 + type: string
119 + title: 标签名
120 + bg_color:
121 + type: string
122 + title: 标签背景色
123 + text_color:
124 + type: string
125 + title: 标签文字色
126 + required:
127 + - id
128 + - name
129 + - bg_color
130 + - text_color
131 + x-apifox-orders:
132 + - id
133 + - name
134 + - bg_color
135 + - text_color
136 + title: 产品标签
137 + documents:
138 + type: array
139 + items:
140 + type: object
141 + properties:
142 + file_url:
143 + type: string
144 + title: 附件地址
145 + file_name:
146 + type: string
147 + title: 附件名
148 + file_size:
149 + type: string
150 + title: 附件大小
151 + file_size_formatted:
152 + type: string
153 + title: 附件大小(转换过显示)
154 + x-apifox-orders:
155 + - file_url
156 + - file_name
157 + - file_size
158 + - file_size_formatted
159 + title: 附件列表
160 + cover_image:
161 + type: string
162 + title: 产品封面图
163 + required:
164 + - id
165 + - product_name
166 + - recommend
167 + - status
168 + - created_by
169 + - created_time
170 + - updated_by
171 + - updated_time
172 + - form_sn
173 + - product_description
174 + - categories
175 + - tags
176 + - documents
177 + - cover_image
178 + x-apifox-orders:
179 + - id
180 + - product_name
181 + - recommend
182 + - status
183 + - created_by
184 + - created_time
185 + - updated_by
186 + - updated_time
187 + - form_sn
188 + - product_description
189 + - cover_image
190 + - categories
191 + - tags
192 + - documents
193 + required:
194 + - code
195 + - msg
196 + - data
197 + x-apifox-orders:
198 + - code
199 + - msg
200 + - data
201 + example:
202 + code: 1
203 + msg: 0
204 + data:
205 + id: 2769848
206 + product_name: '1111'
207 + recommend: normal
208 + sort: 0
209 + status: '3'
210 + created_by: 21580
211 + created_time: '2026-02-02 16:33:01'
212 + updated_by: 21580
213 + updated_time: '2026-02-03 14:52:41'
214 + client_id: 30901
215 + form_sn: customize_jsj_ivleuz
216 + product_description: "<p>5564</p>\r\n<p>hdye</p>"
217 + categories:
218 + - id: '2769851'
219 + name: '11'
220 + tags:
221 + - id: '2769846'
222 + name: 测试1
223 + bg_color: '#1e9fff'
224 + text_color: '#ffffff'
225 + - id: '2769847'
226 + name: '111'
227 + bg_color: '#3e5160'
228 + text_color: '#ffffff'
229 + documents:
230 + - file_url: >-
231 + https://cdn.ipadbiz.cn/space_30901/申请提交-生成计划书_FvdRVOS0K-Wmp05ZKHpx64sEXcKQ.png
232 + file_name: 申请提交-生成计划书.png
233 + file_size: '74356'
234 + file_size_formatted: 72.61 KB
235 + headers: {}
236 + x-apifox-name: 成功
237 + x-apifox-ordering: 0
238 + security: []
239 + x-apifox-folder: 产品
240 + x-apifox-status: done
241 + x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-414567246-run
242 +components:
243 + schemas: {}
244 + responses: {}
245 + securitySchemes: {}
246 +servers: []
247 +security: []
248 +
249 +```
1 +# 产品列表
2 +
3 +## OpenAPI Specification
4 +
5 +```yaml
6 +openapi: 3.0.1
7 +info:
8 + title: ''
9 + version: 1.0.0
10 +paths:
11 + /srv/:
12 + get:
13 + summary: 产品列表
14 + deprecated: false
15 + description: ''
16 + tags:
17 + - 产品
18 + parameters:
19 + - name: f
20 + in: query
21 + description: ''
22 + required: true
23 + example: manulife
24 + schema:
25 + type: string
26 + - name: a
27 + in: query
28 + description: ''
29 + required: true
30 + example: get_product
31 + schema:
32 + type: string
33 + - name: t
34 + in: query
35 + description: ''
36 + required: true
37 + example: list
38 + schema:
39 + type: string
40 + - name: client_id
41 + in: query
42 + description: 主体id
43 + required: false
44 + example: '30901'
45 + schema:
46 + type: string
47 + - name: limit
48 + in: query
49 + description: ''
50 + required: false
51 + example: '10'
52 + schema:
53 + type: string
54 + - name: page
55 + in: query
56 + description: ''
57 + required: false
58 + example: '0'
59 + schema:
60 + type: string
61 + - name: cid
62 + in: query
63 + description: 分类id
64 + required: false
65 + schema:
66 + type: string
67 + - name: recommend
68 + in: query
69 + description: '推荐位: normal-普通, hot-热卖'
70 + required: false
71 + schema:
72 + type: string
73 + responses:
74 + '200':
75 + description: ''
76 + content:
77 + application/json:
78 + schema:
79 + type: object
80 + properties:
81 + code:
82 + type: integer
83 + msg:
84 + type: integer
85 + data:
86 + type: object
87 + properties:
88 + categories:
89 + type: array
90 + items:
91 + type: object
92 + properties:
93 + id:
94 + type: integer
95 + title: 分类id
96 + name:
97 + type: string
98 + title: 分类名
99 + x-apifox-orders:
100 + - id
101 + - name
102 + title: 分类列表
103 + list:
104 + type: array
105 + items:
106 + type: object
107 + properties:
108 + id:
109 + type: integer
110 + title: 产品id
111 + product_name:
112 + type: string
113 + title: 产品名
114 + recommend:
115 + type: string
116 + title: '推荐位: normal-普通, hot-热卖'
117 + form_sn:
118 + type: string
119 + created_time:
120 + type: string
121 + title: 创建时间
122 + categories:
123 + type: array
124 + items:
125 + type: object
126 + properties:
127 + id:
128 + type: string
129 + title: 分类id
130 + name:
131 + type: string
132 + title: 分类名
133 + required:
134 + - id
135 + - name
136 + x-apifox-orders:
137 + - id
138 + - name
139 + title: 产品所属分类
140 + tags:
141 + type: array
142 + items:
143 + type: object
144 + properties:
145 + id:
146 + type: string
147 + title: 标签id
148 + name:
149 + type: string
150 + title: 标签名
151 + bg_color:
152 + type: string
153 + title: 标签背景色
154 + text_color:
155 + type: string
156 + title: 标签文字色
157 + required:
158 + - id
159 + - name
160 + - bg_color
161 + - text_color
162 + x-apifox-orders:
163 + - id
164 + - name
165 + - bg_color
166 + - text_color
167 + title: 产品标签
168 + cover_image:
169 + type: string
170 + title: 产品封面图
171 + required:
172 + - id
173 + - product_name
174 + - recommend
175 + - form_sn
176 + - created_time
177 + - categories
178 + - tags
179 + - cover_image
180 + x-apifox-orders:
181 + - id
182 + - product_name
183 + - recommend
184 + - form_sn
185 + - cover_image
186 + - created_time
187 + - categories
188 + - tags
189 + title: 产品列表
190 + total:
191 + type: integer
192 + title: 产品总数
193 + required:
194 + - categories
195 + - list
196 + - total
197 + x-apifox-orders:
198 + - categories
199 + - list
200 + - total
201 + required:
202 + - code
203 + - msg
204 + - data
205 + x-apifox-orders:
206 + - code
207 + - msg
208 + - data
209 + example:
210 + code: 1
211 + msg: 0
212 + data:
213 + categories:
214 + - id: 2769851
215 + name: '11'
216 + list:
217 + - id: 2769856
218 + product_name: '22'
219 + recommend: hot
220 + form_sn: customize_jsj_pnzuky
221 + created_time: '2026-02-03 10:36:29'
222 + categories:
223 + - id: '2769851'
224 + name: '11'
225 + tags:
226 + - id: '2769847'
227 + name: '111'
228 + bg_color: '#3e5160'
229 + text_color: '#ffffff'
230 + - id: 2769848
231 + product_name: '1111'
232 + recommend: normal
233 + form_sn: customize_jsj_ivleuz
234 + created_time: '2026-02-02 16:33:01'
235 + categories:
236 + - id: '2769851'
237 + name: '11'
238 + tags:
239 + - id: '2769846'
240 + name: 测试1
241 + bg_color: '#1e9fff'
242 + text_color: '#ffffff'
243 + - id: '2769847'
244 + name: '111'
245 + bg_color: '#3e5160'
246 + text_color: '#ffffff'
247 + - id: 2769845
248 + product_name: '1'
249 + recommend: normal
250 + form_sn: customize_jsj_pnzuky
251 + created_time: '2026-02-02 16:22:26'
252 + categories: []
253 + tags: []
254 + total: 3
255 + headers: {}
256 + x-apifox-name: 成功
257 + x-apifox-ordering: 0
258 + security: []
259 + x-apifox-folder: 产品
260 + x-apifox-status: done
261 + x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-414404531-run
262 +components:
263 + schemas: {}
264 + responses: {}
265 + securitySchemes: {}
266 +servers: []
267 +security: []
268 +
269 +```
1 +import { fn, fetch } from '@/api/fn';
2 +
3 +const Api = {
4 + Detail: '/srv/?a=get_product&t=detail',
5 + List: '/srv/?a=get_product&t=list',
6 +}
7 +
8 +/**
9 + * @description 产品详情
10 + * @remark
11 + * @param {Object} params 请求参数
12 + * @param {string} params.client_id (可选) 主体id
13 + * @param {string} params.i 产品id
14 + * @returns {Promise<{
15 + * code: number; // 状态码
16 + * msg: string; // 消息
17 + * data: {
18 + * id: integer; // 产品id
19 + * product_name: string; // 产品名
20 + * recommend: string; // 推荐位: normal-普通, hot-热卖
21 + * status: string; //
22 + * created_by: integer; //
23 + * created_time: string; //
24 + * updated_by: integer; //
25 + * updated_time: string; //
26 + * form_sn: string; // 关联表单sn
27 + * product_description: string; // 产品描述
28 + * categories: Array<{
29 + * id: string; // 分类id
30 + * name: string; // 分类名称
31 + * }>;
32 + * tags: Array<{
33 + * id: string; // 标签id
34 + * name: string; // 标签名
35 + * bg_color: string; // 标签背景色
36 + * text_color: string; // 标签文字色
37 + * }>;
38 + * documents: Array<{
39 + * file_url: string; // 附件地址
40 + * file_name: string; // 附件名
41 + * file_size: string; // 附件大小
42 + * file_size_formatted: string; // 附件大小(转换过显示)
43 + * }>;
44 + * cover_image: string; // 产品封面图
45 + * };
46 + * }>}
47 + */
48 +export const detailAPI = (params) => fn(fetch.get(Api.Detail, params));
49 +
50 +/**
51 + * @description 产品列表
52 + * @remark
53 + * @param {Object} params 请求参数
54 + * @param {string} params.client_id (可选) 主体id
55 + * @param {string} params.limit (可选)
56 + * @param {string} params.page (可选)
57 + * @param {string} params.cid (可选) 分类id
58 + * @param {string} params.recommend (可选) 推荐位: normal-普通, hot-热卖
59 + * @returns {Promise<{
60 + * code: number; // 状态码
61 + * msg: string; // 消息
62 + * data: {
63 + * categories: Array<{
64 + * id: integer; // 分类id
65 + * name: string; // 分类名
66 + * }>;
67 + * list: Array<{
68 + * id: integer; // 产品id
69 + * product_name: string; // 产品名
70 + * recommend: string; // 推荐位: normal-普通, hot-热卖
71 + * form_sn: string; //
72 + * created_time: string; // 创建时间
73 + * categories: array; // 产品所属分类
74 + * tags: array; // 产品标签
75 + * cover_image: string; // 产品封面图
76 + * }>;
77 + * total: integer; // 产品总数
78 + * };
79 + * }>}
80 + */
81 +export const listAPI = (params) => fn(fetch.get(Api.List, params));
...@@ -26,14 +26,14 @@ ...@@ -26,14 +26,14 @@
26 <view class="flex flex-wrap"> 26 <view class="flex flex-wrap">
27 <view 27 <view
28 class="flex flex-col items-center w-1/3 mb-[40rpx]" 28 class="flex flex-col items-center w-1/3 mb-[40rpx]"
29 - v-for="(item, index) in loopData0" 29 + v-for="(item, index) in loopNav"
30 :key="index" 30 :key="index"
31 @tap="handleGridNav(item)" 31 @tap="handleGridNav(item)"
32 > 32 >
33 <view class="w-[88rpx] h-[88rpx] rounded-[24rpx] bg-blue-50 flex items-center justify-center mb-[16rpx]"> 33 <view class="w-[88rpx] h-[88rpx] rounded-[24rpx] bg-blue-50 flex items-center justify-center mb-[16rpx]">
34 <IconFont :name="item.icon" class="text-blue-600" size="24" /> 34 <IconFont :name="item.icon" class="text-blue-600" size="24" />
35 </view> 35 </view>
36 - <text class="text-gray-800 text-[26rpx]">{{ item.lanhutext0 }}</text> 36 + <text class="text-gray-800 text-[26rpx]">{{ item.name }}</text>
37 </view> 37 </view>
38 </view> 38 </view>
39 </view> 39 </view>
...@@ -48,66 +48,43 @@ ...@@ -48,66 +48,43 @@
48 </view> 48 </view>
49 </view> 49 </view>
50 50
51 - <!-- Product Card 1 --> 51 + <!-- 动态产品列表 -->
52 - <view class="bg-gray-50 rounded-[24rpx] p-[28rpx] mb-[24rpx]"> 52 + <view
53 - <text class="block text-gray-800 text-[28rpx] font-medium mb-[20rpx]">家庭财富传承保障计划(分红)</text> 53 + v-for="(product, index) in hotProducts"
54 - <view class="flex flex-wrap gap-[12rpx] mb-[24rpx]"> 54 + :key="product.id"
55 - <view class="bg-red-50 rounded-[8rpx] px-[16rpx] py-[6rpx]"> 55 + class="bg-gray-50 rounded-[24rpx] p-[28rpx]"
56 - <text class="text-red-600 text-[22rpx]">收益率3.5%</text> 56 + :class="{ 'mb-[24rpx]': index < hotProducts.length - 1 }"
57 - </view> 57 + >
58 - <view class="bg-orange-50 rounded-[8rpx] px-[16rpx] py-[6rpx]"> 58 + <text class="block text-gray-800 text-[28rpx] font-medium mb-[20rpx]">{{ product.product_name }}</text>
59 - <text class="text-orange-600 text-[22rpx]">5年超值</text>
60 - </view>
61 - <view class="bg-green-50 rounded-[8rpx] px-[16rpx] py-[6rpx]">
62 - <text class="text-green-600 text-[22rpx]">保证收益万能</text>
63 - </view>
64 - </view>
65 - <view class="flex justify-between gap-[24rpx]">
66 - <nut-button
67 - plain
68 - color="#2563EB"
69 - class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0 !border-blue-600"
70 - @tap="goToProductDetail(1)"
71 - >
72 - 产品详情
73 - </nut-button>
74 - <nut-button
75 - color="#2563EB"
76 - class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0"
77 - @tap="openPlanPopup('A')"
78 - >
79 - 计划书
80 - </nut-button>
81 - </view>
82 - </view>
83 59
84 - <!-- Product Card 2 --> 60 + <!-- 动态标签 -->
85 - <view class="bg-gray-50 rounded-[24rpx] p-[28rpx]"> 61 + <view v-if="product.tags && product.tags.length" class="flex flex-wrap gap-[12rpx] mb-[24rpx]">
86 - <text class="block text-gray-800 text-[28rpx] font-medium mb-[20rpx]">儿童教育金储备方案(分红)</text> 62 + <view
87 - <view class="flex flex-wrap gap-[12rpx] mb-[24rpx]"> 63 + v-for="tag in product.tags"
88 - <view class="bg-red-50 rounded-[8rpx] px-[16rpx] py-[6rpx]"> 64 + :key="tag.id"
89 - <text class="text-red-600 text-[22rpx]">收益率4.2%</text> 65 + class="rounded-[8rpx] px-[16rpx] py-[6rpx]"
90 - </view> 66 + :style="{
91 - <view class="bg-orange-50 rounded-[8rpx] px-[16rpx] py-[6rpx]"> 67 + backgroundColor: tag.bg_color,
92 - <text class="text-orange-600 text-[22rpx]">10年期</text> 68 + color: tag.text_color
93 - </view> 69 + }"
94 - <view class="bg-green-50 rounded-[8rpx] px-[16rpx] py-[6rpx]"> 70 + >
95 - <text class="text-green-600 text-[22rpx]">教育专属</text> 71 + <text class="text-[22rpx]">{{ tag.name }}</text>
96 </view> 72 </view>
97 </view> 73 </view>
74 +
98 <view class="flex justify-between gap-[24rpx]"> 75 <view class="flex justify-between gap-[24rpx]">
99 <nut-button 76 <nut-button
100 plain 77 plain
101 color="#2563EB" 78 color="#2563EB"
102 class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0 !border-blue-600" 79 class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0 !border-blue-600"
103 - @tap="goToProductDetail(2)" 80 + @tap="goToProductDetail(product.id)"
104 > 81 >
105 - 产品资料 82 + 产品详情
106 </nut-button> 83 </nut-button>
107 <nut-button 84 <nut-button
108 color="#2563EB" 85 color="#2563EB"
109 class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0" 86 class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0"
110 - @tap="openPlanPopup('B')" 87 + @tap="openPlanPopup(product.id)"
111 > 88 >
112 计划书 89 计划书
113 </nut-button> 90 </nut-button>
...@@ -187,7 +164,7 @@ ...@@ -187,7 +164,7 @@
187 164
188 <script setup> 165 <script setup>
189 import { ref, shallowRef } from 'vue'; 166 import { ref, shallowRef } from 'vue';
190 -import Taro, { useShareAppMessage } from '@tarojs/taro'; 167 +import Taro, { useShareAppMessage, useLoad } from '@tarojs/taro';
191 import { useGo } from '@/hooks/useGo'; 168 import { useGo } from '@/hooks/useGo';
192 import { useListItemClick, ListType } from '@/composables/useListItemClick'; 169 import { useListItemClick, ListType } from '@/composables/useListItemClick';
193 import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons'; 170 import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons';
...@@ -197,6 +174,7 @@ import PlanPopup from '@/components/PlanPopup/index.vue'; ...@@ -197,6 +174,7 @@ import PlanPopup from '@/components/PlanPopup/index.vue';
197 import SchemeA from '@/components/PlanSchemes/SchemeA.vue'; 174 import SchemeA from '@/components/PlanSchemes/SchemeA.vue';
198 import SchemeB from '@/components/PlanSchemes/SchemeB.vue'; 175 import SchemeB from '@/components/PlanSchemes/SchemeB.vue';
199 import ListItemActions from '@/components/ListItemActions/index.vue'; 176 import ListItemActions from '@/components/ListItemActions/index.vue';
177 +import { listAPI } from '@/api/get_product';
200 178
201 // Plan Popup State 179 // Plan Popup State
202 const showPlanPopup = ref(false); 180 const showPlanPopup = ref(false);
...@@ -226,16 +204,42 @@ const handlePlanSubmit = (formData) => { ...@@ -226,16 +204,42 @@ const handlePlanSubmit = (formData) => {
226 }; 204 };
227 205
228 // Grid navigation data with routes 206 // Grid navigation data with routes
229 -const loopData0 = shallowRef([ 207 +const loopNav = shallowRef([
230 - { icon: 'order', lanhutext0: '计划书', route: '/pages/plan/index' }, 208 + { icon: 'order', name: '计划书', route: '/pages/plan/index' },
231 - { icon: 'my', lanhutext0: '入职相关', route: '/pages/onboarding/index' }, 209 + { icon: 'my', name: '入职相关', route: '/pages/onboarding/index' },
232 - { icon: 'cart', lanhutext0: '签单相关', route: '/pages/signing/index' }, 210 + { icon: 'cart', name: '签单相关', route: '/pages/signing/index' },
233 - { icon: 'home', lanhutext0: '家办相关', route: '/pages/family-office/index' }, 211 + { icon: 'home', name: '家办相关', route: '/pages/family-office/index' },
234 - { icon: 'category', lanhutext0: '产品知识库', route: '/pages/knowledge-base/index' }, 212 + { icon: 'category', name: '产品知识库', route: '/pages/knowledge-base/index' },
235 - { icon: 'star', lanhutext0: '工具箱', route: null }, // 待开发 213 + { icon: 'star', name: '工具箱', route: null }, // 待开发
236 ]); 214 ]);
237 215
238 /** 216 /**
217 + * 热卖产品数据
218 + *
219 + * @description 从服务器获取的热卖产品列表
220 + */
221 +const hotProducts = ref([]);
222 +
223 +/**
224 + * 获取热卖产品列表
225 + *
226 + * @description 调用产品列表API,recommend参数为hot
227 + */
228 +const fetchHotProducts = async () => {
229 + try {
230 + const res = await listAPI({
231 + recommend: 'hot'
232 + });
233 +
234 + if (res.code === 1 && res.data && res.data.list) {
235 + hotProducts.value = res.data.list;
236 + }
237 + } catch (err) {
238 + console.error('获取热卖产品失败:', err);
239 + }
240 +};
241 +
242 +/**
239 * 热门资料数据 243 * 热门资料数据
240 * 244 *
241 * @description 本周热门资料列表数据,包含不同类型的文件 245 * @description 本周热门资料列表数据,包含不同类型的文件
...@@ -328,6 +332,11 @@ const openWebView = (url) => { ...@@ -328,6 +332,11 @@ const openWebView = (url) => {
328 }); 332 });
329 }; 333 };
330 334
335 +// 页面加载时获取热卖产品
336 +useLoad(() => {
337 + fetchHotProducts();
338 +});
339 +
331 useShareAppMessage(() => { 340 useShareAppMessage(() => {
332 return { 341 return {
333 title: '臻奇智荟圈', 342 title: '臻奇智荟圈',
......
...@@ -6,106 +6,89 @@ ...@@ -6,106 +6,89 @@
6 <div class="min-h-screen bg-[#F9FAFB] pb-[calc(160rpx+env(safe-area-inset-bottom))]"> 6 <div class="min-h-screen bg-[#F9FAFB] pb-[calc(160rpx+env(safe-area-inset-bottom))]">
7 <NavHeader title="产品详情" /> 7 <NavHeader title="产品详情" />
8 8
9 - <!-- Banner Image --> 9 + <!-- Loading State -->
10 - <div class="w-full h-[420rpx] relative"> 10 + <div v-if="loading" class="flex items-center justify-center h-screen">
11 - <img 11 + <div class="flex flex-col items-center">
12 - class="w-full h-full object-cover" 12 + <div class="w-[64rpx] h-[64rpx] border-4 border-gray-200 border-t-blue-600 rounded-full animate-spin mb-[24rpx]"></div>
13 - :src="bannerImage" 13 + <text class="text-gray-600 text-[28rpx]">加载中...</text>
14 - mode="aspectFill"
15 - />
16 - <div class="absolute top-[32rpx] right-[32rpx] flex items-center gap-[16rpx]">
17 - <!-- Hot Tag -->
18 - <div class="bg-red-500 text-white text-[24rpx] px-[20rpx] py-[10rpx] rounded-full shadow-sm backdrop-blur-sm bg-opacity-90">
19 - 热卖
20 - </div>
21 </div> 14 </div>
22 </div> 15 </div>
23 16
24 - <!-- Product Header --> 17 + <!-- Content -->
25 - <div class="relative mt-[-40rpx] bg-white rounded-t-[40rpx] px-[40rpx] pt-[48rpx] pb-[40rpx] z-10"> 18 + <div v-else>
26 - <div class="flex items-start justify-between mb-[24rpx]"> 19 + <!-- Banner Image -->
27 - <h1 class="text-[#1F2937] text-[44rpx] font-bold flex-1 mr-[24rpx] leading-[1.2]">终身寿险尊享版</h1> 20 + <div class="w-full h-[420rpx] relative">
28 - <!-- Favorite Button --> 21 + <img
29 - <div 22 + class="w-full h-full object-cover"
30 - class="w-[72rpx] h-[72rpx] flex-shrink-0 flex items-center justify-center rounded-full bg-gray-50 active:scale-95 transition-transform" 23 + :src="productDetail.cover_image || bannerImage"
31 - @tap="toggleCollect" 24 + mode="aspectFill"
32 - > 25 + />
33 - <IconFont 26 + <div class="absolute top-[32rpx] right-[32rpx] flex items-center gap-[16rpx]">
34 - :name="isCollected ? 'heart-fill' : 'heart'" 27 + <!-- Hot Tag -->
35 - size="24" 28 + <div
36 - :color="isCollected ? '#EF4444' : '#9CA3AF'" 29 + v-if="productDetail.recommend === 'hot'"
37 - /> 30 + class="bg-red-500 text-white text-[24rpx] px-[20rpx] py-[10rpx] rounded-full shadow-sm backdrop-blur-sm bg-opacity-90"
31 + >
32 + 热卖
33 + </div>
38 </div> 34 </div>
39 </div> 35 </div>
40 36
41 - <div class="flex flex-wrap gap-[16rpx]"> 37 + <!-- Product Header -->
42 - <div class="px-[16rpx] py-[6rpx] bg-red-50 rounded-[8rpx]"> 38 + <div class="relative mt-[-40rpx] bg-white rounded-t-[40rpx] px-[40rpx] pt-[48rpx] pb-[40rpx] z-10">
43 - <span class="text-red-600 text-[24rpx]">收益率3.5%</span> 39 + <h1 class="text-[#1F2937] text-[44rpx] font-bold leading-[1.2] mb-[24rpx]">
44 - </div> 40 + {{ productDetail.product_name || '产品详情' }}
45 - <div class="px-[16rpx] py-[6rpx] bg-orange-50 rounded-[8rpx]"> 41 + </h1>
46 - <span class="text-orange-600 text-[24rpx]">5年超值</span>
47 - </div>
48 - <div class="px-[16rpx] py-[6rpx] bg-green-50 rounded-[8rpx]">
49 - <span class="text-green-600 text-[24rpx]">保证收益万能</span>
50 - </div>
51 - </div>
52 - </div>
53 42
54 - <!-- Stats Grid --> 43 + <!-- 动态标签 -->
55 - <div class="px-[32rpx] mt-[24rpx]"> 44 + <div v-if="productDetail.tags && productDetail.tags.length" class="flex flex-wrap gap-[16rpx]">
56 - <div class="grid grid-cols-2 gap-[24rpx]"> 45 + <div
57 - <div 46 + v-for="tag in productDetail.tags"
58 - v-for="(item, index) in stats" 47 + :key="tag.id"
59 - :key="index" 48 + class="rounded-[8rpx] px-[16rpx] py-[6rpx]"
60 - class="bg-white rounded-[24rpx] p-[32rpx] border border-gray-100" 49 + :style="{
61 - > 50 + backgroundColor: tag.bg_color,
62 - <div class="text-[#6B7280] text-[24rpx] mb-[12rpx]">{{ item.label }}</div> 51 + color: tag.text_color
63 - <div class="text-[#1F2937] text-[30rpx] font-medium">{{ item.value }}</div> 52 + }"
53 + >
54 + <span class="text-[24rpx]">{{ tag.name }}</span>
55 + </div>
64 </div> 56 </div>
65 </div> 57 </div>
66 - </div>
67 58
68 - <!-- Product Features --> 59 + <!-- Product Description (富文本) -->
69 - <div class="px-[32rpx] mt-[32rpx]"> 60 + <div v-if="productDetail.product_description" class="px-[32rpx] mt-[32rpx]">
70 - <div class="bg-white rounded-[32rpx] p-[40rpx]"> 61 + <div class="bg-white rounded-[32rpx] p-[40rpx]">
71 - <h2 class="text-[#1F2937] text-[32rpx] font-bold mb-[32rpx]">产品特色</h2> 62 + <h2 class="text-[#1F2937] text-[32rpx] font-bold mb-[32rpx]">产品描述</h2>
72 - <div class="flex flex-col gap-[32rpx]"> 63 + <!-- 使用 rich-text 渲染富文本 -->
73 - <div v-for="(feature, index) in features" :key="index" class="flex items-start"> 64 + <rich-text :nodes="productDetail.product_description" class="text-[#4B5563] text-[28rpx] leading-[1.6]"></rich-text>
74 - <div class="w-[48rpx] h-[48rpx] rounded-full bg-blue-50 flex items-center justify-center mr-[24rpx] flex-shrink-0">
75 - <IconFont name="Check" size="14" color="#2563EB" />
76 - </div>
77 - <div class="flex-1">
78 - <div class="text-[#1F2937] text-[28rpx] font-medium leading-[1.4]">{{ feature.title }}</div>
79 - <div class="text-[#6B7280] text-[24rpx] mt-[8rpx] leading-[1.4]">{{ feature.desc }}</div>
80 - </div>
81 - </div>
82 </div> 65 </div>
83 </div> 66 </div>
84 - </div>
85 67
86 - <!-- Attachments --> 68 + <!-- Attachments -->
87 - <div class="px-[32rpx] mt-[32rpx]"> 69 + <div v-if="productDetail.documents && productDetail.documents.length" class="px-[32rpx] mt-[32rpx]">
88 - <div class="bg-white rounded-[32rpx] p-[40rpx]"> 70 + <div class="bg-white rounded-[32rpx] p-[40rpx]">
89 - <h2 class="text-[#1F2937] text-[32rpx] font-bold mb-[32rpx]">相关附件</h2> 71 + <h2 class="text-[#1F2937] text-[32rpx] font-bold mb-[32rpx]">相关附件</h2>
90 - <div class="flex flex-col gap-[24rpx]"> 72 + <div class="flex flex-col gap-[24rpx]">
91 - <div 73 + <div
92 - v-for="(file, index) in files" 74 + v-for="(doc, index) in productDetail.documents"
93 - :key="index" 75 + :key="index"
94 - class="flex flex-col p-[24rpx] bg-gray-50 rounded-[16rpx]" 76 + class="flex flex-col p-[24rpx] bg-gray-50 rounded-[16rpx]"
95 - > 77 + >
96 - <div class="flex items-center justify-between mb-[8rpx]"> 78 + <div class="flex items-center justify-between">
97 - <div class="flex items-center flex-1 mr-[24rpx]"> 79 + <div class="flex items-center flex-1 mr-[24rpx]">
98 - <image 80 + <image
99 - :src="getDocumentIcon(file.fileName)" 81 + :src="getDocumentIcon(doc.file_name)"
100 - class="w-[48rpx] h-[48rpx] mr-[24rpx]" 82 + class="w-[48rpx] h-[48rpx] mr-[24rpx]"
101 - mode="aspectFit" 83 + mode="aspectFit"
102 - /> 84 + />
103 - <div class="flex flex-col"> 85 + <div class="flex flex-col">
104 - <span class="text-[#1F2937] text-[28rpx] font-medium mb-[4rpx] line-clamp-1">{{ file.name }}</span> 86 + <span class="text-[#1F2937] text-[28rpx] font-medium mb-[4rpx] line-clamp-1">{{ doc.file_name }}</span>
105 - <span class="text-[#9CA3AF] text-[24rpx]">{{ file.size }}</span> 87 + <span class="text-[#9CA3AF] text-[24rpx]">{{ doc.file_size_formatted }}</span>
88 + </div>
106 </div> 89 </div>
90 + <IconFont name="eye" size="20" color="#2563EB" @tap="viewDocument(doc)" />
107 </div> 91 </div>
108 - <IconFont name="eye" size="20" color="#2563EB" @tap="viewFile(file)" />
109 </div> 92 </div>
110 </div> 93 </div>
111 </div> 94 </div>
...@@ -125,12 +108,75 @@ import IconFont from '@/components/IconFont.vue' ...@@ -125,12 +108,75 @@ import IconFont from '@/components/IconFont.vue'
125 import { useFileOperation } from '@/composables/useFileOperation' 108 import { useFileOperation } from '@/composables/useFileOperation'
126 import Taro, { useLoad } from '@tarojs/taro' 109 import Taro, { useLoad } from '@tarojs/taro'
127 import { getDocumentIcon } from '@/utils/documentIcons' 110 import { getDocumentIcon } from '@/utils/documentIcons'
111 +import { detailAPI } from '@/api/get_product'
128 112
129 const { viewFile } = useFileOperation() 113 const { viewFile } = useFileOperation()
130 114
131 // 接收页面参数 115 // 接收页面参数
132 const productId = ref(null) 116 const productId = ref(null)
133 117
118 +// 加载状态
119 +const loading = ref(true)
120 +
121 +// 产品详情数据
122 +const productDetail = ref({
123 + id: null,
124 + product_name: '',
125 + recommend: '',
126 + product_description: '',
127 + cover_image: '',
128 + tags: [],
129 + documents: []
130 +})
131 +
132 +/**
133 + * 获取产品详情
134 + *
135 + * @description 调用 detailAPI 获取产品详情数据
136 + * @param {string} id - 产品ID
137 + */
138 +const fetchProductDetail = async (id) => {
139 + try {
140 + loading.value = true
141 +
142 + const res = await detailAPI({
143 + i: id
144 + })
145 +
146 + if (res.code === 1 && res.data) {
147 + productDetail.value = res.data
148 + } else {
149 + Taro.showToast({
150 + title: res.msg || '获取产品详情失败',
151 + icon: 'none',
152 + duration: 2000
153 + })
154 + }
155 + } catch (err) {
156 + console.error('获取产品详情失败:', err)
157 + Taro.showToast({
158 + title: '网络错误,请重试',
159 + icon: 'none',
160 + duration: 2000
161 + })
162 + } finally {
163 + loading.value = false
164 + }
165 +}
166 +
167 +/**
168 + * 查看文档
169 + *
170 + * @description 打开文档预览
171 + * @param {Object} doc - 文档对象
172 + */
173 +const viewDocument = (doc) => {
174 + viewFile({
175 + fileName: doc.file_name,
176 + downloadUrl: doc.file_url
177 + })
178 +}
179 +
134 useLoad((options) => { 180 useLoad((options) => {
135 console.log('产品详情页参数:', options) 181 console.log('产品详情页参数:', options)
136 182
...@@ -138,11 +184,11 @@ useLoad((options) => { ...@@ -138,11 +184,11 @@ useLoad((options) => {
138 productId.value = options.id 184 productId.value = options.id
139 console.log('产品ID:', productId.value) 185 console.log('产品ID:', productId.value)
140 186
141 - // TODO: 根据 productId 获取产品详情数据 187 + // 获取产品详情数据
142 - // 这里可以调用 API 获取对应产品的数据
143 fetchProductDetail(options.id) 188 fetchProductDetail(options.id)
144 } else { 189 } else {
145 console.warn('未接收到产品ID') 190 console.warn('未接收到产品ID')
191 + loading.value = false
146 Taro.showToast({ 192 Taro.showToast({
147 title: '产品ID不存在', 193 title: '产品ID不存在',
148 icon: 'none', 194 icon: 'none',
...@@ -151,107 +197,6 @@ useLoad((options) => { ...@@ -151,107 +197,6 @@ useLoad((options) => {
151 } 197 }
152 }) 198 })
153 199
154 -// 根据 ID 获取产品详情(模拟) 200 +// Random banner image (fallback)
155 -const fetchProductDetail = async (id) => {
156 - console.log('正在获取产品ID', id, '的详情...')
157 -
158 - // TODO: 实际调用 API
159 - // const res = await getProductDetailAPI({ i: id })
160 - // if (res.code === 1) {
161 - // // 更新产品数据
162 - // }
163 -
164 - // 模拟根据不同ID显示不同产品
165 - const productNames = {
166 - '1': '家庭财富传承保障计划(分红)',
167 - '2': '儿童教育金储备方案(分红)'
168 - }
169 -
170 - if (productNames[id]) {
171 - console.log('产品名称:', productNames[id])
172 - }
173 -}
174 -
175 -// Random banner image
176 const bannerImage = `https://picsum.photos/seed/${Math.floor(Math.random() * 1000)}/750/420` 201 const bannerImage = `https://picsum.photos/seed/${Math.floor(Math.random() * 1000)}/750/420`
177 -
178 -const stats = ref([
179 - { label: '投保年龄', value: '30天-70周岁' },
180 - { label: '保障期限', value: '终身' },
181 - { label: '缴费方式', value: '3/5/10年交' },
182 - { label: '起投金额', value: '10000元起' }
183 -])
184 -
185 -const features = ref([
186 - { title: '身故保险金', desc: '赔付100%基本保额,给家人留爱不留债' },
187 - { title: '全残保险金', desc: '赔付100%基本保额,生活有保障' },
188 - { title: '保费豁免', desc: '确诊重疾后免交剩余保费,保障继续有效' },
189 - { title: '保单贷款', desc: '最高可贷现金价值80%,资金周转灵活' }
190 -])
191 -
192 -const files = ref([
193 - {
194 - name: '产品条款.pdf',
195 - size: '2.3MB',
196 - fileName: '产品条款.pdf',
197 - downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/test.pdf',
198 - iconName: 'order',
199 - iconColor: '#EF4444',
200 - fileType: 'pdf',
201 - showTip: false,
202 - tipText: ''
203 - },
204 - {
205 - name: '投保须知.docx',
206 - size: '1.8MB',
207 - fileName: '投保须知.docx',
208 - downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/%E7%94%A8%E6%88%B7%E5%8D%8F%E8%AE%AE%E6%9C%80%E7%BB%88v3.1.docx',
209 - iconName: 'order',
210 - iconColor: '#2563EB',
211 - fileType: 'docx',
212 - showTip: true,
213 - tipText: 'Word 文档可能无法正常显示,建议打开后点击右上角"..."菜单选择"发送给朋友"保存到电脑查看'
214 - },
215 - {
216 - name: '健康告知.pptx',
217 - size: '3.2MB',
218 - fileName: '健康告知.pptx',
219 - downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/%E8%82%A1%E5%88%A4%E5%90%88%E5%8F%8B%E7%94%A8%E7%9F%A5%E8%AF%86%E8%AF%B4%E6%98%8E20240112110417414.pptx',
220 - iconName: 'order',
221 - iconColor: '#F59E0B',
222 - fileType: 'pptx',
223 - showTip: true,
224 - tipText: 'PPT 文档可能无法正常显示,建议打开后点击右上角"..."菜单选择"发送给朋友"保存到电脑查看'
225 - },
226 - {
227 - name: '保险责任说明.xlsx',
228 - size: '1.5MB',
229 - fileName: '保险责任说明.xlsx',
230 - downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/%E7%94%A8%E6%88%B7%E5%8D%8F%E8%AE%AE%E6%9C%80%E7%BB%88v3.1.docx',
231 - iconName: 'order',
232 - iconColor: '#10B981',
233 - fileType: 'xlsx',
234 - showTip: true,
235 - tipText: 'Excel 文档可能无法正常显示,建议打开后点击右上角"..."菜单选择"发送给朋友"保存到电脑查看'
236 - }
237 -])
238 -
239 -// 收藏状态
240 -const isCollected = ref(false)
241 -
242 -// 切换收藏状态
243 -const toggleCollect = () => {
244 - isCollected.value = !isCollected.value
245 - if (isCollected.value) {
246 - Taro.showToast({
247 - title: '已收藏',
248 - icon: 'success'
249 - })
250 - } else {
251 - Taro.showToast({
252 - title: '已取消收藏',
253 - icon: 'none'
254 - })
255 - }
256 -}
257 </script> 202 </script>
......