| | |
| | | <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> |
| | | |
| | |
| | | </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() |
| | |
| | | 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(() => { |
| | |
| | | }) |
| | | |
| | | onMounted(() => { |
| | | const pages = getCurrentPages() |
| | | const currentPage = pages[pages.length - 1] |
| | | approveId.value = currentPage.options.approveId |
| | | approveId.value = uni.getStorageSync('approveId') |
| | | if (approveId.value) { |
| | | loadApprovalData() |
| | | } |
| | |
| | | // 基本申请信息 |
| | | 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 => { |
| | |
| | | }) |
| | | } |
| | | |
| | | 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() |
| | | } |
| | | |
| | |
| | | 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 { |
| | |
| | | } |
| | | |
| | | .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% { |