refactor(plan): 提取计划书查看规则为统一工具函数
消除 plan/index.vue 与 message-detail/index.vue 之间重复的 order_status 映射和可查看判断逻辑,新增 proposalView 工具模块 统一管理 canViewProposal / getProposalFiles 等规则,同时支持 "处理中但已有文件"的业务场景。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Showing
8 changed files
with
163 additions
and
149 deletions
| ... | @@ -120,6 +120,23 @@ describe('计划书模块集成测试', () => { | ... | @@ -120,6 +120,23 @@ describe('计划书模块集成测试', () => { |
| 120 | icon: 'none' | 120 | icon: 'none' |
| 121 | }) | 121 | }) |
| 122 | }) | 122 | }) |
| 123 | + | ||
| 124 | + it('应该允许查看处理中且已有文件的计划书', async () => { | ||
| 125 | + viewAPI.mockResolvedValue({ code: 1 }) | ||
| 126 | + const proposal = { | ||
| 127 | + id: 790, | ||
| 128 | + order_status: '5', // PROCESSING | ||
| 129 | + proposal_files: [{ file_name: '计划书.pdf', file_url: 'https://example.com/plan.pdf', id: 1 }] | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + await viewProposal(proposal) | ||
| 133 | + | ||
| 134 | + expect(Taro.showToast).toHaveBeenCalledWith({ | ||
| 135 | + title: '已标记为查看', | ||
| 136 | + icon: 'success' | ||
| 137 | + }) | ||
| 138 | + expect(viewAPI).toHaveBeenCalledWith({ i: 790 }) | ||
| 139 | + }) | ||
| 123 | }) | 140 | }) |
| 124 | 141 | ||
| 125 | describe('字段依赖关系测试', () => { | 142 | describe('字段依赖关系测试', () => { | ... | ... |
| ... | @@ -15,6 +15,7 @@ import Taro from '@tarojs/taro' | ... | @@ -15,6 +15,7 @@ import Taro from '@tarojs/taro' |
| 15 | import { mapOrderStatus, getStatusText } from '@/config/constants/orderStatus' | 15 | import { mapOrderStatus, getStatusText } from '@/config/constants/orderStatus' |
| 16 | import { viewAPI } from '@/api/plan' | 16 | import { viewAPI } from '@/api/plan' |
| 17 | import { useFileOperation } from './useFileOperation' | 17 | import { useFileOperation } from './useFileOperation' |
| 18 | +import { canViewProposal, getProposalFiles } from '@/utils/proposalView' | ||
| 18 | 19 | ||
| 19 | export const viewProposal = async (proposal, callbacks = {}) => { | 20 | export const viewProposal = async (proposal, callbacks = {}) => { |
| 20 | const { beforeView, onViewSuccess, onViewError, onError } = callbacks | 21 | const { beforeView, onViewSuccess, onViewError, onError } = callbacks |
| ... | @@ -41,27 +42,26 @@ export const viewProposal = async (proposal, callbacks = {}) => { | ... | @@ -41,27 +42,26 @@ export const viewProposal = async (proposal, callbacks = {}) => { |
| 41 | } | 42 | } |
| 42 | 43 | ||
| 43 | const status = proposal.status || mapOrderStatus(proposal.order_status) | 44 | const status = proposal.status || mapOrderStatus(proposal.order_status) |
| 44 | - if (status === 'pending' || status === 'processing') { | 45 | + if (!canViewProposal(proposal)) { |
| 46 | + // `pending` 与“已有状态但文件为空”是两类不同问题,提示文案需要区分。 | ||
| 45 | Taro.showToast({ | 47 | Taro.showToast({ |
| 46 | - title: '计划书尚未生成,请稍后', | 48 | + title: status === 'pending' ? '计划书尚未生成,请稍后' : '暂无可查看的计划书', |
| 47 | - icon: 'none' | ||
| 48 | - }) | ||
| 49 | - emitError(new Error(`计划书状态不允许查看: ${getStatusText(status)}`)) | ||
| 50 | - return | ||
| 51 | - } | ||
| 52 | - | ||
| 53 | - const proposalFiles = proposal.proposal_files || proposal.proposalFiles || [] | ||
| 54 | - | ||
| 55 | - if (!proposalFiles || proposalFiles.length === 0) { | ||
| 56 | - Taro.showToast({ | ||
| 57 | - title: '暂无可查看的计划书', | ||
| 58 | icon: 'none' | 49 | icon: 'none' |
| 59 | }) | 50 | }) |
| 51 | + const error = new Error( | ||
| 52 | + status === 'pending' | ||
| 53 | + ? `计划书状态不允许查看: ${getStatusText(status)}` | ||
| 54 | + : 'proposalFiles 为空' | ||
| 55 | + ) | ||
| 56 | + if (status !== 'pending') { | ||
| 60 | console.error('[usePlanView] proposalFiles 为空:', proposal) | 57 | console.error('[usePlanView] proposalFiles 为空:', proposal) |
| 61 | - emitError(new Error('proposalFiles 为空')) | 58 | + } |
| 59 | + emitError(error) | ||
| 62 | return | 60 | return |
| 63 | } | 61 | } |
| 64 | 62 | ||
| 63 | + const proposalFiles = getProposalFiles(proposal) | ||
| 64 | + | ||
| 65 | if (beforeView) { | 65 | if (beforeView) { |
| 66 | try { | 66 | try { |
| 67 | const shouldContinue = await beforeView(proposal) | 67 | const shouldContinue = await beforeView(proposal) | ... | ... |
| ... | @@ -74,7 +74,7 @@ | ... | @@ -74,7 +74,7 @@ |
| 74 | 74 | ||
| 75 | <!-- 查看按钮 --> | 75 | <!-- 查看按钮 --> |
| 76 | <nut-button | 76 | <nut-button |
| 77 | - v-if="canViewProposal" | 77 | + v-if="canViewCurrentProposal" |
| 78 | type="primary" | 78 | type="primary" |
| 79 | color="#2563EB" | 79 | color="#2563EB" |
| 80 | block | 80 | block |
| ... | @@ -108,6 +108,8 @@ import { useUserStore } from '@/stores/user' | ... | @@ -108,6 +108,8 @@ import { useUserStore } from '@/stores/user' |
| 108 | import { mockDetailAPI } from '@/utils/mockData' | 108 | import { mockDetailAPI } from '@/utils/mockData' |
| 109 | import { USE_MOCK_DATA } from '@/config/app' | 109 | import { USE_MOCK_DATA } from '@/config/app' |
| 110 | import { usePlanView } from '@/composables/usePlanView' | 110 | import { usePlanView } from '@/composables/usePlanView' |
| 111 | +import { mapOrderStatus, getStatusText } from '@/config/constants/orderStatus' | ||
| 112 | +import { canViewProposal as canViewProposalRule, getProposalFiles } from '@/utils/proposalView' | ||
| 111 | 113 | ||
| 112 | const detail = ref(null) | 114 | const detail = ref(null) |
| 113 | const loading = ref(true) | 115 | const loading = ref(true) |
| ... | @@ -123,16 +125,7 @@ const { viewProposal } = usePlanView() | ... | @@ -123,16 +125,7 @@ const { viewProposal } = usePlanView() |
| 123 | */ | 125 | */ |
| 124 | const proposalStatus = computed(() => { | 126 | const proposalStatus = computed(() => { |
| 125 | if (!detail.value?.proposal) return null | 127 | if (!detail.value?.proposal) return null |
| 126 | - | 128 | + return mapOrderStatus(detail.value.proposal.order_status) |
| 127 | - // 状态映射(与 plan/index.vue 保持一致) | ||
| 128 | - const statusMap = { | ||
| 129 | - '3': 'pending', // 待处理 | ||
| 130 | - '5': 'processing', // 处理中 | ||
| 131 | - '7': 'generated', // 已生成 | ||
| 132 | - '9': 'viewed' // 已查看 | ||
| 133 | - } | ||
| 134 | - | ||
| 135 | - return statusMap[detail.value.proposal.order_status] || 'pending' | ||
| 136 | }) | 129 | }) |
| 137 | 130 | ||
| 138 | /** | 131 | /** |
| ... | @@ -142,18 +135,17 @@ const proposalStatus = computed(() => { | ... | @@ -142,18 +135,17 @@ const proposalStatus = computed(() => { |
| 142 | * @returns {Array} 文件列表 | 135 | * @returns {Array} 文件列表 |
| 143 | */ | 136 | */ |
| 144 | const proposalFiles = computed(() => { | 137 | const proposalFiles = computed(() => { |
| 145 | - return detail.value?.proposal?.proposal_files || [] | 138 | + return getProposalFiles(detail.value?.proposal) |
| 146 | }) | 139 | }) |
| 147 | 140 | ||
| 148 | /** | 141 | /** |
| 149 | * 是否可以查看计划书 | 142 | * 是否可以查看计划书 |
| 150 | * | 143 | * |
| 151 | - * @description 只有"已生成"和"已查看"状态才能查看 | 144 | + * @description 根据统一规则判断是否可查看 |
| 152 | * @returns {boolean} | 145 | * @returns {boolean} |
| 153 | */ | 146 | */ |
| 154 | -const canViewProposal = computed(() => { | 147 | +const canViewCurrentProposal = computed(() => { |
| 155 | - const status = proposalStatus.value | 148 | + return canViewProposalRule(detail.value?.proposal) |
| 156 | - return status === 'generated' || status === 'viewed' | ||
| 157 | }) | 149 | }) |
| 158 | 150 | ||
| 159 | /** | 151 | /** |
| ... | @@ -163,13 +155,7 @@ const canViewProposal = computed(() => { | ... | @@ -163,13 +155,7 @@ const canViewProposal = computed(() => { |
| 163 | * @returns {string} 状态中文文本 | 155 | * @returns {string} 状态中文文本 |
| 164 | */ | 156 | */ |
| 165 | const getProposalStatusText = (status) => { | 157 | const getProposalStatusText = (status) => { |
| 166 | - const textMap = { | 158 | + return getStatusText(status) |
| 167 | - 'pending': '待处理', | ||
| 168 | - 'processing': '处理中', | ||
| 169 | - 'generated': '已生成', | ||
| 170 | - 'viewed': '已查看' | ||
| 171 | - } | ||
| 172 | - return textMap[status] || '待处理' | ||
| 173 | } | 159 | } |
| 174 | 160 | ||
| 175 | /** | 161 | /** | ... | ... |
| ... | @@ -100,9 +100,8 @@ | ... | @@ -100,9 +100,8 @@ |
| 100 | <view class="h-[1rpx] bg-gray-100 my-[20rpx]"></view> | 100 | <view class="h-[1rpx] bg-gray-100 my-[20rpx]"></view> |
| 101 | 101 | ||
| 102 | <!-- Actions --> | 102 | <!-- Actions --> |
| 103 | - <!-- 只对已生成和已查看状态显示查看按钮 --> | ||
| 104 | <ListItemActions | 103 | <ListItemActions |
| 105 | - :viewable="item.status === 'generated' || item.status === 'viewed'" | 104 | + :viewable="canViewProposalRule(item)" |
| 106 | :deletable="true" | 105 | :deletable="true" |
| 107 | @view="onView(item)" | 106 | @view="onView(item)" |
| 108 | @delete="onDelete(item)" | 107 | @delete="onDelete(item)" |
| ... | @@ -136,16 +135,18 @@ | ... | @@ -136,16 +135,18 @@ |
| 136 | <script setup> | 135 | <script setup> |
| 137 | import { ref, nextTick } from 'vue' | 136 | import { ref, nextTick } from 'vue' |
| 138 | import Taro, { useLoad, useReachBottom } from '@tarojs/taro' | 137 | import Taro, { useLoad, useReachBottom } from '@tarojs/taro' |
| 139 | -import { useFileOperation } from '@/composables/useFileOperation' | 138 | +import { listAPI, deleteAPI } from '@/api/plan' |
| 140 | -import { listAPI, viewAPI, deleteAPI } from '@/api/plan' | ||
| 141 | import { mockPlanListAPI } from '@/utils/mockData' | 139 | import { mockPlanListAPI } from '@/utils/mockData' |
| 142 | import NavHeader from '@/components/navigation/NavHeader.vue' | 140 | import NavHeader from '@/components/navigation/NavHeader.vue' |
| 143 | import ListItemActions from '@/components/list/ListItemActions/index.vue' | 141 | import ListItemActions from '@/components/list/ListItemActions/index.vue' |
| 144 | import SearchBar from '@/components/forms/SearchBar.vue' | 142 | import SearchBar from '@/components/forms/SearchBar.vue' |
| 145 | import { USE_MOCK_DATA } from '@/config/app' | 143 | import { USE_MOCK_DATA } from '@/config/app' |
| 144 | +import { mapOrderStatus, getStatusText } from '@/config/constants/orderStatus' | ||
| 145 | +import { canViewProposal as canViewProposalRule } from '@/utils/proposalView' | ||
| 146 | +import { usePlanView } from '@/composables/usePlanView' | ||
| 146 | // ⚠️ MOCK 数据开关 - 统一从 @/config/app 导入 | 147 | // ⚠️ MOCK 数据开关 - 统一从 @/config/app 导入 |
| 147 | 148 | ||
| 148 | -const { viewFile } = useFileOperation() | 149 | +const { viewProposal } = usePlanView() |
| 149 | 150 | ||
| 150 | const searchValue = ref('') | 151 | const searchValue = ref('') |
| 151 | const activeTabId = ref('') | 152 | const activeTabId = ref('') |
| ... | @@ -177,42 +178,6 @@ const tabsData = ref([ | ... | @@ -177,42 +178,6 @@ const tabsData = ref([ |
| 177 | ]) | 178 | ]) |
| 178 | 179 | ||
| 179 | /** | 180 | /** |
| 180 | - * 订单状态映射 | ||
| 181 | - * @description 将 API 返回的 order_status 映射到前端使用的状态 | ||
| 182 | - * @param {string} orderStatus - API 返回的状态值 | ||
| 183 | - * @returns {string} 前端状态:'pending' | 'processing' | 'generated' | 'viewed' | ||
| 184 | - */ | ||
| 185 | -const mapOrderStatus = (orderStatus) => { | ||
| 186 | - // 根据API文档: | ||
| 187 | - // "3" = 待处理 | ||
| 188 | - // "5" = 处理中 | ||
| 189 | - // "7" = 已生成 | ||
| 190 | - // "9" = 已查看 | ||
| 191 | - const statusMap = { | ||
| 192 | - '3': 'pending', | ||
| 193 | - '5': 'processing', | ||
| 194 | - '7': 'generated', | ||
| 195 | - '9': 'viewed' | ||
| 196 | - } | ||
| 197 | - return statusMap[orderStatus] || 'pending' | ||
| 198 | -} | ||
| 199 | - | ||
| 200 | -/** | ||
| 201 | - * 获取状态文本 | ||
| 202 | - * @param {string} status - 前端状态值 | ||
| 203 | - * @returns {string} 状态文本 | ||
| 204 | - */ | ||
| 205 | -const getStatusText = (status) => { | ||
| 206 | - const textMap = { | ||
| 207 | - 'pending': '待处理', | ||
| 208 | - 'processing': '处理中', | ||
| 209 | - 'generated': '已生成', | ||
| 210 | - 'viewed': '已查看' | ||
| 211 | - } | ||
| 212 | - return textMap[status] || '待处理' | ||
| 213 | -} | ||
| 214 | - | ||
| 215 | -/** | ||
| 216 | * 从 API 数据转换为组件数据格式 | 181 | * 从 API 数据转换为组件数据格式 |
| 217 | * @description 将 API 返回的数据结构转换为组件使用的格式 | 182 | * @description 将 API 返回的数据结构转换为组件使用的格式 |
| 218 | * @param {Object} apiItem - API 返回的计划书对象 | 183 | * @param {Object} apiItem - API 返回的计划书对象 |
| ... | @@ -451,77 +416,10 @@ useReachBottom(() => { | ... | @@ -451,77 +416,10 @@ useReachBottom(() => { |
| 451 | * @param {Object} item - 计划书对象 | 416 | * @param {Object} item - 计划书对象 |
| 452 | */ | 417 | */ |
| 453 | const onView = async (item) => { | 418 | const onView = async (item) => { |
| 454 | - // 检查是否已生成(只有"已生成"或"已查看"状态才能查看) | 419 | + await viewProposal(item, { |
| 455 | - if (item.status === 'pending' || item.status === 'processing') { | 420 | + onViewSuccess: () => { |
| 456 | - Taro.showToast({ | 421 | + // 列表页本地同步为已查看,避免用户返回后还看到旧状态。 |
| 457 | - title: '计划书尚未生成,请稍后', | ||
| 458 | - icon: 'none' | ||
| 459 | - }) | ||
| 460 | - return | ||
| 461 | - } | ||
| 462 | - | ||
| 463 | - // 检查是否有计划书文件 | ||
| 464 | - if (!item.proposalFiles || item.proposalFiles.length === 0) { | ||
| 465 | - Taro.showToast({ | ||
| 466 | - title: '暂无可查看的计划书', | ||
| 467 | - icon: 'none' | ||
| 468 | - }) | ||
| 469 | - return | ||
| 470 | - } | ||
| 471 | - | ||
| 472 | - /** | ||
| 473 | - * 处理文件查看 | ||
| 474 | - * @param {Object} file - 文件对象 | ||
| 475 | - */ | ||
| 476 | - const handleFileView = async (file) => { | ||
| 477 | - try { | ||
| 478 | - const previewSuccess = await viewFile({ | ||
| 479 | - downloadUrl: file.file_url, | ||
| 480 | - fileName: file.file_name | ||
| 481 | - }) | ||
| 482 | - | ||
| 483 | - if (!previewSuccess) { | ||
| 484 | - return | ||
| 485 | - } | ||
| 486 | - | ||
| 487 | - if (item.status !== 'viewed') { | ||
| 488 | - const viewRes = await viewAPI({ i: item.id }) | ||
| 489 | - | ||
| 490 | - if (viewRes.code === 1) { | ||
| 491 | item.status = 'viewed' | 422 | item.status = 'viewed' |
| 492 | - Taro.showToast({ | ||
| 493 | - title: '已标记为查看', | ||
| 494 | - icon: 'success', | ||
| 495 | - duration: 1000 | ||
| 496 | - }) | ||
| 497 | - } | ||
| 498 | - } | ||
| 499 | - } catch (error) { | ||
| 500 | - console.error('标记查看失败:', error) | ||
| 501 | - } | ||
| 502 | - } | ||
| 503 | - | ||
| 504 | - // 如果只有一个文件,直接查看 | ||
| 505 | - if (item.proposalFiles.length === 1) { | ||
| 506 | - await handleFileView(item.proposalFiles[0]) | ||
| 507 | - return | ||
| 508 | - } | ||
| 509 | - | ||
| 510 | - // 如果有多个文件,显示选择列表 | ||
| 511 | - const fileList = item.proposalFiles.map((file, index) => ({ | ||
| 512 | - text: file.file_name || `计划书 ${index + 1}`, | ||
| 513 | - file: file | ||
| 514 | - })) | ||
| 515 | - | ||
| 516 | - // 使用 Taro.showActionSheet 显示文件选择列表 | ||
| 517 | - Taro.showActionSheet({ | ||
| 518 | - itemList: fileList.map(f => f.text), | ||
| 519 | - success: async (res) => { | ||
| 520 | - const selectedIndex = res.tapIndex | ||
| 521 | - if (selectedIndex !== undefined && selectedIndex >= 0) { | ||
| 522 | - const selectedFile = fileList[selectedIndex].file | ||
| 523 | - await handleFileView(selectedFile) | ||
| 524 | - } | ||
| 525 | } | 423 | } |
| 526 | }) | 424 | }) |
| 527 | } | 425 | } | ... | ... |
| 1 | +import { afterEach, describe, expect, it, vi } from 'vitest' | ||
| 2 | +import { mockPlanListAPI } from '../mockData' | ||
| 3 | + | ||
| 4 | +describe('mockPlanListAPI', () => { | ||
| 5 | + afterEach(() => { | ||
| 6 | + vi.restoreAllMocks() | ||
| 7 | + }) | ||
| 8 | + | ||
| 9 | + it('处理中的 mock 计划书也应该能返回可查看文件场景', async () => { | ||
| 10 | + const randomSpy = vi.spyOn(Math, 'random') | ||
| 11 | + randomSpy | ||
| 12 | + .mockReturnValueOnce(0) | ||
| 13 | + .mockReturnValueOnce(0) | ||
| 14 | + .mockReturnValueOnce(0) | ||
| 15 | + .mockReturnValueOnce(0.3) | ||
| 16 | + .mockReturnValueOnce(0) | ||
| 17 | + .mockReturnValueOnce(0) | ||
| 18 | + .mockReturnValueOnce(0) | ||
| 19 | + | ||
| 20 | + const res = await mockPlanListAPI({ page: 0, limit: 1 }) | ||
| 21 | + const item = res.data.list[0] | ||
| 22 | + | ||
| 23 | + expect(item.order_status).toBe('5') | ||
| 24 | + expect(item.proposal_files.length).toBeGreaterThan(0) | ||
| 25 | + }) | ||
| 26 | +}) |
src/utils/__tests__/proposalView.test.js
0 → 100644
| 1 | +import { describe, expect, it } from 'vitest' | ||
| 2 | +import { canViewProposal, getProposalFiles } from '../proposalView' | ||
| 3 | + | ||
| 4 | +describe('proposalView', () => { | ||
| 5 | + it('处理中且有 proposal_files 时应该允许查看', () => { | ||
| 6 | + expect(canViewProposal({ | ||
| 7 | + order_status: '5', | ||
| 8 | + proposal_files: [{ file_name: '计划书.pdf', file_url: 'https://example.com/plan.pdf' }] | ||
| 9 | + })).toBe(true) | ||
| 10 | + }) | ||
| 11 | + | ||
| 12 | + it('处理中但没有文件时不应该允许查看', () => { | ||
| 13 | + expect(canViewProposal({ | ||
| 14 | + order_status: '5', | ||
| 15 | + proposal_files: [] | ||
| 16 | + })).toBe(false) | ||
| 17 | + }) | ||
| 18 | + | ||
| 19 | + it('待处理即使有文件时也不应该允许查看', () => { | ||
| 20 | + expect(canViewProposal({ | ||
| 21 | + order_status: '3', | ||
| 22 | + proposal_files: [{ file_name: '计划书.pdf', file_url: 'https://example.com/plan.pdf' }] | ||
| 23 | + })).toBe(false) | ||
| 24 | + }) | ||
| 25 | + | ||
| 26 | + it('应该兼容 proposalFiles 字段', () => { | ||
| 27 | + const files = [{ file_name: '计划书.pdf', file_url: 'https://example.com/plan.pdf' }] | ||
| 28 | + | ||
| 29 | + expect(getProposalFiles({ proposalFiles: files })).toEqual(files) | ||
| 30 | + expect(canViewProposal({ | ||
| 31 | + status: 'processing', | ||
| 32 | + proposalFiles: files | ||
| 33 | + })).toBe(true) | ||
| 34 | + }) | ||
| 35 | +}) |
| ... | @@ -1099,7 +1099,8 @@ function generatePlanItem(id) { | ... | @@ -1099,7 +1099,8 @@ function generatePlanItem(id) { |
| 1099 | const createTime = new Date(now.getTime() - Math.random() * 30 * 24 * 60 * 60 * 1000) | 1099 | const createTime = new Date(now.getTime() - Math.random() * 30 * 24 * 60 * 60 * 1000) |
| 1100 | 1100 | ||
| 1101 | // 根据状态决定是否有计划书文件 | 1101 | // 根据状态决定是否有计划书文件 |
| 1102 | - const hasFiles = orderStatus === '7' || orderStatus === '9' // 已生成或已查看才有文件 | 1102 | + // 处理中场景也需要覆盖“文件已可查看但状态尚未流转”的联调需求 |
| 1103 | + const hasFiles = orderStatus === '7' || orderStatus === '9' || (orderStatus === '5' && Math.random() < 0.5) | ||
| 1103 | const proposalFiles = [] | 1104 | const proposalFiles = [] |
| 1104 | 1105 | ||
| 1105 | if (hasFiles) { | 1106 | if (hasFiles) { | ... | ... |
src/utils/proposalView.js
0 → 100644
| 1 | +import { mapOrderStatus } from '@/config/constants/orderStatus' | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 统一获取计划书前端状态。 | ||
| 5 | + * | ||
| 6 | + * 兼容列表页传入的 `status` 和接口原始返回的 `order_status`, | ||
| 7 | + * 让页面显隐判断与查看逻辑都走同一套口径。 | ||
| 8 | + */ | ||
| 9 | +export const getProposalStatus = (proposal = {}) => { | ||
| 10 | + if (proposal.status) { | ||
| 11 | + return proposal.status | ||
| 12 | + } | ||
| 13 | + | ||
| 14 | + return mapOrderStatus(proposal.order_status) | ||
| 15 | +} | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * 统一提取计划书文件列表。 | ||
| 19 | + * | ||
| 20 | + * 列表页使用 `proposalFiles`,接口原始对象使用 `proposal_files`, | ||
| 21 | + * 这里做一次兼容,避免各处重复写字段分支。 | ||
| 22 | + */ | ||
| 23 | +export const getProposalFiles = (proposal = {}) => { | ||
| 24 | + if (Array.isArray(proposal.proposal_files)) { | ||
| 25 | + return proposal.proposal_files | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + if (Array.isArray(proposal.proposalFiles)) { | ||
| 29 | + return proposal.proposalFiles | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + return [] | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +/** | ||
| 36 | + * 统一判断计划书是否可查看。 | ||
| 37 | + * | ||
| 38 | + * 当前业务规则: | ||
| 39 | + * - 待处理不可查看 | ||
| 40 | + * - 处理中/已生成/已查看,只要存在文件就允许查看 | ||
| 41 | + */ | ||
| 42 | +export const canViewProposal = (proposal = {}) => { | ||
| 43 | + const status = getProposalStatus(proposal) | ||
| 44 | + const proposalFiles = getProposalFiles(proposal) | ||
| 45 | + | ||
| 46 | + if (proposalFiles.length === 0) { | ||
| 47 | + return false | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + return status === 'processing' || status === 'generated' || status === 'viewed' | ||
| 51 | +} |
-
Please register or login to post a comment