hookehuyr

feat(plan): 添加消息详情计划书查看功能

- 新增 usePlanView composable 封装计划书查看逻辑
- 支持单文件直接预览,多文件显示选择弹框
- 预览成功后自动标记为已查看
- 消息列表第一页插入3条测试数据(1001/1002/1003)
- 更新测试文件地址,使用项目 CDN 资源

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 +/**
2 + * 计划书查看 Composable
3 + *
4 + * @description 封装计划书查看逻辑,支持:
5 + * - 单文件直接预览
6 + * - 多文件显示选择弹框
7 + * - 预览成功后标记为已查看
8 + * - 传入 proposal 数据自动处理状态和文件
9 + *
10 + * @example
11 + * const { viewProposal } = usePlanView()
12 + *
13 + * // 方式1:传入完整的 proposal 对象(从消息详情 API 获取)
14 + * viewProposal({
15 + * id: 123,
16 + * order_status: '7',
17 + * proposal_files: [
18 + * { file_name: '计划书.pdf', file_url: 'xxx', id: 1 }
19 + * ]
20 + * })
21 + *
22 + * // 方式2:传入已转换的 item(从计划书列表获取)
23 + * viewProposal(planItem)
24 + *
25 + * @author Claude Code
26 + * @version 1.0.0
27 + */
28 +
29 +import { useFileOperation } from './useFileOperation'
30 +import { viewAPI } from '@/api/plan'
31 +import Taro from '@tarojs/taro'
32 +
33 +/**
34 + * 计划书查看 Hook
35 + *
36 + * @returns {Object} 包含 viewProposal 方法的对象
37 + */
38 +export function usePlanView() {
39 + const { viewFile } = useFileOperation()
40 +
41 + /**
42 + * 订单状态映射
43 + *
44 + * @param {string} orderStatus - API 返回的状态值
45 + * @returns {string} 前端状态:'pending' | 'processing' | 'generated' | 'viewed'
46 + *
47 + * @description 状态值说明(根据API文档):
48 + * - "3" = 待处理 (pending)
49 + * - "5" = 处理中 (processing)
50 + * - "7" = 已生成 (generated)
51 + * - "9" = 已查看 (viewed)
52 + */
53 + const mapOrderStatus = (orderStatus) => {
54 + const statusMap = {
55 + '3': 'pending', // 待处理
56 + '5': 'processing', // 处理中
57 + '7': 'generated', // 已生成
58 + '9': 'viewed' // 已查看
59 + }
60 + return statusMap[orderStatus] || 'pending'
61 + }
62 +
63 + /**
64 + * 获取状态文本
65 + *
66 + * @param {string} status - 前端状态值
67 + * @returns {string} 状态文本
68 + */
69 + const getStatusText = (status) => {
70 + const textMap = {
71 + 'pending': '待处理',
72 + 'processing': '处理中',
73 + 'generated': '已生成',
74 + 'viewed': '已查看'
75 + }
76 + return textMap[status] || '待处理'
77 + }
78 +
79 + /**
80 + * 查看计划书
81 + *
82 + * @param {Object} proposal - 计划书对象(支持两种格式)
83 + * @param {number} proposal.id - 计划书 ID(必需)
84 + * @param {string} proposal.order_status - 订单状态(API 格式:'3'|'5'|'7'|'9')
85 + * @param {Array} proposal.proposal_files - 文件列表(API 格式)
86 + * @param {string} proposal.status - 订单状态(前端格式,兼容列表数据)
87 + * @param {Array} proposal.proposalFiles - 文件列表(兼容列表数据)
88 + * @param {Object} callbacks - 回调函数
89 + * @param {Function} callbacks.onViewSuccess - 查看成功后回调,参数为 proposalId
90 + * @param {Function} callbacks.beforeView - 查看前回调,返回 false 可取消查看
91 + * @returns {Promise<void>}
92 + */
93 + const viewProposal = async (proposal, callbacks = {}) => {
94 + const { beforeView, onViewSuccess } = callbacks
95 +
96 + // 1. 状态检查 - 解析两种可能的状态字段
97 + const status = proposal.status || mapOrderStatus(proposal.order_status)
98 +
99 + if (status === 'pending' || status === 'processing') {
100 + Taro.showToast({
101 + title: '计划书尚未生成,请稍后',
102 + icon: 'none'
103 + })
104 + return
105 + }
106 +
107 + // 2. 解析文件列表 - 支持两种可能的字段名
108 + const proposalFiles = proposal.proposal_files || proposal.proposalFiles || []
109 +
110 + if (!proposalFiles || proposalFiles.length === 0) {
111 + Taro.showToast({
112 + title: '暂无可查看的计划书',
113 + icon: 'none'
114 + })
115 + return
116 + }
117 +
118 + // 3. 执行查看前回调
119 + if (beforeView) {
120 + const shouldContinue = await beforeView(proposal)
121 + if (shouldContinue === false) return
122 + }
123 +
124 + /**
125 + * 处理单个文件的查看
126 + *
127 + * @param {Object} file - 文件对象
128 + * @param {string} file.file_url - 文件 URL
129 + * @param {string} file.file_name - 文件名称
130 + */
131 + const handleFileView = async (file) => {
132 + try {
133 + const previewSuccess = await viewFile({
134 + downloadUrl: file.file_url,
135 + fileName: file.file_name
136 + })
137 +
138 + if (!previewSuccess) return
139 +
140 + // 4. 预览成功后标记为已查看
141 + if (status !== 'viewed' && proposal.id) {
142 + const viewRes = await viewAPI({ i: proposal.id })
143 +
144 + if (viewRes.code === 1) {
145 + Taro.showToast({
146 + title: '已标记为查看',
147 + icon: 'success',
148 + duration: 1000
149 + })
150 +
151 + // 触发成功回调
152 + if (onViewSuccess) {
153 + onViewSuccess(proposal.id)
154 + }
155 + }
156 + }
157 + } catch (error) {
158 + console.error('查看计划书文件失败:', error)
159 + }
160 + }
161 +
162 + // 5. 单文件直接查看
163 + if (proposalFiles.length === 1) {
164 + await handleFileView(proposalFiles[0])
165 + return
166 + }
167 +
168 + // 6. 多文件显示选择弹框
169 + const fileList = proposalFiles.map((file, index) => ({
170 + text: file.file_name || `计划书 ${index + 1}`,
171 + file: file
172 + }))
173 +
174 + Taro.showActionSheet({
175 + itemList: fileList.map(f => f.text),
176 + success: async (res) => {
177 + const selectedIndex = res.tapIndex
178 + if (selectedIndex !== undefined && selectedIndex >= 0) {
179 + const selectedFile = fileList[selectedIndex].file
180 + await handleFileView(selectedFile)
181 + }
182 + }
183 + })
184 + }
185 +
186 + return {
187 + viewProposal,
188 + mapOrderStatus,
189 + getStatusText
190 + }
191 +}
1 <!-- 1 <!--
2 * @Date:2026-02-03 21:26:58 2 * @Date:2026-02-03 21:26:58
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2026-02-12 21:09:32 4 + * @LastEditTime: 2026-02-13
5 * @FilePath: /manulife-weapp/src/pages/message-detail/index.vue 5 * @FilePath: /manulife-weapp/src/pages/message-detail/index.vue
6 - * @Description: 消息详情页 6 + * @Description: 消息详情页 - API 新增 title 字段,添加 Mock 支持,新增计划书查看功能
7 --> 7 -->
8 <template> 8 <template>
9 <view class="min-h-screen bg-gray-50 pb-safe"> 9 <view class="min-h-screen bg-gray-50 pb-safe">
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
12 <view v-if="detail" class="p-4"> 12 <view v-if="detail" class="p-4">
13 <!-- 消息卡片 --> 13 <!-- 消息卡片 -->
14 <view class="bg-white rounded-lg p-5 shadow-sm"> 14 <view class="bg-white rounded-lg p-5 shadow-sm">
15 - <!-- 标题区域 (模拟) --> 15 + <!-- 标题区域:API 返回 title 字段 -->
16 <view class="mb-3"> 16 <view class="mb-3">
17 <text class="text-xl font-bold text-gray-900 leading-snug block"> 17 <text class="text-xl font-bold text-gray-900 leading-snug block">
18 {{ detail.title || '系统消息通知' }} 18 {{ detail.title || '系统消息通知' }}
...@@ -33,6 +33,57 @@ ...@@ -33,6 +33,57 @@
33 <view class="rich-text-content"> 33 <view class="rich-text-content">
34 <rich-text :nodes="formattedContent" /> 34 <rich-text :nodes="formattedContent" />
35 </view> 35 </view>
36 +
37 + <!-- 计划书卡片 -->
38 + <view
39 + v-if="detail.proposal"
40 + class="mt-6 pt-6 border-t border-gray-200"
41 + >
42 + <!-- 卡片标题 -->
43 + <view class="flex justify-between items-center mb-4">
44 + <text class="text-base font-bold text-gray-900">关联计划书</text>
45 + <!-- 状态标记 -->
46 + <view
47 + :class="[
48 + 'status-badge',
49 + proposalStatus === 'generated' ? 'status-generated' : '',
50 + proposalStatus === 'viewed' ? 'status-viewed' : ''
51 + ]"
52 + >
53 + {{ getProposalStatusText(proposalStatus) }}
54 + </view>
55 + </view>
56 +
57 + <!-- 计划书信息 -->
58 + <view class="bg-gray-50 rounded-lg p-4 mb-4">
59 + <view class="flex justify-between text-sm mb-2">
60 + <text class="text-gray-500">申请人</text>
61 + <text class="text-gray-900 font-medium">{{ detail.proposal.customer_name || '-' }}</text>
62 + </view>
63 + <view class="flex justify-between text-sm mb-2">
64 + <text class="text-gray-500">产品</text>
65 + <text class="text-gray-900 font-medium">{{ detail.proposal.product_name || '-' }}</text>
66 + </view>
67 + <view class="flex justify-between text-sm">
68 + <text class="text-gray-500">创建时间</text>
69 + <text class="text-gray-900 font-medium">{{ detail.proposal.created_time || '-' }}</text>
70 + </view>
71 + </view>
72 +
73 + <!-- 查看按钮 -->
74 + <nut-button
75 + v-if="canViewProposal"
76 + type="primary"
77 + color="#2563EB"
78 + block
79 + class="!rounded-lg !text-base"
80 + @tap="handleViewProposal"
81 + >
82 + {{ proposalFiles.length > 1
83 + ? `查看计划书 (${proposalFiles.length}个文件)`
84 + : '查看计划书' }}
85 + </nut-button>
86 + </view>
36 </view> 87 </view>
37 </view> 88 </view>
38 89
...@@ -52,12 +103,75 @@ import { useLoad } from '@tarojs/taro' ...@@ -52,12 +103,75 @@ import { useLoad } from '@tarojs/taro'
52 import NavHeader from '@/components/navigation/NavHeader.vue' 103 import NavHeader from '@/components/navigation/NavHeader.vue'
53 import { detailAPI } from '@/api/news' 104 import { detailAPI } from '@/api/news'
54 import { useUserStore } from '@/stores/user' 105 import { useUserStore } from '@/stores/user'
106 +import { mockDetailAPI } from '@/utils/mockData'
107 +import { USE_MOCK_DATA } from '@/config/app'
108 +import { usePlanView } from '@/composables/usePlanView'
55 109
56 const detail = ref(null) 110 const detail = ref(null)
57 const loading = ref(true) 111 const loading = ref(true)
58 112
113 +// 使用计划书查看 Composable
114 +const { viewProposal } = usePlanView()
115 +
59 /** 116 /**
60 - * @description 格式化富文本内容,处理图片宽度等问题 117 + * 计划书状态
118 + *
119 + * @description 将 API 返回的 order_status 映射为前端状态
120 + * @returns {string|null} 'pending' | 'processing' | 'generated' | 'viewed' | null
121 + */
122 +const proposalStatus = computed(() => {
123 + if (!detail.value?.proposal) return null
124 +
125 + // 状态映射(与 plan/index.vue 保持一致)
126 + const statusMap = {
127 + '3': 'pending', // 待处理
128 + '5': 'processing', // 处理中
129 + '7': 'generated', // 已生成
130 + '9': 'viewed' // 已查看
131 + }
132 +
133 + return statusMap[detail.value.proposal.order_status] || 'pending'
134 +})
135 +
136 +/**
137 + * 计划书文件列表
138 + *
139 + * @description 从 proposal 对象中提取文件列表
140 + * @returns {Array} 文件列表
141 + */
142 +const proposalFiles = computed(() => {
143 + return detail.value?.proposal?.proposal_files || []
144 +})
145 +
146 +/**
147 + * 是否可以查看计划书
148 + *
149 + * @description 只有"已生成"和"已查看"状态才能查看
150 + * @returns {boolean}
151 + */
152 +const canViewProposal = computed(() => {
153 + const status = proposalStatus.value
154 + return status === 'generated' || status === 'viewed'
155 +})
156 +
157 +/**
158 + * 获取状态文本
159 + *
160 + * @param {string} status - 前端状态值
161 + * @returns {string} 状态中文文本
162 + */
163 +const getProposalStatusText = (status) => {
164 + const textMap = {
165 + 'pending': '待处理',
166 + 'processing': '处理中',
167 + 'generated': '已生成',
168 + 'viewed': '已查看'
169 + }
170 + return textMap[status] || '待处理'
171 +}
172 +
173 +/**
174 + * 格式化富文本内容,处理图片宽度等问题
61 */ 175 */
62 const formattedContent = computed(() => { 176 const formattedContent = computed(() => {
63 if (!detail.value?.note) return '' 177 if (!detail.value?.note) return ''
...@@ -72,21 +186,37 @@ const formattedContent = computed(() => { ...@@ -72,21 +186,37 @@ const formattedContent = computed(() => {
72 }) 186 })
73 187
74 /** 188 /**
75 - * @description 获取消息详情 189 + * 查看计划书
190 + *
191 + * @description 调用 usePlanView 的 viewProposal 方法查看计划书
192 + * 支持单文件直接预览,多文件显示选择弹框
193 + */
194 +const handleViewProposal = () => {
195 + viewProposal(detail.value.proposal, {
196 + onViewSuccess: (proposalId) => {
197 + // 查看成功后的回调
198 + console.log('计划书已查看:', proposalId)
199 + }
200 + })
201 +}
202 +
203 +/**
204 + * 获取消息详情
205 + *
206 + * @description 根据 ID 获取消息详情,API 已返回 title 字段
76 * @param {string|number} id 消息ID 207 * @param {string|number} id 消息ID
77 */ 208 */
78 const fetchDetail = async (id) => { 209 const fetchDetail = async (id) => {
79 loading.value = true 210 loading.value = true
80 try { 211 try {
81 - const res = await detailAPI({ i: id }) 212 + // 根据环境选择使用 Mock 数据还是真实 API
213 + const res = USE_MOCK_DATA
214 + ? await mockDetailAPI({ i: id })
215 + : await detailAPI({ i: id })
216 +
82 if (res.code === 1) { 217 if (res.code === 1) {
83 - // 模拟标题数据,实际项目中应由后端返回 218 + // API 已返回 title 字段,直接使用后端数据
84 - // 这里为了演示效果,如果后端没有返回 title,就模拟一个 219 + detail.value = res.data
85 - const mockTitle = '关于系统维护升级的通知公告'
86 - detail.value = {
87 - ...res.data,
88 - title: res.data.title || mockTitle
89 - }
90 220
91 // 查看消息后刷新用户信息,更新未读消息数 221 // 查看消息后刷新用户信息,更新未读消息数
92 const userStore = useUserStore() 222 const userStore = useUserStore()
...@@ -128,4 +258,23 @@ useLoad((options) => { ...@@ -128,4 +258,23 @@ useLoad((options) => {
128 margin-bottom: 10rpx; 258 margin-bottom: 10rpx;
129 } 259 }
130 } 260 }
261 +
262 +/* 计划书状态标记样式 */
263 +.status-badge {
264 + padding: 4rpx 12rpx;
265 + border-radius: 9999rpx;
266 + font-size: 22rpx;
267 + font-weight: 500;
268 + white-space: nowrap;
269 +}
270 +
271 +.status-generated {
272 + background-color: #D1FAE5;
273 + color: #059669;
274 +}
275 +
276 +.status-viewed {
277 + background-color: #E5E7EB;
278 + color: #6B7280;
279 +}
131 </style> 280 </style>
......
...@@ -67,11 +67,17 @@ function mockDelay(min = 100, max = 300) { ...@@ -67,11 +67,17 @@ function mockDelay(min = 100, max = 300) {
67 67
68 /** 68 /**
69 * 真实的可预览测试文件地址 69 * 真实的可预览测试文件地址
70 - * 来源:GitHub 和其他公开的测试资源 70 + *
71 + * 来源说明:
72 + * - 项目 CDN: cdn.ipadbiz.cn(项目自有 CDN,最稳定)
73 + * - calibre-ebook.com: Calibre 官方测试文件
74 + * - filesamples.com: 文件格式测试样本
75 + * - Microsoft: 官方示例文件
71 */ 76 */
72 const TEST_FILES = { 77 const TEST_FILES = {
73 - // PDF 文档 78 + // PDF 文档(优先使用项目 CDN)
74 pdf: [ 79 pdf: [
80 + 'https://cdn.ipadbiz.cn/manulife/document/test.pdf', // 项目 CDN(最可靠)
75 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf', 81 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
76 'https://www.africau.edu/images/default/sample.pdf' 82 'https://www.africau.edu/images/default/sample.pdf'
77 ], 83 ],
...@@ -83,12 +89,14 @@ const TEST_FILES = { ...@@ -83,12 +89,14 @@ const TEST_FILES = {
83 89
84 // Excel 表格 (xlsx) 90 // Excel 表格 (xlsx)
85 xlsx: [ 91 xlsx: [
92 + 'https://filesamples.com/samples/document/xlsx/sample1.xlsx', // filesamples 更稳定
86 'https://go.microsoft.com/fwlink/?LinkID=512104&clcid=0x0409' 93 'https://go.microsoft.com/fwlink/?LinkID=512104&clcid=0x0409'
87 ], 94 ],
88 95
89 - // PPT 演示文稿 (pptx) 96 + // PPT 演示文稿 (ppt/pptx)
90 pptx: [ 97 pptx: [
91 - 'https://www.africau.edu/images/default/sample.pptx' 98 + 'https://www.africau.edu/images/default/sample.pptx',
99 + 'https://filesamples.com/samples/document/ppt/sample1.ppt'
92 ], 100 ],
93 101
94 // 图片 102 // 图片
...@@ -617,20 +625,24 @@ const MESSAGE_TITLES = [ ...@@ -617,20 +625,24 @@ const MESSAGE_TITLES = [
617 625
618 /** 626 /**
619 * 生成消息列表项 627 * 生成消息列表项
628 + *
629 + * @description 按 API 规范生成消息 Mock 数据
630 + * @param {number} id - 消息 ID
631 + * @returns {Object} 消息对象
620 */ 632 */
621 function generateMessageItem(id) { 633 function generateMessageItem(id) {
622 const title = MESSAGE_TITLES[Math.floor(Math.random() * MESSAGE_TITLES.length)] 634 const title = MESSAGE_TITLES[Math.floor(Math.random() * MESSAGE_TITLES.length)]
623 const now = new Date() 635 const now = new Date()
624 const createDate = new Date(now.getTime() - Math.random() * 30 * 24 * 60 * 60 * 1000) 636 const createDate = new Date(now.getTime() - Math.random() * 30 * 24 * 60 * 60 * 1000)
637 + const isUnread = Math.random() > 0.5
625 638
626 return { 639 return {
627 id: id, 640 id: id,
628 - title: title, 641 + title: title, // API 返回的标题字段
629 - intro: `这是一条关于"${title}"的重要通知,请及时查看详情...`, 642 + note: `这是一条关于"${title}"的通知。\n点击查看详情了解更多信息。`, // 消息内容(note 字段)
630 - content: `这是一条关于"${title}"的重要通知,请及时查看详情。如需了解更多信息,请联系客服。`, 643 + created_time: formatDate(createDate), // 发消息时间
631 - create_time: formatDate(createDate), 644 + status: isUnread ? 'send' : 'read', // send=已发送未读取,read=已读取
632 - is_read: Math.random() > 0.5 ? 1 : 0, 645 + pk_id: Math.floor(Math.random() * 10000) // 计划书订单 ID(可选)
633 - type: Math.random() > 0.5 ? 'notice' : 'system'
634 } 646 }
635 } 647 }
636 648
...@@ -646,21 +658,52 @@ function formatDate(date) { ...@@ -646,21 +658,52 @@ function formatDate(date) {
646 658
647 /** 659 /**
648 * Mock: myListAPI (消息列表) 660 * Mock: myListAPI (消息列表)
661 + *
662 + * @description 第1页(page=0)前面会插入三条测试消息用于测试计划书查看功能
649 */ 663 */
650 export async function mockMessageListAPI(params) { 664 export async function mockMessageListAPI(params) {
651 await mockDelay() 665 await mockDelay()
652 666
653 - const { page = 1, limit = 10 } = params 667 + const { page = 0, limit = 10 } = params // 前端传的是从0开始的页码
654 const totalPages = 8 668 const totalPages = 8
655 669
656 - if (page > totalPages) { 670 + if (page >= totalPages) {
657 return { code: 1, msg: 'success', data: { list: [] } } 671 return { code: 1, msg: 'success', data: { list: [] } }
658 } 672 }
659 673
660 const list = [] 674 const list = []
661 - const startIndex = (page - 1) * limit
662 675
663 - for (let i = 0; i < limit; i++) { 676 + // 第1页(page=0):在前面插入三条测试消息(用于测试计划书查看)
677 + if (page === 0) {
678 + list.push(
679 + {
680 + id: '1001',
681 + title: '【测试】已生成计划书(单文件)',
682 + note: '测试场景:状态为"已生成",只有一个计划书文件,可直接查看。',
683 + created_time: '2026-02-13',
684 + status: 'send'
685 + },
686 + {
687 + id: '1002',
688 + title: '【测试】已生成计划书(多文件)',
689 + note: '测试场景:状态为"已生成",有3个计划书文件,点击后会显示选择弹框。',
690 + created_time: '2026-02-13',
691 + status: 'send'
692 + },
693 + {
694 + id: '1003',
695 + title: '【测试】已查看计划书',
696 + note: '测试场景:状态为"已查看",查看后不会再次标记。',
697 + created_time: '2026-02-13',
698 + status: 'read'
699 + }
700 + )
701 + }
702 +
703 + const startIndex = page * limit
704 + const remainingCount = limit - list.length
705 +
706 + for (let i = 0; i < remainingCount; i++) {
664 list.push(generateMessageItem(startIndex + i + 1)) 707 list.push(generateMessageItem(startIndex + i + 1))
665 } 708 }
666 709
...@@ -673,6 +716,136 @@ export async function mockMessageListAPI(params) { ...@@ -673,6 +716,136 @@ export async function mockMessageListAPI(params) {
673 } 716 }
674 } 717 }
675 718
719 +/**
720 + * Mock: detailAPI (消息详情)
721 + *
722 + * @description 根据 ID 返回消息详情,包含完整的 proposal 数据
723 + * @param {Object} params 请求参数
724 + * @param {string|number} params.i 消息ID
725 + * @returns {Promise} 详情数据
726 + *
727 + * @description 测试数据说明:
728 + * - id='1001': 已生成 + 单文件
729 + * - id='1002': 已生成 + 多文件 (3个文件)
730 + * - id='1003': 已查看 + 单文件
731 + * - 其他ID: 待处理状态 + 2个文件
732 + */
733 +export async function mockDetailAPI(params) {
734 + await mockDelay()
735 +
736 + const { i: id } = params
737 +
738 + if (!id) {
739 + return { code: 0, msg: '消息ID不能为空', data: null }
740 + }
741 +
742 + // 生成基础消息数据
743 + const messageItem = generateMessageItem(id)
744 +
745 + // 根据消息 ID 返回不同状态的计划书数据(用于测试)
746 + let proposal = null
747 +
748 + if (id === '1001') {
749 + // 场景1: 已生成 + 单文件
750 + proposal = {
751 + id: 1001,
752 + customer_name: '张三',
753 + product_name: '年金险产品A',
754 + categories: [{ id: '1', name: '基本信息' }],
755 + created_time: messageItem.created_time,
756 + order_status: '7', // 已生成
757 + proposal_files: [
758 + {
759 + id: 1,
760 + file_name: '计划书.pdf',
761 + file_url: TEST_FILES.pdf[0] // 使用真实的 PDF 测试文件
762 + }
763 + ]
764 + }
765 + } else if (id === '1002') {
766 + // 场景2: 已生成 + 多文件 (3个文件)
767 + proposal = {
768 + id: 1002,
769 + customer_name: '李四',
770 + product_name: '终身寿险产品B',
771 + categories: [
772 + { id: '1', name: '基本信息' },
773 + { id: '2', name: '保障内容' },
774 + { id: '3', name: '缴费方式' }
775 + ],
776 + created_time: messageItem.created_time,
777 + order_status: '7', // 已生成
778 + proposal_files: [
779 + {
780 + id: 1,
781 + file_name: '计划书完整版.pdf',
782 + file_url: TEST_FILES.pdf[0]
783 + },
784 + {
785 + id: 2,
786 + file_name: '产品条款说明书.pdf',
787 + file_url: TEST_FILES.pdf[0]
788 + },
789 + {
790 + id: 3,
791 + file_name: '费率表.pdf',
792 + file_url: TEST_FILES.pdf[0]
793 + }
794 + ]
795 + }
796 + } else if (id === '1003') {
797 + // 场景3: 已查看 + 单文件
798 + proposal = {
799 + id: 1003,
800 + customer_name: '王五',
801 + product_name: '重疾险产品C',
802 + categories: [{ id: '1', name: '基本信息' }],
803 + created_time: messageItem.created_time,
804 + order_status: '9', // 已查看
805 + proposal_files: [
806 + {
807 + id: 1,
808 + file_name: '计划书.pdf',
809 + file_url: TEST_FILES.pdf[0]
810 + }
811 + ]
812 + }
813 + } else {
814 + // 默认: 待处理状态 + 2个文件(无法查看)
815 + proposal = {
816 + id: id,
817 + customer_name: '测试用户',
818 + product_name: '测试产品',
819 + categories: [{ id: '1', name: '基本信息' }],
820 + created_time: messageItem.created_time,
821 + order_status: '3', // 待处理
822 + proposal_files: [
823 + {
824 + id: 1,
825 + file_name: '计划书文件.pdf',
826 + file_url: TEST_FILES.pdf[0]
827 + },
828 + {
829 + id: 2,
830 + file_name: '产品说明.pdf',
831 + file_url: TEST_FILES.pdf[0]
832 + }
833 + ]
834 + }
835 + }
836 +
837 + console.log(`[Mock] detailAPI - 消息ID: ${id}, 计划书状态: ${proposal.order_status}`)
838 +
839 + return {
840 + code: 1,
841 + msg: 'success',
842 + data: {
843 + ...messageItem,
844 + proposal
845 + }
846 + }
847 +}
848 +
676 // ============================================================================ 849 // ============================================================================
677 // 6. 收藏列表 Mock (favoriteListAPI) 850 // 6. 收藏列表 Mock (favoriteListAPI)
678 // ============================================================================ 851 // ============================================================================
...@@ -1018,6 +1191,8 @@ export async function mockAPI(apiName, params) { ...@@ -1018,6 +1191,8 @@ export async function mockAPI(apiName, params) {
1018 return await mockSearchAPI(params) 1191 return await mockSearchAPI(params)
1019 case 'myListAPI': 1192 case 'myListAPI':
1020 return await mockMessageListAPI(params) 1193 return await mockMessageListAPI(params)
1194 + case 'detailAPI':
1195 + return await mockDetailAPI(params)
1021 case 'favoriteListAPI': 1196 case 'favoriteListAPI':
1022 return await mockFavoriteListAPI(params) 1197 return await mockFavoriteListAPI(params)
1023 case 'feedbackListAPI': 1198 case 'feedbackListAPI':
......