生产工序
This commit is contained in:
196
klp-ui/src/views/wms/work/productionLine/GanttChartEcharts.vue
Normal file
196
klp-ui/src/views/wms/work/productionLine/GanttChartEcharts.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<div ref="ganttChart" class="echarts-gantt-wrapper" style="width:100%;height:320px;"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
const colorList = [
|
||||
'#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399',
|
||||
'#13C2C2', '#B37FEB', '#FF85C0', '#36CBCB', '#FFC53D'
|
||||
];
|
||||
function getColor(lineId, orderId, idx) {
|
||||
if (!lineId) return colorList[idx % colorList.length];
|
||||
const base = Math.abs(Number(lineId)) % colorList.length;
|
||||
if (!orderId) return colorList[base];
|
||||
return colorList[(base + Math.abs(Number(orderId)) % colorList.length) % colorList.length];
|
||||
}
|
||||
export default {
|
||||
name: 'GanttChartEcharts',
|
||||
props: {
|
||||
tasks: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
tasks: {
|
||||
handler() {
|
||||
this.renderChart();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.renderChart();
|
||||
window.addEventListener('resize', this.resizeChart);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.chart) this.chart.dispose();
|
||||
window.removeEventListener('resize', this.resizeChart);
|
||||
},
|
||||
methods: {
|
||||
renderChart() {
|
||||
if (!this.$refs.ganttChart) return;
|
||||
if (this.chart) this.chart.dispose();
|
||||
this.chart = echarts.init(this.$refs.ganttChart);
|
||||
if (!this.tasks || this.tasks.length === 0) {
|
||||
this.chart.clear();
|
||||
return;
|
||||
}
|
||||
// 处理数据,兼容多种字段名,保证任务名唯一
|
||||
const taskData = this.tasks.map((item, idx) => {
|
||||
const name = (item.remark || item.taskName || item.productName || item.name || `任务${idx+1}`) + (item.productName ? `-${item.productName}` : '');
|
||||
const start = item.startDate || item.start_time || item.start || item.start_date;
|
||||
const end = item.endDate || item.end_time || item.end || item.end_date;
|
||||
console.log(item.lineId, item.orderId, idx, '颜色取值依据')
|
||||
return {
|
||||
name,
|
||||
value: [start, end],
|
||||
itemStyle: {
|
||||
color: getColor(item.lineId, item.orderId, idx),
|
||||
borderRadius: 6
|
||||
},
|
||||
lineId: item.lineId,
|
||||
orderId: item.orderId,
|
||||
productId: item.productId,
|
||||
quantity: item.quantity,
|
||||
startDate: start,
|
||||
endDate: end
|
||||
};
|
||||
});
|
||||
// 先全部用 getColor 分配基础色
|
||||
taskData.forEach((item, idx) => {
|
||||
item._color = getColor(item.lineId, item.orderId, idx);
|
||||
});
|
||||
// 检查冲突(同产线时间重叠),有冲突的都标红
|
||||
for (let i = 0; i < taskData.length; i++) {
|
||||
for (let j = i + 1; j < taskData.length; j++) {
|
||||
if (
|
||||
taskData[i].lineId &&
|
||||
taskData[i].lineId === taskData[j].lineId &&
|
||||
new Date(taskData[i].value[0]) < new Date(taskData[j].value[1]) &&
|
||||
new Date(taskData[i].value[1]) > new Date(taskData[j].value[0])
|
||||
) {
|
||||
taskData[i]._color = '#F56C6C';
|
||||
taskData[j]._color = '#F56C6C';
|
||||
}
|
||||
}
|
||||
}
|
||||
// 颜色映射
|
||||
const colorMap = taskData.map(d => d._color);
|
||||
// Y轴任务名
|
||||
const yData = taskData.map(d => d.name);
|
||||
// X轴时间范围
|
||||
const minDate = Math.min(...taskData.map(d => new Date(d.value[0]).getTime()));
|
||||
const maxDate = Math.max(...taskData.map(d => new Date(d.value[1]).getTime()));
|
||||
// 自动调整时间轴范围,避免跨度过大导致任务条重叠
|
||||
const oneMonth = 30 * 24 * 3600 * 1000;
|
||||
let xMin = minDate - oneMonth;
|
||||
let xMax = maxDate + oneMonth;
|
||||
// 如果跨度大于一年,仍然只扩展一个月
|
||||
// 如果跨度小于一个月,最小跨度为两个月
|
||||
if (xMax - xMin < 2 * oneMonth) {
|
||||
xMax = xMin + 2 * oneMonth;
|
||||
}
|
||||
console.log(taskData);
|
||||
|
||||
// 配置
|
||||
const option = {
|
||||
tooltip: {
|
||||
confine: true,
|
||||
formatter: params => {
|
||||
// 用 params.data[2] 作为索引查找 taskData
|
||||
const idx = params.data && params.data[2];
|
||||
const d = (typeof idx === 'number' && taskData[idx]) ? taskData[idx] : {};
|
||||
return `任务:${d.name || ''}` +
|
||||
`<br/>开始:${d.startDate || ''}` +
|
||||
`<br/>结束:${d.endDate || ''}` +
|
||||
`<br/>日产能:${d.capacity != null ? d.capacity : d.capacity || ''}` +
|
||||
`<br/>总产能:${d.totalCapacity != null ? d.totalCapacity : d.total_capacity || ''}` +
|
||||
`<br/>目标生产:${d.planQuantity != null ? d.planQuantity : d.plan_quantity || ''}` +
|
||||
`<br/>天数:${d.days != null ? d.days : d.day || ''}` +
|
||||
`<br/>数量:${d.quantity != null ? d.quantity : ''}`;
|
||||
}
|
||||
},
|
||||
grid: { left: 120, right: 40, top: 30, bottom: 80 },
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
min: xMin,
|
||||
max: xMax,
|
||||
axisLabel: {
|
||||
formatter: v => echarts.format.formatTime('yyyy-MM-dd', v),
|
||||
rotate: 45 // 关键:倾斜45度
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: yData,
|
||||
axisTick: { show: false },
|
||||
axisLine: { show: false },
|
||||
axisLabel: { fontWeight: 'bold' }
|
||||
},
|
||||
series: [{
|
||||
type: 'custom',
|
||||
renderItem: (params, api) => {
|
||||
const categoryIndex = api.value(2);
|
||||
const start = api.coord([api.value(0), categoryIndex]);
|
||||
const end = api.coord([api.value(1), categoryIndex]);
|
||||
const barHeight = 18;
|
||||
// 颜色从 colorMap 查
|
||||
const idx = params.dataIndex;
|
||||
let fillColor = colorMap[idx] || '#409EFF';
|
||||
return {
|
||||
type: 'rect',
|
||||
shape: {
|
||||
x: start[0],
|
||||
y: start[1] - barHeight / 2,
|
||||
width: end[0] - start[0],
|
||||
height: barHeight,
|
||||
r: 6
|
||||
},
|
||||
style: {
|
||||
fill: fillColor
|
||||
}
|
||||
};
|
||||
},
|
||||
encode: {
|
||||
x: [0, 1],
|
||||
y: 2
|
||||
},
|
||||
data: taskData.map((d, i) => [d.value[0], d.value[1], i]),
|
||||
itemStyle: {
|
||||
borderRadius: 6
|
||||
}
|
||||
}]
|
||||
};
|
||||
this.chart.setOption(option);
|
||||
},
|
||||
resizeChart() {
|
||||
if (this.chart) this.chart.resize();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.echarts-gantt-wrapper {
|
||||
width: 100%;
|
||||
min-height: 220px;
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
416
klp-ui/src/views/wms/work/productionLine/index.vue
Normal file
416
klp-ui/src/views/wms/work/productionLine/index.vue
Normal file
@@ -0,0 +1,416 @@
|
||||
<template>
|
||||
<div class="production-line-page">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="列表" name="list">
|
||||
<!-- 原有列表内容 -->
|
||||
<div v-show="activeTab === 'list'">
|
||||
<!-- 保持原有内容不变 -->
|
||||
<div class="list-content">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="产线编号" prop="lineCode">
|
||||
<el-input
|
||||
v-model="queryParams.lineCode"
|
||||
placeholder="请输入产线编号"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="产线名称" prop="lineName">
|
||||
<el-input
|
||||
v-model="queryParams.lineName"
|
||||
placeholder="请输入产线名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
>修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="productionLineList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="产线编号" align="center" prop="lineCode" />
|
||||
<el-table-column label="产线名称" align="center" prop="lineName" />
|
||||
<el-table-column label="日产能" align="center" prop="capacity" />
|
||||
<el-table-column label="负载任务数/预计耗时" align="center" prop="unit">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.planDetailCount }}</span> /
|
||||
<span>{{ scope.row.totalPlanDays }}天</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产能单位" align="center" prop="unit" />
|
||||
<el-table-column label="是否启用" align="center" prop="isEnabled">
|
||||
<template slot-scope="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.isEnabled"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-text="启用"
|
||||
inactive-text="禁用"
|
||||
@change="handleEnabledChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 添加或修改产线对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="产线编号" prop="lineCode">
|
||||
<el-input v-model="form.lineCode" placeholder="请输入产线编号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产线名称" prop="lineName">
|
||||
<el-input v-model="form.lineName" placeholder="请输入产线名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日产能" prop="capacity">
|
||||
<el-input v-model="form.capacity" placeholder="请输入日产能" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产能单位" prop="unit">
|
||||
<el-input v-model="form.unit" placeholder="请输入产能单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否启用" prop="isEnabled">
|
||||
<el-switch
|
||||
v-model="form.isEnabled"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-text="启用"
|
||||
inactive-text="禁用"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="甘特图" name="gantt">
|
||||
<div v-show="activeTab === 'gantt'">
|
||||
<div style="margin-bottom: 16px; display: flex; gap: 16px; align-items: center;">
|
||||
<el-select v-model="selectedLineId" placeholder="选择产线" style="width: 180px" @change="fetchGanttData">
|
||||
<el-option v-for="item in lineList" :key="item.lineId" :label="item.lineName" :value="item.lineId" />
|
||||
</el-select>
|
||||
</div>
|
||||
<GanttChartEcharts :tasks="ganttTasks" v-if="ganttTasks.length > 0" />
|
||||
<el-empty v-else description="暂无甘特图数据" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GanttChartEcharts from './GanttChartEcharts.vue';
|
||||
import { listProductionLine, getProductionLine, delProductionLine, addProductionLine, updateProductionLine, ganttProductionLine } from "@/api/wms/productionLine";
|
||||
import { listOrder } from '@/api/wms/order';
|
||||
|
||||
export default {
|
||||
name: "ProductionLine",
|
||||
components: { GanttChartEcharts },
|
||||
data() {
|
||||
return {
|
||||
// 按钮loading
|
||||
buttonLoading: false,
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 产线表格数据
|
||||
productionLineList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
lineCode: undefined,
|
||||
lineName: undefined,
|
||||
capacity: undefined,
|
||||
unit: undefined,
|
||||
isEnabled: undefined,
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
lineCode: [
|
||||
{ required: true, message: "产线编号不能为空", trigger: "blur" }
|
||||
],
|
||||
lineName: [
|
||||
{ required: true, message: "产线名称不能为空", trigger: "blur" }
|
||||
],
|
||||
capacity: [
|
||||
{ required: true, message: "日产能不能为空", trigger: "blur" }
|
||||
],
|
||||
unit: [
|
||||
{ required: true, message: "产能单位不能为空", trigger: "blur" }
|
||||
],
|
||||
isEnabled: [
|
||||
{ required: true, message: "是否启用不能为空", trigger: "blur" }
|
||||
],
|
||||
},
|
||||
activeTab: 'list',
|
||||
lineList: [],
|
||||
orderList: [],
|
||||
selectedLineId: null,
|
||||
selectedOrderId: null,
|
||||
ganttTasks: [],
|
||||
ganttOrders: [],
|
||||
ganttOrder: {},
|
||||
ganttOrderDetails: []
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
this.loadLines();
|
||||
this.loadOrders();
|
||||
},
|
||||
methods: {
|
||||
/** 查询产线列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
listProductionLine(this.queryParams).then(response => {
|
||||
this.productionLineList = response.rows;
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false;
|
||||
this.reset();
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
lineId: undefined,
|
||||
lineCode: undefined,
|
||||
lineName: undefined,
|
||||
capacity: undefined,
|
||||
unit: undefined,
|
||||
isEnabled: undefined,
|
||||
remark: undefined,
|
||||
createTime: undefined,
|
||||
createBy: undefined,
|
||||
updateTime: undefined,
|
||||
updateBy: undefined
|
||||
};
|
||||
this.resetForm("form");
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm");
|
||||
this.handleQuery();
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.lineId)
|
||||
this.single = selection.length!==1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset();
|
||||
this.open = true;
|
||||
this.title = "添加产线";
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.loading = true;
|
||||
this.reset();
|
||||
const lineId = row.lineId || this.ids
|
||||
getProductionLine(lineId).then(response => {
|
||||
this.loading = false;
|
||||
this.form = response.data;
|
||||
this.open = true;
|
||||
this.title = "修改产线";
|
||||
});
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
this.buttonLoading = true;
|
||||
if (this.form.lineId != null) {
|
||||
updateProductionLine(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false;
|
||||
});
|
||||
} else {
|
||||
addProductionLine(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const lineIds = row.lineId || this.ids;
|
||||
this.$modal.confirm('是否确认删除产线编号为"' + lineIds + '"的数据项?').then(() => {
|
||||
this.loading = true;
|
||||
return delProductionLine(lineIds);
|
||||
}).then(() => {
|
||||
this.loading = false;
|
||||
this.getList();
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('wms/productionLine/export', {
|
||||
...this.queryParams
|
||||
}, `productionLine_${new Date().getTime()}.xlsx`)
|
||||
},
|
||||
handleEnabledChange(row) {
|
||||
// 只更新isEnabled字段
|
||||
const updateData = {
|
||||
lineId: row.lineId,
|
||||
isEnabled: row.isEnabled
|
||||
};
|
||||
this.loading = true;
|
||||
updateProductionLine(updateData)
|
||||
.then(() => {
|
||||
this.$modal.msgSuccess("状态更新成功");
|
||||
})
|
||||
.catch(() => {
|
||||
this.$modal.msgError("状态更新失败");
|
||||
// 回滚状态
|
||||
row.isEnabled = row.isEnabled === 1 ? 0 : 1;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
loadLines() {
|
||||
listProductionLine({}).then(res => {
|
||||
this.lineList = res.rows || [];
|
||||
});
|
||||
},
|
||||
loadOrders() {
|
||||
listOrder({}).then(res => {
|
||||
this.orderList = res.rows || [];
|
||||
});
|
||||
},
|
||||
fetchGanttData() {
|
||||
if (!this.selectedLineId) {
|
||||
this.ganttTasks = [];
|
||||
this.ganttOrders = [];
|
||||
return;
|
||||
}
|
||||
ganttProductionLine({
|
||||
lineId: this.selectedLineId
|
||||
}).then(res => {
|
||||
this.ganttTasks = res.data.tasks || [];
|
||||
this.ganttOrders = res.data.orders || [];
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.production-line-page {
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user