usePlanView.js 5.32 KB
/**
* 计划书查看 Composable
*
* @description 封装计划书查看功能,包括单文件预览、多文件选择、查看状态记录等
* @module composables/usePlanView
* @author Claude Code
* @created 2026-02-14
* @version 1.1.0 - 增强错误处理,添加完整日志
* @example
* const { viewProposal } = usePlanView()
* await viewProposal({ id: 123, proposal_files: [...] })
*/

import { ref } from 'vue'
import Taro from '@tarojs/taro'
import { mapOrderStatus, getStatusText } from '@/config/constants/orderStatus'
import { viewAPI } from '@/api/plan'

export const viewProposal = async (proposal, callbacks = {}) => {
  const { beforeView, onViewSuccess, onViewError, onError } = callbacks
  const emitError = (error) => {
    onViewError?.(error)
    onError?.(error)
  }

  try {
    if (!proposal || typeof proposal !== 'object') {
      const error = new Error('计划书数据格式错误')
      console.error('[usePlanView] proposal 参数无效:', error)
      emitError(error)
      return
    }

    if (!proposal.id && proposal.id !== 0) {
      Taro.showToast({
        title: '计划书 ID 缺失',
        icon: 'none'
      })
      emitError(new Error('计划书 ID 缺失'))
      return
    }

    const status = proposal.status || mapOrderStatus(proposal.order_status)
    if (status === 'pending' || status === 'processing') {
      Taro.showToast({
        title: '计划书尚未生成,请稍后',
        icon: 'none'
      })
      emitError(new Error(`计划书状态不允许查看: ${getStatusText(status)}`))
      return
    }

    const proposalFiles = proposal.proposal_files || proposal.proposalFiles || []

    if (!proposalFiles || proposalFiles.length === 0) {
      Taro.showToast({
        title: '暂无可查看的计划书',
        icon: 'none'
      })
      console.error('[usePlanView] proposalFiles 为空:', proposal)
      emitError(new Error('proposalFiles 为空'))
      return
    }

    if (beforeView) {
      try {
        const shouldContinue = await beforeView(proposal)
        if (shouldContinue === false) {
          console.log('[usePlanView] 用户取消查看')
          return
        }
      } catch (error) {
        console.error('[usePlanView] beforeView 回调失败:', error)
      }
    }

    if (proposalFiles.length === 1) {
      const previewSuccess = await handleFileView(proposalFiles[0], emitError)
      if (previewSuccess) {
        await markViewed(proposal, onViewSuccess)
      }
      return
    }

    const fileList = proposalFiles.map((file, index) => ({
      text: file.file_name || `计划书 ${index + 1}`,
      file
    }))

    Taro.showActionSheet({
      itemList: fileList.map(item => item.text),
      success: async (res) => {
        if (res.tapIndex === undefined || res.tapIndex === null) return

        const selectedFile = fileList[res.tapIndex]?.file
        if (!selectedFile) return

        const previewSuccess = await handleFileView(selectedFile, emitError)
        if (previewSuccess) {
          await markViewed(proposal, onViewSuccess)
        }
      }
    })
  } catch (error) {
    const errorMessage = error?.message || '查看计划书失败,请重试'
    Taro.showToast({
      title: errorMessage,
      icon: 'none'
    })
    emitError(error)
  }
}

const handleFileView = async (file, emitError) => {
  if (!file?.file_url) {
    const errorMsg = '文件链接无效'
    console.error('[usePlanView] 文件链接无效:', file)
    Taro.showToast({
      title: errorMsg,
      icon: 'none'
    })
    emitError(new Error(errorMsg))
    return false
  }

  if (!file?.file_name) {
    const errorMsg = '文件名缺失'
    console.error('[usePlanView] 文件名缺失:', file)
    Taro.showToast({
      title: errorMsg,
      icon: 'none'
    })
    emitError(new Error(errorMsg))
    return false
  }

  const hasShownOfficeTip = ref(false)
  const isOffice = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx']

  try {
    if (file.file_type && isOffice.includes(file.file_type)) {
      if (!hasShownOfficeTip.value) {
        const res = await Taro.showModal({
          title: '提示',
          content: 'Office 文档建议使用电脑端查看',
          confirmText: '继续',
          cancelText: '取消'
        })

        if (res.confirm) {
          hasShownOfficeTip.value = true
        } else {
          console.log('[usePlanView] 用户取消 Office 文档预览')
          return false
        }
      }
    }

    const previewImage = Taro.previewImage
    if (typeof previewImage !== 'function') {
      return true
    }

    await previewImage({
      current: file.file_url,
      urls: [file.file_url]
    })

    return true
  } catch (error) {
    console.error('[usePlanView] 文件预览失败:', error)

    const errorMsg = error?.message || '文件打开失败'
    Taro.showToast({
      title: errorMsg,
      icon: 'none'
    })
    emitError(error)
    return false
  }
}

const markViewed = async (proposal, onViewSuccess) => {
  if (!proposal?.id && proposal?.id !== 0) return

  try {
    const viewRes = await viewAPI({ i: proposal.id })
    if (viewRes.code === 1) {
      Taro.showToast({
        title: '已标记为查看',
        icon: 'success'
      })
      onViewSuccess?.(proposal.id)
    }
  } catch (error) {
    console.error('[usePlanView] 标记查看状态失败:', error)
  }
}

export const usePlanView = () => ({
  viewProposal
})