<template>
|
<div class="app-container">
|
<div class="search_form">
|
<el-form :model="searchForm"
|
:inline="true">
|
<el-form-item label="客户名称:">
|
<el-input v-model="searchForm.customerName"
|
placeholder="请输入"
|
clearable
|
style="width: 160px;"
|
@keyup.enter="handleQuery" />
|
</el-form-item>
|
<el-form-item label="产品名称:">
|
<el-input v-model="searchForm.productName"
|
placeholder="请输入"
|
clearable
|
style="width: 160px;"
|
@keyup.enter="handleQuery" />
|
</el-form-item>
|
<el-form-item label="产品规格:">
|
<el-input v-model="searchForm.specification"
|
placeholder="请输入"
|
clearable
|
style="width: 160px;"
|
@keyup.enter="handleQuery" />
|
</el-form-item>
|
<el-form-item label="物料编码:">
|
<el-input v-model="searchForm.materialCode"
|
placeholder="请输入"
|
clearable
|
style="width: 160px;"
|
@keyup.enter="handleQuery" />
|
</el-form-item>
|
<el-form-item label="申请单编号:">
|
<el-input v-model="searchForm.applyNo"
|
placeholder="请输入"
|
clearable
|
style="width: 160px;"
|
@keyup.enter="handleQuery" />
|
</el-form-item>
|
<el-form-item label="计划日期范围:">
|
<el-date-picker v-model="searchForm.dateRange"
|
type="daterange"
|
range-separator="至"
|
start-placeholder="开始日期"
|
end-placeholder="结束日期"
|
value-format="YYYY-MM-DD"
|
style="width: 240px;"
|
@change="handleQuery" />
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary"
|
@click="handleQuery">搜索</el-button>
|
<el-button type="info"
|
@click="handleReset">重置</el-button>
|
<el-button type="primary"
|
@click="handleAdd">新增</el-button>
|
<el-button type="warning"
|
@click="getLoadProdData">拉取数据</el-button>
|
<el-button type="warning"
|
@click="handleMerge">合并下发</el-button>
|
<el-button type="warning"
|
@click="handleImport">导入</el-button>
|
<el-button type="warning"
|
@click="handleExport">导出</el-button>
|
</el-form-item>
|
</el-form>
|
<div>
|
</div>
|
</div>
|
<div class="table_list">
|
<PIMTable rowKey="id"
|
:column="tableColumn"
|
:tableData="tableData"
|
:page="page"
|
height="calc(100vh - 350px)"
|
:tableLoading="tableLoading"
|
:isSelection="true"
|
:selectable="isSelectable"
|
@selection-change="handleSelectionChange"
|
@pagination="pagination">
|
</PIMTable>
|
</div>
|
<!-- 合并下发弹窗 -->
|
<el-dialog v-model="isShowNewModal"
|
title="合并下发"
|
width="600px">
|
<el-form :model="mergeForm"
|
label-width="120px">
|
<el-row :gutter="20">
|
<el-col :span="10">
|
<el-form-item label="物料编码">
|
<div class="info-display">{{ mergeForm.materialCode || '-' }}</div>
|
</el-form-item>
|
</el-col>
|
<el-col :span="10">
|
<el-form-item label="产品名称">
|
<el-tag class="info-display">{{ mergeForm.productName || '-' }}</el-tag>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="20">
|
<el-col :span="10">
|
<el-form-item label="产品规格">
|
<div class="info-display">{{ mergeForm.specification || '-' }}</div>
|
</el-form-item>
|
</el-col>
|
<el-col :span="10">
|
<el-form-item label="长*宽*高">
|
<div class="info-display">{{ mergeForm.length || '-' }}*{{ mergeForm.width || '-' }}*{{ mergeForm.height || '-' }}</div>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-form-item label="计划完成时间">
|
<el-date-picker v-model="mergeForm.planCompleteTime"
|
type="date"
|
value-format="YYYY-MM-DD"
|
style="width: 100%" />
|
</el-form-item>
|
<el-form-item label="生产方数">
|
<el-input-number v-model="mergeForm.totalAssignedQuantity"
|
:min="0"
|
:max="sumAssignedQuantity"
|
@change="onBlur"
|
style="width: 100%" />
|
</el-form-item>
|
<!-- <el-form-item label="备注">
|
<el-input v-model="mergeForm.remark"
|
type="textarea" />
|
</el-form-item> -->
|
</el-form>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="isShowNewModal = false">取消</el-button>
|
<el-button type="primary"
|
@click="handleMergeSubmit">确定下发</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
<!-- 追踪进度弹窗 -->
|
<el-dialog v-model="showTrackProgressDialog"
|
:title="`追踪进度 - ${trackProgressForm.materialCode || ''}`"
|
width="600px">
|
<el-form :model="trackProgressForm"
|
label-width="120px">
|
<el-form-item label="物料编码">
|
<el-input v-model="trackProgressForm.materialCode"
|
disabled />
|
</el-form-item>
|
<el-form-item label="当前状态">
|
<el-select v-model="trackProgressForm.currentStatus"
|
placeholder="请选择状态">
|
<el-option label="待处理"
|
value="pending" />
|
<el-option label="进行中"
|
value="processing" />
|
<el-option label="已完成"
|
value="completed" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="完成进度">
|
<el-progress :percentage="trackProgressForm.completionRate"
|
:status="trackProgressForm.completionRate === 100 ? 'success' : ''" />
|
</el-form-item>
|
<el-form-item label="进度详情">
|
<el-table :data="trackProgressForm.progressDetails"
|
border
|
style="width: 100%">
|
<el-table-column prop="step"
|
label="步骤"
|
align="center"
|
width="100" />
|
<el-table-column prop="status"
|
label="状态"
|
align="center"
|
width="100">
|
<template #default="scope">
|
<el-tag :type="scope.row.status === 'completed' ? 'success' : scope.row.status === 'processing' ? 'warning' : 'info'">
|
{{ scope.row.status === 'completed' ? '已完成' : scope.row.status === 'processing' ? '进行中' : '待开始' }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="startTime"
|
label="开始时间"
|
align="center"
|
width="180" />
|
<el-table-column prop="endTime"
|
label="结束时间"
|
align="center"
|
width="180" />
|
</el-table>
|
</el-form-item>
|
<el-form-item label="备注">
|
<el-input v-model="trackProgressForm.remark"
|
type="textarea" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="showTrackProgressDialog = false">关闭</el-button>
|
<el-button type="primary"
|
@click="handleUpdateProgress">更新进度</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
<!-- 导入弹窗 -->
|
<ImportDialog ref="importDialogRef"
|
v-model="importDialogVisible"
|
title="导入生产计划"
|
:action="importAction"
|
:headers="importHeaders"
|
:auto-upload="false"
|
:on-success="handleImportSuccess"
|
:on-error="handleImportError"
|
@confirm="handleImportConfirm"
|
@download-template="handleDownloadTemplate"
|
@close="handleImportClose" />
|
<!-- 新增/编辑弹窗 -->
|
<el-dialog v-model="dialogVisible"
|
:title="operationType === 'add' ? '新增生产计划' : '编辑生产计划'"
|
width="600px">
|
<el-form ref="formRef"
|
:model="form"
|
:rules="rules"
|
label-width="120px">
|
<el-form-item label="申请单编号"
|
prop="applyNo">
|
<el-input v-model="form.applyNo"
|
placeholder="请输入申请单编号" />
|
</el-form-item>
|
<el-form-item label="客户名称"
|
prop="customerName">
|
<el-input v-model="form.customerName"
|
placeholder="请输入客户名称" />
|
</el-form-item>
|
<el-form-item label="产品名称"
|
prop="productMaterialId">
|
<el-tree-select v-model="form.productMaterialId"
|
placeholder="请选择"
|
clearable
|
:data="productOptions"
|
:render-after-expand="false"
|
filterable
|
@change="handleProductChange"
|
style="width: 100%" />
|
</el-form-item>
|
<el-form-item label="产品规格"
|
prop="productMaterialSkuId">
|
<el-select v-model="form.productMaterialSkuId"
|
@change="handleChangeSpecification"
|
filterable
|
placeholder="请选择">
|
<el-option v-for="item in specificationOptions"
|
:key="item.skuId"
|
:label="item.specification"
|
:value="item.skuId" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="块数"
|
prop="quantity">
|
<el-input-number v-model="form.quantity"
|
:min="0"
|
placeholder="请输入块数" />
|
</el-form-item>
|
<el-form-item label="方数"
|
prop="volume">
|
<el-input-number v-model="form.volume"
|
:min="0"
|
placeholder="请输入方数" />
|
</el-form-item>
|
<el-form-item label="长"
|
prop="length">
|
<el-input-number v-model="form.length"
|
:min="0"
|
placeholder="请输入长度" />
|
</el-form-item>
|
<el-form-item label="宽"
|
prop="width">
|
<el-input-number v-model="form.width"
|
:min="0"
|
placeholder="请输入宽度" />
|
</el-form-item>
|
<el-form-item label="高"
|
prop="height">
|
<el-input-number v-model="form.height"
|
:min="0"
|
placeholder="请输入高度" />
|
</el-form-item>
|
<el-form-item label="计划开始日期"
|
prop="startDate">
|
<el-date-picker v-model="form.startDate"
|
type="date"
|
value-format="YYYY-MM-DD"
|
placeholder="请选择计划开始日期" />
|
</el-form-item>
|
<el-form-item label="计划结束日期"
|
prop="endDate">
|
<el-date-picker v-model="form.endDate"
|
type="date"
|
value-format="YYYY-MM-DD"
|
placeholder="请选择计划结束日期" />
|
</el-form-item>
|
<el-form-item label="强度"
|
prop="strength">
|
<el-select v-model="form.strength"
|
placeholder="请选择强度"
|
style="width: 100%">
|
<el-option label="A3.5"
|
value="A3.5" />
|
<el-option label="A5.0"
|
value="A5.0" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="备注 1"
|
prop="remarkOne">
|
<el-input v-model="form.remarkOne"
|
placeholder="请输入备注 1" />
|
</el-form-item>
|
<el-form-item label="备注 2"
|
prop="remarkTwo">
|
<el-input v-model="form.remarkTwo"
|
placeholder="请输入备注 2" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="dialogVisible = false">取消</el-button>
|
<el-button type="primary"
|
@click="handleSubmit">确定</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { onMounted, ref, reactive, getCurrentInstance } from "vue";
|
import { ElMessage } from "element-plus";
|
import dayjs from "dayjs";
|
import ImportDialog from "@/components/Dialog/ImportDialog.vue";
|
import { getToken } from "@/utils/auth";
|
import {
|
productionPlanListPage,
|
loadProdData,
|
exportProductionPlan,
|
productionPlanAdd,
|
productionPlanUpdate,
|
productionPlanDelete,
|
productionPlanCombine,
|
} from "@/api/productionPlan/productionPlan.js";
|
import PIMTable from "./components/PIMTable.vue";
|
import {
|
modelListPage,
|
productTreeList,
|
productTreeListQuery,
|
} from "@/api/basicData/newProduct.js";
|
|
const { proxy } = getCurrentInstance();
|
|
const tableColumn = ref([
|
{
|
label: "申请单编号",
|
prop: "applyNo",
|
width: "150px",
|
className: "code-cell",
|
},
|
{
|
label: "客户名称",
|
prop: "customerName",
|
width: "150px",
|
},
|
{
|
label: "产品名称",
|
prop: "productName",
|
width: "100px",
|
dataType: "tag",
|
formatType: params => {
|
const typeMap = {
|
板材: "primary",
|
砌块: "info",
|
};
|
return typeMap[params] || "info";
|
},
|
},
|
{
|
label: "产品规格",
|
prop: "specification",
|
width: "150px",
|
className: "spec-cell",
|
},
|
{
|
label: "物料编码",
|
prop: "materialCode",
|
width: "150px",
|
className: "code-cell",
|
},
|
{
|
label: "块数",
|
prop: "quantity",
|
className: "quantity-cell",
|
formatData: cell => (cell ? `${cell}块` : ""),
|
},
|
{
|
label: "方数",
|
prop: "volume",
|
width: "150px",
|
className: "volume-cell",
|
formatData: cell => (cell ? `${cell}方` : ""),
|
},
|
{
|
label: "已下发方数",
|
prop: "assignedQuantity",
|
width: "150px",
|
className: "spec-cell",
|
formatData: cell => (cell ? `${cell}方` : ""),
|
},
|
{
|
label: "长",
|
prop: "length",
|
className: "dimension-cell",
|
formatData: cell => (cell ? `${cell}mm` : ""),
|
},
|
{
|
label: "宽",
|
prop: "width",
|
className: "dimension-cell",
|
formatData: cell => (cell ? `${cell}mm` : ""),
|
},
|
{
|
label: "高",
|
prop: "height",
|
className: "dimension-cell",
|
formatData: cell => (cell ? `${cell}mm` : ""),
|
},
|
// {
|
// label: "流水号",
|
// prop: "serialNo",
|
// width: "150px",
|
// className: "code-cell",
|
// },
|
{
|
label: "计划开始日期",
|
prop: "startDate",
|
width: "150px",
|
className: "date-cell",
|
formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
|
},
|
{
|
label: "计划结束日期",
|
prop: "endDate",
|
width: "150px",
|
className: "date-cell",
|
formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""),
|
},
|
{
|
label: "强度",
|
prop: "strength",
|
},
|
{
|
label: "数据来源",
|
width: "100px",
|
prop: "dataSourceType",
|
formatData: cell => (cell == 1 ? "同步" : "手动"),
|
},
|
{
|
label: "备注 1",
|
prop: "remarkOne",
|
},
|
{
|
label: "备注 2",
|
prop: "remarkTwo",
|
},
|
|
{
|
dataType: "action",
|
label: "操作",
|
align: "center",
|
fixed: "right",
|
width: 300,
|
operation: [
|
{
|
name: "编辑",
|
type: "primary",
|
link: true,
|
showHide: row => {
|
return row.status == 0;
|
},
|
clickFun: row => {
|
handleEdit(row);
|
},
|
},
|
{
|
name: "删除",
|
type: "danger",
|
link: true,
|
showHide: row => {
|
return row.status == 0;
|
},
|
clickFun: row => {
|
handleDelete(row);
|
},
|
},
|
{
|
name: "下发",
|
type: "text",
|
showHide: row => {
|
// 计算剩余方数
|
const remainingVolume =
|
(row.volume || 0) - (row.assignedQuantity || 0);
|
// 如果剩余方数小于等于0,禁止选择
|
return remainingVolume > 0;
|
},
|
clickFun: row => {
|
// 单独下发操作
|
// 设置表单数据
|
mergeForm.ids = [row.id];
|
mergeForm.materialCode = row.materialCode;
|
mergeForm.productName = row.productName || "";
|
mergeForm.specification = row.specification || "";
|
mergeForm.length = row.length || 0;
|
mergeForm.width = row.width || 0;
|
mergeForm.height = row.height || 0;
|
mergeForm.totalAssignedQuantity =
|
(Number(row.volume) - Number(row.assignedQuantity)).toFixed(4) || 0;
|
mergeForm.planCompleteTime = row.planCompleteTime || "";
|
sumAssignedQuantity.value = mergeForm.totalAssignedQuantity;
|
// 打开弹窗
|
isShowNewModal.value = true;
|
},
|
},
|
{
|
name: "追踪进度",
|
type: "text",
|
clickFun: row => {
|
handleTrackProgress(row);
|
},
|
},
|
],
|
},
|
]);
|
const tableData = ref([]);
|
const tableLoading = ref(false);
|
const page = reactive({
|
current: 1,
|
size: 100,
|
total: 0,
|
});
|
const selectedRows = ref([]);
|
|
// 产品类别汇总统计数据
|
const categorySummary = ref([]);
|
// 产品类别汇总弹窗控制
|
const showCategorySummaryDialog = ref(false);
|
|
// 合并下发弹窗控制
|
const isShowNewModal = ref(false);
|
// 合并下发表单数据
|
const mergeForm = reactive({
|
materialCode: "",
|
productName: "",
|
specification: "",
|
length: 0,
|
width: 0,
|
height: 0,
|
totalAssignedQuantity: 0,
|
planCompleteTime: "",
|
});
|
|
// 追踪进度弹窗控制
|
const showTrackProgressDialog = ref(false);
|
// 追踪进度表单数据
|
const trackProgressForm = reactive({
|
materialCode: "",
|
currentStatus: "",
|
completionRate: 0,
|
progressDetails: [],
|
remark: "",
|
});
|
|
// 导入相关
|
const importDialogRef = ref(null);
|
const importDialogVisible = ref(false);
|
const importAction =
|
import.meta.env.VITE_APP_BASE_API + "/productionPlan/import";
|
const importHeaders = ref({
|
Authorization: `Bearer ${getToken()}`,
|
});
|
|
// 新增/编辑相关
|
const dialogVisible = ref(false);
|
const operationType = ref("add"); // add | edit
|
const productOptions = ref([]);
|
const specificationOptions = ref([]);
|
const formRef = ref(null);
|
const form = reactive({
|
id: undefined,
|
applyNo: "",
|
customerName: "",
|
productMaterialId: undefined,
|
productMaterialSkuId: undefined,
|
productName: "",
|
specification: "",
|
materialCode: "",
|
quantity: 0,
|
volume: 0,
|
length: 0,
|
width: 0,
|
height: 0,
|
startDate: "",
|
endDate: "",
|
strength: "",
|
remarkOne: "",
|
remarkTwo: "",
|
});
|
const rules = reactive({
|
applyNo: [{ required: true, message: "请输入申请单编号", trigger: "blur" }],
|
customerName: [
|
{ required: true, message: "请输入客户名称", trigger: "blur" },
|
],
|
productMaterialSkuId: [
|
{ required: true, message: "请选择产品规格", trigger: "change" },
|
],
|
productMaterialId: [
|
{ required: true, message: "请选择产品", trigger: "change" },
|
],
|
});
|
|
// 处理追踪进度按钮点击
|
const handleTrackProgress = row => {
|
// 设置表单数据
|
trackProgressForm.materialCode = row.materialCode;
|
trackProgressForm.currentStatus = row.status;
|
|
// 生成模拟进度数据
|
trackProgressForm.progressDetails = generateProgressDetails(row.status);
|
|
// 计算完成率
|
trackProgressForm.completionRate = calculateCompletionRate(
|
trackProgressForm.progressDetails
|
);
|
trackProgressForm.remark = "";
|
|
// 打开弹窗
|
showTrackProgressDialog.value = true;
|
};
|
const onBlur = value => {
|
// 限制四位小数
|
mergeForm.totalAssignedQuantity = Number(value.toFixed(4));
|
};
|
|
const fetchProductOptions = () => {
|
return productTreeList({ type: 2 }).then(res => {
|
productOptions.value = convertIdToValue(res.data);
|
return res;
|
});
|
};
|
|
const convertIdToValue = data => {
|
return data.map(item => {
|
const newItem = {
|
value: `config_${item.configId}`, // 使用config_前缀确保唯一性
|
label: item.configName,
|
disabled: item.materialList.length === 0,
|
};
|
if (item.materialList && item.materialList.length > 0) {
|
newItem.children = item.materialList.map(material => ({
|
value: material.id, // 使用material的id作为value
|
label: material.materialName, // 使用materialName作为label
|
}));
|
}
|
|
return newItem;
|
});
|
};
|
|
const handleProductChange = value => {
|
form.productMaterialSkuId = undefined;
|
fetchSpecificationOptions(value);
|
};
|
|
const fetchSpecificationOptions = materialId => {
|
specificationOptions.value = [];
|
if (materialId) {
|
modelListPage({ materialId: materialId, size: -1, current: -1 }).then(
|
res => {
|
specificationOptions.value = res.data.records;
|
}
|
);
|
}
|
};
|
|
const handleChangeSpecification = value => {
|
form.materialCode = undefined;
|
const selectedModel = specificationOptions.value.find(
|
item => item.skuId === value
|
);
|
if (selectedModel) {
|
form.materialCode = selectedModel.materialCode;
|
// 解析规格字符串获取长宽高
|
const specification = selectedModel.specification;
|
if (specification) {
|
const dimensions = specification.match(/^(\d+)\*(\d+)\*(\d+)$/);
|
if (dimensions && dimensions.length === 4) {
|
form.length = parseInt(dimensions[1]);
|
form.width = parseInt(dimensions[2]);
|
form.height = parseInt(dimensions[3]);
|
}
|
}
|
}
|
};
|
|
// 生成模拟进度详情数据
|
const generateProgressDetails = status => {
|
const details = [
|
{
|
step: "计划确认",
|
status: "completed",
|
startTime: "2026-03-01 09:00:00",
|
endTime: "2026-03-01 10:00:00",
|
},
|
{
|
step: "物料准备",
|
status:
|
status === "completed"
|
? "completed"
|
: status === "processing"
|
? "completed"
|
: "pending",
|
startTime:
|
status === "completed" || status === "processing"
|
? "2026-03-01 10:30:00"
|
: "",
|
endTime:
|
status === "completed" || status === "processing"
|
? "2026-03-02 16:00:00"
|
: "",
|
},
|
{
|
step: "生产加工",
|
status:
|
status === "completed"
|
? "completed"
|
: status === "processing"
|
? "processing"
|
: "pending",
|
startTime:
|
status === "completed" || status === "processing"
|
? "2026-03-03 08:00:00"
|
: "",
|
endTime: status === "completed" ? "2026-03-08 17:00:00" : "",
|
},
|
{
|
step: "质量检验",
|
status: status === "completed" ? "completed" : "pending",
|
startTime: status === "completed" ? "2026-03-09 09:00:00" : "",
|
endTime: status === "completed" ? "2026-03-09 15:00:00" : "",
|
},
|
{
|
step: "入库",
|
status: status === "completed" ? "completed" : "pending",
|
startTime: status === "completed" ? "2026-03-10 10:00:00" : "",
|
endTime: status === "completed" ? "2026-03-10 11:00:00" : "",
|
},
|
];
|
return details;
|
};
|
|
// 计算完成率
|
const calculateCompletionRate = details => {
|
const completedSteps = details.filter(
|
step => step.status === "completed"
|
).length;
|
return Math.round((completedSteps / details.length) * 100);
|
};
|
|
// 处理进度更新
|
const handleUpdateProgress = () => {
|
// 这里可以添加更新进度的逻辑
|
ElMessage.success("进度更新成功");
|
showTrackProgressDialog.value = false;
|
};
|
|
const data = reactive({
|
searchForm: {
|
customerName: "",
|
productName: "",
|
specification: "",
|
materialCode: "",
|
applyNo: "",
|
dateRange: [],
|
},
|
});
|
const { searchForm } = toRefs(data);
|
|
// 查询列表
|
/** 搜索按钮操作 */
|
const handleQuery = () => {
|
page.current = 1;
|
getList();
|
};
|
|
/** 重置按钮操作 */
|
const handleReset = () => {
|
Object.assign(searchForm.value, {
|
customerName: "",
|
productName: "",
|
specification: "",
|
materialCode: "",
|
applyNo: "",
|
dateRange: [],
|
});
|
page.current = 1;
|
getList();
|
};
|
const pagination = obj => {
|
page.current = obj.page;
|
page.size = obj.limit;
|
getList();
|
};
|
// 计算产品类别汇总统计
|
const calculateCategorySummary = () => {
|
const summary = {};
|
|
// 遍历表格数据,按产品类别汇总
|
tableData.value.forEach(row => {
|
const category = row.materialCode;
|
if (!summary[category]) {
|
summary[category] = {
|
materialCode: category,
|
totalAssignedQuantity: 0,
|
};
|
}
|
summary[category].totalAssignedQuantity += (
|
Number(row.volume) - Number(row.assignedQuantity)
|
).toFixed(4);
|
});
|
|
// 转换为数组格式
|
categorySummary.value = Object.values(summary);
|
};
|
|
const getList = () => {
|
tableLoading.value = true;
|
// 构造搜索参数
|
const params = { ...searchForm.value, ...page };
|
params.startDate = params.dateRange ? params.dateRange[0] : "";
|
params.endDate = params.dateRange ? params.dateRange[1] : "";
|
delete params.dateRange;
|
productionPlanListPage(params)
|
.then(res => {
|
tableLoading.value = false;
|
|
tableData.value = res.data.records;
|
page.total = res.data.total;
|
// 计算产品类别汇总统计
|
calculateCategorySummary();
|
})
|
.catch(() => {
|
tableLoading.value = false;
|
});
|
};
|
|
// 选中的序列号
|
const selectedserialNo = ref("");
|
|
// 表格选择数据
|
const handleSelectionChange = selection => {
|
selectedRows.value = selection;
|
// 如果有选中的行,记录第一个选中行的序列号
|
if (selection.length > 0) {
|
selectedserialNo.value = selection[0].materialCode;
|
} else {
|
// 如果没有选中的行,清空序列号
|
selectedserialNo.value = "";
|
}
|
};
|
|
// 判断行是否可选择
|
const isSelectable = row => {
|
// 计算剩余方数
|
const remainingVolume = (row.volume || 0) - (row.assignedQuantity || 0);
|
// 如果剩余方数小于等于0,禁止选择
|
if (remainingVolume <= 0) {
|
return false;
|
}
|
// 如果没有选中的行,所有行都可选择
|
if (!selectedserialNo.value) {
|
return true;
|
}
|
// 如果有选中的行,只有序列号相同的行才可选择
|
return row.materialCode === selectedserialNo.value;
|
};
|
// 拉取数据按钮操作
|
const getLoadProdData = () => {
|
loadProdData()
|
.then(res => {
|
getList();
|
})
|
.catch(() => {});
|
};
|
const sumAssignedQuantity = ref(0);
|
// 处理合并下发按钮点击
|
const handleMerge = () => {
|
if (selectedRows.value.length === 0) {
|
ElMessage.warning("请选择要合并下发的生产计划");
|
return;
|
}
|
console.log(selectedRows.value);
|
// 计算总制造数量
|
const totalAssignedQuantity = selectedRows.value.reduce((sum, row) => {
|
return (
|
sum +
|
(row.volume == null
|
? 0
|
: (Number(row.volume) - Number(row.assignedQuantity)).toFixed(4))
|
);
|
}, 0);
|
sumAssignedQuantity.value = totalAssignedQuantity;
|
console.log(totalAssignedQuantity);
|
// 设置表单数据
|
const firstRow = selectedRows.value[0];
|
mergeForm.materialCode = selectedserialNo.value;
|
mergeForm.productName = firstRow.productName || "";
|
mergeForm.specification = firstRow.specification || "";
|
mergeForm.length = firstRow.length || 0;
|
mergeForm.width = firstRow.width || 0;
|
mergeForm.height = firstRow.height || 0;
|
mergeForm.totalAssignedQuantity = totalAssignedQuantity;
|
mergeForm.planCompleteTime = firstRow.planCompleteTime || "";
|
mergeForm.ids = selectedRows.value.map(row => row.id);
|
|
// 打开弹窗
|
isShowNewModal.value = true;
|
};
|
|
// 处理合并下发提交
|
const handleMergeSubmit = () => {
|
if (mergeForm.totalAssignedQuantity === 0) {
|
ElMessage.warning("请输入生产方数");
|
return;
|
}
|
console.log(sumAssignedQuantity.value, "sumAssignedQuantity");
|
// 计算当前选中行的总方数
|
const totalVolume = selectedRows.value.reduce((sum, row) => {
|
return sum + (Number(row.volume) - Number(row.assignedQuantity) || 0);
|
}, 0);
|
|
// 验证totalAssignedQuantity不能大于总方数
|
if (mergeForm.totalAssignedQuantity > sumAssignedQuantity.value) {
|
ElMessage.error("生产方数不能大于当前计算的总值");
|
return;
|
}
|
|
console.log(mergeForm, "mergeForm");
|
productionPlanCombine(mergeForm)
|
.then(res => {
|
if (res.code === 200) {
|
ElMessage.success("下发成功");
|
getList();
|
isShowNewModal.value = false;
|
// 可以选择刷新列表或其他操作
|
getList();
|
} else {
|
ElMessage.error(res.message || "下发失败");
|
}
|
})
|
.catch(err => {
|
console.error("合并下发异常:", err);
|
ElMessage.error("系统异常,合并下发失败");
|
});
|
// 可以选择刷新列表或其他操作
|
};
|
|
// 导入
|
const handleImport = () => {
|
importDialogVisible.value = true;
|
};
|
|
// 导出
|
const handleExport = () => {
|
const fileName = `生产计划.xlsx`;
|
exportProductionPlan()
|
.then(res => {
|
// 返回的数据是否为空
|
if (!res) {
|
proxy.$modal.msgError("导出失败,返回数据为空");
|
return;
|
}
|
|
const blob = new Blob([res], {
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
});
|
const downloadElement = document.createElement("a");
|
const href = window.URL.createObjectURL(blob);
|
|
downloadElement.style.display = "none";
|
downloadElement.href = href;
|
downloadElement.download = fileName;
|
|
document.body.appendChild(downloadElement);
|
downloadElement.click();
|
|
document.body.removeChild(downloadElement);
|
window.URL.revokeObjectURL(href);
|
|
proxy.$modal.msgSuccess("导出成功");
|
})
|
.catch(err => {
|
console.error("导出异常:", err);
|
proxy.$modal.msgError("系统异常,导出失败");
|
});
|
};
|
|
// 导入成功
|
const handleImportSuccess = response => {
|
if (response.code === 200) {
|
ElMessage.success("导入成功");
|
importDialogVisible.value = false;
|
getList();
|
} else {
|
ElMessage.error(response.message || "导入失败");
|
}
|
};
|
|
// 导入失败
|
const handleImportError = error => {
|
ElMessage.error("导入失败,请检查文件格式是否正确");
|
};
|
|
// 确认导入
|
const handleImportConfirm = () => {
|
if (importDialogRef.value) {
|
importDialogRef.value.submit();
|
}
|
};
|
|
// 下载模板
|
const handleDownloadTemplate = () => {
|
proxy.download(
|
"/productionPlan/downloadTemplate",
|
{},
|
"生产计划导入模板.xlsx"
|
);
|
};
|
|
// 关闭导入弹窗
|
const handleImportClose = () => {
|
importDialogVisible.value = false;
|
};
|
|
// 新增
|
const handleAdd = () => {
|
operationType.value = "add";
|
Object.assign(form, {
|
applyNo: "",
|
customerName: "",
|
productName: "",
|
productMaterialId: undefined,
|
productMaterialSkuId: undefined,
|
specification: "",
|
materialCode: "",
|
quantity: 0,
|
volume: 0,
|
length: 0,
|
width: 0,
|
height: 0,
|
startDate: "",
|
endDate: "",
|
strength: "",
|
remarkOne: "",
|
remarkTwo: "",
|
});
|
dialogVisible.value = true;
|
fetchProductOptions();
|
};
|
|
// 编辑
|
const handleEdit = row => {
|
operationType.value = "edit";
|
Object.assign(form, {
|
id: row.id,
|
applyNo: row.applyNo || "",
|
customerName: row.customerName || "",
|
productName: row.productName || "",
|
productMaterialId: row.productMaterialId || undefined,
|
productMaterialSkuId: row.productMaterialSkuId || undefined,
|
specification: row.specification || "",
|
materialCode: row.materialCode || "",
|
quantity: row.quantity || 0,
|
volume: row.volume || 0,
|
length: row.length || 0,
|
width: row.width || 0,
|
height: row.height || 0,
|
startDate: row.startDate || "",
|
endDate: row.endDate || "",
|
strength: row.strength || "",
|
remarkOne: row.remarkOne || "",
|
remarkTwo: row.remarkTwo || "",
|
});
|
dialogVisible.value = true;
|
fetchProductOptions();
|
fetchSpecificationOptions(row.productMaterialId);
|
};
|
|
// 删除
|
const handleDelete = row => {
|
proxy.$modal
|
.confirm("确认删除该生产计划?", "提示", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
productionPlanDelete([row.id])
|
.then(() => {
|
proxy.$modal.msgSuccess("删除成功");
|
getList();
|
})
|
.catch(() => {
|
proxy.$modal.msgError("删除失败");
|
});
|
})
|
.catch(() => {});
|
};
|
|
// 提交表单
|
const handleSubmit = () => {
|
formRef.value.validate(valid => {
|
if (valid) {
|
const payload = { ...form };
|
if (operationType.value === "add") {
|
productionPlanAdd(payload)
|
.then(() => {
|
proxy.$modal.msgSuccess(
|
operationType.value === "add" ? "新增成功" : "修改成功"
|
);
|
dialogVisible.value = false;
|
getList();
|
})
|
.catch(() => {
|
proxy.$modal.msgError(
|
operationType.value === "add" ? "新增失败" : "修改失败"
|
);
|
});
|
}
|
if (operationType.value === "edit") {
|
productionPlanUpdate(payload)
|
.then(() => {
|
proxy.$modal.msgSuccess(
|
operationType.value === "add" ? "新增成功" : "修改成功"
|
);
|
dialogVisible.value = false;
|
getList();
|
})
|
.catch(() => {
|
proxy.$modal.msgError(
|
operationType.value === "add" ? "新增失败" : "修改失败"
|
);
|
});
|
}
|
}
|
});
|
};
|
|
onMounted(() => {
|
getList();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.app-container {
|
padding: 24px;
|
background-color: #f0f2f5;
|
min-height: calc(100vh - 48px);
|
}
|
|
.search_form {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 24px;
|
padding: 20px;
|
background-color: #ffffff;
|
border-radius: 6px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
transition: all 0.3s ease;
|
|
&:hover {
|
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
|
}
|
}
|
|
.table_list {
|
// margin-bottom: 24px;
|
background-color: #ffffff;
|
border-radius: 6px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
overflow: hidden;
|
height: calc(100vh - 250px);
|
}
|
|
:deep(.el-table) {
|
border: none;
|
border-radius: 6px;
|
overflow: hidden;
|
box-shadow: 0 4px 16px rgba(102, 126, 234, 0.1);
|
|
.el-table__header-wrapper {
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
th {
|
background: transparent;
|
font-weight: 600;
|
color: #ffffff;
|
border-bottom: none;
|
padding: 16px 0;
|
font-size: 14px;
|
letter-spacing: 0.5px;
|
}
|
}
|
|
.el-table__body-wrapper {
|
tr {
|
transition: all 0.3s ease;
|
|
&:hover {
|
background: linear-gradient(
|
90deg,
|
rgba(102, 126, 234, 0.05) 0%,
|
rgba(118, 75, 162, 0.05) 100%
|
);
|
transform: scale(1.002);
|
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
|
}
|
|
td {
|
border-bottom: 1px solid #f0f0f0;
|
padding: 14px 0;
|
color: #303133;
|
font-size: 13px;
|
}
|
}
|
|
tr.current-row {
|
background: linear-gradient(
|
90deg,
|
rgba(102, 126, 234, 0.08) 0%,
|
rgba(118, 75, 162, 0.08) 100%
|
);
|
}
|
|
// 数值字段样式
|
.quantity-cell,
|
.volume-cell,
|
.dimension-cell {
|
font-weight: 600;
|
color: #409eff;
|
font-family: "Courier New", monospace;
|
font-size: 14px;
|
text-shadow: 0 1px 2px rgba(64, 158, 255, 0.2);
|
}
|
|
// 规格字段样式
|
.spec-cell {
|
color: #67c23a;
|
font-weight: 500;
|
|
padding: 4px 8px;
|
border-radius: 4px;
|
}
|
|
// 编码字段样式
|
.code-cell {
|
color: #e6a23c;
|
font-family: "Courier New", monospace;
|
font-weight: 500;
|
padding: 4px 8px;
|
border-radius: 4px;
|
}
|
|
// 日期字段样式
|
.date-cell {
|
color: #909399;
|
font-size: 12px;
|
font-style: italic;
|
}
|
|
// 状态标签样式
|
.status-tag {
|
&.pending {
|
background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
|
color: #d63031;
|
padding: 4px 12px;
|
border-radius: 12px;
|
font-weight: 500;
|
box-shadow: 0 2px 4px rgba(253, 203, 110, 0.3);
|
}
|
|
&.processing {
|
background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
|
color: #ffffff;
|
padding: 4px 12px;
|
border-radius: 12px;
|
font-weight: 500;
|
box-shadow: 0 2px 4px rgba(9, 132, 227, 0.3);
|
}
|
|
&.completed {
|
background: linear-gradient(135deg, #55efc4 0%, #00b894 100%);
|
color: #ffffff;
|
padding: 4px 12px;
|
border-radius: 12px;
|
font-weight: 500;
|
box-shadow: 0 2px 4px rgba(0, 184, 148, 0.3);
|
}
|
}
|
}
|
|
.el-table__empty-block {
|
padding: 60px 0;
|
background-color: #fafafa;
|
}
|
}
|
|
// 操作按钮样式
|
:deep(.el-table .cell .el-button--text) {
|
padding: 6px 10px;
|
border-radius: 4px;
|
transition: all 0.3s ease;
|
font-weight: 500;
|
|
&:hover {
|
background-color: rgba(64, 158, 255, 0.1);
|
transform: translateY(-1px);
|
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.2);
|
}
|
|
&:nth-of-type(1) {
|
color: #409eff;
|
background: linear-gradient(
|
135deg,
|
rgba(64, 158, 255, 0.1) 0%,
|
rgba(64, 158, 255, 0.05) 100%
|
);
|
}
|
|
&:nth-of-type(2) {
|
color: #67c23a;
|
background: linear-gradient(
|
135deg,
|
rgba(103, 194, 58, 0.1) 0%,
|
rgba(103, 194, 58, 0.05) 100%
|
);
|
}
|
}
|
|
// 信息展示样式
|
.info-display {
|
border-radius: 6px;
|
color: #303133;
|
font-size: 14px;
|
min-height: 32px;
|
display: flex;
|
align-items: center;
|
}
|
|
.pagination-container {
|
display: flex;
|
justify-content: flex-end;
|
padding: 16px 20px;
|
background-color: #ffffff;
|
border-top: 1px solid #ebeef5;
|
border-radius: 0 0 12px 12px;
|
}
|
|
:deep(.el-button) {
|
transition: all 0.3s ease;
|
|
&:hover {
|
transform: translateY(-1px);
|
}
|
}
|
|
:deep(.el-dialog) {
|
border-radius: 6px;
|
overflow: hidden;
|
|
.el-dialog__header {
|
background-color: #fafafa;
|
border-bottom: 1px solid #ebeef5;
|
padding: 20px 24px;
|
|
.el-dialog__title {
|
font-size: 16px;
|
font-weight: 600;
|
color: #303133;
|
}
|
}
|
|
.el-dialog__body {
|
padding: 24px;
|
}
|
|
.el-dialog__footer {
|
padding: 16px 24px;
|
border-top: 1px solid #ebeef5;
|
background-color: #fafafa;
|
}
|
}
|
|
:deep(.el-form) {
|
.el-form-item {
|
margin-bottom: 20px;
|
|
.el-form-item__label {
|
font-weight: 500;
|
color: #303133;
|
}
|
|
.el-input,
|
.el-select,
|
.el-date-picker,
|
.el-input-number {
|
width: 100%;
|
|
// .el-input__inner {
|
// border-radius: 6px;
|
// border: 1px solid #dcdfe6;
|
// transition: all 0.3s ease;
|
|
// &:focus {
|
// border-color: #409eff;
|
// box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
// }
|
// }
|
}
|
}
|
}
|
|
:deep(.el-tag) {
|
border-radius: 4px;
|
padding: 2px 8px;
|
font-size: 12px;
|
}
|
|
@media (max-width: 768px) {
|
.app-container {
|
padding: 16px;
|
}
|
|
.search_form {
|
flex-direction: column;
|
align-items: flex-start;
|
gap: 12px;
|
|
.el-form {
|
width: 100%;
|
|
.el-form-item {
|
width: 100%;
|
}
|
}
|
|
> div {
|
width: 100%;
|
display: flex;
|
gap: 12px;
|
|
.el-button {
|
flex: 1;
|
}
|
}
|
}
|
|
:deep(.el-table) {
|
th,
|
td {
|
padding: 10px 0;
|
font-size: 12px;
|
}
|
}
|
|
:deep(.el-dialog) {
|
width: 90% !important;
|
margin: 20px auto !important;
|
}
|
}
|
.consumption-value {
|
font-weight: bold;
|
color: #409eff;
|
}
|
|
.consumption-unit {
|
font-size: 12px;
|
color: #909399;
|
margin-left: 4px;
|
}
|
// .search_form {
|
// :deep(.el-form-item) {
|
// margin-bottom: 0px !important;
|
// }
|
// }
|
</style>
|