yyb
23 小时以前 2122e30c435e4287a035162aee4469a8e558f408
src/pages/cooperativeOffice/collaborativeApproval/approve.vue
@@ -25,6 +25,139 @@
          <text class="info-label">申请日期</text>
          <text class="info-value">{{ approvalData.approveTime }}</text>
        </view>
        <!-- approveType=2 请假相关字段 -->
        <template v-if="approvalData.approveType === 2">
          <view class="info-row">
            <text class="info-label">请假开始时间</text>
            <text class="info-value">{{ approvalData.startDate || '-' }}</text>
          </view>
          <view class="info-row">
            <text class="info-label">请假结束时间</text>
            <text class="info-value">{{ approvalData.endDate || '-' }}</text>
          </view>
        </template>
        <!-- approveType=3 出差相关字段 -->
        <view v-if="approvalData.approveType === 3" class="info-row">
          <text class="info-label">出差地点</text>
          <text class="info-value">{{ approvalData.location || '-' }}</text>
        </view>
        <!-- approveType=4 报销相关字段 -->
        <view v-if="approvalData.approveType === 4" class="info-row">
          <text class="info-label">报销金额</text>
          <text class="info-value">{{ approvalData.price ? `¥${approvalData.price}` : '-' }}</text>
        </view>
      </view>
    </view>
    <!-- 入库详情 -->
    <view v-if="approvalData.approveType === 9" class="stockin-info">
      <view class="info-header stockin-header" @click="toggleStockInCollapse">
        <text class="info-title">入库详情</text>
        <text class="collapse-text">{{ stockInCollapsed ? '展开' : '收起' }}</text>
      </view>
      <view v-show="!stockInCollapsed" class="stockin-table">
        <view class="stockin-row">
          <text class="stockin-label">销售合同号</text>
          <text class="stockin-value">{{ stockInOrderInfoData.salesContractNo || '-' }}</text>
          <text class="stockin-label">客户名称</text>
          <text class="stockin-value">{{ stockInOrderInfoData.customerName || stockInOrderInfoData.supplierName || '-' }}</text>
        </view>
        <view class="stockin-row">
          <text class="stockin-label">项目名称</text>
          <text class="stockin-value">{{ stockInOrderInfoData.projectName || '-' }}</text>
          <text class="stockin-label">业务员</text>
          <text class="stockin-value">{{ stockInOrderInfoData.businessUserName || stockInOrderInfoData.recorderName || '-' }}</text>
        </view>
        <view v-for="(product, index) in stockInProducts" :key="product.id || index">
          <view class="stockin-row">
            <text class="stockin-label">产品名称</text>
            <text class="stockin-value">{{ product.speculativeTradingName || product.productCategory || '-' }}</text>
            <text class="stockin-label">规格型号</text>
            <text class="stockin-value">{{ product.specificationModel || '-' }}</text>
          </view>
          <view class="stockin-row">
            <text class="stockin-label">单位</text>
            <text class="stockin-value">{{ product.unit || '-' }}</text>
            <text class="stockin-label">数量</text>
            <text class="stockin-value">{{ product.quantity ?? '-' }}</text>
          </view>
        </view>
        <view v-if="!stockInProducts.length" class="stockin-row">
          <text class="stockin-label">产品名称</text>
          <text class="stockin-value">-</text>
          <text class="stockin-label">规格型号</text>
          <text class="stockin-value">-</text>
        </view>
        <view v-if="!stockInProducts.length" class="stockin-row">
          <text class="stockin-label">单位</text>
          <text class="stockin-value">-</text>
          <text class="stockin-label">数量</text>
          <text class="stockin-value">-</text>
        </view>
      </view>
    </view>
    <!-- 采购详情 -->
    <view v-if="approvalData.approveType === 5" class="purchase-info">
      <view class="info-header purchase-header" @click="togglePurchaseCollapse">
        <text class="info-title">采购详情</text>
        <text class="collapse-text">{{ purchaseCollapsed ? '展开' : '收起' }}</text>
      </view>
      <view v-show="!purchaseCollapsed" class="purchase-table">
        <view class="purchase-row">
          <text class="purchase-label">采购合同号</text>
          <text class="purchase-value">{{ purchaseInfoData.purchaseContractNumber || extractPurchaseContractNumber() || '-' }}</text>
          <text class="purchase-label">供应商名称</text>
          <text class="purchase-value">{{ purchaseInfoData.supplierName || '-' }}</text>
        </view>
        <view class="purchase-row">
          <text class="purchase-label">项目名称</text>
          <text class="purchase-value">{{ purchaseInfoData.projectName || '-' }}</text>
          <text class="purchase-label">销售合同号</text>
          <text class="purchase-value">{{ purchaseInfoData.salesContractNo || '-' }}</text>
        </view>
        <view class="purchase-row">
          <text class="purchase-label">签订日期</text>
          <text class="purchase-value">{{ purchaseInfoData.executionDate || purchaseInfoData.signDate || '-' }}</text>
          <text class="purchase-label">录入日期</text>
          <text class="purchase-value">{{ purchaseInfoData.entryDate || '-' }}</text>
        </view>
        <view class="purchase-row">
          <text class="purchase-label">付款方式</text>
          <text class="purchase-value">{{ purchaseInfoData.paymentMethod || '-' }}</text>
          <text class="purchase-label">合同金额</text>
          <text class="purchase-value amount">{{ formatMoney(purchaseInfoData.contractAmount || purchaseInfoData.totalAmount) }}</text>
        </view>
      </view>
    </view>
    <!-- 产品明细 -->
    <view v-if="approvalData.approveType === 5" class="purchase-info">
      <view class="info-header purchase-header" @click="togglePurchaseProductCollapse">
        <text class="info-title">产品明细</text>
        <text class="collapse-text">{{ purchaseProductCollapsed ? '展开' : '收起' }}</text>
      </view>
      <view v-show="!purchaseProductCollapsed" class="product-table">
        <view class="product-head">
          <text class="product-cell">产品名称</text>
          <text class="product-cell">规格型号</text>
          <text class="product-cell">单位</text>
          <text class="product-cell">数量</text>
          <text class="product-cell">含税单价</text>
          <text class="product-cell">含税总价</text>
        </view>
        <view v-for="(product, idx) in purchaseProducts" :key="product.id || idx" class="product-body-row">
          <text class="product-cell body">{{ product.productCategory || product.speculativeTradingName || '-' }}</text>
          <text class="product-cell body">{{ product.specificationModel || '-' }}</text>
          <text class="product-cell body">{{ product.unit || '-' }}</text>
          <text class="product-cell body">{{ product.quantity ?? '-' }}</text>
          <text class="product-cell body">{{ formatMoney(product.taxInclusiveUnitPrice) }}</text>
          <text class="product-cell body">{{ formatMoney(product.taxInclusiveTotalPrice) }}</text>
        </view>
        <view v-if="!purchaseProducts.length" class="product-empty">暂无产品明细</view>
      </view>
    </view>
