Industrial-image-management.../src/views/system-resource/device-management/procurement/index.vue

1471 lines
42 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="equipment-procurement-container">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-content">
<div class="header-left">
<div class="page-title">
<IconDesktop style="font-size: 24px; margin-right: 12px; color: var(--color-primary);" />
<h1>设备采购管理</h1>
</div>
<div class="page-description">
管理企业设备采购流程包括采购申请订单管理供应商管理等
</div>
</div>
<div class="header-right">
<a-space>
<ProcurementSearch
:loading="loading"
@search="handleSearch"
@reset="handleReset"
/>
<a-button type="primary" @click="handleAdd" size="large">
<template #icon>
<IconPlus />
</template>
新增采购
</a-button>
</a-space>
</div>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-container">
<a-row :gutter="16">
<a-col :span="6">
<a-card class="stat-card" :bordered="false">
<div class="stat-content">
<div class="stat-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
<IconDesktop />
</div>
<div class="stat-info">
<div class="stat-number">{{ pagination.total }}</div>
<div class="stat-label">采购总数</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card" :bordered="false">
<div class="stat-content">
<div class="stat-icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
<IconClockCircle />
</div>
<div class="stat-info">
<div class="stat-number">{{ getPendingCount() }}</div>
<div class="stat-label">待处理</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card" :bordered="false">
<div class="stat-content">
<div class="stat-icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
<IconCheckCircle />
</div>
<div class="stat-info">
<div class="stat-number">{{ getCompletedCount() }}</div>
<div class="stat-label">已完成</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="stat-card" :bordered="false">
<div class="stat-content">
<div class="stat-icon" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">
<IconApps />
</div>
<div class="stat-info">
<div class="stat-number">¥{{ getTotalAmount() }}</div>
<div class="stat-label">采购总额</div>
</div>
</div>
</a-card>
</a-col>
</a-row>
</div>
<!-- 数据表格 -->
<a-card class="table-card" :bordered="false">
<template #title>
<div class="card-title">
<span>采购记录</span>
<div class="table-actions">
<a-space>
<a-button type="text" @click="refreshData">
<template #icon>
<IconRefresh />
</template>
刷新
</a-button>
<a-button type="text" @click="handleExport">
<template #icon>
<IconDownload />
</template>
导出
</a-button>
</a-space>
</div>
</div>
</template>
<a-table
:columns="columns"
:data="tableData"
:loading="loading"
:pagination="false"
:row-selection="rowSelection"
row-key="equipmentId"
:scroll="{ x: 'max-content', y: 400 }"
@change="handleTableChange"
>
<!-- 设备状态 -->
<template #equipmentStatus="{ record }">
<a-tag :color="getEquipmentStatusColor(record.equipmentStatus)">
{{ getEquipmentStatusText(record.equipmentStatus) }}
</a-tag>
</template>
<!-- 采购状态 -->
<template #procurementStatus="{ record }">
<a-tag
v-if="record.procurementStatus && record.procurementStatus !== 'NOT_STARTED'"
:color="getProcurementStatusColor(record.procurementStatus)"
>
{{ getProcurementStatusText(record.procurementStatus) }}
</a-tag>
<a-tag v-else color="gray">未开始</a-tag>
</template>
<!-- 位置状态 -->
<template #locationStatus="{ record }">
<a-tag :color="getLocationStatusColor(record.locationStatus)">
{{ getLocationStatusText(record.locationStatus) }}
</a-tag>
</template>
<!-- 健康状态 -->
<template #healthStatus="{ record }">
<a-tag :color="getHealthStatusColor(record.healthStatus)">
{{ getHealthStatusText(record.healthStatus) }}
</a-tag>
</template>
<!-- 采购价格 -->
<template #purchasePrice="{ record }">
<span v-if="record.purchasePrice" class="price-text">
¥{{ formatPrice(record.purchasePrice) }}
</span>
<span v-else class="no-data">-</span>
</template>
<!-- 当前净值 -->
<template #currentNetValue="{ record }">
<span v-if="record.currentNetValue" class="price-text">
¥{{ formatPrice(record.currentNetValue) }}
</span>
<span v-else class="no-data">-</span>
</template>
<!-- 创建时间 -->
<template #createTime="{ record }">
<span v-if="record.createTime" class="time-text">
{{ formatDateTime(record.createTime) }}
</span>
<span v-else class="no-data">-</span>
</template>
<!-- 收货状态 -->
<template #receiptStatus="{ record }">
<a-tag :color="getReceiptStatusColor(record.receiptStatus)">
{{ getReceiptStatusText(record.receiptStatus) }}
</a-tag>
</template>
<!-- 支付状态 -->
<template #paymentStatus="{ record }">
<a-tag :color="getPaymentStatusColor(record.paymentStatus)">
{{ getPaymentStatusText(record.paymentStatus) }}
</a-tag>
</template>
<!-- 操作 -->
<template #action="{ record }">
<a-space>
<a-button type="text" size="small" @click="handleView(record)">
查看
</a-button>
<a-button type="text" size="small" @click="handleEdit(record)">
编辑
</a-button>
<!-- 1. 采购相关操作 -->
<!-- 申请采购按钮 - 只在特定状态下显示 -->
<a-button
v-if="canApplyProcurement(record)"
type="primary"
size="small"
@click="handleApplyProcurement(record)"
>
申请采购
</a-button>
<!-- 2. 合同发票管理相关操作 -->
<!-- 管理合同发票按钮 - 在采购审批通过后显示 -->
<a-button
v-if="canManageContractInvoice(record)"
type="outline"
size="small"
@click="handleManageContractInvoice(record)"
>
管理合同发票
</a-button>
<!-- 3. 支付相关操作 -->
<!-- 申请付款按钮 -->
<a-button
v-if="canMakePayment(record)"
type="outline"
size="small"
@click="handleMakePayment(record)"
>
申请付款
</a-button>
<a-button
v-if="record.paymentStatus === 'PAID'"
type="text"
size="small"
@click="handleViewPayment(record)"
>
查看支付详情
</a-button>
<!-- 4. 收货相关操作 -->
<!-- 收货操作按钮 -->
<a-button
v-if="canReceiveGoods(record)"
type="primary"
size="small"
@click="handleReceiveGoods(record)"
>
确认收货
</a-button>
<a-button
v-if="record.receiptStatus === 'RECEIVED'"
type="text"
size="small"
@click="handleViewReceipt(record)"
>
查看收货
</a-button>
<!-- 状态标签 - 按照要求的顺序排列 -->
<!-- 1. 采购状态 -->
<a-tag
v-if="record.procurementStatus && record.procurementStatus !== 'NOT_STARTED'"
:color="getProcurementStatusColor(record.procurementStatus)"
>
{{ getProcurementStatusText(record.procurementStatus) }}
</a-tag>
<!-- 2. 合同发票管理状态 -->
<a-tag
v-if="hasContractInvoiceInfo(record)"
color="green"
>
已完善
</a-tag>
<a-tag
v-else-if="record.procurementStatus === 'APPROVED'"
color="orange"
>
待完善
</a-tag>
<a-tag
v-else
color="gray"
>
未开始
</a-tag>
<!-- 3. 支付状态 -->
<a-tag
v-if="record.paymentStatus && record.paymentStatus !== 'NOT_PAID'"
:color="getPaymentStatusColor(record.paymentStatus)"
>
{{ getPaymentStatusText(record.paymentStatus) }}
</a-tag>
<a-tag v-else color="gray">未支付</a-tag>
<!-- 4. 收货状态 -->
<a-tag
v-if="record.receiptStatus && record.receiptStatus !== 'NOT_RECEIVED'"
:color="getReceiptStatusColor(record.receiptStatus)"
>
{{ getReceiptStatusText(record.receiptStatus) }}
</a-tag>
<a-tag v-else color="gray">未收货</a-tag>
<!-- 删除按钮 -->
<a-popconfirm
content="确定要删除这条记录吗?"
@ok="handleDelete(record)"
>
<a-button type="text" size="small" status="danger">
删除
</a-button>
</a-popconfirm>
</a-space>
</template>
</a-table>
<!-- 分页器 - 固定在表格下方 -->
<div class="pagination-container">
<a-pagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:show-total="true"
:show-jumper="true"
:show-page-size="true"
:page-size-options="[10, 20, 50, 100]"
:hide-on-single-page="false"
size="default"
@change="handlePageChange"
@page-size-change="handlePageSizeChange"
/>
</div>
</a-card>
<!-- 新增/编辑弹窗 -->
<ProcurementModal
v-model:visible="modalVisible"
:procurement-data="currentProcurement"
:mode="modalMode"
@success="handleModalSuccess"
/>
<!-- 采购申请弹窗 -->
<ProcurementApplicationModal
v-model:visible="applicationModalVisible"
:equipment-data="currentApplicationData"
@success="handleApplicationSuccess"
/>
<!-- 收货详情弹窗 -->
<ReceiptDetailModal
v-model:visible="receiptDetailModalVisible"
:receipt-data="currentReceiptData"
/>
<!-- 支付详情弹窗 -->
<PaymentDetailModal
v-model:visible="paymentDetailModalVisible"
:payment-data="currentPaymentData"
/>
<!-- 收货弹窗 -->
<ReceiptModal
v-model:visible="receiptModalVisible"
:equipment-data="currentReceiptData"
@success="handleReceiptSuccess"
/>
<!-- 支付弹窗 -->
<PaymentModal
v-model:visible="paymentModalVisible"
:equipment-data="currentPaymentData"
@success="handlePaymentSuccess"
/>
<!-- 合同发票管理弹窗 -->
<ContractInvoiceModal
v-model:visible="contractInvoiceModalVisible"
:equipment-data="currentContractInvoiceData"
@success="handleContractInvoiceSuccess"
/>
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref, watch } from 'vue'
import { Modal } from '@arco-design/web-vue'
import {
IconCheckCircle,
IconClockCircle,
IconDownload,
IconPlus,
IconRefresh,
IconDesktop,
IconApps
} from '@arco-design/web-vue/es/icon'
import message from '@arco-design/web-vue/es/message'
import ProcurementModal from './components/ProcurementModal.vue'
import ProcurementSearch from './components/ProcurementSearch.vue'
import ProcurementApplicationModal from './components/ProcurementApplicationModal.vue'
import ReceiptDetailModal from './components/ReceiptDetailModal.vue'
import PaymentDetailModal from './components/PaymentDetailModal.vue'
import ReceiptModal from './components/ReceiptModal.vue'
import PaymentModal from './components/PaymentModal.vue'
import ContractInvoiceModal from './components/ContractInvoiceModal.vue'
import { equipmentProcurementApi } from '@/apis/equipment/procurement'
import { equipmentApprovalApi } from '@/apis/equipment/approval'
import type { EquipmentListReq, EquipmentResp } from '@/apis/equipment/type'
defineOptions({ name: 'EquipmentProcurement' })
// 当前搜索参数
const currentSearchParams = ref<EquipmentListReq>({
minPrice: undefined,
maxPrice: undefined
})
// 表格数据
const tableData = ref<EquipmentResp[]>([])
const loading = ref(false)
// 分页配置
const pagination = reactive<any>({
current: 1,
pageSize: 10,
total: 0,
showPageSize: true,
showJumper: true,
showTotal: (total: number) => `共 ${total} 条记录`,
pageSizeOptions: [10, 20, 50, 100]
})
// 弹窗控制
const modalVisible = ref(false)
const currentProcurement = ref<EquipmentResp | null>(null)
const modalMode = ref<'add' | 'edit' | 'view'>('add')
// 采购申请弹窗控制
const applicationModalVisible = ref(false)
const currentApplicationData = ref<EquipmentResp | null>(null)
// 收货详情弹窗控制
const receiptDetailModalVisible = ref(false)
const currentReceiptData = ref<EquipmentResp | null>(null)
// 支付详情弹窗控制
const paymentDetailModalVisible = ref(false)
const currentPaymentData = ref<EquipmentResp | null>(null)
// 收货弹窗控制
const receiptModalVisible = ref(false)
const paymentModalVisible = ref(false)
// 合同发票管理弹窗控制
const contractInvoiceModalVisible = ref(false)
const currentContractInvoiceData = ref<EquipmentResp | null>(null)
// 表格选择
const selectedRowKeys = ref<string[]>([])
const rowSelection = reactive({
type: 'checkbox' as const,
showCheckedAll: true,
selectedRowKeys,
onChange: (keys: string[]) => {
selectedRowKeys.value = keys
},
})
// 表格列配置
const columns = [
{
title: '资产编号',
dataIndex: 'assetCode',
key: 'assetCode',
width: 120,
},
{
title: '设备名称',
dataIndex: 'equipmentName',
key: 'equipmentName',
width: 150,
},
{
title: '设备类型',
dataIndex: 'equipmentType',
key: 'equipmentType',
width: 120,
},
{
title: '设备型号',
dataIndex: 'equipmentModel',
key: 'equipmentModel',
width: 120,
},
{
title: '品牌',
dataIndex: 'brand',
key: 'brand',
width: 100,
},
{
title: '供应商',
dataIndex: 'supplierName',
key: 'supplierName',
width: 150,
},
{
title: '采购订单',
dataIndex: 'purchaseOrder',
key: 'purchaseOrder',
width: 120,
},
{
title: '采购价格',
dataIndex: 'purchasePrice',
key: 'purchasePrice',
slotName: 'purchasePrice',
width: 120,
},
{
title: '当前净值',
dataIndex: 'currentNetValue',
key: 'currentNetValue',
slotName: 'currentNetValue',
width: 120,
},
{
title: '设备状态',
dataIndex: 'equipmentStatus',
key: 'equipmentStatus',
slotName: 'equipmentStatus',
width: 120,
},
{
title: '采购状态',
dataIndex: 'procurementStatus',
key: 'procurementStatus',
slotName: 'procurementStatus',
width: 120,
fixed: false,
},
{
title: '位置状态',
dataIndex: 'locationStatus',
key: 'locationStatus',
slotName: 'locationStatus',
width: 120,
},
{
title: '健康状态',
dataIndex: 'healthStatus',
key: 'healthStatus',
slotName: 'healthStatus',
width: 100,
},
{
title: '负责人',
dataIndex: 'responsiblePerson',
key: 'responsiblePerson',
width: 100,
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
slotName: 'createTime',
width: 160,
},
{
title: '收货状态',
dataIndex: 'receiptStatus',
key: 'receiptStatus',
slotName: 'receiptStatus',
width: 120,
},
{
title: '支付状态',
dataIndex: 'paymentStatus',
key: 'paymentStatus',
slotName: 'paymentStatus',
width: 120,
},
{
title: '操作',
key: 'action',
slotName: 'action',
width: 200,
fixed: 'right',
},
]
// 获取设备状态颜色
const getEquipmentStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
normal: 'green',
repair: 'orange',
maintain: 'blue',
scrap: 'red',
}
return colorMap[status] || 'blue'
}
// 获取设备状态文本
const getEquipmentStatusText = (status: string) => {
const textMap: Record<string, string> = {
normal: '正常',
repair: '维修中',
maintain: '保养中',
scrap: '报废',
}
return textMap[status] || '未知'
}
// 获取采购状态颜色
const getProcurementStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
NOT_STARTED: 'gray',
PENDING_APPROVAL: 'blue',
APPROVED: 'green',
REJECTED: 'red',
COMPLETED: 'purple',
}
return colorMap[status] || 'blue'
}
// 获取采购状态文本
const getProcurementStatusText = (status: string) => {
const textMap: Record<string, string> = {
NOT_STARTED: '未开始',
PENDING_APPROVAL: '待审批',
APPROVED: '已通过',
REJECTED: '已拒绝',
COMPLETED: '已完成',
}
return textMap[status] || '未知'
}
// 获取位置状态颜色
const getLocationStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
not_in_stock: 'gray',
in_stock: 'blue',
allocated: 'green',
repair: 'orange',
scrap: 'red',
scrapped: 'red',
borrowed: 'purple',
lost: 'gray',
}
return colorMap[status] || 'blue'
}
// 获取位置状态文本
const getLocationStatusText = (status: string) => {
const textMap: Record<string, string> = {
not_in_stock: '未入库',
in_stock: '库存中',
allocated: '已分配',
repair: '维修中',
scrap: '待报废',
scrapped: '已报废',
borrowed: '外借中',
lost: '丢失',
}
return textMap[status] || '未知'
}
// 获取健康状态颜色
const getHealthStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
excellent: 'green',
good: 'blue',
normal: 'orange',
poor: 'red',
critical: 'red',
}
return colorMap[status] || 'blue'
}
// 获取健康状态文本
const getHealthStatusText = (status: string) => {
const textMap: Record<string, string> = {
excellent: '优秀',
good: '良好',
normal: '一般',
poor: '较差',
critical: '危险',
}
return textMap[status] || '未知'
}
// 获取收货状态颜色
const getReceiptStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
NOT_RECEIVED: 'gray',
RECEIVED: 'green',
PARTIALLY_RECEIVED: 'orange',
REJECTED: 'red',
}
return colorMap[status] || 'blue'
}
// 获取收货状态文本
const getReceiptStatusText = (status: string) => {
const textMap: Record<string, string> = {
NOT_RECEIVED: '未收货',
RECEIVED: '已收货',
PARTIALLY_RECEIVED: '部分收货',
REJECTED: '已拒收',
}
return textMap[status] || '未知'
}
// 获取支付状态颜色
const getPaymentStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
NOT_PAID: 'gray',
PAID: 'green',
PARTIALLY_PAID: 'orange',
REJECTED: 'red',
}
return colorMap[status] || 'blue'
}
// 获取支付状态文本
const getPaymentStatusText = (status: string) => {
const textMap: Record<string, string> = {
NOT_PAID: '未支付',
PAID: '已支付',
PARTIALLY_PAID: '部分支付',
REJECTED: '已拒付',
}
return textMap[status] || '未知'
}
// 格式化价格
const formatPrice = (price: number) => {
return price.toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})
}
// 格式化日期时间
const formatDateTime = (dateTime: string) => {
if (!dateTime) return '-'
const date = new Date(dateTime)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
}
// 转换后端数据
const transformBackendData = (data: any[]): EquipmentResp[] => {
return data.map((item: any) => ({
equipmentId: item.equipmentId || item.id,
assetCode: item.assetCode,
equipmentName: item.equipmentName,
equipmentType: item.equipmentType,
equipmentTypeLabel: item.equipmentTypeLabel,
equipmentModel: item.equipmentModel,
equipmentSn: item.equipmentSn,
brand: item.brand,
specification: item.specification,
equipmentStatus: item.equipmentStatus,
equipmentStatusLabel: item.equipmentStatusLabel,
useStatus: item.useStatus,
locationStatus: item.locationStatus,
locationStatusLabel: item.locationStatusLabel,
physicalLocation: item.physicalLocation,
responsiblePerson: item.responsiblePerson,
healthStatus: item.healthStatus,
healthStatusLabel: item.healthStatusLabel,
purchaseTime: item.purchaseTime,
inStockTime: item.inStockTime,
activationTime: item.activationTime,
expectedScrapTime: item.expectedScrapTime,
actualScrapTime: item.actualScrapTime,
statusChangeTime: item.statusChangeTime,
purchaseOrder: item.purchaseOrder,
supplierName: item.supplierName,
purchasePrice: item.purchasePrice,
currentNetValue: item.currentNetValue,
depreciationMethod: item.depreciationMethod,
depreciationYears: item.depreciationYears,
salvageValue: item.salvageValue,
warrantyExpireDate: item.warrantyExpireDate,
lastMaintenanceDate: item.lastMaintenanceDate,
nextMaintenanceDate: item.nextMaintenanceDate,
maintenancePerson: item.maintenancePerson,
inventoryBarcode: item.inventoryBarcode,
assetRemark: item.assetRemark,
projectId: item.projectId,
projectName: item.projectName,
userId: item.userId,
name: item.name,
createTime: item.createTime,
updateTime: item.updateTime,
accountNumber: item.accountNumber,
quantity: item.quantity,
unitPrice: item.unitPrice,
totalPrice: item.totalPrice,
inventoryBasis: item.inventoryBasis,
dynamicRecord: item.dynamicRecord,
procurementStatus: item.procurementStatus,
receiptStatus: item.receiptStatus || 'NOT_RECEIVED',
paymentStatus: item.paymentStatus || 'NOT_PAID',
}))
}
// 加载数据
const loadData = async (searchParams?: EquipmentListReq) => {
console.log('📊 loadData - 开始加载数据')
console.log('📊 loadData - 接收到的搜索参数:', searchParams)
loading.value = true
try {
// 构建请求参数,确保分页参数正确
const params: EquipmentListReq = {
pageNum: pagination.current, // 当前页码
pageSize: pagination.pageSize, // 每页条数
minPrice: undefined,
maxPrice: undefined,
...(searchParams || {}),
}
console.log('📊 loadData - 构建的完整请求参数:', params)
console.log('📊 当前分页状态:', {
current: pagination.current,
pageSize: pagination.pageSize,
total: pagination.total
})
const res = await equipmentProcurementApi.page(params)
console.log('API响应:', res)
if (res.code === 200 || res.success || res.status === 200) {
let dataList: any[] = []
let totalCount = 0
// 检查不同的数据字段 - 后端返回的是 PageResult 格式
if ((res as any).rows && Array.isArray((res as any).rows)) {
// 后端返回的是 PageResult 格式,数据在 rows 字段中
dataList = (res as any).rows
totalCount = (res as any).total || 0
console.log('✅ 从 rows 字段获取数据,总数:', totalCount, '当前页数据:', dataList.length)
} else if (Array.isArray(res.data)) {
dataList = res.data
totalCount = (res as any).total || dataList.length || 0
console.log('✅ 从 data 字段获取数据,总数:', totalCount, '当前页数据:', dataList.length)
} else if (res.data && Array.isArray((res.data as any).records)) {
dataList = (res.data as any).records
totalCount = (res.data as any).total || dataList.length || 0
console.log('✅ 从 records 字段获取数据,总数:', totalCount, '当前页数据:', dataList.length)
} else if (res.data && Array.isArray((res.data as any).list)) {
dataList = (res.data as any).list
totalCount = (res.data as any).total || dataList.length || 0
console.log('✅ 从 list 字段获取数据,总数:', totalCount, '当前页数据:', dataList.length)
} else {
console.warn('⚠️ 未找到有效的数据字段,响应结构:', res)
dataList = []
totalCount = 0
}
console.log('处理后的数据列表:', dataList)
if (dataList.length > 0) {
const transformedData = transformBackendData(dataList)
console.log('转换后的数据:', transformedData)
// 调试采购状态字段
transformedData.forEach((item, index) => {
console.log(`设备 ${index + 1} - 名称: ${item.equipmentName}, 采购状态: ${item.procurementStatus}`)
})
tableData.value = transformedData
} else {
tableData.value = []
}
// 设置总数 - 优先使用后端返回的总数
pagination.total = totalCount
console.log('📊 总数:', totalCount)
console.log('📊 分页组件状态:', {
current: pagination.current,
pageSize: pagination.pageSize,
total: pagination.total
})
} else {
console.error('❌ 请求失败,响应:', res)
message.error(res.msg || (res as any).message || '加载数据失败')
tableData.value = []
pagination.total = 0
}
} catch (error: any) {
console.error('❌ 加载数据失败:', error)
message.error(error?.message || '加载数据失败')
tableData.value = []
pagination.total = 0
} finally {
loading.value = false
}
}
// 搜索
const handleSearch = (searchParams: EquipmentListReq) => {
console.log('🔍 主组件 - 接收到的搜索参数:', searchParams)
pagination.current = 1
currentSearchParams.value = { ...searchParams }
loadData(searchParams)
}
// 重置
const handleReset = () => {
console.log('🔄 主组件 - 重置操作')
pagination.current = 1
currentSearchParams.value = {
minPrice: undefined,
maxPrice: undefined
}
loadData()
}
// 表格变化
const handleTableChange = (pag: any) => {
pagination.current = pag.current || 1
pagination.pageSize = pag.pageSize || 10
loadData(currentSearchParams.value)
}
// 分页变化处理
const handlePageChange = (page: number) => {
console.log('页码变化:', page)
pagination.current = page
loadData(currentSearchParams.value)
}
// 每页条数变化处理
const handlePageSizeChange = (pageSize: number) => {
console.log('每页条数变化:', pageSize)
pagination.pageSize = pageSize
pagination.current = 1 // 重置到第一页
loadData(currentSearchParams.value)
}
// 新增
const handleAdd = () => {
modalMode.value = 'add'
currentProcurement.value = null
modalVisible.value = true
}
// 查看
const handleView = (record: EquipmentResp) => {
modalMode.value = 'view'
currentProcurement.value = { ...record }
modalVisible.value = true
}
// 编辑
const handleEdit = (record: EquipmentResp) => {
modalMode.value = 'edit'
currentProcurement.value = { ...record }
modalVisible.value = true
}
// 申请采购
const handleApplyProcurement = (record: EquipmentResp) => {
currentApplicationData.value = { ...record }
applicationModalVisible.value = true
}
// 删除
const handleDelete = async (record: EquipmentResp) => {
try {
await equipmentProcurementApi.delete(record.equipmentId)
message.success('删除成功')
loadData(currentSearchParams.value)
} catch (error: any) {
console.error('删除失败:', error)
message.error(error?.message || '删除失败')
}
}
// 弹窗成功回调
const handleModalSuccess = () => {
modalVisible.value = false
loadData(currentSearchParams.value)
}
// 采购申请成功回调
const handleApplicationSuccess = async () => {
applicationModalVisible.value = false
console.log('✅ 采购申请成功,准备更新本地数据...')
// 立即更新本地数据,让申请采购按钮消失
if (currentApplicationData.value) {
const equipmentId = currentApplicationData.value.equipmentId
// 找到对应的记录并更新采购状态
const recordIndex = tableData.value.findIndex(item => item.equipmentId === equipmentId)
if (recordIndex !== -1) {
// 立即更新本地状态为待审批
tableData.value[recordIndex] = {
...tableData.value[recordIndex],
procurementStatus: 'PENDING_APPROVAL'
}
console.log('✅ 本地数据已更新,申请采购按钮应该消失')
console.log('🔍 更新后的记录:', tableData.value[recordIndex])
message.success('采购申请已提交,请等待审批')
} else {
console.warn('⚠️ 未找到对应的记录,无法更新本地状态')
message.warning('状态更新失败,请手动刷新页面')
}
}
// 可选:延迟刷新数据以确保后端状态同步
setTimeout(async () => {
try {
console.log('🔄 延迟刷新数据,确保后端状态同步...')
await loadData(currentSearchParams.value)
console.log('✅ 后端数据同步完成')
} catch (error) {
console.error('❌ 后端数据同步失败:', error)
// 不显示错误提示,因为本地状态已经更新
}
}, 1000) // 1秒后刷新
}
// 刷新数据
const refreshData = () => {
loadData(currentSearchParams.value)
}
// 导出数据
const handleExport = async () => {
try {
const response = await equipmentProcurementApi.export(currentSearchParams.value)
const blob = response.data || response
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `设备采购记录_${new Date().toISOString().split('T')[0]}.xlsx`
link.click()
window.URL.revokeObjectURL(url)
message.success('导出成功')
} catch (error: any) {
console.error('导出失败:', error)
message.error(error?.message || '导出失败')
}
}
// 统计函数
const getPendingCount = () => {
return tableData.value.filter(item =>
item.equipmentStatus === 'pending' ||
item.locationStatus === 'pending'
).length
}
const getCompletedCount = () => {
return tableData.value.filter(item =>
item.equipmentStatus === 'completed' ||
item.locationStatus === 'completed'
).length
}
const getTotalAmount = () => {
const total = tableData.value.reduce((sum, item) => {
return sum + (item.purchasePrice || 0)
}, 0)
return formatPrice(total)
}
// 检查是否可以申请采购
const canApplyProcurement = (record: EquipmentResp) => {
// 根据采购状态判断是否可以申请采购
// 只有未开始、已拒绝的设备可以申请采购
// 待审批、已通过、已完成等状态不能重复申请
const allowedStatuses = ['NOT_STARTED', 'REJECTED', null, undefined]
const canApply = allowedStatuses.includes(record.procurementStatus)
// 添加详细的调试日志
console.log(`🔍 申请采购按钮显示检查 - 设备: ${record.equipmentName}`)
console.log(`🔍 当前采购状态: ${record.procurementStatus}`)
console.log(`🔍 允许申请的状态: ${allowedStatuses.join(', ')}`)
console.log(`🔍 是否显示按钮: ${canApply}`)
console.log(`🔍 完整记录:`, record)
return canApply
}
// 检查是否可以收货
const canReceiveGoods = (record: EquipmentResp) => {
// 只有在采购状态为已通过且收货状态为未收货时才显示确认收货按钮
// 同时需要确保合同发票信息已经完善
const procurementStatus = record.procurementStatus
const receiptStatus = record.receiptStatus
// 检查是否有合同发票信息(这里可以根据实际业务逻辑调整)
// 暂时使用一个简单的判断,后续可以根据实际数据结构调整
const hasContractInvoice = true // 暂时设为true后续根据实际业务逻辑调整
console.log('🔍 canReceiveGoods 检查:', {
equipmentName: record.equipmentName,
procurementStatus,
receiptStatus,
hasContractInvoice,
canReceive: procurementStatus === 'APPROVED' &&
(receiptStatus === 'NOT_RECEIVED' || receiptStatus === 'PARTIALLY_RECEIVED') &&
hasContractInvoice
})
return procurementStatus === 'APPROVED' &&
(receiptStatus === 'NOT_RECEIVED' || receiptStatus === 'PARTIALLY_RECEIVED') &&
hasContractInvoice
}
// 收货操作
const handleReceiveGoods = async (record: EquipmentResp) => {
currentReceiptData.value = { ...record }
receiptModalVisible.value = true
}
// 查看收货详情
const handleViewReceipt = (record: EquipmentResp) => {
currentReceiptData.value = { ...record }
receiptDetailModalVisible.value = true
}
// 收货成功回调
const handleReceiptSuccess = () => {
receiptModalVisible.value = false
// 收货成功后,采购状态应该更新为已收货
// 重新加载数据以显示最新状态
loadData(currentSearchParams.value)
message.success('收货成功!设备已自动入库')
}
// 检查是否可以付款
const canMakePayment = (record: EquipmentResp) => {
const paymentStatus = (record as any).paymentStatus
return paymentStatus === 'NOT_PAID' || paymentStatus === 'PARTIALLY_PAID'
}
// 付款操作
const handleMakePayment = async (record: EquipmentResp) => {
currentPaymentData.value = { ...record }
paymentModalVisible.value = true
}
// 查看支付详情
const handleViewPayment = (record: EquipmentResp) => {
currentPaymentData.value = { ...record }
paymentDetailModalVisible.value = true
}
// 支付成功回调
const handlePaymentSuccess = () => {
paymentModalVisible.value = false
loadData(currentSearchParams.value)
}
// 获取审批状态颜色
const getApprovalStatusColor = (status: string) => {
const colorMap: Record<string, string> = {
'PENDING_APPLICATION': 'blue',
'PENDING': 'orange',
'APPROVED': 'green',
'REJECTED': 'red',
'WITHDRAWN': 'gray'
}
return colorMap[status] || 'blue'
}
// 获取审批状态文本
const getApprovalStatusText = (status: string) => {
const textMap: Record<string, string> = {
'PENDING_APPLICATION': '待申请',
'PENDING': '待审批',
'APPROVED': '已通过',
'REJECTED': '已拒绝',
'WITHDRAWN': '已撤回'
}
return textMap[status] || '未知'
}
// 手动刷新单条记录
const handleRefreshRecord = async (record: EquipmentResp) => {
console.log('🔄 手动刷新记录:', record.equipmentId)
try {
// 显示加载提示
const loadingMessage = message.loading('正在刷新数据...')
// 重新加载整个表格数据,确保获取最新状态
await loadData(currentSearchParams.value)
// 清除加载提示
if (loadingMessage && typeof loadingMessage.close === 'function') {
loadingMessage.close()
}
message.success('记录状态已刷新')
console.log('✅ 表格数据刷新完成')
} catch (error: any) {
console.error('❌ 刷新失败:', error)
message.error(error?.message || '刷新失败')
}
}
// 检查是否可以管理合同发票
const canManageContractInvoice = (record: EquipmentResp) => {
// 只有在采购状态为已通过时才显示管理合同发票按钮
return record.procurementStatus === 'APPROVED'
}
// 管理合同发票操作
const handleManageContractInvoice = (record: EquipmentResp) => {
currentContractInvoiceData.value = { ...record }
contractInvoiceModalVisible.value = true
}
// 合同发票管理成功回调
const handleContractInvoiceSuccess = () => {
contractInvoiceModalVisible.value = false
loadData(currentSearchParams.value)
}
// 检查是否有合同发票信息
const hasContractInvoiceInfo = (record: EquipmentResp) => {
// 这里可以根据实际业务逻辑调整判断条件
// 暂时使用一个简单的判断,后续可以根据实际数据结构调整
// 例如:检查是否有合同编号、发票号码等字段
return record.procurementStatus === 'APPROVED' &&
(record.receiptStatus === 'RECEIVED' || record.paymentStatus === 'PAID')
}
onMounted(() => {
loadData()
})
</script>
<style scoped lang="scss">
.equipment-procurement-container {
.page-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
.header-left {
.page-title {
display: flex;
align-items: center;
margin-bottom: 8px;
h1 {
margin: 0;
color: white;
font-size: 28px;
font-weight: 600;
background: linear-gradient(135deg, #ffffff 0%, #f0f0f0 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
}
.page-description {
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
}
}
.header-right {
.arco-btn {
border-radius: 8px;
font-weight: 500;
}
}
}
}
.stats-container {
margin-bottom: 24px;
.stat-card {
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.stat-content {
display: flex;
align-items: center;
.stat-icon {
width: 60px;
height: 60px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
.arco-icon {
font-size: 24px;
color: white;
}
}
.stat-info {
flex: 1;
.stat-number {
font-size: 24px;
font-weight: 600;
color: var(--color-text-1);
margin-bottom: 4px;
}
.stat-label {
font-size: 14px;
color: var(--color-text-3);
}
}
}
}
}
.table-card {
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
.card-title {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-size: 18px;
font-weight: 600;
color: var(--color-text-1);
}
.table-actions {
.arco-btn {
border-radius: 6px;
}
}
}
.arco-table {
.arco-table-th {
background-color: var(--color-fill-2);
font-weight: 600;
}
.arco-table-tr:hover {
background-color: var(--color-fill-1);
}
}
}
.price-text {
color: #f56c6c;
font-weight: 500;
}
.time-text {
color: var(--color-text-2);
font-size: 12px;
}
.no-data {
color: var(--color-text-4);
font-style: italic;
}
// 分页器容器样式 - 固定在表格下方
.pagination-container {
position: sticky;
bottom: 0;
background: white;
padding: 16px 24px;
border-top: 1px solid var(--color-border);
display: flex;
justify-content: flex-end;
align-items: center;
z-index: 10;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
.arco-pagination {
margin: 0;
.arco-pagination-item {
border-radius: 6px;
margin: 0 4px;
&.arco-pagination-item-active {
background: var(--color-primary);
border-color: var(--color-primary);
}
}
.arco-pagination-size-changer {
margin-left: 16px;
}
.arco-pagination-jumper {
margin-left: 16px;
}
}
}
}
// 响应式设计
@media (max-width: 768px) {
.equipment-procurement-container {
.page-header {
.header-content {
flex-direction: column;
align-items: flex-start;
.header-right {
margin-top: 16px;
width: 100%;
.arco-space {
width: 100%;
justify-content: space-between;
}
}
}
}
.stats-container {
.arco-col {
margin-bottom: 16px;
}
}
}
}
</style>