feat: 实现产需单按工序步骤生成排产明细功能
1. 重构接收产需单接口,支持按配置工序步骤生成明细 2. 新增工艺、工艺步骤CRUD接口与管理页面 3. 新增工序选择组件 4. 优化产需单页面,增加历史记录功能 5. 为排产明细添加工序步骤名称展示
This commit is contained in:
589
klp-ui/src/views/wms/post/aps/processes.vue
Normal file
589
klp-ui/src/views/wms/post/aps/processes.vue
Normal file
@@ -0,0 +1,589 @@
|
||||
<template>
|
||||
<div class="app-container" style="height: calc(100vh - 84px); display: flex;">
|
||||
<!-- 左侧工艺列表 -->
|
||||
<div class="left-panel" v-loading="processLoading"
|
||||
style="width: 35%; border-right: 1px solid #e4e7ed; overflow-y: auto;">
|
||||
<!-- 筛选区 -->
|
||||
<div class="filter-section" style="padding: 10px; border-bottom: 1px solid #e4e7ed;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
|
||||
<div style="display: flex; align-items: center; gap: 4px; flex-wrap: wrap;">
|
||||
<el-input v-model="queryParams.processName" placeholder="工艺名称" clearable @keyup.enter.native="handleSearch"
|
||||
style="width: 160px;" />
|
||||
<el-button class="aps-btn-red" icon="el-icon-search" size="mini" @click="handleSearch">筛选</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" class="aps-btn-silver" @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<el-button class="aps-btn-red" icon="el-icon-plus" size="mini" @click="handleAddProcess">新增工艺</el-button>
|
||||
<span style="font-size: 12px; color: #909399;">
|
||||
共 <span class="aps-total-count">{{ total }}</span> 条
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 列表区域 -->
|
||||
<div class="custom-list">
|
||||
<div class="list-body">
|
||||
<div v-for="item in processList" :key="item.processId" class="list-item"
|
||||
:class="{ 'list-item-active': currentProcess && currentProcess.processId === item.processId }"
|
||||
@click="handleProcessClick(item)">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||
<div style="font-weight: bold; font-size: 14px;">{{ item.processName }}</div>
|
||||
<div style="display: flex; gap: 4px;">
|
||||
<el-button type="text" icon="el-icon-edit" size="mini" @click.stop="handleUpdateProcess(item)"></el-button>
|
||||
<el-button type="text" icon="el-icon-delete" size="mini" style="color: #F56C6C;" @click.stop="handleDeleteProcess(item)"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #909399; margin-bottom: 4px;">
|
||||
{{ item.processDesc || '暂无描述' }}
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #909399;">
|
||||
备注: {{ item.remark || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="processList.length === 0 && !processLoading"
|
||||
style="padding: 40px; text-align: center; color: #909399;">
|
||||
暂无工艺数据
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
|
||||
@pagination="getProcessList" style="padding: 10px; margin-bottom: 10px !important;" />
|
||||
</div>
|
||||
|
||||
<!-- 右侧工艺步骤区域 -->
|
||||
<div class="right-panel" v-if="currentProcess && currentProcess.processId"
|
||||
style="flex: 1; display: flex; flex-direction: column;" v-loading="stepLoading">
|
||||
<div class="detail-panel">
|
||||
<!-- 工艺基本信息卡片 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-header">
|
||||
<span>工艺基本信息</span>
|
||||
</div>
|
||||
<div class="detail-card-body">
|
||||
<div class="form-grid-2">
|
||||
<div class="form-field"><label>工艺名称</label>
|
||||
<div class="field-value">{{ currentProcess.processName }}</div>
|
||||
</div>
|
||||
<div class="form-field"><label>工艺描述</label>
|
||||
<div class="field-value">{{ currentProcess.processDesc || '-' }}</div>
|
||||
</div>
|
||||
<div class="form-field" style="grid-column:1/3;"><label>备注</label>
|
||||
<div class="field-value">{{ currentProcess.remark || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工艺步骤卡片 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-header">
|
||||
<span>工艺步骤({{ stepList.length }} 步)</span>
|
||||
<el-button type="text" icon="el-icon-plus" size="mini" style="color: white;" @click="handleAddStep">新增步骤</el-button>
|
||||
</div>
|
||||
<div class="detail-card-body">
|
||||
<div v-if="stepList.length > 0" class="process-timeline-wrap">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(step, index) in stepList"
|
||||
:key="step.stepId"
|
||||
:timestamp="'步骤 ' + step.stepOrder"
|
||||
placement="top"
|
||||
:type="index === stepList.length - 1 ? 'success' : 'primary'"
|
||||
:hollow="index !== stepList.length - 1"
|
||||
size="large"
|
||||
>
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-header">
|
||||
<span class="step-name">{{ step.stepName }}</span>
|
||||
<div class="step-actions">
|
||||
<el-button type="text" icon="el-icon-edit" size="mini" @click="handleUpdateStep(step)"></el-button>
|
||||
<el-button type="text" icon="el-icon-delete" size="mini" style="color: #F56C6C;" @click="handleDeleteStep(step)"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="step.remark" class="step-remark">{{ step.remark }}</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
<el-empty v-else description="暂无工艺步骤" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else style="flex: 1; display: flex; flex-direction: column;">
|
||||
<el-empty description="选择工艺后查看步骤" />
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑工艺弹窗 -->
|
||||
<el-dialog :title="processTitle" :visible.sync="processOpen" width="500px" append-to-body>
|
||||
<el-form ref="processForm" :model="processForm" :rules="processRules" label-width="80px">
|
||||
<el-form-item label="工艺名称" prop="processName">
|
||||
<el-input v-model="processForm.processName" placeholder="请输入工艺名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工艺描述" prop="processDesc">
|
||||
<el-input v-model="processForm.processDesc" type="textarea" :rows="3" placeholder="请输入工艺描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="processForm.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" :loading="submitLoading" @click="submitProcessForm">确 定</el-button>
|
||||
<el-button @click="cancelProcess">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 新增/编辑工艺步骤弹窗 -->
|
||||
<el-dialog :title="stepTitle" :visible.sync="stepOpen" width="500px" append-to-body>
|
||||
<el-form ref="stepForm" :model="stepForm" :rules="stepRules" label-width="80px">
|
||||
<el-form-item label="步骤顺序" prop="stepOrder">
|
||||
<el-input-number v-model="stepForm.stepOrder" :min="1" :max="99" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="步骤名称" prop="stepName">
|
||||
<el-input v-model="stepForm.stepName" placeholder="请输入步骤名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="stepForm.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" :loading="submitLoading" @click="submitStepForm">确 定</el-button>
|
||||
<el-button @click="cancelStep">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listProcess, getProcess, addProcess, updateProcess, delProcess } from '@/api/aps/process'
|
||||
import { listProcessStep, addProcessStep, updateProcessStep, delProcessStep } from '@/api/aps/processStep'
|
||||
|
||||
export default {
|
||||
name: 'ApsProcesses',
|
||||
data() {
|
||||
return {
|
||||
// 工艺相关
|
||||
processLoading: false,
|
||||
processList: [],
|
||||
total: 0,
|
||||
currentProcess: null,
|
||||
queryParams: {
|
||||
processName: '',
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
},
|
||||
processTitle: '',
|
||||
processOpen: false,
|
||||
processForm: {},
|
||||
processRules: {
|
||||
processName: [
|
||||
{ required: true, message: '工艺名称不能为空', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
// 工艺步骤相关
|
||||
stepLoading: false,
|
||||
submitLoading: false,
|
||||
stepList: [],
|
||||
stepTitle: '',
|
||||
stepOpen: false,
|
||||
stepForm: {},
|
||||
stepRules: {
|
||||
stepOrder: [
|
||||
{ required: true, message: '步骤顺序不能为空', trigger: 'blur' }
|
||||
],
|
||||
stepName: [
|
||||
{ required: true, message: '步骤名称不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getProcessList()
|
||||
},
|
||||
methods: {
|
||||
/** 查询工艺列表 */
|
||||
getProcessList() {
|
||||
this.processLoading = true
|
||||
listProcess(this.queryParams).then(res => {
|
||||
this.processList = res.rows || []
|
||||
this.total = res.total || 0
|
||||
}).catch(() => {
|
||||
this.processList = []
|
||||
this.total = 0
|
||||
}).finally(() => {
|
||||
this.processLoading = false
|
||||
})
|
||||
},
|
||||
/** 搜索 */
|
||||
handleSearch() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getProcessList()
|
||||
},
|
||||
/** 重置 */
|
||||
resetQuery() {
|
||||
this.queryParams.processName = ''
|
||||
this.handleSearch()
|
||||
},
|
||||
/** 点击工艺 */
|
||||
handleProcessClick(process) {
|
||||
this.currentProcess = process
|
||||
this.getStepList(process.processId)
|
||||
},
|
||||
/** 查询工艺步骤列表 */
|
||||
getStepList(processId) {
|
||||
this.stepLoading = true
|
||||
listProcessStep({ processId }).then(res => {
|
||||
this.stepList = res.rows || []
|
||||
}).catch(() => {
|
||||
this.stepList = []
|
||||
}).finally(() => {
|
||||
this.stepLoading = false
|
||||
})
|
||||
},
|
||||
/** 新增工艺 */
|
||||
handleAddProcess() {
|
||||
this.resetProcessForm()
|
||||
this.processTitle = '新增工艺'
|
||||
this.processOpen = true
|
||||
},
|
||||
/** 修改工艺 */
|
||||
handleUpdateProcess(row) {
|
||||
this.resetProcessForm()
|
||||
const processId = row.processId || this.currentProcess.processId
|
||||
getProcess(processId).then(res => {
|
||||
this.processForm = res.data
|
||||
this.processTitle = '修改工艺'
|
||||
this.processOpen = true
|
||||
})
|
||||
},
|
||||
/** 提交工艺表单 */
|
||||
submitProcessForm() {
|
||||
this.$refs['processForm'].validate(valid => {
|
||||
if (valid) {
|
||||
this.submitLoading = true
|
||||
if (this.processForm.processId != undefined) {
|
||||
updateProcess(this.processForm).then(response => {
|
||||
this.$modal.msgSuccess('修改成功')
|
||||
this.processOpen = false
|
||||
this.getProcessList()
|
||||
}).finally(() => {
|
||||
this.submitLoading = false
|
||||
})
|
||||
} else {
|
||||
addProcess(this.processForm).then(response => {
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
this.processOpen = false
|
||||
this.getProcessList()
|
||||
}).finally(() => {
|
||||
this.submitLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 删除工艺 */
|
||||
handleDeleteProcess(row) {
|
||||
const processIds = row.processId || this.currentProcess.processId
|
||||
this.$modal.confirm('是否确认删除该工艺?').then(() => {
|
||||
return delProcess(processIds)
|
||||
}).then(() => {
|
||||
this.getProcessList()
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
this.currentProcess = null
|
||||
this.stepList = []
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 取消工艺弹窗 */
|
||||
cancelProcess() {
|
||||
this.processOpen = false
|
||||
this.resetProcessForm()
|
||||
},
|
||||
/** 重置工艺表单 */
|
||||
resetProcessForm() {
|
||||
this.processForm = {
|
||||
processId: undefined,
|
||||
processName: undefined,
|
||||
processDesc: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
this.resetForm('processForm')
|
||||
},
|
||||
/** 新增步骤 */
|
||||
handleAddStep() {
|
||||
this.resetStepForm()
|
||||
this.stepForm.processId = this.currentProcess.processId
|
||||
// 自动计算下一步骤顺序
|
||||
if (this.stepList.length > 0) {
|
||||
const maxOrder = Math.max(...this.stepList.map(s => s.stepOrder || 0))
|
||||
this.stepForm.stepOrder = maxOrder + 1
|
||||
} else {
|
||||
this.stepForm.stepOrder = 1
|
||||
}
|
||||
this.stepTitle = '新增工艺步骤'
|
||||
this.stepOpen = true
|
||||
},
|
||||
/** 修改步骤 */
|
||||
handleUpdateStep(row) {
|
||||
this.resetStepForm()
|
||||
this.stepForm = { ...row }
|
||||
this.stepTitle = '修改工艺步骤'
|
||||
this.stepOpen = true
|
||||
},
|
||||
/** 提交步骤表单 */
|
||||
submitStepForm() {
|
||||
this.$refs['stepForm'].validate(valid => {
|
||||
if (valid) {
|
||||
this.submitLoading = true
|
||||
if (this.stepForm.stepId != undefined) {
|
||||
updateProcessStep(this.stepForm).then(response => {
|
||||
this.$modal.msgSuccess('修改成功')
|
||||
this.stepOpen = false
|
||||
this.getStepList(this.currentProcess.processId)
|
||||
}).finally(() => {
|
||||
this.submitLoading = false
|
||||
})
|
||||
} else {
|
||||
addProcessStep(this.stepForm).then(response => {
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
this.stepOpen = false
|
||||
this.getStepList(this.currentProcess.processId)
|
||||
}).finally(() => {
|
||||
this.submitLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 删除步骤 */
|
||||
handleDeleteStep(row) {
|
||||
const stepIds = row.stepId
|
||||
this.$modal.confirm('是否确认删除该工艺步骤?').then(() => {
|
||||
return delProcessStep(stepIds)
|
||||
}).then(() => {
|
||||
this.getStepList(this.currentProcess.processId)
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 取消步骤弹窗 */
|
||||
cancelStep() {
|
||||
this.stepOpen = false
|
||||
this.resetStepForm()
|
||||
},
|
||||
/** 重置步骤表单 */
|
||||
resetStepForm() {
|
||||
this.stepForm = {
|
||||
stepId: undefined,
|
||||
processId: undefined,
|
||||
stepOrder: undefined,
|
||||
stepName: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
this.resetForm('stepForm')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './scss/aps-theme.scss';
|
||||
|
||||
.app-container {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
height: calc(100vh - 84px);
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
height: calc(100vh - 84px);
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.custom-list {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
padding: 12px;
|
||||
border-bottom: 2px solid #dddddd;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.list-item:hover {
|
||||
background-color: $aps-silver-1;
|
||||
}
|
||||
|
||||
.list-item-active {
|
||||
background-color: $aps-red-1;
|
||||
border-left: 3px solid $aps-red-2;
|
||||
}
|
||||
|
||||
.aps-total-count {
|
||||
color: $aps-red-2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.aps-btn-red {
|
||||
@include aps-btn-red;
|
||||
}
|
||||
|
||||
.aps-btn-silver {
|
||||
@include aps-btn-silver;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
flex: 1;
|
||||
overflow-y: scroll;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
background: $aps-bg;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background: $aps-white;
|
||||
border: 1px solid $aps-border;
|
||||
border-radius: $aps-radius;
|
||||
box-shadow: $aps-shadow-sm;
|
||||
}
|
||||
|
||||
.detail-card-header {
|
||||
background: linear-gradient(to right, $aps-red-2, $aps-red-3);
|
||||
color: white;
|
||||
padding: 8px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.detail-card-body {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.form-grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px 16px;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.form-field label {
|
||||
font-size: 11px;
|
||||
color: $aps-text-muted;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-field .field-value {
|
||||
font-size: 13px;
|
||||
color: $aps-text;
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid $aps-silver-mid;
|
||||
}
|
||||
|
||||
.aps-product-table {
|
||||
width: 100%;
|
||||
|
||||
::v-deep th {
|
||||
background: $aps-silver-1 !important;
|
||||
color: $aps-silver-5 !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// ====== 工艺步骤时间线 ======
|
||||
.process-timeline-wrap {
|
||||
padding: 20px 10px;
|
||||
|
||||
::v-deep .el-timeline {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
::v-deep .el-timeline-item__wrapper {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
::v-deep .el-timeline-item__timestamp {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: $aps-text-muted;
|
||||
}
|
||||
|
||||
::v-deep .el-timeline-item__tail {
|
||||
border-left-color: $aps-silver-3;
|
||||
}
|
||||
|
||||
::v-deep .el-timeline-item__node--primary {
|
||||
background-color: $aps-red-2;
|
||||
border-color: $aps-red-2;
|
||||
}
|
||||
|
||||
::v-deep .el-timeline-item__node--success {
|
||||
background-color: #67C23A;
|
||||
border-color: #67C23A;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
background: $aps-white;
|
||||
border: 1px solid $aps-border;
|
||||
border-radius: $aps-radius;
|
||||
padding: 12px 16px;
|
||||
transition: box-shadow 0.2s, border-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: $aps-red-2;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.step-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $aps-text;
|
||||
}
|
||||
|
||||
.step-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
.timeline-content:hover & {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.step-remark {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: $aps-text-muted;
|
||||
padding-top: 8px;
|
||||
border-top: 1px dashed $aps-silver-3;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user