p-honggang.li
5 天以前 751dfe21d19a22bb130a6a14857470868d7be53a
src/views/approveManage/tradeApproval/approve.vue
@@ -1,93 +1,1226 @@
<template>
  <div class="default-main">
    <!-- 订单信息 + 申请人信息 + 交易内容(合并为同一卡片) -->
    <el-card shadow="never">
      <div class="title">订单信息</div>
      <el-descriptions :column="3" border class="mt10">
      <el-descriptions
        :column="2"
        border
        class="mt10 order-desc fixed-label"
        label-width="180px"
        :label-style="labelStyle"
        :content-style="contentStyle"
      >
        <el-descriptions-item :span="2" class="section-header">
          <template #label>
            <el-icon class="section-icon"><Document /></el-icon>
            <span>订单信息</span>
          </template>
          <template #default></template>
        </el-descriptions-item>
        <el-descriptions-item label="订单编号">{{ detail.orderNo }}</el-descriptions-item>
        <el-descriptions-item label="交易资源类型">{{ detail.resourceTypeName }}</el-descriptions-item>
        <el-descriptions-item label="交易状态">{{ detail.statusName }}</el-descriptions-item>
        <el-descriptions-item label="申请时间">{{ detail.applyTime }}</el-descriptions-item>
        <el-descriptions-item label="单位">{{ detail.unitName }}</el-descriptions-item>
        <el-descriptions-item label="用户名">{{ detail.userName }}</el-descriptions-item>
        <el-descriptions-item label="交易状态">
          <el-tag :type="getStatusType(detail.status)" size="small">{{ detail.statusName }}</el-tag>
        </el-descriptions-item>
      </el-descriptions>
    </el-card>
      <!-- 申请人信息(与订单信息同卡片,复用分隔标题样式) -->
      <el-descriptions
        :column="2"
        border
        class="mt15 order-desc fixed-label"
        label-width="180px"
        :label-style="labelStyle"
        :content-style="contentStyle"
      >
        <el-descriptions-item :span="2" class="section-header">
          <template #label>
            <el-icon class="section-icon"><User /></el-icon>
            <span>申请人信息</span>
          </template>
          <template #default></template>
        </el-descriptions-item>
        <el-descriptions-item label="姓名">{{ detail.userName || '-' }}</el-descriptions-item>
        <el-descriptions-item label="单位">{{ detail.unitName || '-' }}</el-descriptions-item>
        <el-descriptions-item label="部门">{{ detail.userDept || '-' }}</el-descriptions-item>
        <el-descriptions-item label="用户名">{{ detail.userAccount || '-' }}</el-descriptions-item>
      </el-descriptions>
    <el-card class="mt15" shadow="never">
      <div class="title">交易内容</div>
      <el-table :data="detail.items" border class="mt10">
        <el-table-column label="详情" min-width="280">
          <template #default="{ row }">
            <div>
              <div>{{ row.name }}</div>
              <div class="gray">客户对象:{{ row.customerTarget }}</div>
              <div class="gray">并发节点数:{{ row.concurrentNodes }}</div>
            </div>
      <!-- 交易内容(紧随申请人信息,同卡片,复用分隔标题样式) -->
      <el-descriptions
        :column="2"
        border
        class="mt15 order-desc fixed-label"
        label-width="180px"
        :label-style="labelStyle"
        :content-style="contentStyle"
      >
        <el-descriptions-item :span="2" class="section-header">
          <template #label>
            <el-icon class="section-icon"><Goods /></el-icon>
            <span>交易内容</span>
          </template>
          <template #default></template>
        </el-descriptions-item>
        <el-descriptions-item label="产品名称">
          <el-link type="primary" :underline="false">{{ detail.productName }}</el-link>
        </el-descriptions-item>
        <el-descriptions-item label="提供者">{{ detail.supplier }}</el-descriptions-item>
        <el-descriptions-item label="行业领域">{{ detail.industry }}</el-descriptions-item>
        <el-descriptions-item label="单位工程">{{ detail.projectUnit }}</el-descriptions-item>
        <el-descriptions-item label="产品类型">{{ detail.productType || '-' }}</el-descriptions-item>
        <el-descriptions-item label="产品简介">
          <div class="desc-wrap">{{ detail.productDesc }}</div>
        </el-descriptions-item>
      </el-descriptions>
      <!-- 订单详情(移动到交易内容下面,同一卡片内) -->
      <div ref="orderTableWrapRef">
        <el-table
          :data="tableData"
          border
          class="mt10 order-table"
          :header-cell-style="headerCenterStyle"
          :cell-style="bodyCellStyle"
          :row-class-name="getRowClassName"
          :span-method="arraySpanMethod"
        >
        <el-table-column>
          <template #header>
            <el-icon class="header-icon"><List /></el-icon>
            <span>详情</span>
          </template>
          <el-table-column label="" :width="colWidths.detail1">
            <template #default="{ row }">
              <div v-if="!row.isSummary">{{ row.name }}</div>
              <div v-else class="summary-merged">
                <div class="summary-left">
                  共 <span class="count">{{ detail.items.length }}</span> 件
                </div>
                <div class="summary-right">
                  总计:<span class="price">{{ detail.pointTotal }}</span> 积分
                  <span class="ml20 price">{{ detail.cashTotal }}</span> 元
                </div>
              </div>
            </template>
          </el-table-column>
          <el-table-column label="" :width="colWidths.detail2">
            <template #default="{ row }">
              <div v-if="!row.isSummary" class="gray">销售形式:{{ row.saleType || '-' }}</div>
              <div v-if="!row.isSummary" class="gray">账户数量:{{ row.accountCount ?? '-' }}</div>
            </template>
          </el-table-column>
          <el-table-column label="" :width="colWidths.detail3">
            <template #default="{ row }">
              <div v-if="!row.isSummary" class="gray">客户对象:{{ row.customerTarget || '-' }}</div>
              <div v-if="!row.isSummary" class="gray">并发节点数量:{{ row.concurrentNodes ?? '-' }}</div>
            </template>
          </el-table-column>
        </el-table-column>
        <el-table-column label="单价">
          <el-table-column label="" :width="colWidths.price">
            <template #default="{ row }">
              <div v-if="!row.isSummary">{{ formatPrice(row) }}</div>
            </template>
          </el-table-column>
        </el-table-column>
        <el-table-column label="数量">
          <el-table-column label="" :width="colWidths.quantity">
            <template #default="{ row }">
              <div v-if="!row.isSummary">{{ row.quantity }}</div>
            </template>
          </el-table-column>
        </el-table-column>
        <el-table-column label="期限(年)">
          <el-table-column label="" :width="colWidths.period">
            <template #default="{ row }">
              <div v-if="!row.isSummary">{{ formatPeriod(row) }}</div>
            </template>
          </el-table-column>
        </el-table-column>
        </el-table>
      </div>
      <!-- 移除原来的表格底部信息,因为已经移到表格最后一行 -->
       <!-- 交易文件(移动到订单详情下面,同一卡片内) -->
       <div class="file-section" v-if="isAgreementOrder && fileList.length > 0">
         <el-table
           :data="fileList"
           border
           class="file-table"
           :header-cell-style="fileTableHeaderStyle"
           :cell-style="fileTableCellStyle"
         >
           <el-table-column min-width="200">
             <template #header>
               <el-icon class="header-icon"><Document /></el-icon>
               <span>交易文件</span>
             </template>
             <template #default="{ row }">
               <div class="file-name">
                 <el-icon class="file-icon"><Document /></el-icon>
                 <span>{{ row.name }}</span>
               </div>
             </template>
           </el-table-column>
           <el-table-column label="文件大小" prop="size" width="120">
             <template #default="{ row }">
               {{ formatFileSize(row.size) }}
             </template>
           </el-table-column>
                     <el-table-column label="操作" width="180">
            <template #default="{ row }">
              <div class="file-actions">
                <el-button
                  type="text"
                  size="small"
                  class="preview-btn"
                  @click="handlePreview(row)"
                  v-if="isPreviewable(row)"
                  :disabled="!isFileUploaded(row)"
                >
                  预览
                </el-button>
                <el-button
                  type="text"
                  size="small"
                  class="download-btn"
                  @click="handleDownload(row)"
                  :disabled="!isFileUploaded(row)"
                >
                  下载
                </el-button>
              </div>
            </template>
          </el-table-column>
         </el-table>
       </div>
     </el-card>
         <!-- 交易信息备注 -->
   <el-card class="mt15" shadow="never">
     <div class="title">交易信息备注</div>
     <el-table :data="form.items" border class="mt10 remark-table">
       <el-table-column label="详情" prop="name" min-width="200" />
        <el-table-column label="备注" min-width="400">
          <template #default="{ row, $index }">
            <el-input
              v-model="form.items[$index].remark"
              type="textarea"
              :rows="1"
              placeholder="开通账号信息及个数,登录提示等信息"
              maxlength="500"
              show-word-limit
              class="remark-input"
            />
          </template>
        </el-table-column>
        <el-table-column label="单价" prop="priceName" width="140" />
        <el-table-column label="数量" prop="quantity" width="80" />
        <el-table-column label="期限(年)" prop="period" width="120" />
      </el-table>
      <div class="total">
        总计:<span class="price">{{ detail.pointTotal }}</span> 积分
        <span class="ml20 price">{{ detail.cashTotal }}</span> 元
      </div>
            </el-table>
    </el-card>
    <!-- 审批内容 -->
    <el-card class="mt15" shadow="never">
      <div class="title">审批</div>
      <el-form :model="form" label-width="100px" class="mt10" ref="formRef" :rules="rules">
        <el-form-item label="审批意见" prop="remark">
          <el-input v-model="form.remark" type="textarea" :autosize="{ minRows: 4 }" placeholder="请输入审批意见" />
        </el-form-item>
      </el-form>
      <div class="ba-center mt15">
        <el-button @click="goBack">返回</el-button>
        <el-button type="success" @click="approve(true)">审批通过</el-button>
        <el-button type="danger" @click="approve(false)">驳回</el-button>
      <div class="title">审批内容</div>
      <div class="approval-content">
        <div class="approval-form">
          <div class="form-item">
            <label class="required">审批意见:</label>
            <el-input
              v-model="approvalForm.comments"
              type="textarea"
              :rows="4"
              placeholder="请输入审批意见"
              maxlength="500"
              show-word-limit
            />
          </div>
        </div>
        <div class="approval-actions">
          <el-button @click="goBack">返回</el-button>
          <el-button
            type="primary"
            @click="handleApprove"
            :loading="approvalLoading"
          >
            {{ isAgreementOrder ? '审批通过' : '完成授权' }}
          </el-button>
          <el-button
            v-if="isAgreementOrder"
            type="danger"
            @click="handleReject"
            :loading="approvalLoading"
          >
            驳回
          </el-button>
        </div>
      </div>
    </el-card>
  </div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { onMounted, reactive, ref, computed, type CSSProperties } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { fetchApprovalDetail, submitApproval } from '@/api/approvalManage'
