2026-02-27 23:50:25 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="page-container">
|
|
|
|
|
|
<!-- 搜索区域 -->
|
|
|
|
|
|
<el-card class="search-card" shadow="never">
|
|
|
|
|
|
<el-form :model="queryParams" :inline="true" @submit.prevent="handleQuery">
|
|
|
|
|
|
<!-- 明细视图搜索 -->
|
|
|
|
|
|
<template v-if="viewMode === 'detail'">
|
|
|
|
|
|
<el-form-item label="跟单单号">
|
|
|
|
|
|
<el-input v-model="queryParams.salesOrderCode" placeholder="请输入" clearable style="width: 140px" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="单据编码">
|
|
|
|
|
|
<el-input v-model="queryParams.purchaseCode" placeholder="请输入" clearable style="width: 140px" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="物料编码">
|
|
|
|
|
|
<el-input v-model="queryParams.itemCode" placeholder="请输入" clearable style="width: 140px" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="物料名称">
|
|
|
|
|
|
<el-input v-model="queryParams.itemName" placeholder="请输入" clearable style="width: 140px" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 单据视图搜索 -->
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<el-form-item label="单据编码">
|
|
|
|
|
|
<el-input v-model="queryParams.purchaseCode" placeholder="请输入" clearable style="width: 160px" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="单据状态">
|
|
|
|
|
|
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 120px">
|
|
|
|
|
|
<el-option label="开立" value="PREPARE" />
|
|
|
|
|
|
<el-option label="审核" value="APPROVED" />
|
|
|
|
|
|
<el-option label="退回" value="REJECTED" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="业务状态">
|
|
|
|
|
|
<el-select v-model="queryParams.businessStatus" placeholder="全部" clearable style="width: 120px">
|
|
|
|
|
|
<el-option label="正常" value="NORMAL" />
|
|
|
|
|
|
<el-option label="暂停" value="PAUSE" />
|
|
|
|
|
|
<el-option label="取消" value="CANCEL" />
|
|
|
|
|
|
<el-option label="完成" value="COMPLETED" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 公共搜索:日期范围 -->
|
|
|
|
|
|
<el-form-item label="开始日期">
|
|
|
|
|
|
<el-date-picker
|
|
|
|
|
|
v-model="queryParams.beginDate"
|
|
|
|
|
|
type="date"
|
|
|
|
|
|
placeholder="开始日期"
|
|
|
|
|
|
value-format="YYYY-MM-DD"
|
|
|
|
|
|
style="width: 140px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="结束日期">
|
|
|
|
|
|
<el-date-picker
|
|
|
|
|
|
v-model="queryParams.endDate"
|
|
|
|
|
|
type="date"
|
|
|
|
|
|
placeholder="结束日期"
|
|
|
|
|
|
value-format="YYYY-MM-DD"
|
|
|
|
|
|
style="width: 140px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item>
|
|
|
|
|
|
<el-button type="primary" @click="handleQuery">
|
|
|
|
|
|
<el-icon><Search /></el-icon>搜索
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 工具栏与表格 -->
|
|
|
|
|
|
<el-card class="table-card" shadow="never">
|
|
|
|
|
|
<!-- 工具栏 -->
|
|
|
|
|
|
<div class="toolbar">
|
|
|
|
|
|
<div class="toolbar-left">
|
|
|
|
|
|
<!-- 视图切换按钮 -->
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="viewMode === 'detail'"
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
@click="switchView('document')"
|
|
|
|
|
|
>单据</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-else
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
@click="switchView('detail')"
|
|
|
|
|
|
>明细</el-button>
|
|
|
|
|
|
|
|
|
|
|
|
<el-button @click="handleQueryAll">
|
|
|
|
|
|
<el-icon><Tickets /></el-icon>查询所有
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button type="success" @click="handleAdd">
|
|
|
|
|
|
<el-icon><Plus /></el-icon>新增
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 明细视图特有按钮 -->
|
|
|
|
|
|
<el-button v-if="viewMode === 'detail'" @click="handleExport">
|
|
|
|
|
|
<el-icon><Download /></el-icon>导出
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 单据视图特有按钮 -->
|
|
|
|
|
|
<template v-if="viewMode === 'document'">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="danger"
|
|
|
|
|
|
:disabled="selectedRows.length === 0"
|
|
|
|
|
|
@click="handleBatchDelete"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-icon><Delete /></el-icon>删除
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
:disabled="!canBatchApprove"
|
|
|
|
|
|
@click="handleBatchApprove"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-icon><Check /></el-icon>审核
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
:disabled="!canBatchUnapprove"
|
|
|
|
|
|
@click="handleBatchUnapprove"
|
|
|
|
|
|
>反审核
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 明细视图 - 物料分类快捷筛选标签 -->
|
|
|
|
|
|
<div v-if="viewMode === 'detail'" class="toolbar-right">
|
|
|
|
|
|
<div class="category-tags">
|
|
|
|
|
|
<el-check-tag
|
|
|
|
|
|
v-for="tag in categoryTags"
|
|
|
|
|
|
:key="tag.itemTypeId"
|
|
|
|
|
|
:checked="queryParams.itemTypeId === tag.itemTypeId"
|
|
|
|
|
|
@change="handleCategoryTag(tag.itemTypeId)"
|
|
|
|
|
|
>{{ tag.itemTypeName }}</el-check-tag>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ============ 明细视图表格 ============ -->
|
|
|
|
|
|
<el-table
|
|
|
|
|
|
v-if="viewMode === 'detail'"
|
|
|
|
|
|
v-loading="loading"
|
|
|
|
|
|
:data="tableData"
|
|
|
|
|
|
stripe
|
|
|
|
|
|
border
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
:max-height="tableHeight"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
|
|
|
|
|
<el-table-column prop="salesOrderCode" label="跟单编号" width="140">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-link v-if="row.salesOrderCode" type="primary">{{ row.salesOrderCode }}</el-link>
|
|
|
|
|
|
<span v-else>-</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="deliveryDate" label="订单交期" width="110" align="center" />
|
|
|
|
|
|
<el-table-column prop="purchaseCode" label="单据编码" width="140">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-link type="primary" @click="handleViewDetail(row)">{{ row.purchaseCode }}</el-link>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="status" label="单据状态" width="90" align="center">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-tag :type="getStatusType(row.status)" size="small">{{ getStatusLabel(row.status) }}</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="itemName" label="物料名称" min-width="200" show-overflow-tooltip />
|
|
|
|
|
|
<el-table-column prop="itemCode" label="物料编码" width="120" />
|
|
|
|
|
|
<el-table-column prop="purchaseQty" label="采购数量" width="100" align="right">
|
|
|
|
|
|
<template #default="{ row }">{{ formatNumber(row.purchaseQty) }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="orderedQty" label="已订数量" width="100" align="right">
|
|
|
|
|
|
<template #default="{ row }">{{ formatNumber(row.orderedQty ?? 0) }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="purchaseDate" label="单据日期" width="110" align="center" />
|
|
|
|
|
|
<el-table-column prop="remark" label="采购说明" width="120" show-overflow-tooltip />
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ============ 单据视图表格 ============ -->
|
|
|
|
|
|
<el-table
|
|
|
|
|
|
v-else
|
|
|
|
|
|
v-loading="loading"
|
|
|
|
|
|
:data="tableData"
|
|
|
|
|
|
stripe
|
|
|
|
|
|
border
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
:max-height="tableHeight"
|
|
|
|
|
|
@selection-change="handleSelectionChange"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-table-column type="selection" width="55" align="center" />
|
|
|
|
|
|
<el-table-column prop="purchaseCode" label="单据编码" width="140">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-link type="primary" @click="handleViewDetail(row)">{{ row.purchaseCode }}</el-link>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="purchaseDate" label="单据日期" width="120" align="center" sortable />
|
|
|
|
|
|
<el-table-column prop="status" label="单据状态" width="90" align="center">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-tag :type="getStatusType(row.status)" size="small">{{ getStatusLabel(row.status) }}</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="businessType" label="业务类型" width="100" align="center">
|
|
|
|
|
|
<template #default="{ row }">{{ getBusinessTypeLabel(row.businessType) }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="deptName" label="采购部门" width="100" align="center" />
|
|
|
|
|
|
<el-table-column prop="operatorName" label="采购人员" width="100" align="center" />
|
|
|
|
|
|
<el-table-column prop="businessStatus" label="业务状态" width="90" align="center">
|
|
|
|
|
|
<template #default="{ row }">{{ getBusinessStatusLabel(row.businessStatus) }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="approveDate" label="审核日期" width="120" align="center" />
|
|
|
|
|
|
<el-table-column label="操作" width="160" align="center" fixed="right">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-button type="success" link size="small" @click="handleViewDetail(row)">查看</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="isEditable(row.status)"
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
link
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleEdit(row)"
|
|
|
|
|
|
>编辑</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="isApproved(row.status)"
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
link
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleCopyPlan(row)"
|
|
|
|
|
|
>翻单</el-button>
|
|
|
|
|
|
<el-popconfirm
|
|
|
|
|
|
v-if="isEditable(row.status)"
|
|
|
|
|
|
title="确认删除该采购计划吗?"
|
|
|
|
|
|
@confirm="handleDeleteRow(row)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #reference>
|
|
|
|
|
|
<el-button type="danger" link size="small">删除</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-popconfirm>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 底部汇总和分页 -->
|
|
|
|
|
|
<div class="table-footer">
|
|
|
|
|
|
<div class="summary">
|
|
|
|
|
|
采购数量:<span class="summary-value primary">{{ formatNumber(totalPurchaseQty) }}</span>
|
|
|
|
|
|
已订数量:<span class="summary-value primary">{{ formatNumber(totalOrderedQty) }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-pagination
|
|
|
|
|
|
v-model:current-page="queryParams.pageNum"
|
|
|
|
|
|
v-model:page-size="queryParams.pageSize"
|
|
|
|
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
|
:total="total"
|
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
|
@current-change="handleCurrentChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 导出确认弹窗 -->
|
|
|
|
|
|
<el-dialog v-model="showExportDialog" title="导出确认" width="420px">
|
|
|
|
|
|
<p>确认导出当前查询条件下的采购计划数据吗?</p>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="showExportDialog = false">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" :loading="exportLoading" @click="confirmExport">确定</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
|
|
|
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
|
import { Search, Plus, Delete, Download, Check, Tickets } from '@element-plus/icons-vue'
|
|
|
|
|
|
import {
|
|
|
|
|
|
getPurchasePlanList,
|
|
|
|
|
|
getPurchasePlanDocList,
|
|
|
|
|
|
getPurchasePlanSummary,
|
|
|
|
|
|
deletePurchasePlan,
|
|
|
|
|
|
approvePurchasePlan,
|
|
|
|
|
|
unapprovePurchasePlan,
|
|
|
|
|
|
exportPurchasePlan,
|
|
|
|
|
|
getItemTypeList,
|
|
|
|
|
|
STATUS_MAP,
|
|
|
|
|
|
BUSINESS_STATUS_MAP,
|
|
|
|
|
|
BUSINESS_TYPE_OPTIONS,
|
|
|
|
|
|
type PurchasePlan,
|
|
|
|
|
|
type PurchasePlanQuery,
|
|
|
|
|
|
type PurchasePlanSummary
|
|
|
|
|
|
} from '@/api/purchasePlan'
|
|
|
|
|
|
|
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 响应式状态 ============
|
|
|
|
|
|
|
|
|
|
|
|
/** 当前视图模式:明细视图 / 单据视图 */
|
|
|
|
|
|
const viewMode = ref<'detail' | 'document'>('detail')
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
/** 本地搜索参数(用户交互用,loadData 时转换为 RuoYi 格式) */
|
|
|
|
|
|
const queryParams = reactive({
|
|
|
|
|
|
salesOrderCode: '',
|
|
|
|
|
|
purchaseCode: '',
|
|
|
|
|
|
itemCode: '',
|
|
|
|
|
|
itemName: '',
|
|
|
|
|
|
itemTypeId: '' as number | string,
|
|
|
|
|
|
needType: 0,
|
|
|
|
|
|
beginDate: '',
|
|
|
|
|
|
endDate: '',
|
|
|
|
|
|
status: '',
|
|
|
|
|
|
businessStatus: '',
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 100
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const tableData = ref<PurchasePlan[]>([])
|
|
|
|
|
|
const total = ref(0)
|
|
|
|
|
|
const selectedRows = ref<PurchasePlan[]>([])
|
|
|
|
|
|
const tableHeight = ref(500)
|
|
|
|
|
|
|
|
|
|
|
|
/** 物料分类快捷筛选标签 */
|
|
|
|
|
|
const categoryTags = ref<Array<{ itemTypeId: number; itemTypeName: string }>>([])
|
|
|
|
|
|
|
|
|
|
|
|
/** 底部汇总数据(来自后端 /summary 接口) */
|
|
|
|
|
|
const summaryData = ref<PurchasePlanSummary>({ totalPurchaseQty: 0, totalOrderedQty: 0 })
|
|
|
|
|
|
|
|
|
|
|
|
/** 导出弹窗 */
|
|
|
|
|
|
const showExportDialog = ref(false)
|
|
|
|
|
|
const exportLoading = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 计算属性 ============
|
|
|
|
|
|
|
|
|
|
|
|
/** 底部汇总优先使用后端 summary 接口,fallback 到前端计算 */
|
|
|
|
|
|
const totalPurchaseQty = computed(() =>
|
|
|
|
|
|
summaryData.value.totalPurchaseQty || tableData.value.reduce((sum, r) => sum + (r.purchaseQty || 0), 0)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const totalOrderedQty = computed(() =>
|
|
|
|
|
|
summaryData.value.totalOrderedQty || tableData.value.reduce((sum, r) => sum + (r.orderedQty || 0), 0)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const canBatchApprove = computed(() =>
|
|
|
|
|
|
selectedRows.value.length > 0 &&
|
|
|
|
|
|
selectedRows.value.every(row => isEditable(row.status))
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const canBatchUnapprove = computed(() =>
|
|
|
|
|
|
selectedRows.value.length > 0 &&
|
|
|
|
|
|
selectedRows.value.every(row => isApproved(row.status))
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 工具方法 ============
|
|
|
|
|
|
|
|
|
|
|
|
function getStatusType(status: string): string {
|
|
|
|
|
|
return (STATUS_MAP[status]?.type ?? 'info') as string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getStatusLabel(status: string): string {
|
|
|
|
|
|
return STATUS_MAP[status]?.label ?? status ?? '开立'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getBusinessTypeLabel(type: string): string {
|
|
|
|
|
|
const opt = BUSINESS_TYPE_OPTIONS.find(o => o.value === type)
|
|
|
|
|
|
return opt?.label ?? type ?? '-'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getBusinessStatusLabel(status: string): string {
|
|
|
|
|
|
return BUSINESS_STATUS_MAP[status] ?? status ?? '-'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isEditable(status: string): boolean {
|
|
|
|
|
|
return status === 'PREPARE' || status === '开立' || status === 'REJECTED' || status === '退回'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isApproved(status: string): boolean {
|
|
|
|
|
|
return status === 'APPROVED' || status === '审核'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatNumber(val: number | undefined | null): string {
|
|
|
|
|
|
if (val === undefined || val === null) return '-'
|
|
|
|
|
|
return Number(val).toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 2 })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 数据加载 ============
|
|
|
|
|
|
|
|
|
|
|
|
/** 将本地 queryParams 转换为后端 RuoYi 格式参数 */
|
|
|
|
|
|
function buildApiParams(): PurchasePlanQuery {
|
|
|
|
|
|
const p: PurchasePlanQuery = {
|
|
|
|
|
|
pageNum: queryParams.pageNum,
|
|
|
|
|
|
pageSize: queryParams.pageSize,
|
|
|
|
|
|
needType: queryParams.needType
|
|
|
|
|
|
}
|
|
|
|
|
|
if (queryParams.salesOrderCode) p.salesOrderCode = queryParams.salesOrderCode
|
|
|
|
|
|
if (queryParams.purchaseCode) p.purchaseCode = queryParams.purchaseCode
|
|
|
|
|
|
if (queryParams.itemCode) p.itemCode = queryParams.itemCode
|
|
|
|
|
|
if (queryParams.itemName) p.itemName = queryParams.itemName
|
|
|
|
|
|
if (queryParams.status) p.status = queryParams.status
|
|
|
|
|
|
if (queryParams.businessStatus) p.businessStatus = queryParams.businessStatus
|
|
|
|
|
|
if (queryParams.beginDate) p['params[beginDate]'] = queryParams.beginDate
|
|
|
|
|
|
if (queryParams.endDate) p['params[endDate]'] = queryParams.endDate
|
|
|
|
|
|
if (queryParams.itemTypeId) p['params[itemTypeId]'] = queryParams.itemTypeId
|
|
|
|
|
|
return p
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadData() {
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const params = buildApiParams()
|
|
|
|
|
|
let res
|
|
|
|
|
|
if (viewMode.value === 'detail') {
|
|
|
|
|
|
res = await getPurchasePlanList(params)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
res = await getPurchasePlanDocList(params)
|
|
|
|
|
|
}
|
|
|
|
|
|
tableData.value = res.rows
|
|
|
|
|
|
total.value = res.total
|
|
|
|
|
|
|
|
|
|
|
|
// 同时加载底部汇总
|
|
|
|
|
|
loadSummary(params)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载数据失败:', error)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 加载底部汇总数据 */
|
|
|
|
|
|
async function loadSummary(params: PurchasePlanQuery) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const summary = await getPurchasePlanSummary(params)
|
|
|
|
|
|
summaryData.value = summary
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载汇总失败:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadCategoryTags() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
categoryTags.value = await getItemTypeList()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载物料分类失败:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 搜索与筛选 ============
|
|
|
|
|
|
|
|
|
|
|
|
function handleQuery() {
|
|
|
|
|
|
queryParams.pageNum = 1
|
|
|
|
|
|
loadData()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleQueryAll() {
|
|
|
|
|
|
// 重置所有搜索条件
|
|
|
|
|
|
queryParams.salesOrderCode = ''
|
|
|
|
|
|
queryParams.purchaseCode = ''
|
|
|
|
|
|
queryParams.itemCode = ''
|
|
|
|
|
|
queryParams.itemName = ''
|
|
|
|
|
|
queryParams.itemTypeId = ''
|
|
|
|
|
|
queryParams.beginDate = ''
|
|
|
|
|
|
queryParams.endDate = ''
|
|
|
|
|
|
queryParams.status = ''
|
|
|
|
|
|
queryParams.businessStatus = ''
|
|
|
|
|
|
queryParams.pageNum = 1
|
|
|
|
|
|
loadData()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleCategoryTag(typeId: number) {
|
|
|
|
|
|
if (queryParams.itemTypeId === typeId) {
|
|
|
|
|
|
queryParams.itemTypeId = '' // 取消选中
|
|
|
|
|
|
} else {
|
|
|
|
|
|
queryParams.itemTypeId = typeId
|
|
|
|
|
|
}
|
|
|
|
|
|
handleQuery()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 视图切换 ============
|
|
|
|
|
|
|
|
|
|
|
|
function switchView(mode: 'detail' | 'document') {
|
|
|
|
|
|
// 保留公共搜索条件
|
|
|
|
|
|
const commonFields = {
|
|
|
|
|
|
purchaseCode: queryParams.purchaseCode,
|
|
|
|
|
|
beginDate: queryParams.beginDate,
|
|
|
|
|
|
endDate: queryParams.endDate,
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: queryParams.pageSize,
|
|
|
|
|
|
needType: queryParams.needType
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空视图特有字段
|
|
|
|
|
|
queryParams.salesOrderCode = ''
|
|
|
|
|
|
queryParams.itemCode = ''
|
|
|
|
|
|
queryParams.itemName = ''
|
|
|
|
|
|
queryParams.itemTypeId = ''
|
|
|
|
|
|
queryParams.status = ''
|
|
|
|
|
|
queryParams.businessStatus = ''
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复公共字段
|
|
|
|
|
|
Object.assign(queryParams, commonFields)
|
|
|
|
|
|
|
|
|
|
|
|
viewMode.value = mode
|
|
|
|
|
|
selectedRows.value = []
|
|
|
|
|
|
loadData()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 选择操作 ============
|
|
|
|
|
|
|
|
|
|
|
|
function handleSelectionChange(rows: PurchasePlan[]) {
|
|
|
|
|
|
selectedRows.value = rows
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 路由操作 ============
|
|
|
|
|
|
|
|
|
|
|
|
function handleAdd() {
|
|
|
|
|
|
router.push('/production/purchase-plan/new')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleViewDetail(row: PurchasePlan) {
|
|
|
|
|
|
router.push(`/production/purchase-plan/view/${row.purchaseId}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleEdit(row: PurchasePlan) {
|
|
|
|
|
|
router.push(`/production/purchase-plan/edit/${row.purchaseId}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleCopyPlan(row: PurchasePlan) {
|
|
|
|
|
|
// 翻单:复制采购计划生成新单据
|
|
|
|
|
|
router.push({
|
|
|
|
|
|
path: '/production/purchase-plan/new',
|
|
|
|
|
|
query: { copyFrom: String(row.purchaseId) }
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 删除操作 ============
|
|
|
|
|
|
|
|
|
|
|
|
async function handleDeleteRow(row: PurchasePlan) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await deletePurchasePlan(String(row.purchaseId))
|
|
|
|
|
|
ElMessage.success('删除成功')
|
|
|
|
|
|
loadData()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('删除失败:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function handleBatchDelete() {
|
|
|
|
|
|
if (selectedRows.value.length === 0) return
|
|
|
|
|
|
|
|
|
|
|
|
// 校验状态
|
|
|
|
|
|
const invalidRows = selectedRows.value.filter(row => !isEditable(row.status))
|
|
|
|
|
|
if (invalidRows.length > 0) {
|
|
|
|
|
|
ElMessage.warning('只能删除开立或退回状态的单据')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
|
|
`确认删除选中的 ${selectedRows.value.length} 条采购计划吗?`,
|
|
|
|
|
|
'删除确认',
|
|
|
|
|
|
{ type: 'warning' }
|
|
|
|
|
|
)
|
|
|
|
|
|
const ids = selectedRows.value.map(row => row.purchaseId).join(',')
|
|
|
|
|
|
await deletePurchasePlan(ids)
|
|
|
|
|
|
ElMessage.success('删除成功')
|
|
|
|
|
|
selectedRows.value = []
|
|
|
|
|
|
loadData()
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
|
console.error('删除失败:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 审核操作 ============
|
|
|
|
|
|
|
|
|
|
|
|
async function handleBatchApprove() {
|
|
|
|
|
|
if (selectedRows.value.length === 0) return
|
|
|
|
|
|
|
|
|
|
|
|
const invalidRows = selectedRows.value.filter(row => !isEditable(row.status))
|
|
|
|
|
|
if (invalidRows.length > 0) {
|
|
|
|
|
|
ElMessage.warning('只能审核开立或退回状态的单据')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
|
|
`确认审核选中的 ${selectedRows.value.length} 条采购计划吗?`,
|
|
|
|
|
|
'审核确认',
|
|
|
|
|
|
{ type: 'info' }
|
|
|
|
|
|
)
|
|
|
|
|
|
for (const row of selectedRows.value) {
|
|
|
|
|
|
await approvePurchasePlan(row.purchaseId)
|
|
|
|
|
|
}
|
|
|
|
|
|
ElMessage.success('审核成功')
|
|
|
|
|
|
selectedRows.value = []
|
|
|
|
|
|
loadData()
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
|
console.error('审核失败:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 反审核操作 ============
|
|
|
|
|
|
|
|
|
|
|
|
async function handleBatchUnapprove() {
|
|
|
|
|
|
if (selectedRows.value.length === 0) return
|
|
|
|
|
|
|
|
|
|
|
|
const invalidRows = selectedRows.value.filter(row => !isApproved(row.status))
|
|
|
|
|
|
if (invalidRows.length > 0) {
|
|
|
|
|
|
ElMessage.warning('只能反审核已审核状态的单据')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
|
|
`确认反审核选中的 ${selectedRows.value.length} 条采购计划吗?反审核后可重新编辑。`,
|
|
|
|
|
|
'反审核确认',
|
|
|
|
|
|
{ type: 'warning' }
|
|
|
|
|
|
)
|
|
|
|
|
|
const ids = selectedRows.value.map(row => row.purchaseId).join(',')
|
|
|
|
|
|
await unapprovePurchasePlan(ids)
|
|
|
|
|
|
ElMessage.success('反审核成功')
|
|
|
|
|
|
selectedRows.value = []
|
|
|
|
|
|
loadData()
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
|
console.error('反审核失败:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 导出操作 ============
|
|
|
|
|
|
|
|
|
|
|
|
function handleExport() {
|
|
|
|
|
|
showExportDialog.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function confirmExport() {
|
|
|
|
|
|
exportLoading.value = true
|
|
|
|
|
|
try {
|
2026-03-01 16:40:38 +08:00
|
|
|
|
const blob = (await exportPurchasePlan(queryParams)).data
|
2026-02-27 23:50:25 +08:00
|
|
|
|
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)
|
|
|
|
|
|
showExportDialog.value = false
|
|
|
|
|
|
ElMessage.success('导出成功')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('导出失败:', error)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
exportLoading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 分页操作 ============
|
|
|
|
|
|
|
|
|
|
|
|
function handleSizeChange() {
|
|
|
|
|
|
queryParams.pageNum = 1
|
|
|
|
|
|
loadData()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleCurrentChange() {
|
|
|
|
|
|
loadData()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 表格高度 ============
|
|
|
|
|
|
|
|
|
|
|
|
function calcTableHeight() {
|
|
|
|
|
|
tableHeight.value = window.innerHeight - 310
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 生命周期 ============
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
loadData()
|
|
|
|
|
|
loadCategoryTags()
|
|
|
|
|
|
calcTableHeight()
|
|
|
|
|
|
window.addEventListener('resize', calcTableHeight)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
window.removeEventListener('resize', calcTableHeight)
|
|
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.page-container {
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-card {
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-card :deep(.el-card__body) {
|
|
|
|
|
|
padding: 12px 12px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-card :deep(.el-form-item) {
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
margin-right: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.table-card :deep(.el-card__body) {
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar {
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar-left {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar-right {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.category-tags {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.category-tags :deep(.el-check-tag) {
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
padding: 2px 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.table-footer {
|
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary-value {
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-left: 4px;
|
|
|
|
|
|
margin-right: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary-value.primary {
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|