1. 重构接收产需单接口,支持按配置工序步骤生成明细 2. 新增工艺、工艺步骤CRUD接口与管理页面 3. 新增工序选择组件 4. 优化产需单页面,增加历史记录功能 5. 为排产明细添加工序步骤名称展示
590 lines
18 KiB
Vue
590 lines
18 KiB
Vue
<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>
|