<template>
|
<div class="app-container">
|
<!-- 搜索区域 -->
|
<el-card class="search-card"
|
shadow="never">
|
<el-form :model="searchForm"
|
:inline="true"
|
class="search-form">
|
<el-form-item label="计划名称">
|
<el-input v-model="searchForm.planName"
|
placeholder="请输入计划名称"
|
clearable />
|
</el-form-item>
|
<el-form-item label="状态">
|
<el-select v-model="searchForm.status"
|
placeholder="请选择状态"
|
clearable
|
style="width: 150px">
|
<el-option label="启用"
|
value="active" />
|
<el-option label="禁用"
|
value="disabled" />
|
</el-select>
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary"
|
@click="handleSearch">
|
<el-icon>
|
<Search />
|
</el-icon>
|
搜索
|
</el-button>
|
<el-button @click="handleReset">
|
<el-icon>
|
<Refresh />
|
</el-icon>
|
重置
|
</el-button>
|
</el-form-item>
|
</el-form>
|
</el-card>
|
<!-- 操作按钮 -->
|
<el-card class="table-card"
|
shadow="never">
|
<div class="table-header">
|
<div class="table-title">采购计划列表</div>
|
<div class="table-actions">
|
<el-button type="primary"
|
@click="handleAdd">
|
<el-icon>
|
<Plus />
|
</el-icon>
|
新增计划
|
</el-button>
|
<el-button type="info"
|
@click="handleExport">
|
<el-icon>
|
<Download />
|
</el-icon>
|
导出
|
</el-button>
|
</div>
|
</div>
|
<!-- 数据表格 -->
|
<el-table v-loading="loading"
|
:data="tableData"
|
stripe
|
border
|
style="width: 100%">
|
<el-table-column prop="planName"
|
label="计划名称"
|
min-width="150" />
|
<el-table-column prop="description"
|
label="描述"
|
min-width="200"
|
show-overflow-tooltip />
|
<el-table-column prop="formula"
|
label="计算公式"
|
min-width="200"
|
show-overflow-tooltip>
|
<template #default="{ row }">
|
<el-tag type="info"
|
size="small">{{ row.formula }}</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="status"
|
label="状态"
|
width="80"
|
align="center">
|
<template #default="{ row }">
|
<el-tag :type="row.status === 'active' ? 'success' : 'info'"
|
size="small">
|
{{ row.status === 'active' ? '启用' : '禁用' }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="updateTime"
|
label="最后计算时间"
|
width="160" />
|
<el-table-column label="操作"
|
width="200"
|
fixed="right"
|
align="center">
|
<template #default="{ row }">
|
<el-button type="primary"
|
link
|
@click="handleEdit(row)">编辑</el-button>
|
<el-button type="success"
|
link
|
@click="handleCalculate(row)">计算</el-button>
|
<el-button type="danger"
|
link
|
@click="handleDelete(row)">删除</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
<!-- 分页 -->
|
<div class="pagination-container">
|
<el-pagination v-model:current-page="pagination.current"
|
v-model:page-size="pagination.size"
|
: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="dialogVisible"
|
:title="dialogType === 'add' ? '新增采购计划' : '编辑采购计划'"
|
width="1000px"
|
:close-on-click-modal="false">
|
<div class="form-container">
|
<!-- 基本信息 -->
|
<div class="form-section">
|
<div class="section-title">基本信息</div>
|
<el-form ref="formRef"
|
:model="formData"
|
:rules="formRules"
|
label-width="120px">
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="编码"
|
prop="code">
|
<el-input v-model="formData.code"
|
placeholder="系统自动生成"
|
disabled />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="名称"
|
prop="planName"
|
required>
|
<el-input v-model="formData.planName"
|
placeholder="请输入计划名称" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-form-item label="描述"
|
prop="description">
|
<el-input v-model="formData.description"
|
type="textarea"
|
:rows="3"
|
placeholder="请输入计划描述" />
|
</el-form-item>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="状态"
|
prop="status">
|
<el-select v-model="formData.status"
|
placeholder="请选择状态"
|
style="width: 100%">
|
<el-option label="启用"
|
value="active" />
|
<el-option label="禁用"
|
value="disabled" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="是否系统预置">
|
<el-checkbox v-model="formData.isSystemPreset">是</el-checkbox>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
</div>
|
<!-- 计算参数 -->
|
<div class="form-section">
|
<div class="section-title">计算参数</div>
|
<el-tabs v-model="activeTab"
|
class="param-tabs">
|
<el-tab-pane label="需求参数"
|
name="demand">
|
<div class="checkbox-group">
|
<el-checkbox v-model="formData.considerExistingStock">考虑现有库存</el-checkbox>
|
<el-checkbox v-model="formData.warehouseControl">仓库运行MRP的控制</el-checkbox>
|
<el-checkbox v-model="formData.calculateTotalDemand">计算总需求</el-checkbox>
|
<el-checkbox v-model="formData.considerSafetyStock">考虑安全库存</el-checkbox>
|
<el-checkbox v-model="formData.considerLockedStock">考虑锁库</el-checkbox>
|
<el-checkbox v-model="formData.notConsiderMaterialAux">不考虑物料辅助属性</el-checkbox>
|
<el-checkbox v-model="formData.negativeStockAsDemand">负库存作为需求</el-checkbox>
|
</div>
|
</el-tab-pane>
|
<el-tab-pane label="计算参数"
|
name="calculation">
|
<div class="checkbox-group">
|
<el-checkbox v-model="formData.considerExistingStock">考虑现有库存</el-checkbox>
|
<el-checkbox v-model="formData.warehouseControl">仓库运行MRP的控制</el-checkbox>
|
<el-checkbox v-model="formData.calculateTotalDemand">计算总需求</el-checkbox>
|
<el-checkbox v-model="formData.considerSafetyStock">考虑安全库存</el-checkbox>
|
<el-checkbox v-model="formData.considerLockedStock">考虑锁库</el-checkbox>
|
<el-checkbox v-model="formData.notConsiderMaterialAux">不考虑物料辅助属性</el-checkbox>
|
<el-checkbox v-model="formData.negativeStockAsDemand">负库存作为需求</el-checkbox>
|
</div>
|
</el-tab-pane>
|
</el-tabs>
|
</div>
|
<!-- 汇总合并选项 -->
|
<div class="form-section">
|
<div class="section-title">汇总合并选项</div>
|
<div class="checkbox-group">
|
<el-checkbox v-model="formData.summaryMaterial">物料</el-checkbox>
|
<el-checkbox v-model="formData.summaryAuxAttributes">辅助属性</el-checkbox>
|
<el-checkbox v-model="formData.summaryDemandDate">需求日期</el-checkbox>
|
</div>
|
</div>
|
<!-- 计算公式 -->
|
<div class="form-section">
|
<div class="section-title">计算公式</div>
|
<div class="formula-input-section">
|
<el-form-item label="计算公式"
|
prop="formula"
|
required>
|
<el-input v-model="formData.formula"
|
placeholder="例如: 预计出库数量 - 现有库存 + 安全库存 - 预计入库数量"
|
@input="validateFormula" />
|
</el-form-item>
|
<div class="formula-help">
|
<el-text type="info"
|
size="small">
|
支持变量:预计出库数量、现有库存、安全库存、预计入库数量
|
</el-text>
|
</div>
|
</div>
|
</div>
|
</div>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button type="primary"
|
@click="handleSubmit"
|
:loading="submitLoading">确定</el-button>
|
<el-button @click="dialogVisible = false">取消</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
<!-- 产品选择对话框 -->
|
<el-dialog v-model="productSelectDialogVisible"
|
title="选择产品"
|
width="800px"
|
:close-on-click-modal="false">
|
<div class="product-select">
|
<el-alert title="请选择要计算的产品"
|
type="info"
|
:closable="false"
|
show-icon>
|
<template #default>
|
<p>选择产品后,系统将根据当前计算公式和产品库存情况进行计算。</p>
|
</template>
|
</el-alert>
|
<el-table v-loading="productLoading"
|
:data="productList"
|
@selection-change="handleProductSelectionChange"
|
stripe
|
border
|
style="width: 100%; margin-top: 20px;">
|
<el-table-column type="selection"
|
width="55" />
|
<el-table-column prop="productCategory"
|
label="产品大类"
|
min-width="150" />
|
<el-table-column prop="specificationModel"
|
label="产品规格"
|
width="120" />
|
<el-table-column prop="inboundNum0"
|
label="现有库存"
|
width="100"
|
align="right" />
|
<el-table-column prop="inboundNum"
|
label="安全库存"
|
width="100"
|
align="right" />
|
<el-table-column prop="inboundNum"
|
label="预计出库"
|
width="100"
|
align="right" />
|
<el-table-column prop="inboundNum0"
|
label="预计入库"
|
width="100"
|
align="right" />
|
</el-table>
|
</div>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button type="primary"
|
@click="handleConfirmProductSelection"
|
:disabled="selectedProducts.length === 0">
|
确认计算
|
</el-button>
|
<el-button @click="productSelectDialogVisible = false">取消</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
<!-- 计算结果对话框 -->
|
<el-dialog v-model="calculateDialogVisible"
|
title="采购计算结果"
|
width="1000px"
|
:close-on-click-modal="false">
|
<div class="calculate-result">
|
<el-alert title="计算结果"
|
type="success"
|
:closable="false"
|
show-icon>
|
<template #default>
|
<p>基于当前配置的计算公式和库存情况,系统已计算出各产品的采购需求。</p>
|
</template>
|
</el-alert>
|
<el-table :data="calculateResult"
|
stripe
|
border
|
style="width: 100%; margin-top: 20px;">
|
<el-table-column prop="productCategory"
|
label="产品大类"
|
min-width="150" />
|
<el-table-column prop="specificationModel"
|
label="产品规格"
|
width="120" />
|
<el-table-column prop="inboundNum0"
|
label="现有库存"
|
width="100"
|
align="right" />
|
<el-table-column prop="inboundNum"
|
label="安全库存"
|
width="100"
|
align="right" />
|
<el-table-column prop="inboundNum"
|
label="预计出库数量"
|
width="120"
|
align="right" />
|
<el-table-column prop="inboundNum0"
|
label="预计入库数量"
|
width="120"
|
align="right" />
|
<el-table-column prop="weeklyNetDemand"
|
label="按周净需求"
|
width="120"
|
align="right">
|
<template #default="{ row }">
|
<el-tag :type="row.weeklyNetDemand > 0 ? 'warning' : 'success'"
|
size="small">
|
{{ row.weeklyNetDemand }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="suggestedPurchase"
|
label="建议采购"
|
width="100"
|
align="right">
|
<template #default="{ row }">
|
<el-tag :type="row.suggestedPurchase > 0 ? 'danger' : 'success'"
|
size="small">
|
{{ row.suggestedPurchase }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
</el-table>
|
</div>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button type="primary"
|
@click="handleCreatePurchaseOrder">确认</el-button>
|
<el-button @click="calculateDialogVisible = false">关闭</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, getCurrentInstance } from "vue";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
import { Search, Refresh, Plus, Download } from "@element-plus/icons-vue";
|
import {
|
listPage,
|
add,
|
update,
|
del,
|
listPageCopy,
|
} from "@/api/procurementManagement/procurementPlan.js";
|
|
// 响应式数据
|
const loading = ref(false);
|
const submitLoading = ref(false);
|
const dialogVisible = ref(false);
|
const productSelectDialogVisible = ref(false);
|
const calculateDialogVisible = ref(false);
|
const dialogType = ref("add");
|
const productLoading = ref(false);
|
const selectedProducts = ref([]);
|
const currentPlan = ref(null);
|
|
// 搜索表单
|
const searchForm = reactive({
|
planName: "",
|
status: "",
|
});
|
|
// 分页数据
|
const pagination = reactive({
|
current: 1,
|
size: 20,
|
});
|
|
// 表单数据
|
const formData = reactive({
|
code: "",
|
planName: "",
|
description: "",
|
status: "",
|
isSystemPreset: false,
|
formula: "",
|
// 计算参数
|
considerExistingStock: false,
|
warehouseControl: false,
|
calculateTotalDemand: false,
|
considerSafetyStock: false,
|
considerLockedStock: false,
|
notConsiderMaterialAux: false,
|
negativeStockAsDemand: false,
|
// 汇总合并选项
|
summaryMaterial: false,
|
summaryAuxAttributes: false,
|
summaryDemandDate: false,
|
});
|
|
// 当前激活的标签页
|
const activeTab = ref("demand");
|
|
// 表单验证规则
|
const formRules = {
|
planName: [{ required: true, message: "请输入计划名称", trigger: "blur" }],
|
status: [{ required: true, message: "请选择状态", trigger: "change" }],
|
formula: [{ required: true, message: "请输入计算公式", trigger: "blur" }],
|
};
|
|
// 表格数据
|
const tableData = ref([]);
|
|
// 产品列表数据
|
const productList = ref([
|
{
|
id: 4,
|
productName: "产品D",
|
productCode: "PD004",
|
existingStock: 90,
|
safetyStock: 40,
|
expectedOutbound: 160,
|
expectedInbound: 35,
|
},
|
]);
|
|
// 计算结果数据
|
const calculateResult = ref([
|
{
|
productName: "产品A",
|
existingStock: 100,
|
safetyStock: 50,
|
expectedOutbound: 200,
|
expectedInbound: 30,
|
weeklyNetDemand: 120,
|
suggestedPurchase: 150,
|
},
|
{
|
productName: "产品B",
|
existingStock: 80,
|
safetyStock: 30,
|
expectedOutbound: 150,
|
expectedInbound: 20,
|
weeklyNetDemand: 100,
|
suggestedPurchase: 120,
|
},
|
]);
|
const total = ref(0);
|
// 方法
|
const handleSearch = () => {
|
pagination.current = 1;
|
loadData();
|
};
|
|
const handleReset = () => {
|
Object.assign(searchForm, {
|
planName: "",
|
status: "",
|
});
|
handleSearch();
|
};
|
|
const loadData = () => {
|
loading.value = true;
|
listPage({ ...searchForm, ...pagination }).then(res => {
|
if (res.code === 200) {
|
tableData.value = res.data.records;
|
total.value = res.data.total;
|
loading.value = false;
|
}
|
});
|
};
|
|
const handleAdd = () => {
|
dialogType.value = "add";
|
resetForm();
|
// 自动生成编码
|
formData.code = "CGJH" + String(Date.now()).slice(-4);
|
dialogVisible.value = true;
|
};
|
|
const handleEdit = row => {
|
dialogType.value = "edit";
|
Object.assign(formData, row);
|
dialogVisible.value = true;
|
};
|
|
const handleDelete = async row => {
|
try {
|
await ElMessageBox.confirm("确定要删除这个采购计划吗?", "提示", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning",
|
});
|
let ids = [row.id];
|
del(ids).then(res => {
|
if (res.code === 200) {
|
ElMessage.success("删除成功");
|
loadData();
|
}
|
});
|
} catch {
|
// 用户取消删除
|
}
|
};
|
|
const handleSubmit = async () => {
|
try {
|
// 表单验证
|
if (!formData.planName || !formData.formula) {
|
ElMessage.error("请填写必填项");
|
return;
|
}
|
|
submitLoading.value = true;
|
|
if (dialogType.value === "add") {
|
add(formData).then(res => {
|
if (res.code === 200) {
|
ElMessage.success("新增成功");
|
dialogVisible.value = false;
|
loadData();
|
}
|
});
|
} else {
|
// 编辑
|
update(formData).then(res => {
|
if (res.code === 200) {
|
ElMessage.success("编辑成功");
|
dialogVisible.value = false;
|
loadData();
|
}
|
});
|
}
|
} catch (error) {
|
ElMessage.error("操作失败");
|
} finally {
|
submitLoading.value = false;
|
}
|
};
|
|
const resetForm = () => {
|
Object.assign(formData, {
|
code: "",
|
planName: "",
|
description: "",
|
status: "",
|
isSystemPreset: false,
|
formula: "预计出库数量 - 现有库存 + 安全库存 - 预计入库数量",
|
// 计算参数
|
considerExistingStock: false,
|
warehouseControl: false,
|
calculateTotalDemand: false,
|
considerSafetyStock: false,
|
considerLockedStock: false,
|
notConsiderMaterialAux: false,
|
negativeStockAsDemand: false,
|
// 汇总合并选项
|
summaryMaterial: false,
|
summaryAuxAttributes: false,
|
summaryDemandDate: false,
|
});
|
activeTab.value = "demand";
|
};
|
|
const validateFormula = () => {
|
// 简单的公式验证
|
const formula = formData.formula;
|
if (formula && !/^[a-zA-Z\u4e00-\u9fa5\s\*\+\-\/\(\)\d\.]+$/.test(formula)) {
|
ElMessage.warning("公式格式可能不正确,请检查");
|
}
|
};
|
|
const handleCalculate = row => {
|
currentPlan.value = row;
|
productSelectDialogVisible.value = true;
|
loadProductList();
|
};
|
|
const loadProductList = () => {
|
productLoading.value = true;
|
// 模拟加载产品数据
|
listPageCopy({ size: -1 }).then(res => {
|
if (res.code === 200) {
|
productList.value = res.data.records;
|
productLoading.value = false;
|
}
|
});
|
};
|
|
const handleProductSelectionChange = selection => {
|
selectedProducts.value = selection;
|
};
|
|
const handleConfirmProductSelection = () => {
|
if (selectedProducts.value.length === 0) {
|
ElMessage.warning("请选择要计算的产品");
|
return;
|
}
|
|
ElMessage.success(`正在计算 ${currentPlan.value.planName} 的采购需求...`);
|
productSelectDialogVisible.value = false;
|
|
// 根据选择的产品和计算公式进行计算
|
calculateWithSelectedProducts();
|
};
|
|
const calculateWithSelectedProducts = () => {
|
// 模拟计算过程
|
// 根据选择的产品更新计算结果
|
const result = selectedProducts.value.map(product => {
|
// 这里应该根据实际的计算公式进行计算
|
// 示例:预计出库数量 - 现有库存 + 安全库存 - 预计入库数量
|
const weeklyNetDemand =
|
product.inboundNum -
|
product.inboundNum0 +
|
product.inboundNum -
|
product.inboundNum0;
|
const suggestedPurchase = Math.max(0, weeklyNetDemand);
|
|
return {
|
productCategory: product.productCategory,
|
specificationModel: product.specificationModel,
|
inboundNum0: product.inboundNum0,
|
inboundNum: product.inboundNum,
|
weeklyNetDemand: weeklyNetDemand,
|
suggestedPurchase: suggestedPurchase,
|
};
|
});
|
|
calculateResult.value = result;
|
calculateDialogVisible.value = true;
|
};
|
|
const handleCreatePurchaseOrder = () => {
|
calculateDialogVisible.value = false;
|
};
|
const { proxy } = getCurrentInstance();
|
const handleExport = () => {
|
ElMessageBox.confirm("内容将被导出,是否确认导出?", "导出", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
proxy.download("/procurementPlan/export", {}, "采购计划.xlsx");
|
})
|
.catch(() => {
|
proxy.$modal.msg("已取消");
|
});
|
};
|
|
const handleSizeChange = size => {
|
pagination.size = size;
|
loadData();
|
};
|
|
const handleCurrentChange = current => {
|
pagination.current = current;
|
loadData();
|
};
|
|
// 生命周期
|
onMounted(() => {
|
loadData();
|
});
|
</script>
|
|
<style scoped>
|
.app-container {
|
padding: 20px;
|
}
|
|
.page-header {
|
margin-bottom: 20px;
|
}
|
|
.page-header h2 {
|
margin: 0 0 8px 0;
|
color: #303133;
|
font-size: 24px;
|
font-weight: 600;
|
}
|
|
.page-header p {
|
margin: 0;
|
color: #909399;
|
font-size: 14px;
|
}
|
|
.search-card {
|
margin-bottom: 20px;
|
}
|
|
.search-form {
|
margin-bottom: 0;
|
}
|
|
.table-card {
|
margin-bottom: 20px;
|
}
|
|
.table-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
}
|
|
.table-title {
|
font-size: 16px;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.table-actions {
|
display: flex;
|
gap: 10px;
|
}
|
|
.pagination-container {
|
margin-top: 20px;
|
display: flex;
|
justify-content: end;
|
}
|
|
.form-container {
|
padding: 0 20px;
|
}
|
|
.formula-help {
|
margin-top: 5px;
|
}
|
|
.calculate-result {
|
padding: 20px 0;
|
}
|
|
.dialog-footer {
|
text-align: right;
|
}
|
|
:deep(.el-card__body) {
|
padding: 20px;
|
}
|
|
:deep(.el-table) {
|
font-size: 14px;
|
}
|
|
:deep(.el-form-item__label) {
|
font-weight: 500;
|
}
|
|
.form-container {
|
padding: 0;
|
}
|
|
.form-section {
|
margin-bottom: 24px;
|
border: 1px solid #e4e7ed;
|
border-radius: 6px;
|
overflow: hidden;
|
}
|
|
.section-title {
|
background-color: #f5f7fa;
|
padding: 12px 16px;
|
font-weight: 600;
|
color: #303133;
|
border-bottom: 1px solid #e4e7ed;
|
}
|
|
.form-section .el-form {
|
padding: 20px;
|
}
|
|
.param-tabs {
|
padding: 20px;
|
}
|
|
.param-tabs :deep(.el-tabs__header) {
|
margin-bottom: 20px;
|
}
|
|
.param-tabs :deep(.el-tabs__item.is-active) {
|
color: #f56c6c;
|
border-bottom-color: #f56c6c;
|
}
|
|
.checkbox-group {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 16px;
|
}
|
|
.checkbox-group .el-checkbox {
|
margin-right: 0;
|
margin-bottom: 8px;
|
}
|
|
.formula-input-section {
|
padding: 20px;
|
}
|
|
.formula-input-section .el-form-item {
|
margin-bottom: 12px;
|
}
|
|
.formula-help {
|
text-align: center;
|
margin-top: 8px;
|
}
|
</style>
|