import { ElMessage, FormInstance } from 'element-plus'
import { Document, User, Goods, List } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import orderApi from '@/api/orderApi'
import { approveOrder } from '@/api/approvalManage'
import { useUserInfo } from '@/stores/modules/userInfo'
import createAxios from '@/utils/axios'
import productApi from '@/api/productApi'
import sysUserService from "@/api/sysUser";
import workFlowApi from "@/api/workFlowApi";
import {queryUserDetail} from "@/api/userInfo";
const route = useRoute()
const router = useRouter()
const formRef = ref<FormInstance>()
const userStore = useUserInfo()
const detail = reactive<any>({ items: [] })
const form = reactive({ remark: '' })
const rules = { remark: [{ required: true, message: '请输入审批意见', trigger: 'blur' }] }
const form = reactive<any>({ items: [] })
const fileList = ref<any[]>([])
const orderTableWrapRef = ref<HTMLElement | null>(null)
const labelStyle = { width: '180px', maxWidth: '180px' }
const contentStyle = { width: 'calc(50% - 180px)' }
// 是否为协议订单
const isAgreementOrder = ref(false)
// 审批表单数据
const approvalForm = reactive({
  comments: ''
})
// 审批loading状态
const approvalLoading = ref(false)
// 计算表格数据,添加汇总行
const tableData = computed(() => {
  const summaryRow = {
    id: 'summary',
    isSummary: true,
    name: '',
    saleType: '',
    accountCount: 0,
    customerTarget: '',
    concurrentNodes: 0,
    pricePoint: 0,
    priceCash: 0,
    quantity: 0,
    period: 0,
  }
  return [...detail.items, summaryRow]
})
const taskId = computed(() => {
  return String(route.params.taskId || '')
})
// 状态映射(后端中文 -> 前端枚举)
const statusServerToUi: Record<string, string> = {
  '待上传文件': 'WAIT_UPLOAD',
  '待授权': 'WAIT_AUTHORIZE',
  '待交易确认': 'WAIT_CONFIRM',
  '已完成': 'COMPLETED',
  '已评价': 'EVALUATED',
}
const formatDateTime = (val?: string) => (val ? val.replace('T', ' ').slice(0, 19) : '')
const normalizePriceType = (val?: string): 'points' | 'currency' | 'agreement' | 'free' => {
  if (!val) return 'currency'
  const s = String(val)
  if (/(积分|points)/i.test(s)) return 'points'
  if (/(协议|agreement)/i.test(s)) return 'agreement'
  if (/(免费|free)/i.test(s)) return 'free'
  return 'currency'
}
onMounted(async () => {
  const { data } = (await fetchApprovalDetail({ id: route.params.id })) as any
  Object.assign(detail, data || {})
  const orderId = String(route.params.id || '')
  console.log(route.params.taskId)
  if (!orderId) return
  // 获取用户信息
  if (!userStore.getUserId) {
    try {
      const res: any = await queryUserDetail()
      if (res?.code === 200 && res.data) {
        userStore.updateUserDetail(res.data)
      } else {
        ElMessage.error(res?.msg || '无法获取用户信息,请先登录')
        return
      }
    } catch (e) {
      console.error('获取用户详情失败:', e)
      ElMessage.error('获取用户信息失败,请稍后重试')
      return
    }
  }
  try {
    // 并行获取订单详情和协议类型检查
    const [orderRes, agreementRes] = await Promise.all([
      orderApi.getOrderDetail(orderId),
      orderApi.checkAgreementPriceType(orderId)
    ])
    const res = orderRes as any
    const data = res?.data || {}
    // 设置是否为协议订单
    const agreementResult = agreementRes as any
    isAgreementOrder.value = agreementResult?.data === true
    const statusName: string = data.orderStatus || ''
    const uiStatus = statusServerToUi[statusName] || 'INFO'
    // 根据产品id获取产品信息,更新头部展示
    try {
      if (data.productId) {
        const detailRes: any = await productApi.getProductById({ id: data.productId })
        if (detailRes?.code === 200 && detailRes.data) {
          // 用产品详情补全头信息
          data.productName = detailRes.data.name || data.productName
          data.providerName = detailRes.data.submissionUnit || data.providerName
          data.industry = detailRes.data.industrialChainName || data.industry
          data.productDesc = detailRes.data.describe || data.productDesc
          data.projectUnit = detailRes.data.importantAreaName || data.productDesc
          data.productType = detailRes.data.typeName || data.productDesc
        }
      }
    } catch (e) {
      // 忽略产品详情失败,不阻塞订单详情
    }
    // 获取用户信息
    // 获取用户信息
    try {
      const userRes: any = await sysUserService.getUserdetail({ userId: data.userId })
      if (userRes?.code === 200 && userRes.data) {
        // 用产品详情补全头信息
        data.unitName = userRes.data.unitName || data.unitName
        data.userName = userRes.data.name || data.userName
        data.userDept = userRes.data.departmentName || data.userDept
        data.userPhone = userRes.data.phone || data.userPhone
        data.userAccount = userRes.data.username || data.userAccount
      }
    }catch (e){
    }
    // 映射订单详情头部信息
    const head = {
      orderNo: data.orderId,
      resourceTypeName: '软件产品',
      status: uiStatus,
      statusName,
      applyTime: formatDateTime(data.applyTime),
      unitName: data.unitName || '-',
      userName: data.userName || '-',
      userAccount: data.userAccount || '-',
      userDept: data.userDept || '-',
      userPhone: data.userPhone || '-',
      productName: data.productName || '-',
      supplier: data.providerName || '-',
      industry: data.industry || '-',
      projectUnit: data.projectUnit || '-',
      productType: data.productType || '-',
      productDesc: data.productDesc || '-',
    }
    // 明细项映射
    const items: any[] = Array.isArray(data.orderDetails)
      ? data.orderDetails.map((d: any, idx: number) => {
          const pt = normalizePriceType(d.priceType)
          return {
            id: String(d.id ?? idx + 1),
            name: d.suiteName,
            saleType: d.salesForm,
            accountCount: d.accountLimit,
            customerTarget: d.customerType,
            concurrentNodes: d.concurrentNodes,
            pricePoint: pt === 'points' ? Number(d.unitPrice || 0) : 0,
            priceCash: pt === 'currency' ? Number(d.unitPrice || 0) : 0,
            priceProtocol: pt === 'agreement',
            quantity: Number(d.quantity || 0),
            period: Number(d.duration || 0),
            remarks: d.remarks || '', // 新增 remarks 字段
          }
        })
      : []
    // 汇总(简单相加:单价*数量)
    const pointTotalNum = items.reduce((sum, it) => sum + Number(it.pricePoint || 0) * Number(it.quantity || 0), 0)
    const cashTotalNum = items.reduce((sum, it) => sum + Number(it.priceCash || 0) * Number(it.quantity || 0), 0)
    Object.assign(detail, head, {
      items,
      pointTotal: pointTotalNum.toLocaleString(),
      cashTotal: cashTotalNum.toLocaleString(),
      workflowId: data.workflowId || data.processinstId || ''
    })
    // 初始化表单数据
    form.items = (detail.items || []).map((item: any, index: number) => {
      return {
        id: item.id, // 保存order_detail的id
        name: item.name,
        remark: item.remarks || '' // 使用套件信息中的remarks字段
      }
    })
    // 获取订单附件列表(如果有的话)
    if (data.attachments && Array.isArray(data.attachments)) {
      fileList.value = data.attachments.map((file: any) => ({
        name: file.fileName || file.originalName,
        size: file.fileSize || 0,
        uid: file.id,
        status: 'success',
        url: file.fileUrl,
        uploaded: true
      }))
    } else {
      fileList.value = []
    }
  } catch (error) {
    console.error('获取订单详情失败:', error)
    ElMessage.error('获取订单详情失败')
  }
})
const goBack = () => router.back()
const approve = async (pass: boolean) => {
  await formRef.value?.validate()
  const { code } = (await submitApproval({ id: route.params.id, pass, remark: form.remark })) as any
  if (code === 200) {
    ElMessage.success('提交成功')
    router.back()
// 审批通过处理
const handleApprove = async () => {
  if (!approvalForm.comments.trim()) {
    ElMessage.warning('请输入审批意见')
    return
  }
  try {
    approvalLoading.value = true
    await ElMessageBox.confirm('确定要审批通过吗?', '确认操作', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    const orderId = String(route.params.id || '')
    console.log(String(route.params.taskId || ''))
    const taskId1 = taskId.value
    const userId = userStore.getUserId ? Number(userStore.getUserId) : undefined
    const comments = approvalForm.comments.trim()
    if (!orderId || !userId) {
      ElMessage.error('订单ID或用户ID不能为空')
      approvalLoading.value = false
      return
    }
    // 调用审批通过API
    const result = await approveOrder({
      orderId: orderId,
      taskId: taskId1,
      approvalOpinion: comments,
      approverId: userId,
      approverName: userStore.getUserDetail || '管理员',
      approvalType: isAgreementOrder.value ? '审批' : '授权',
      approvalResult: '通过',
      orderDetails: form.items.map((item: any) => ({
        id: item.id,
        remarks: item.remark || '' // 使用表单中的备注
      }))
    })
    if (result && result.code === 200) {
      // 更新交易信息备注(只更新remarks,不更新订单状态)
      // const updateData = {
      //   orderId: orderId,
      //   orderDetails: form.items.map((item: any) => ({
      //     id: item.id,
      //     remarks: item.remark || '' // 使用表单中的备注
      //   }))
      // }
      //
      // await orderApi.updateOrderDetailRemarksOnly(updateData)
      //
      // // 审批通过后,使用新的API接口更新订单状态到下一个状态
      // await orderApi.updateOrderStatusToNext(orderId)
      ElMessage.success('审批通过成功')
      // if(!detail.workflowId.value){
      //   ElMessage.error("工作流id为空不能进行工作流任务提交")
      //   return
      // }
      // // 调用工作流
      // const wfRes: any = await workFlowApi.completeWorkflow({
      //   taskId: String(detail.workflowId.value),
      //   userid: userStore.getUserId,
      //   commponet: '审核通过'
      // })
      // if (wfRes?.code === 200 && wfRes.data?.processinstId) {
      //    console.log('工作流提交成功')
      // }
      router.back()
    } else {
      ElMessage.error(result?.msg || '审批通过失败')
    }
  } catch (error) {
    if (error !== 'cancel') {
      console.error('审批失败:', error)
      ElMessage.error('审批失败')
    }
  } finally {
    approvalLoading.value = false
  }
}
// 驳回处理
const handleReject = async () => {
  if (!approvalForm.comments.trim()) {
    ElMessage.warning('请输入审批意见')
    return
  }
  try {
    approvalLoading.value = true
    await ElMessageBox.confirm('确定要驳回吗?', '确认操作', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    const orderId = String(route.params.id || '')
    const userId = userStore.getUserId ? Number(userStore.getUserId) : undefined
    const comments = approvalForm.comments.trim()
    const taskId1 = String(route.params.taskId || '')
    if (!orderId || !userId) {
      ElMessage.error('订单ID或用户ID不能为空')
      approvalLoading.value = false
      return
    }
    // 调用审批驳回API
    const result = await approveOrder({
      orderId: orderId,
      approvalOpinion: comments,
      approverId: userId,
      taskId: taskId1,
      approverName: userStore.getUserDetail || '管理员',
      approvalType: isAgreementOrder.value ? '审批' : '授权',
      approvalResult: '驳回'
    })
    if (result && result.code === 200) {
      // 驳回订单,更新订单状态到上一个状态
      // await orderApi.updateOrderStatusToPrevious(orderId)
      ElMessage.success('驳回成功')
      // 调用工作流
      // const wfRes: any = await workFlowApi.rejectStartNodeWorkflow({
      //   taskId: String(detail.workflowId.value),
      //   userid: userStore.getUserId,
      //   commponet: '驳回'
      // })
      // if (wfRes?.code === 200 && wfRes.data?.processinstId) {
      //   console.log('工作流驳回成功')
      // }
      router.back()
    } else {
      ElMessage.error(result?.msg || '驳回失败')
    }
  } catch (error) {
    if (error !== 'cancel') {
      console.error('驳回失败:', error)
      ElMessage.error('驳回失败')
    }
  } finally {
    approvalLoading.value = false
  }
}
// 与列表页保持一致的状态类型映射(UI展示用)
const getStatusType = (status: string) => {
  const statusMap: Record<string, 'warning' | 'danger' | 'success' | 'info'> = {
    WAIT_APPROVAL: 'warning',
    WAIT_UPLOAD: 'warning',
    WAIT_CHECK: 'warning',
    WAIT_CONFIRM: 'warning',
    REJECTED: 'danger',
    FINISH: 'success',
  }
  return statusMap[status] || 'info'
}
// 订单详情中"单价"显示:优先显示积分,其次显示货币;格式示例:
// "积分:50,000/套" 或 "货币:7,500/套/年" 或 "免费:/年"
const formatPrice = (row: any) => {
  const point = Number(row.pricePoint || 0)
  const cash = Number(row.priceCash || 0)
  const protocol = Boolean(row.priceProtocol)
  // 免费
  if (!point && !cash) {
    return protocol ? '协议:/年' : '免费:/年'
  }
  if (point) {
    return `积分:${point.toLocaleString()}/套`
  }
  // 仅现金
  return `货币:${cash.toLocaleString()}/套/年`
}
// 期限展示:0 表示"永久",其他显示数字
const formatPeriod = (row: any) => {
  const p = Number(row.period || 0)
  return p === 0 ? '永久' : `${p}`
}
// 表头文字居中,但第一行的"详情"文字靠左对齐
const headerCenterStyle: CSSProperties = {
  textAlign: 'center',
  fontSize: '14px',
  background: '#f3f6fb'
}
// 表体文字大小
const bodyCellStyle: CSSProperties = { fontSize: '12px' }
// 为汇总行添加特殊样式类名
const getRowClassName = ({ row }: { row: any }) => {
  return row.isSummary ? 'summary-row' : ''
}
// 单元格合并方法
const arraySpanMethod = ({ row, column, rowIndex, columnIndex }: any) => {
  if (row.isSummary) {
    // 汇总行:第一列显示合并内容,其他列隐藏
    if (columnIndex === 0) {
      return [1, 6] // 合并1行6列
    } else {
      return [0, 0] // 隐藏其他列
    }
  }
  return [1, 1] // 普通行正常显示
}
// 文件列表表格表头文字居中,但第一列的"交易文件"文字靠左对齐
const fileTableHeaderStyle: CSSProperties = {
  textAlign: 'center',
  fontSize: '14px',
  background: '#f3f6fb'
}
// 文件列表表格表体文字大小
const fileTableCellStyle: CSSProperties = { fontSize: '12px' }
// 文件大小格式化
const formatFileSize = (size: number) => {
  if (!size || size === 0) return '0 Bytes';
  const k = 1024;
  const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
  const i = Math.floor(Math.log(size) / Math.log(k));
  return parseFloat((size / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
// 判断文件是否可预览
const isPreviewable = (file: any) => {
  // 首先检查MIME类型
  const previewableTypes = [
    'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/bmp', 'image/webp',
    'text/plain', 'text/html', 'text/css', 'text/javascript',
    'application/pdf',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
    'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx
  ]
  // 如果MIME类型匹配,直接返回true
  if (previewableTypes.includes(file.type || '')) {
    return true
  }
  // 如果MIME类型为空或不匹配,根据文件扩展名判断
  const fileName = file.name || ''
  const fileExtension = fileName.toLowerCase().split('.').pop()
  const previewableExtensions = [
    'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp',
    'txt', 'html', 'htm', 'css', 'js',
    'pdf',
    'docx', 'xlsx', 'pptx'
  ]
  return previewableExtensions.includes(fileExtension)
}
// 判断文件是否已上传成功
const isFileUploaded = (file: any) => {
  // 文件有url且状态为success表示已上传成功
  return file.url && (file.status === 'success' || file.uploaded)
}
// 文件预览
const handlePreview = async (file: any) => {
  if (!file.url) {
    ElMessage.warning('文件链接不存在')
    return
  }
  // 获取文件扩展名
  const fileName = file.name || ''
  const fileExtension = fileName.toLowerCase().split('.').pop()
  let previewUrl = file.url
  // 如果文件存储在MinIO,优先使用预览URL
  if (file.url.includes('order-attachments')) {
    try {
      // 首先尝试获取预览URL
      const previewResponse = await createAxios({
        url: `/admin/file/preview`,
        method: 'GET',
        params: {
          fileName: file.url
        }
      })
      console.log('预览URL响应:', previewResponse)
      // 检查响应格式
      const responseData = previewResponse as any
      if (responseData && responseData.code === 200 && responseData.data) {
        previewUrl = responseData.data.replaceAll('http://192.168.20.52:9000',import.meta.env.VITE_FILE_PREVIEW_URL)
        console.log('使用预览URL:', previewUrl)
      } else {
        console.log('预览URL获取失败,使用下载方式')
        // 如果预览URL获取失败,回退到下载方式
        const response = await createAxios({
          url: `/admin/file/download`,
          method: 'GET',
          responseType: 'blob',
          params: {
            fileName: file.url,
            originalName: file.name
          }
        })
        // 创建预览URL
        const blob = new Blob([response as any])
        previewUrl = window.URL.createObjectURL(blob)
        console.log('使用Blob URL:', previewUrl)
      }
    } catch (error) {
      console.error('获取文件失败:', error)
      ElMessage.error('获取文件失败,无法预览')
      return
    }
  }
  // 图片文件直接在新窗口打开
  if ((file.type && file.type.startsWith('image/')) ||
      ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileExtension)) {
    console.log('预览图片文件:', previewUrl)
    window.open(previewUrl, '_blank')
    return
  }
  // PDF文件在新窗口打开
  if (file.type === 'application/pdf' || fileExtension === 'pdf') {
    console.log('预览PDF文件:', previewUrl)
    window.open(previewUrl, '_blank')
    return
  }
  // 文本文件在新窗口打开
  if ((file.type && file.type.startsWith('text/')) ||
      ['txt', 'html', 'htm', 'css', 'js'].includes(fileExtension)) {
    console.log('预览文本文件:', previewUrl)
    window.open(previewUrl, '_blank')
    return
  }
  // Office文档和其他文件类型,尝试在新窗口打开
  try {
    console.log('预览其他文件:', previewUrl)
    window.open(previewUrl, '_blank')
  } catch (error) {
    console.error('预览失败:', error)
    ElMessage.error('预览失败,请下载后查看')
  }
}
// 文件下载
const handleDownload = async (file: any) => {
  if (!file.url) {
    ElMessage.warning('文件链接不存在')
    return
  }
  console.log('开始下载文件:', file.name, 'URL:', file.url)
  try {
    // 如果文件存储在MinIO,使用后端直接下载API
    if (file.url.includes('order-attachments')) {
      console.log('使用MinIO下载API')
      // 使用axios通过代理访问后端API
      const response = await createAxios({
        url: `/admin/file/download`,
        method: 'GET',
        responseType: 'blob',
        params: {
          fileName: file.url,
          originalName: file.name
        }
      })
      console.log('下载响应:', response)
      // 创建下载链接
      const blob = new Blob([response as any])
      const downloadUrl = window.URL.createObjectURL(blob)
      console.log('创建下载链接:', downloadUrl)
      const link = document.createElement('a')
      link.href = downloadUrl
      link.download = file.name || 'download'
      link.target = '_blank'
      link.rel = 'noopener noreferrer'
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      // 清理URL对象
      window.URL.revokeObjectURL(downloadUrl)
      ElMessage.success('文件下载成功')
    } else {
      console.log('使用直接URL下载')
      // 其他情况直接使用原URL
      const link = document.createElement('a')
      link.href = file.url
      link.download = file.name || 'download'
      link.target = '_blank'
      link.rel = 'noopener noreferrer'
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      ElMessage.success('开始下载文件')
    }
  } catch (error) {
    console.error('下载失败:', error)
    ElMessage.error('下载失败,请重试')
  }
}
// 症结与修复说明:
// 1) Element Plus 的 el-table 子列 width 百分比是相对于表格容器的像素宽度计算,但只有在表格容器有明确宽度时才生效。
// 2) 在父列/多级表头下,子列 width 为百分比时,更稳定的做法是将其计算为像素值绑定给子列。
// 3) 因此我们读取表格外层容器宽度,按比例计算像素宽度,避免出现百分比与 table-layout 导致的错位与拉伸。
const colWidths = computed(() => {
  const containerWidth = orderTableWrapRef.value?.clientWidth || 0
  // 百分比分别为:详情 20% x 3,单价 15%,数量 10%,期限 15% => 合计 100%
  return {
    detail1: Math.floor(containerWidth * 0.2),
    detail2: Math.floor(containerWidth * 0.2),
    detail3: Math.floor(containerWidth * 0.2),
    price: Math.floor(containerWidth * 0.15),
    quantity: Math.floor(containerWidth * 0.1),
    period: Math.floor(containerWidth * 0.15),
  }
})
</script>
<style scoped lang="scss">
.title { font-weight: 600; }
.sub-title { font-weight: 600; margin: 10px 0; }
.mt10 { margin-top: 10px; }
.mt15 { margin-top: 15px; }
.gray { color: #909399; font-size: 12px; }
.total { text-align: right; margin-top: 10px; }
.price { color: #f56c6c; font-weight: 600; }
.ml20 { margin-left: 20px; }
.item-block { padding: 10px; border: 1px solid #ebeef5; border-radius: 4px; margin-bottom: 10px; }
/* 统一表格内容文字大小 */
.order-table :deep(.el-table__body),
.order-table :deep(.el-table__header) {
  font-size: 12px;
}
/* 表头第一行背景色(与交易内容标题行保持一致) */
.order-table :deep(.el-table__header-wrapper thead tr:first-child > th) {
  background: #f3f6fb !important;
  font-size: 14px !important;
}
/* 表头第二行高度与内边距 */
.order-table :deep(.el-table__header-wrapper thead tr:nth-child(2) > th) {
  height: 5px !important;
  padding-top: 0 !important;
  padding-bottom: 0 !important;
  padding-left: 0 !important;
  padding-right: 0 !important;
}
/* 强制表格列固定布局,避免内容影响列宽 */
.order-table :deep(table) {
  table-layout: fixed;
  width: 100% !important;
}
/* 汇总行样式 */
.summary-row {
  font-weight: 600;
  color: #f56c6c;
}
.summary-total {
  text-align: right;
  font-weight: 600;
}
.summary-merged {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
}
.summary-left {
  text-align: left;
}
.summary-left .count {
  color: #f56c6c;
  font-weight: 600;
}
.summary-right {
  text-align: right;
}
.order-table :deep(.summary-row) {
  background-color: #fafafa;
}
.order-table :deep(.summary-row td) {
  border-top: 2px solid #e4e7ed;
}
/* 表头第一行"详情"文字靠左对齐 */
.order-table :deep(.el-table__header-wrapper thead tr:first-child th:first-child) {
  text-align: left !important;
}
/* 表头图标样式 */
.header-icon {
  margin-right: 6px;
  color: #409eff;
  vertical-align: middle;
}
/* 分隔标题风格 */
.section-header {
  background: #f3f6fb;
  font-weight: 600;
  --el-border-color: #e4e7ed;
}
.section-header :deep(.el-descriptions__label) {
  background: #f3f6fb;
  border-right: none !important;
  width: 100%;
}
.section-header :deep(.el-descriptions__cell) {
  background: #f3f6fb;
}
.section-header :deep(.el-descriptions__content) {
  display: none !important;
  padding: 0 !important;
  border: 0 !important;
}
.section-icon {
  margin-right: 6px;
  color: #409eff;
}
/* 调整描述组件边框以便标题行颜色覆盖中间分隔线 */
:deep(.el-descriptions--border .el-descriptions__body .el-descriptions__table .el-descriptions__cell) {
  border-right: 1px solid var(--el-border-color);
}
.section-header :deep(.el-descriptions__cell) {
  border-right-color: transparent !important;
}
/* 强制 Element Plus 描述项的 label 宽度遵循 label-width(避免内容撑开) */
/* 兜底:即使内联样式被覆盖,也用 important 强制固定 */
.fixed-label :deep(.el-descriptions__label) {
  width: 180px !important;
  max-width: 180px !important;
}
.fixed-label :deep(.el-descriptions__content) {
  width: calc(50% - 180px) !important;
}
/* 强化第一行分隔标题的背景与边框覆盖 */
.order-desc :deep(.el-descriptions__table tr:first-child .el-descriptions__cell),
.order-desc :deep(.el-descriptions__table tr:first-child .el-descriptions__label),
.order-desc :deep(.el-descriptions__table tr:first-child .el-descriptions__content) {
  background: #eef3fb !important;
  border-top-color: transparent !important;
  border-bottom-color: #dcdfe6 !important;
}
.order-desc :deep(.el-descriptions__table tr:first-child .el-descriptions__label) {
  border-right-color: transparent !important;
}
/* 统一两个描述表格的列对齐(标签列固定宽度,内容列等分剩余宽度) */
.order-desc :deep(.el-descriptions__table) {
  table-layout: fixed;
  width: 100%;
}
/* 使用类选择器而非 nth-child,提升稳定性,确保每行两列严格对齐 */
.order-desc :deep(.el-descriptions__table tr:not(:first-child) .el-descriptions__label) {
  width: 180px !important;
  max-width: 180px !important;
  box-sizing: border-box;
}
.order-desc :deep(.el-descriptions__table tr:not(:first-child) .el-descriptions__content) {
  width: calc(50% - 180px) !important;
}
.desc-wrap {
  white-space: pre-wrap;
  line-height: 22px;
}
/* 文件列表表格样式 */
.file-section {
  margin-top: 15px;
  .file-title {
    font-weight: 600;
    margin-bottom: 10px;
  }
  .file-table {
    width: 100%;
    .file-name {
      display: flex;
      align-items: center;
      .file-icon {
        margin-right: 8px;
        color: #409eff;
      }
    }
    .preview-btn {
      color: #409eff;
      &:hover {
        text-decoration: underline;
      }
    }
  }
}
/* 文件表格表头第一列"交易文件"文字靠左对齐 */
.file-table :deep(.el-table__header-wrapper thead tr th:first-child) {
  text-align: left !important;
}
/* 文件操作按钮样式 */
.file-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: center;
  .preview-btn {
    color: #409eff;
    &:hover {
      text-decoration: underline;
    }
    &:disabled {
      color: #c0c4cc;
      cursor: not-allowed;
    }
  }
  .download-btn {
    color: #67c23a;
    &:hover {
      text-decoration: underline;
    }
    &:disabled {
      color: #c0c4cc;
      cursor: not-allowed;
    }
  }
}
/* 交易信息备注表格样式 */
.remark-table {
  width: 100%;
  .remark-input {
    width: 100%;
    :deep(.el-textarea__inner) {
      border: none;
      box-shadow: none;
      background: transparent;
      resize: none;
      padding: 8px 0;
      &:focus {
        border: 1px solid #409eff;
        box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
        border-radius: 4px;
        background: #fff;
        padding: 8px 12px;
      }
    }
    :deep(.el-input__count) {
      background: transparent;
      bottom: 2px;
      right: 8px;
    }
  }
}
/* 审批内容样式 */
.approval-content {
  .approval-form {
    .form-item {
      display: flex;
      align-items: flex-start;
      margin-bottom: 20px;
      label {
        width: 120px;
        line-height: 32px;
        margin-right: 10px;
        font-weight: 500;
        &.required::before {
          content: '*';
          color: #f56c6c;
          margin-right: 4px;
        }
      }
      .el-textarea {
        flex: 1;
      }
    }
  }
  .approval-actions {
    display: flex;
    justify-content: center;
    gap: 15px;
    margin-top: 30px;
    .el-button {
      min-width: 100px;
    }
  }
}
</style>