You need to sign in or sign up before continuing.
usePlanView.js 5.52 KB
/**
* 计划书查看 Composable
*
* @description 封装计划书查看功能,包括单文件预览、多文件选择、查看状态记录等
* @module composables/usePlanView
* @author Claude Code
* @created 2026-02-14
* @version 1.2.0 - 修复文件预览功能,恢复 useFileOperation 依赖
* @example
* const { viewProposal } = usePlanView()
* await viewProposal({ id: 123, proposal_files: [...] })
*/

import Taro from '@tarojs/taro'
import { ref } from 'vue'
import { mapOrderStatus, getStatusText } from '@/config/constants/orderStatus'
import { viewAPI } from '@/api/plan'
import { useFileOperation } from './useFileOperation'
import { canViewProposal, getProposalFiles } from '@/utils/proposalView'

const actionSheetVisible = ref(false)
const actionSheetMenuItems = ref([])
let currentProposal = null
let currentEmitError = null
let currentOnViewSuccess = null

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 (!canViewProposal(proposal)) {
      // `pending` 与“已有状态但文件为空”是两类不同问题,提示文案需要区分。
      Taro.showToast({
        title: status === 'pending' ? '计划书尚未生成,请稍后' : '暂无可查看的计划书',
        icon: 'none'
      })
      const error = new Error(
        status === 'pending'
          ? `计划书状态不允许查看: ${getStatusText(status)}`
          : 'proposalFiles 为空'
      )
      if (status !== 'pending') {
        console.error('[usePlanView] proposalFiles 为空:', proposal)
      }
      emitError(error)
      return
    }

    const proposalFiles = getProposalFiles(proposal)

    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) => ({
      name: file.file_name || `计划书 ${index + 1}`,
      subname: `文件 ${index + 1}`,
      file,
      proposalId: proposal.id,
      index
    }))

    currentProposal = proposal
    currentEmitError = emitError
    currentOnViewSuccess = onViewSuccess
    actionSheetMenuItems.value = fileList
    actionSheetVisible.value = true
  } catch (error) {
    const errorMessage = error?.message || '查看计划书失败,请重试'
    Taro.showToast({
      title: errorMessage,
      icon: 'none'
    })
    emitError(error)
  }
}

export const closeActionSheet = () => {
  actionSheetVisible.value = false
  actionSheetMenuItems.value = []
  currentProposal = null
  currentEmitError = null
  currentOnViewSuccess = null
}

export const handleChooseFile = async (item) => {
  const selectedFile = item?.file
  if (!selectedFile || !currentProposal) {
    closeActionSheet()
    return
  }

  const previewSuccess = await handleFileView(selectedFile, currentEmitError || (() => {}))
  if (previewSuccess) {
    await markViewed(currentProposal, currentOnViewSuccess)
  }
  closeActionSheet()
}

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
  }

  try {
    // 使用 useFileOperation 的 viewFile 方法,支持多种文件类型
    const { viewFile } = useFileOperation()
    const result = await viewFile({
      downloadUrl: file.file_url,
      fileName: file.file_name
    })
    return result
  } 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,
  actionSheetVisible,
  actionSheetMenuItems,
  handleChooseFile,
  closeActionSheet
})