数据贯通完成,规程重构

This commit is contained in:
2026-04-27 20:37:59 +08:00
parent 5b38ef734a
commit b7161e9541
13 changed files with 1492 additions and 861 deletions

View File

@@ -0,0 +1,533 @@
<template>
<div class="plan-spec-page" v-loading="pageLoading">
<!-- 头部 -->
<div class="page-header">
<el-button type="text" icon="el-icon-arrow-left" @click="goBack">返回</el-button>
<span class="page-title">方案详情</span>
<span v-if="versionCode" class="version-badge">版本 {{ versionCode }}</span>
</div>
<!-- 可配置 / 不可配置 切换 -->
<div class="config-tabs">
<span
:class="['config-tab', { active: configMode === 'configurable' }]"
@click="configMode = 'configurable'"
>可配置</span>
<span
:class="['config-tab', { active: configMode === 'readonly' }]"
@click="configMode = 'readonly'"
>不可配置</span>
</div>
<div class="main-layout">
<!-- 左侧段分组 -->
<div class="left-tree">
<div
:class="['tree-item', { active: activeSegment === '' }]"
@click="activeSegment = ''"
>全部</div>
<div
v-for="seg in segmentOptions"
:key="seg.value"
:class="['tree-item', { active: activeSegment === seg.value }]"
@click="activeSegment = seg.value"
>
<span class="tree-arrow"></span> {{ seg.label }}
</div>
</div>
<!-- 右侧内容 -->
<div class="right-content">
<!-- 搜索栏 -->
<div class="search-bar">
<span class="search-label">点位名称</span>
<el-input
v-model="filterName"
placeholder="请输入"
size="small"
style="width:200px"
clearable
@keyup.enter.native="applyFilter"
/>
<el-button size="mini" type="primary" @click="applyFilter">查询</el-button>
<el-button size="mini" @click="resetFilter">重置</el-button>
<el-button
size="mini"
type="primary"
icon="el-icon-plus"
style="margin-left:auto"
@click="openPlanDialog()"
>新建方案点位</el-button>
</div>
<!-- 方案点位表 -->
<el-table
v-loading="planLoading"
:data="filteredPlans"
size="small"
highlight-current-row
@current-change="onPlanSelect"
>
<el-table-column label="序号" type="index" align="center" />
<el-table-column label="父级名称" show-overflow-tooltip>
<template slot-scope="{ row }">{{ segLabel(row.segmentType) }} {{ row.segmentName || '—' }}</template>
</el-table-column>
<el-table-column label="点位名称" prop="pointName" show-overflow-tooltip />
<el-table-column label="实际值ID" prop="actualValueId" show-overflow-tooltip />
<el-table-column label="L1设定值ID" prop="l1SetValueId" show-overflow-tooltip />
<el-table-column label="设定值" prop="targetValue" align="center" />
<el-table-column label="下限" prop="lowerLimit" align="center" />
<el-table-column label="上限" prop="upperLimit" align="center" />
<el-table-column label="操作" align="right">
<template slot-scope="{ row }">
<el-button type="text" size="mini" @click.stop="openPlanDialog(row)">编辑</el-button>
<el-button type="text" size="mini" @click.stop="openParamDialog(row)">参数</el-button>
<el-button type="text" size="mini" class="btn-danger" @click.stop="removePlan(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 方案参数面板 -->
<template v-if="selectedPlan">
<div class="param-header">
<span>{{ selectedPlan.pointName || selectedPlan.pointCode }} 参数</span>
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openParamDialog()">新建参数</el-button>
</div>
<el-table v-loading="paramLoading" :data="paramList" size="small">
<el-table-column label="参数编码" prop="paramCode" show-overflow-tooltip />
<el-table-column label="参数名称" prop="paramName" show-overflow-tooltip />
<el-table-column label="设定值" prop="targetValue" align="center" />
<el-table-column label="下限" prop="lowerLimit" align="center" />
<el-table-column label="上限" prop="upperLimit" align="center" />
<el-table-column label="单位" prop="unit" align="center" />
<el-table-column label="操作" align="right">
<template slot-scope="{ row }">
<el-button type="text" size="mini" @click="openParamDialog(null, row)">编辑</el-button>
<el-button type="text" size="mini" class="btn-danger" @click="removeParam(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
</div>
</div>
<!-- 方案点位 dialog -->
<el-dialog :title="planTitle" :visible.sync="planOpen" width="520px" append-to-body @close="planForm = {}">
<el-form ref="planFormRef" :model="planForm" :rules="planRules" label-width="90px" size="small">
<el-form-item label="段类型" prop="segmentType">
<el-select v-model="planForm.segmentType" style="width:100%">
<el-option v-for="s in segmentOptions" :key="s.value" :label="s.label" :value="s.value" />
</el-select>
</el-form-item>
<el-form-item label="段名称" prop="segmentName">
<el-input v-model="planForm.segmentName" maxlength="100" />
</el-form-item>
<el-form-item label="点位名称" prop="pointName">
<el-input v-model="planForm.pointName" maxlength="200" />
</el-form-item>
<el-form-item label="点位编码" prop="pointCode">
<el-input v-model="planForm.pointCode" maxlength="64" />
</el-form-item>
<el-form-item label="实际值ID" prop="actualValueId">
<el-input v-model="planForm.actualValueId" maxlength="64" />
</el-form-item>
<el-form-item label="L1设定值ID" prop="l1SetValueId">
<el-input v-model="planForm.l1SetValueId" maxlength="64" />
</el-form-item>
<el-form-item label="设定值" prop="targetValue">
<el-input v-model="planForm.targetValue" />
</el-form-item>
<el-form-item label="下限" prop="lowerLimit">
<el-input v-model="planForm.lowerLimit" />
</el-form-item>
<el-form-item label="上限" prop="upperLimit">
<el-input v-model="planForm.upperLimit" />
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="planForm.sortOrder" :min="0" controls-position="right" style="width:100%" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="planForm.remark" type="textarea" rows="2" maxlength="500" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button size="small" @click="planOpen = false">取消</el-button>
<el-button size="small" type="primary" :loading="planSubmitLoading" @click="submitPlan">确定</el-button>
</div>
</el-dialog>
<!-- 方案参数 dialog -->
<el-dialog :title="paramTitle" :visible.sync="paramOpen" width="480px" append-to-body @close="paramForm = {}">
<el-form ref="paramFormRef" :model="paramForm" :rules="paramRules" label-width="90px" size="small">
<el-form-item label="参数编码" prop="paramCode">
<el-input v-model="paramForm.paramCode" maxlength="64" />
</el-form-item>
<el-form-item label="参数名称" prop="paramName">
<el-input v-model="paramForm.paramName" maxlength="200" />
</el-form-item>
<el-form-item label="设定值">
<el-input v-model="paramForm.targetValue" />
</el-form-item>
<el-form-item label="下限">
<el-input v-model="paramForm.lowerLimit" />
</el-form-item>
<el-form-item label="上限">
<el-input v-model="paramForm.upperLimit" />
</el-form-item>
<el-form-item label="单位">
<el-input v-model="paramForm.unit" maxlength="32" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="paramForm.remark" type="textarea" rows="2" maxlength="500" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button size="small" @click="paramOpen = false">取消</el-button>
<el-button size="small" type="primary" :loading="paramSubmitLoading" @click="submitParam">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listProcessPlan, addProcessPlan, updateProcessPlan, delProcessPlan } from '@/api/wms/processPlan'
import { listProcessPlanParam, addProcessPlanParam, updateProcessPlanParam, delProcessPlanParam } from '@/api/wms/processPlanParam'
const SEGMENTS = [
{ label: '入口段', value: 'INLET' },
{ label: '工艺段', value: 'PROCESS' },
{ label: '出口段', value: 'OUTLET' }
]
export default {
name: 'ProcessSpecPlanSpec',
data() {
return {
pageLoading: false,
versionId: undefined,
versionCode: '',
specId: undefined,
configMode: 'configurable',
activeSegment: '',
filterName: '',
appliedFilterName: '',
segmentOptions: SEGMENTS,
planList: [],
planLoading: false,
selectedPlan: null,
paramList: [],
paramLoading: false,
planOpen: false,
planTitle: '',
planSubmitLoading: false,
planForm: {},
planRules: {
segmentType: [{ required: true, message: '请选择段类型', trigger: 'change' }],
pointName: [{ required: true, message: '点位名称不能为空', trigger: 'blur' }],
pointCode: [{ required: true, message: '点位编码不能为空', trigger: 'blur' }]
},
paramOpen: false,
paramTitle: '',
paramSubmitLoading: false,
paramForm: {},
paramRules: {
paramCode: [{ required: true, message: '参数编码不能为空', trigger: 'blur' }],
paramName: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }]
}
}
},
computed: {
filteredPlans() {
return this.planList.filter(p => {
const segOk = !this.activeSegment || p.segmentType === this.activeSegment
const nameOk = !this.appliedFilterName || (p.pointName || '').includes(this.appliedFilterName)
return segOk && nameOk
})
}
},
watch: {
$route: { immediate: true, handler() { this.syncFromRoute() } }
},
methods: {
syncFromRoute() {
const q = this.$route.query
this.versionId = q.versionId || undefined
this.versionCode = q.versionCode || ''
this.specId = q.specId || undefined
if (this.versionId) this.loadPlans()
},
goBack() { this.$router.go(-1) },
segLabel(val) {
const hit = SEGMENTS.find(s => s.value === val)
return hit ? hit.label : val || ''
},
loadPlans() {
this.planLoading = true
this.selectedPlan = null
this.paramList = []
listProcessPlan({ versionId: this.versionId, pageNum: 1, pageSize: 500 }).then(res => {
this.planList = res.rows || []
}).catch(e => console.error(e)).finally(() => { this.planLoading = false })
},
loadParams(planId) {
this.paramLoading = true
listProcessPlanParam({ planId, pageNum: 1, pageSize: 500 }).then(res => {
this.paramList = res.rows || []
}).catch(e => console.error(e)).finally(() => { this.paramLoading = false })
},
onPlanSelect(row) {
if (!row) return
this.selectedPlan = row
this.loadParams(row.planId)
},
applyFilter() { this.appliedFilterName = this.filterName },
resetFilter() { this.filterName = ''; this.appliedFilterName = '' },
openPlanDialog(row) {
this.planForm = row
? { ...row }
: {
versionId: this.versionId,
segmentType: 'PROCESS',
segmentName: undefined,
pointName: undefined,
pointCode: undefined,
actualValueId: undefined,
l1SetValueId: undefined,
targetValue: undefined,
lowerLimit: undefined,
upperLimit: undefined,
sortOrder: 0,
remark: undefined
}
this.planTitle = row ? '编辑方案点位' : '新建方案点位'
this.planOpen = true
this.$nextTick(() => this.$refs.planFormRef && this.$refs.planFormRef.clearValidate())
},
submitPlan() {
this.$refs.planFormRef.validate(ok => {
if (!ok) return
this.planSubmitLoading = true
const req = this.planForm.planId ? updateProcessPlan(this.planForm) : addProcessPlan(this.planForm)
req.then(() => {
this.$modal.msgSuccess('保存成功')
this.planOpen = false
this.loadPlans()
}).catch(e => console.error(e)).finally(() => { this.planSubmitLoading = false })
})
},
removePlan(row) {
this.$modal.confirm('确认删除该方案点位?').then(() => {
return delProcessPlan(row.planId)
}).then(() => {
this.$modal.msgSuccess('删除成功')
if (this.selectedPlan && this.selectedPlan.planId === row.planId) {
this.selectedPlan = null
this.paramList = []
}
this.loadPlans()
}).catch(() => {})
},
openParamDialog(planRow, paramRow) {
const targetPlan = planRow || this.selectedPlan
if (!targetPlan) { this.$message.warning('请先选择一个方案点位'); return }
if (!this.selectedPlan || this.selectedPlan.planId !== targetPlan.planId) {
this.selectedPlan = targetPlan
this.loadParams(targetPlan.planId)
}
this.paramForm = paramRow
? { ...paramRow }
: {
planId: targetPlan.planId,
paramCode: undefined,
paramName: undefined,
targetValue: undefined,
lowerLimit: undefined,
upperLimit: undefined,
unit: undefined,
remark: undefined
}
this.paramTitle = paramRow ? '编辑方案参数' : '新建方案参数'
this.paramOpen = true
this.$nextTick(() => this.$refs.paramFormRef && this.$refs.paramFormRef.clearValidate())
},
submitParam() {
this.$refs.paramFormRef.validate(ok => {
if (!ok) return
this.paramSubmitLoading = true
const req = this.paramForm.paramId ? updateProcessPlanParam(this.paramForm) : addProcessPlanParam(this.paramForm)
req.then(() => {
this.$modal.msgSuccess('保存成功')
this.paramOpen = false
this.loadParams(this.selectedPlan.planId)
}).catch(e => console.error(e)).finally(() => { this.paramSubmitLoading = false })
})
},
removeParam(row) {
this.$modal.confirm('确认删除该参数?').then(() => {
return delProcessPlanParam(row.paramId)
}).then(() => {
this.$modal.msgSuccess('删除成功')
this.loadParams(this.selectedPlan.planId)
}).catch(() => {})
}
}
}
</script>
<style scoped>
.plan-spec-page {
padding: 16px 20px;
min-height: 100%;
}
.page-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
}
.page-title {
font-size: 15px;
font-weight: 600;
color: #303133;
}
.version-badge {
font-size: 12px;
color: #909399;
background: #f0f2f5;
padding: 2px 8px;
border-radius: 10px;
}
.config-tabs {
display: flex;
gap: 0;
margin-bottom: 14px;
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
width: fit-content;
}
.config-tab {
padding: 5px 20px;
font-size: 12px;
cursor: pointer;
color: #606266;
background: #fff;
border: none;
border-right: 1px solid #dcdfe6;
transition: color .15s, background .15s;
user-select: none;
line-height: 1;
}
.config-tab:last-child { border-right: none; }
.config-tab:hover { color: #5F7BA0; }
.config-tab.active {
color: #fff;
background: #5F7BA0;
}
.main-layout {
display: flex;
gap: 0;
background: #fff;
border-radius: 4px;
border: 1px solid #ebeef5;
overflow: hidden;
}
.left-tree {
width: 120px;
flex-shrink: 0;
border-right: 1px solid #ebeef5;
padding: 8px 0;
}
.tree-item {
padding: 9px 16px;
font-size: 13px;
cursor: pointer;
color: #606266;
transition: all .15s;
user-select: none;
}
.tree-item:hover { background: #f5f7fa; }
.tree-item.active {
background: #5F7BA0;
color: #fff;
font-weight: 500;
}
.tree-item.active .tree-arrow { color: rgba(255,255,255,.6); }
.tree-arrow {
margin-right: 4px;
color: #c0c4cc;
}
.right-content {
flex: 1;
min-width: 0;
padding: 12px;
}
.search-bar {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
.search-label {
font-size: 13px;
color: #606266;
white-space: nowrap;
}
.param-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0 8px;
margin-top: 12px;
border-top: 1px solid #ebeef5;
font-size: 13px;
font-weight: 600;
color: #606266;
}
.el-table { border-radius: 0; }
::v-deep .el-button--primary {
color: #fff !important;
background: #5F7BA0 !important;
border-color: #5F7BA0 !important;
}
::v-deep .el-button--primary:hover,
::v-deep .el-button--primary:focus { background: #4d6a8e !important; border-color: #4d6a8e !important; }
::v-deep .el-button--primary:active { background: #4a6585 !important; border-color: #4a6585 !important; }
::v-deep .el-button--primary.is-disabled { opacity: .5; }
::v-deep .el-button:not(.el-button--primary):not(.el-button--text):not(.el-button--danger) {
color: #606266 !important;
background: #fff !important;
border-color: #dcdfe6 !important;
}
::v-deep .el-button:not(.el-button--primary):not(.el-button--text):not(.el-button--danger):hover {
color: #5F7BA0 !important;
border-color: #5F7BA0 !important;
}
::v-deep .el-button--text { background: transparent !important; border-color: transparent !important; }
::v-deep .el-button--text.btn-danger { color: #f56c6c !important; }
.btn-danger { color: #f56c6c; }
</style>