@@ -83,30 +216,41 @@
      </view>
      
      <view class="input-content">
        <van-field
        <u-textarea
          v-model="approvalOpinion"
          type="textarea"
          rows="4"
          placeholder="请输入审核意见"
          maxlength="200"
          show-word-limit
          count
        />
      </view>
    </view>
    <!-- 底部操作按钮 -->
    <view v-if="canApprove" class="footer-actions">
      <van-button class="reject-btn" @click="handleReject">驳回</van-button>
      <van-button class="approve-btn" @click="handleApprove">通过</van-button>
      <u-button class="reject-btn" @click="handleReject">驳回</u-button>
      <u-button class="approve-btn" @click="handleApprove">通过</u-button>
    </view>
  </view>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { approveProcessGetInfo, approveProcessDetails, updateApproveNode } from '@/api/collaborativeApproval/approvalProcess'
import {
  approveProcessGetInfo,
  approveProcessDetails,
  updateApproveNode,
  stockInOrderInfo,
  stockInProductList,
  getPurchaseByCode
} from '@/api/collaborativeApproval/approvalProcess'
import useUserStore from '@/store/modules/user'
import { showToast } from 'vant'
const showToast = (message) => {
   uni.showToast({
      title: message,
      icon: 'none'
   })
}
import PageHeader from "@/components/PageHeader.vue";
const userStore = useUserStore()
@@ -114,6 +258,13 @@
const approvalSteps = ref([])
const approvalOpinion = ref('')
const approveId = ref('')
const stockInOrderInfoData = ref({})
const stockInProducts = ref([])
const stockInCollapsed = ref(true)
const purchaseInfoData = ref({})
const purchaseProducts = ref([])
const purchaseCollapsed = ref(true)
const purchaseProductCollapsed = ref(true)
// 从详情接口字段对齐 canApprove:仅当有 isShen 的节点时可审批
const canApprove = computed(() => {
@@ -121,9 +272,7 @@
})
onMounted(() => {
  const pages = getCurrentPages()
  const currentPage = pages[pages.length - 1]
  approveId.value = currentPage.options.approveId
  approveId.value = uni.getStorageSync('approveId')
  if (approveId.value) {
    loadApprovalData()
  }
@@ -133,6 +282,20 @@
  // 基本申请信息
  approveProcessGetInfo({ id: approveId.value }).then(res => {
    approvalData.value = res.data || {}
    if (approvalData.value.approveType === 9) {
      loadStockInInfo()
      purchaseInfoData.value = {}
      purchaseProducts.value = []
    } else if (approvalData.value.approveType === 5) {
      loadPurchaseInfo()
      stockInOrderInfoData.value = {}
      stockInProducts.value = []
    } else {
      stockInOrderInfoData.value = {}
      stockInProducts.value = []
      purchaseInfoData.value = {}
      purchaseProducts.value = []
    }
  })
  // 审批节点详情
  approveProcessDetails(approveId.value).then(res => {
@@ -159,7 +322,79 @@
  })
}
const loadStockInInfo = () => {
  const query = { approveId: approveId.value, id: approveId.value }
  Promise.all([stockInOrderInfo(query), stockInProductList(query)])
    .then(([orderRes, productRes]) => {
      const orderInfo = orderRes?.data?.orderInfo || {}
      stockInOrderInfoData.value = orderInfo
      stockInProducts.value = Array.isArray(productRes?.data) ? productRes.data : []
    })
    .catch(() => {
      stockInOrderInfoData.value = {}
      stockInProducts.value = []
    })
}
const toggleStockInCollapse = () => {
  stockInCollapsed.value = !stockInCollapsed.value
}
const togglePurchaseCollapse = () => {
  purchaseCollapsed.value = !purchaseCollapsed.value
}
const togglePurchaseProductCollapse = () => {
  purchaseProductCollapsed.value = !purchaseProductCollapsed.value
}
const extractPurchaseContractNumber = () => {
  const raw = approvalData.value || {}
  const candidates = [
    raw.purchaseContractNumber,
    raw.purchaseLedgerNo,
    raw.businessNo,
    raw.contractNo
  ]
  const target = candidates.find(item => String(item || '').trim())
  if (target) {
    return String(target).trim()
  }
  const reason = String(raw.approveReason || '')
  const match = reason.match(/[A-Za-z]{1,4}\d{6,}/)
  return match ? match[0] : ''
}
const loadPurchaseInfo = () => {
  const purchaseContractNumber = extractPurchaseContractNumber()
  if (!purchaseContractNumber) {
    purchaseInfoData.value = {}
    purchaseProducts.value = []
    return
  }
  getPurchaseByCode({ purchaseContractNumber })
    .then(res => {
      const payload = res?.data || res || {}
      const orderInfo = payload.orderInfo || payload.purchaseInfo || payload
      const products = payload.productData || payload.productList || payload.products || []
      purchaseInfoData.value = orderInfo || {}
      purchaseProducts.value = Array.isArray(products) ? products : []
    })
    .catch(() => {
      purchaseInfoData.value = {}
      purchaseProducts.value = []
    })
}
const formatMoney = (value) => {
  if (value === null || value === undefined || value === '') return '-'
  const num = Number(value)
  if (Number.isNaN(num)) return `¥${value}`
  return `¥${num.toFixed(2)}`
}
const goBack = () => {
  uni.removeStorageSync('approveId');
  uni.navigateBack()
}
@@ -291,6 +526,143 @@
  margin: 16px;
  border-radius: 12px;
  overflow: hidden;
}
.stockin-info {
  background: #fff;
  margin: 16px;
  border-radius: 12px;
  overflow: hidden;
}
.purchase-info {
  background: #fff;
  margin: 16px;
  border-radius: 12px;
  overflow: hidden;
}
.purchase-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.purchase-table {
  border-top: 1px solid #f0f0f0;
}
.purchase-row {
  display: grid;
  grid-template-columns: 18% 32% 18% 32%;
}
.purchase-label {
  font-size: 11px;
  color: #666;
  padding: 12px 8px;
  background: #f7f8fa;
  border-right: 1px solid #f0f0f0;
  border-bottom: 1px solid #f0f0f0;
}
.purchase-value {
  font-size: 11px;
  color: #333;
  padding: 12px 8px;
  border-right: 1px solid #f0f0f0;
  border-bottom: 1px solid #f0f0f0;
  word-break: break-all;
}
.purchase-value.amount {
  color: #ed8d05;
  font-weight: 600;
}
.purchase-row .purchase-value:last-child,
.purchase-row .purchase-label:last-child {
  border-right: none;
}
.product-table {
  border-top: 1px solid #f0f0f0;
}
.product-head,
.product-body-row {
  display: grid;
  grid-template-columns: 16% 16% 12% 12% 22% 22%;
}
.product-cell {
  font-size: 10px;
  color: #666;
  padding: 10px 6px;
  text-align: center;
  background: #f7f8fa;
  border-right: 1px solid #f0f0f0;
  border-bottom: 1px solid #f0f0f0;
}
.product-cell.body {
  color: #333;
  background: #fff;
}
.product-head .product-cell:last-child,
.product-body-row .product-cell:last-child {
  border-right: none;
}
.product-empty {
  font-size: 11px;
  color: #999;
  text-align: center;
  padding: 20px 0;
}
.stockin-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.collapse-text {
  font-size: 10px;
  color: #666;
}
.stockin-table {
  border-top: 1px solid #f0f0f0;
}
.stockin-row {
  display: grid;
  grid-template-columns: 22% 28% 22% 28%;
}
.stockin-label {
  font-size: 10px;
  color: #666;
  padding: 12px 10px;
  background: #f7f8fa;
  border-right: 1px solid #f0f0f0;
  border-bottom: 1px solid #f0f0f0;
}
.stockin-value {
  font-size: 10px;
  color: #333;
  padding: 12px 10px;
  border-right: 1px solid #f0f0f0;
  border-bottom: 1px solid #f0f0f0;
  word-break: break-all;
}
.stockin-row .stockin-value:last-child,
.stockin-row .stockin-label:last-child {
  border-right: none;
}
.process-header {
@@ -466,18 +838,21 @@
}
.reject-btn {
  width: 120px;
  background: #ff4d4f;
  color: #fff;
  border: none;
}
    width: 120px;
    background: #ff4d4f;
    color: #fff;
  }
.approve-btn {
  width: 120px;
  background: #52c41a;
  color: #fff;
  border: none;
}
  .approve-btn {
    width: 120px;
    background: #52c41a;
    color: #fff;
  }
  /* 适配u-button样式 */
  :deep(.u-button) {
    border-radius: 6px;
  }
@keyframes pulse {
  0% {