新增工艺流程参数与补偿配置
Some checks failed
Release pipeline / Always run job (push) Has been cancelled
Release pipeline / publish (push) Has been cancelled

This commit is contained in:
sheng
2026-06-22 22:44:45 +08:00
parent c1e8626289
commit 7cf7caf31f
12 changed files with 1236 additions and 26 deletions

View File

@@ -0,0 +1,51 @@
import { request } from '@/api/_service'
const BASE = 'production_configuration/technology_model/calculation_script/'
function apiParams (method, data = {}) {
return {
method: `production_configuration_technology_model_calculation_script_${method}`,
platform: 'background',
...data
}
}
export function getCalculationScriptAll (data) {
return request({
url: BASE + 'all',
method: 'get',
params: apiParams('all', data)
})
}
export function getCalculationScriptList (data) {
return request({
url: BASE + 'list',
method: 'get',
params: apiParams('list', data)
})
}
export function createCalculationScript (data) {
return request({
url: BASE + 'create',
method: 'post',
data: apiParams('create', data)
})
}
export function editCalculationScript (data) {
return request({
url: BASE + 'edit',
method: 'put',
data: apiParams('edit', data)
})
}
export function deleteCalculationScript (data) {
return request({
url: BASE + 'delete',
method: 'delete',
data: apiParams('delete', data)
})
}

View File

@@ -65,3 +65,68 @@ export function moveDown (data) {
data: apiParams('move_down', data)
})
}
export function getOptionalParamsDetails (data) {
return request({
url: BASE + 'get_optional_params_details',
method: 'get',
params: apiParams('get_optional_params_details', data)
})
}
export function getOptionalAllParamsDetails (data) {
return request({
url: BASE + 'get_optional_all_params_details',
method: 'get',
params: apiParams('get_optional_all_params_details', data)
})
}
export function getStep (data) {
return request({
url: BASE + 'get_step',
method: 'get',
params: apiParams('get_step', data)
})
}
export function getTemperatureList (data) {
return request({
url: BASE + 'get_temperature_list',
method: 'get',
params: apiParams('get_temperature_list', data)
})
}
export function createTemperature (data) {
return request({
url: BASE + 'create_temperature',
method: 'post',
data: apiParams('create_temperature', data)
})
}
export function getTemperatureTemplate (data) {
return request({
url: BASE + 'get_temperature_template',
method: 'post',
responseType: 'blob',
data: apiParams('get_temperature_template', data)
})
}
export function getAllWorkingsubclassParams (data) {
return request({
url: BASE + 'get_all_workingsubclass_params',
method: 'get',
params: apiParams('get_all_workingsubclass_params', data)
})
}
export function addOptionalParams (data) {
return request({
url: BASE + 'add_optional_params',
method: 'post',
data: apiParams('add_optional_params', data)
})
}

View File

@@ -225,7 +225,69 @@
"length_1_100": "Length must be 1-100 characters",
"batch_bound_no_move": "Batch bound, cannot move",
"select_category": "Please select a category",
"log_operation_step": "Operation step: "
"log_operation_step": "Operation step: ",
"search": "Search",
"query": "Query",
"close": "Close",
"submit": "Submit",
"add": "Add",
"remove": "Remove",
"save": "Save",
"edit": "Edit",
"name": "Name",
"param": "Parameter",
"remark": "Remark",
"enter_name": "Please enter name",
"enter_param": "Please enter parameter",
"enter_code": "Please enter code",
"enter_remark": "Please enter remark",
"add_result_param": "Add Result Params",
"result_param_bind_tip": "Select result parameters to bind to the current process step.",
"all_result_params": "All Result Params",
"selected_result_params": "Selected Result Params",
"please_select_param": "Please select result parameters",
"temperature_step_range": "Select Temperature Compensation Step Range",
"start_step": "Start Step",
"end_step": "End Step",
"select_start_step": "Please select start step",
"select_end_step": "Please select end step",
"temperature_data": "Temperature Compensation Data",
"import_data": "Import Data",
"temperature_value": "Temperature",
"compensation_value": "Compensation Value",
"import_temperature_data": "Import Temperature Data",
"drag_file_here": "Drag file here, or ",
"click_to_upload": "click to upload",
"download_template": "Download Template",
"start_step_greater_than_end": "Start step cannot be greater than end step",
"select_at_least_one": "Please select at least one row",
"select_start_step_required": "Please select start step",
"select_end_step_required": "Please select end step",
"temperature_required": "Temperature is required",
"compensation_required": "Compensation value is required",
"temperature_must_be_numeric": "Temperature must be numeric",
"compensation_must_be_numeric": "Compensation value must be numeric",
"duplicate_temperature_exists": "Duplicate temperature exists",
"temperature_template_name": "Temperature Compensation Import Template",
"invalid_upload_format": "Please upload xls or xlsx file",
"temperature_import_columns_required": "File must contain Temperature and Compensation columns",
"add_calculation_script": "Add Calculation Script",
"edit_calculation_script": "Edit Calculation Script",
"script_code": "Script Code",
"script_name": "Script Name",
"interface_name": "Interface Name",
"interface_position": "Interface Position",
"start_position": "Start Position",
"end_position": "End Position",
"status": "Status",
"enabled": "Enabled",
"disabled": "Disabled",
"select_interface": "Please select interface",
"select_interface_position": "Please select interface position",
"script_content": "Script Content",
"script_content_required": "Please enter script content",
"length_1_45": "Length must be 1-45 characters",
"confirm_delete_script": "Are you sure to delete this calculation script?"
}
}
},

View File

@@ -191,7 +191,7 @@
"card": {
"flow_info": "工艺信息",
"flow_name": "流程名称",
"category": "流程类别",
"category": "类别",
"product": "产品型号",
"batch_bound": "绑定批次",
"yes": "是",
@@ -225,7 +225,69 @@
"length_1_100": "长度在 1 到 100 个字符",
"batch_bound_no_move": "已绑定批次,不可移动",
"select_category": "请选择流程类别",
"log_operation_step": "操作工序:"
"log_operation_step": "操作工序:",
"search": "搜索",
"query": "查询",
"close": "关闭",
"submit": "提交",
"add": "新增",
"remove": "移除",
"save": "保存",
"edit": "编辑",
"name": "名称",
"param": "参数",
"remark": "备注",
"enter_name": "请输入名称",
"enter_param": "请输入参数",
"enter_code": "请输入编码",
"enter_remark": "请输入备注",
"add_result_param": "添加结果参数",
"result_param_bind_tip": "请选择需要绑定到当前流程工序的结果参数。",
"all_result_params": "全部结果参数",
"selected_result_params": "已选结果参数",
"please_select_param": "请选择结果参数",
"temperature_step_range": "选择温度补偿工步范围",
"start_step": "开始工步",
"end_step": "结束工步",
"select_start_step": "请选择开始工步",
"select_end_step": "请选择结束工步",
"temperature_data": "录入温度补偿数据",
"import_data": "导入数据",
"temperature_value": "温度",
"compensation_value": "温度补偿值",
"import_temperature_data": "导入温度补偿数据",
"drag_file_here": "将文件拖到此处,或",
"click_to_upload": "点击上传",
"download_template": "下载模板",
"start_step_greater_than_end": "开始工步不能大于结束工步",
"select_at_least_one": "请至少选择一条数据",
"select_start_step_required": "请选择开始工步",
"select_end_step_required": "请选择结束工步",
"temperature_required": "温度不能为空",
"compensation_required": "温度补偿值不能为空",
"temperature_must_be_numeric": "温度必须为数字",
"compensation_must_be_numeric": "温度补偿值必须为数字",
"duplicate_temperature_exists": "存在重复温度值,请检查",
"temperature_template_name": "温度补偿导入模板",
"invalid_upload_format": "请上传 xls 或 xlsx 文件",
"temperature_import_columns_required": "文件必须包含【温度】和【温度补偿值】两列",
"add_calculation_script": "新增计算脚本",
"edit_calculation_script": "编辑计算脚本",
"script_code": "脚本编码",
"script_name": "脚本名称",
"interface_name": "接口名称",
"interface_position": "接口位置",
"start_position": "开始位置",
"end_position": "结束位置",
"status": "状态",
"enabled": "启用",
"disabled": "禁用",
"select_interface": "请选择接口",
"select_interface_position": "请选择接口位置",
"script_content": "脚本内容",
"script_content_required": "请输入脚本内容",
"length_1_45": "长度在 1 到 45 个字符",
"confirm_delete_script": "确定要删除该计算脚本吗?"
}
}
},

View File

@@ -0,0 +1,235 @@
<template>
<el-dialog :title="$t(title)" :visible.sync="visible" append-to-body :close-on-click-modal="false" width="70%" :before-close="handleClose">
<div class="toolbar">
<el-button type="primary" size="mini" icon="el-icon-plus" @click="openAdd">
{{ $t(key('add_calculation_script')) }}
</el-button>
</div>
<el-table :data="tableData" border height="480" v-loading="loading">
<el-table-column prop="id" label="ID" width="70" />
<el-table-column prop="code" :label="$t(key('script_code'))" min-width="130" show-overflow-tooltip />
<el-table-column prop="name" :label="$t(key('script_name'))" min-width="150" show-overflow-tooltip />
<el-table-column prop="interface_code" :label="$t(key('interface_name'))" min-width="220" show-overflow-tooltip />
<el-table-column :label="$t(key('interface_position'))" width="130">
<template slot-scope="{ row }">
{{ row.interface_position === 'begin' ? $t(key('start_position')) : $t(key('end_position')) }}
</template>
</el-table-column>
<el-table-column :label="$t(key('status'))" width="110">
<template slot-scope="{ row }">
<span v-if="Number(row.status) === 1" class="status-on"><i class="el-icon-circle-check" /> {{ $t(key('enabled')) }}</span>
<span v-else class="status-off"><i class="el-icon-circle-close" /> {{ $t(key('disabled')) }}</span>
</template>
</el-table-column>
<el-table-column prop="remark" :label="$t(key('remark'))" show-overflow-tooltip />
<el-table-column :label="$t(key('operation'))" width="150" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="mini" icon="el-icon-edit" @click="openEdit(row)">{{ $t(key('edit')) }}</el-button>
<el-button type="text" size="mini" icon="el-icon-delete" style="color:#F56C6C" @click="handleDelete(row)">{{ $t(key('delete')) }}</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog :title="$t(dialogTitle)" :visible.sync="formVisible" append-to-body width="900px" :close-on-click-modal="false">
<el-row :gutter="20">
<el-col :span="10">
<el-form ref="form" :model="formData" :rules="rules" label-width="120px">
<el-form-item :label="$t(key('script_code'))" prop="code">
<el-input v-model="formData.code" :placeholder="$t(key('enter_code'))" clearable />
</el-form-item>
<el-form-item :label="$t(key('script_name'))" prop="name">
<el-input v-model="formData.name" :placeholder="$t(key('enter_name'))" clearable />
</el-form-item>
<el-form-item :label="$t(key('interface_name'))" prop="interface_code">
<el-select v-model="formData.interface_code" :placeholder="$t(key('select_interface'))" clearable filterable style="width:100%">
<el-option v-for="item in interfaceOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item :label="$t(key('interface_position'))" prop="interface_position">
<el-select v-model="formData.interface_position" :placeholder="$t(key('select_interface_position'))" clearable style="width:100%">
<el-option :label="$t(key('start_position'))" value="begin" />
<el-option :label="$t(key('end_position'))" value="end" />
</el-select>
</el-form-item>
<el-form-item :label="$t(key('status'))" prop="status">
<el-radio-group v-model="formData.status">
<el-radio label="1">{{ $t(key('enabled')) }}</el-radio>
<el-radio label="0">{{ $t(key('disabled')) }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t(key('remark'))">
<el-input v-model="formData.remark" type="textarea" :rows="4" :placeholder="$t(key('enter_remark'))" />
</el-form-item>
</el-form>
</el-col>
<el-col :span="14">
<div class="script-editor-title">{{ $t(key('script_content')) }}</div>
<el-input v-model="scriptContent" type="textarea" :rows="20" class="script-editor" :placeholder="$t(key('script_content_required'))" />
</el-col>
</el-row>
<span slot="footer">
<el-button @click="formVisible = false">{{ $t(key('cancel')) }}</el-button>
<el-button type="primary" :loading="submitting" @click="submitForm">{{ $t(key('confirm')) }}</el-button>
</span>
</el-dialog>
</el-dialog>
</template>
<script>
import { i18nMixin } from '@/composables/useI18n'
import {
getCalculationScriptAll,
createCalculationScript,
editCalculationScript,
deleteCalculationScript
} from '@/api/production-master-data/calculation-script'
export default {
name: 'ProcessRoutingCardCalculationScript',
mixins: [i18nMixin('page.production_master_data.process_model.process_routing.card')],
props: {
title: { type: String, default: '' },
visible: { type: Boolean, default: false },
flowProcessId: { type: [String, Number], default: '' }
},
data () {
return {
loading: false,
submitting: false,
tableData: [],
formVisible: false,
dialogTitle: '',
handleType: 'create',
editId: '',
scriptContent: '',
formData: { code: '', name: '', interface_code: '', interface_position: '', status: '1', remark: '' },
interfaceOptions: [
{ label: 'set_battery_process_result成品段下料接口', value: 'set_battery_process_result' },
{ label: 'set_material_input制片段上料接口', value: 'set_material_input' },
{ label: 'get_battery_process_route电芯路由接口', value: 'get_battery_process_route' },
{ label: 'set_wip_output_by_item_id裸电芯下料接口', value: 'set_wip_output_by_item_id' },
{ label: 'set_tray_process_result托盘下料接口', value: 'set_tray_process_result' }
],
rules: {
code: [
{ required: true, message: this.key('enter_code'), trigger: 'blur' },
{ min: 1, max: 45, message: this.key('length_1_45'), trigger: 'blur' }
],
name: [
{ required: true, message: this.key('enter_name'), trigger: 'blur' },
{ min: 1, max: 45, message: this.key('length_1_45'), trigger: 'blur' }
],
interface_code: [{ required: true, message: this.key('select_interface'), trigger: 'change' }],
interface_position: [{ required: true, message: this.key('select_interface_position'), trigger: 'change' }]
}
}
},
watch: {
visible (val) {
if (val) this.fetchData()
},
flowProcessId (val) {
if (val && this.visible) this.fetchData()
}
},
methods: {
handleClose () {
this.$emit('close')
},
async fetchData () {
if (!this.flowProcessId) return
this.loading = true
try {
const res = await getCalculationScriptAll({ process_id: this.flowProcessId })
this.tableData = (res && res.data) || res || []
} finally {
this.loading = false
}
},
resetForm () {
this.formData = { code: '', name: '', interface_code: '', interface_position: '', status: '1', remark: '' }
this.scriptContent = ''
this.editId = ''
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
},
openAdd () {
this.handleType = 'create'
this.dialogTitle = this.key('add_calculation_script')
this.resetForm()
this.formVisible = true
},
openEdit (row) {
this.handleType = 'edit'
this.dialogTitle = this.key('edit_calculation_script')
this.editId = row.id
this.formData = {
code: row.code || '',
name: row.name || '',
interface_code: row.interface_code || '',
interface_position: row.interface_position || '',
status: row.status !== undefined ? String(row.status) : '1',
remark: row.remark || ''
}
this.scriptContent = row.calculation_script_content || ''
this.formVisible = true
},
submitForm () {
this.$refs.form.validate(async valid => {
if (!valid) return
if (!this.scriptContent) {
this.$message.error(this.$t(this.key('script_content_required')))
return
}
this.submitting = true
try {
const payload = {
...this.formData,
process_id: this.flowProcessId,
calculation_script_content: this.scriptContent
}
if (this.handleType === 'create') {
await createCalculationScript(payload)
} else {
await editCalculationScript({ ...payload, id: this.editId })
}
this.$message.success(this.$t(this.key('operation_success')))
this.formVisible = false
this.fetchData()
} finally {
this.submitting = false
}
})
},
async handleDelete (row) {
await this.$confirm(this.$t(this.key('confirm_delete_script')), this.$t(this.key('tip')), {
confirmButtonText: this.$t(this.key('confirm')),
cancelButtonText: this.$t(this.key('cancel')),
type: 'warning',
closeOnClickModal: false
})
await deleteCalculationScript({ id: row.id })
this.$message.success(this.$t(this.key('operation_success')))
this.fetchData()
}
}
}
</script>
<style scoped>
.toolbar {
margin-bottom: 10px;
}
.status-on {
color: #67C23A;
}
.status-off {
color: #F56C6C;
}
.script-editor-title {
margin-bottom: 8px;
color: #606266;
}
.script-editor /deep/ textarea {
font-family: Menlo, Consolas, monospace;
}
</style>

View File

@@ -0,0 +1,253 @@
<template>
<el-dialog
:title="$t(title)"
:visible.sync="visible"
append-to-body
:close-on-click-modal="false"
width="80%"
:before-close="handleClose"
>
<div class="result-param-layout">
<el-card class="filter-panel" shadow="never">
<div slot="header">
<span>{{ $t(key('search')) }}</span>
<el-button type="text" style="float:right;padding:3px 0" @click="onSearch">
{{ $t(key('query')) }}
</el-button>
</div>
<el-form label-position="top" :model="searchForm" size="mini">
<el-form-item :label="$t(key('name'))">
<el-input v-model.trim="searchForm.name" :placeholder="$t(key('enter_name'))" clearable />
</el-form-item>
<el-form-item :label="$t(key('param'))">
<el-input v-model.trim="searchForm.code" :placeholder="$t(key('enter_param'))" clearable />
</el-form-item>
</el-form>
</el-card>
<div class="table-panel">
<div class="table-actions">
<el-button type="primary" size="mini" icon="el-icon-plus" @click="openBindDialog">
{{ $t(key('add_result_param')) }}
</el-button>
</div>
<el-table :data="tableData" v-loading="loading" border height="480">
<el-table-column prop="name" :label="$t(key('name'))" width="220" show-overflow-tooltip />
<el-table-column prop="code" :label="$t(key('param'))" width="180" show-overflow-tooltip />
<el-table-column prop="field_type" :label="$t(key('category'))" width="140" />
<el-table-column prop="remark" :label="$t(key('remark'))" show-overflow-tooltip />
</el-table>
</div>
</div>
<el-pagination
style="margin-top:12px;text-align:right"
:current-page="pagination.current"
:page-size="pagination.size"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="onSizeChange"
@current-change="onCurrentChange"
/>
<div slot="footer">
<el-button @click="handleClose">{{ $t(key('close')) }}</el-button>
</div>
<el-dialog
:title="$t(key('add_result_param'))"
:visible.sync="bindVisible"
append-to-body
width="720px"
:close-on-click-modal="false"
>
<el-alert :title="$t(key('result_param_bind_tip'))" type="warning" :closable="false" show-icon />
<div class="bind-layout">
<el-card class="bind-card" shadow="never">
<div slot="header">
<span>{{ $t(key('all_result_params')) }}</span>
<el-button type="text" style="float:right;padding:3px 0">{{ checkedCount }} / {{ leftParams.length }}</el-button>
</div>
<el-tree
ref="leftTree"
:data="leftParams"
show-checkbox
node-key="code"
accordion
:check-strictly="true"
:default-checked-keys="defaultCheckedKeys"
:props="treeProps"
@check="onTreeCheck"
/>
</el-card>
<div class="bind-arrow">
<el-button type="primary" icon="el-icon-arrow-right" circle @click="syncRightParams" />
</div>
<el-card class="bind-card" shadow="never">
<div slot="header">
<span>{{ $t(key('selected_result_params')) }}</span>
<el-button type="text" style="float:right;padding:3px 0">{{ rightParams.length }}</el-button>
</div>
<el-tree :data="rightParams" node-key="code" :props="treeProps" />
</el-card>
</div>
<span slot="footer">
<el-button size="mini" @click="bindVisible = false">{{ $t(key('cancel')) }}</el-button>
<el-button size="mini" type="primary" @click="submitBindParams">{{ $t(key('submit')) }}</el-button>
</span>
</el-dialog>
</el-dialog>
</template>
<script>
import { i18nMixin } from '@/composables/useI18n'
import {
getOptionalParamsDetails,
getAllWorkingsubclassParams,
addOptionalParams
} from '@/api/production-master-data/process-routing-card'
function readListData (res) {
const data = res && res.data ? res.data : res
if (!data) return { list: [], total: 0 }
if (Array.isArray(data)) return { list: data, total: data.length }
const list = data.data || data.list || []
return { list: Array.isArray(list) ? list : [], total: Number(data.count || data.total || list.length || 0) }
}
export default {
name: 'ProcessRoutingCardResultParam',
mixins: [i18nMixin('page.production_master_data.process_model.process_routing.card')],
props: {
title: { type: String, default: '' },
visible: { type: Boolean, default: false },
workingsubclassId: { type: [String, Number], default: '' },
flowProcessId: { type: [String, Number], default: '' }
},
data () {
return {
loading: false,
tableData: [],
searchForm: { name: '', code: '' },
pagination: { current: 1, size: 10, total: 0 },
bindVisible: false,
leftParams: [],
rightParams: [],
defaultCheckedKeys: [],
selectedCodes: [],
changed: false,
treeProps: { children: 'children', label: 'label' }
}
},
computed: {
checkedCount () {
return this.selectedCodes.length
}
},
watch: {
visible (val) {
if (val) this.fetchData(true)
},
flowProcessId (val) {
if (val && this.visible) this.fetchData(true)
}
},
methods: {
handleClose () {
this.pagination.current = 1
this.pagination.size = 10
this.$emit('close')
},
onSearch () {
this.fetchData(true)
},
onSizeChange (size) {
this.pagination.size = size
this.fetchData(true)
},
onCurrentChange (current) {
this.pagination.current = current
this.fetchData(false)
},
async fetchData (resetPage = false) {
if (!this.flowProcessId) return
if (resetPage) this.pagination.current = 1
this.loading = true
try {
const res = await getOptionalParamsDetails({
page_no: this.pagination.current,
page_size: this.pagination.size,
process_id: this.flowProcessId,
...this.searchForm
})
const { list, total } = readListData(res)
this.tableData = list
this.pagination.total = total
} finally {
this.loading = false
}
},
async openBindDialog () {
if (!this.flowProcessId) return
const res = await getAllWorkingsubclassParams({ process_id: this.flowProcessId })
const data = res && res.data ? res.data : res || {}
this.leftParams = data.left_optional_params || []
this.rightParams = data.right_optional_params || []
this.defaultCheckedKeys = data.default_checked_keys || []
this.selectedCodes = [...this.defaultCheckedKeys]
this.changed = false
this.bindVisible = true
},
onTreeCheck () {
this.selectedCodes = this.$refs.leftTree ? this.$refs.leftTree.getCheckedKeys() : []
this.changed = true
},
syncRightParams () {
const checked = this.$refs.leftTree ? this.$refs.leftTree.getCheckedNodes() : []
this.rightParams = checked.map(item => ({ ...item }))
this.changed = true
},
async submitBindParams () {
if (!this.changed) {
this.$message.error(this.$t(this.key('please_select_param')))
return
}
await addOptionalParams({
process_id: this.flowProcessId,
optional_params: JSON.stringify(this.rightParams)
})
this.$message.success(this.$t(this.key('operation_success')))
this.bindVisible = false
this.fetchData(true)
}
}
}
</script>
<style scoped>
.result-param-layout,
.bind-layout {
display: flex;
gap: 16px;
}
.filter-panel {
width: 220px;
flex: none;
}
.table-panel {
flex: 1;
min-width: 0;
}
.table-actions {
margin-bottom: 10px;
}
.bind-card {
width: 300px;
min-height: 360px;
}
.bind-arrow {
display: flex;
align-items: center;
}
</style>

View File

@@ -0,0 +1,209 @@
<template>
<el-drawer :visible.sync="visible" :wrapper-closable="false" :with-header="false" size="50%">
<div class="drawer-title">
<el-page-header @back="handleClose" :content="$t(title)" />
</div>
<div class="section">
<div class="section-title">| {{ $t(key('temperature_step_range')) }}</div>
<el-form :inline="true" :model="formTemp" size="mini">
<el-form-item :label="$t(key('start_step'))">
<el-select v-model="formTemp.start_work_step_id" :placeholder="$t(key('select_start_step'))" @change="checkStepRange">
<el-option v-for="item in stepOptions" :key="item.id" :label="item.suffix_name || item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="$t(key('end_step'))">
<el-select v-model="formTemp.end_work_step_id" :placeholder="$t(key('select_end_step'))" @change="checkStepRange">
<el-option v-for="item in stepOptions" :key="item.id" :label="item.suffix_name || item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
</div>
<div class="section">
<div class="section-title">| {{ $t(key('temperature_data')) }}</div>
<div class="toolbar">
<el-button size="mini" icon="el-icon-plus" @click="addRow">{{ $t(key('add')) }}</el-button>
<el-button size="mini" icon="el-icon-delete" @click="removeRows">{{ $t(key('remove')) }}</el-button>
<el-button size="mini" type="primary" icon="el-icon-check" @click="saveRows">{{ $t(key('save')) }}</el-button>
<el-button size="mini" icon="el-icon-upload2" @click="importVisible = true">{{ $t(key('import_data')) }}</el-button>
</div>
<el-table ref="table" :data="tableData" border height="560" @selection-change="selectedRows = $event">
<el-table-column type="selection" width="48" />
<el-table-column :label="$t(key('temperature_value'))" min-width="160">
<template slot-scope="{ row }">
<el-input v-model="row.temperature" size="mini" />
</template>
</el-table-column>
<el-table-column :label="$t(key('compensation_value'))" min-width="160">
<template slot-scope="{ row }">
<el-input v-model="row.compensation_value" size="mini" />
</template>
</el-table-column>
</el-table>
</div>
<el-dialog :title="$t(key('import_temperature_data'))" :visible.sync="importVisible" append-to-body width="420px">
<el-upload
drag
action=""
:auto-upload="false"
:show-file-list="false"
accept=".xls,.xlsx"
:on-change="onFileChange"
>
<i class="el-icon-upload" />
<div class="el-upload__text">{{ $t(key('drag_file_here')) }}<em>{{ $t(key('click_to_upload')) }}</em></div>
<div slot="tip" class="el-upload__tip">{{ fileName }}</div>
</el-upload>
<span slot="footer">
<el-button type="primary" :loading="templateLoading" @click="downloadTemplate">{{ $t(key('download_template')) }}</el-button>
</span>
</el-dialog>
</el-drawer>
</template>
<script>
import { i18nMixin } from '@/composables/useI18n'
import { getStep, getTemperatureList, createTemperature, getTemperatureTemplate } from '@/api/production-master-data/process-routing-card'
import { downloadRename, readExcel } from '@/utils/file'
export default {
name: 'ProcessRoutingCardTemperatureCompensation',
mixins: [i18nMixin('page.production_master_data.process_model.process_routing.card')],
props: {
title: { type: String, default: '' },
visible: { type: Boolean, default: false },
flowProcessId: { type: [String, Number], default: '' }
},
data () {
return {
formTemp: { start_work_step_id: '', end_work_step_id: '' },
stepOptions: [],
tableData: [],
selectedRows: [],
importVisible: false,
fileName: '',
templateLoading: false
}
},
watch: {
visible (val) {
if (val) this.bootstrap()
}
},
methods: {
handleClose () {
this.$emit('close')
},
async bootstrap () {
await Promise.all([this.loadSteps(), this.loadTemperatureList()])
},
async loadSteps () {
const res = await getStep({})
this.stepOptions = (res && res.data) || res || []
},
async loadTemperatureList () {
if (!this.flowProcessId) return
const res = await getTemperatureList({ process_id: this.flowProcessId })
const data = (res && res.data) || res || {}
this.tableData = data.data || []
this.formTemp.start_work_step_id = data.start_work_step_id || ''
this.formTemp.end_work_step_id = data.end_work_step_id || ''
},
checkStepRange () {
const start = Number(this.formTemp.start_work_step_id)
const end = Number(this.formTemp.end_work_step_id)
if (start && end && start > end) this.$message.warning(this.$t(this.key('start_step_greater_than_end')))
},
addRow () {
this.tableData.push({ temperature: '', compensation_value: '' })
},
removeRows () {
if (!this.selectedRows.length) {
this.$message.error(this.$t(this.key('select_at_least_one')))
return
}
this.tableData = this.tableData.filter(row => !this.selectedRows.includes(row))
},
validateRows () {
if (!this.formTemp.start_work_step_id) return this.$t(this.key('select_start_step_required'))
if (!this.formTemp.end_work_step_id) return this.$t(this.key('select_end_step_required'))
const seen = {}
for (const row of this.tableData) {
if (row.temperature === '' || row.temperature === null || row.temperature === undefined) return this.$t(this.key('temperature_required'))
if (row.compensation_value === '' || row.compensation_value === null || row.compensation_value === undefined) return this.$t(this.key('compensation_required'))
if (!/^[-]?\d+(\.\d+)?$/.test(String(row.temperature))) return this.$t(this.key('temperature_must_be_numeric'))
if (!/^[-]?\d+(\.\d+)?$/.test(String(row.compensation_value))) return this.$t(this.key('compensation_must_be_numeric'))
if (seen[row.temperature]) return this.$t(this.key('duplicate_temperature_exists'))
seen[row.temperature] = true
}
return ''
},
async saveRows () {
const error = this.validateRows()
if (error) {
this.$message.error(error)
return
}
await createTemperature({
temp_data: this.tableData,
start_work_step_id: this.formTemp.start_work_step_id,
end_work_step_id: this.formTemp.end_work_step_id,
process_id: this.flowProcessId
})
this.$message.success(this.$t(this.key('operation_success')))
this.loadTemperatureList()
},
async downloadTemplate () {
this.templateLoading = true
try {
const res = await getTemperatureTemplate({})
downloadRename(res, 'xlsx', this.$t(this.key('temperature_template_name')))
} finally {
this.templateLoading = false
}
},
async onFileChange (file) {
if (!file || !/\.(xls|xlsx)$/i.test(file.name)) {
this.$message.error(this.$t(this.key('invalid_upload_format')))
return
}
this.fileName = file.name
const rows = await readExcel(file.raw)
const next = [...this.tableData]
for (const row of rows) {
if (!Object.prototype.hasOwnProperty.call(row, '温度') || !Object.prototype.hasOwnProperty.call(row, '温度补偿值')) {
this.$message.error(this.$t(this.key('temperature_import_columns_required')))
return
}
if (next.some(item => String(item.temperature) === String(row['温度']))) {
this.$message.error(this.$t(this.key('duplicate_temperature_exists')))
return
}
next.push({ temperature: row['温度'], compensation_value: row['温度补偿值'] })
}
this.tableData = next.sort((a, b) => Number(a.temperature) - Number(b.temperature))
this.importVisible = false
}
}
}
</script>
<style scoped>
.drawer-title {
padding: 20px 0 20px 20px;
border-bottom: 1px solid #dcdfe6;
}
.section {
margin: 20px;
}
.section-title {
margin-bottom: 16px;
color: #409EFF;
font-size: 14px;
}
.toolbar {
margin-bottom: 10px;
}
</style>

View File

@@ -79,7 +79,7 @@
v-if="$permission(btn.auth) && __judgeRowButton(row, btn.key)"
@click="btn.onClick(row, index)"
>
{{ btn.label }}
{{ $t(btn.label) }}
</i>
</strong>
</template>
@@ -116,9 +116,24 @@
:visible.sync="resultVisible"
:title="resultTitle"
:workingsubclass-id="resultWorkingsubclassId"
:flow-process-id="resultFlowProcessId"
@close="resultVisible = false"
/>
<temperature-compensation
:visible.sync="temperatureVisible"
:title="temperatureTitle"
:flow-process-id="temperatureFlowProcessId"
@close="temperatureVisible = false"
/>
<calculation-script
:visible.sync="calculationVisible"
:title="calculationTitle"
:flow-process-id="calculationFlowProcessId"
@close="calculationVisible = false"
/>
<el-drawer
:title="$t(key('view_log'))"
:visible.sync="drawer"
@@ -177,11 +192,13 @@ import { getWorkingsubclassAll } from '@/api/production-master-data/process-step
import PageTable from '@/components/page-table'
import PageDialogForm from '@/components/page-dialog-form'
import TechnologyFlowModel from '../process-step/components/technology-flow-model.vue'
import ResultParam from '../process-step/components/result-param.vue'
import ResultParam from './components/result-param.vue'
import TemperatureCompensation from './components/temperature-compensation.vue'
import CalculationScript from './components/calculation-script.vue'
export default {
name: 'production-master-data-process-routing-card',
components: { PageTable, PageDialogForm, TechnologyFlowModel, ResultParam },
components: { PageTable, PageDialogForm, TechnologyFlowModel, ResultParam, TemperatureCompensation, CalculationScript },
mixins: [i18nMixin('page.production_master_data.process_model.process_routing.card'), confirmMixin],
data () {
return {
@@ -210,6 +227,13 @@ export default {
resultVisible: false,
resultTitle: '',
resultWorkingsubclassId: '',
resultFlowProcessId: '',
temperatureVisible: false,
temperatureTitle: '',
temperatureFlowProcessId: '',
calculationVisible: false,
calculationTitle: '',
calculationFlowProcessId: '',
formData: {
workingsubclass_id: '',
code: '',
@@ -322,7 +346,7 @@ export default {
this.rowOperationButtons = [
{
key: 'edit',
label: '编辑',
label: this.key('edit'),
icon: 'el-icon-edit',
cssStyle: { marginRight: '10px', cursor: 'pointer' },
auth: '/production_configuration/technology_model/technology_flow_process/edit',
@@ -330,7 +354,7 @@ export default {
},
{
key: 'delete',
label: '删除',
label: this.key('delete'),
icon: 'el-icon-delete',
cssStyle: { color: 'red', marginRight: '10px', cursor: 'pointer' },
auth: '/production_configuration/technology_model/technology_flow_process/delete',
@@ -338,7 +362,7 @@ export default {
},
{
key: 'setting',
label: '设定值',
label: this.key('setting'),
icon: 'el-icon-setting',
cssStyle: { color: '#409EFF', marginRight: '10px', cursor: 'pointer' },
auth: '/production_configuration/technology_model/technology_flow_process/setting',
@@ -346,7 +370,7 @@ export default {
},
{
key: 'resultParam',
label: '结果参数',
label: this.key('result_param'),
icon: 'el-icon-s-promotion',
cssStyle: { color: '#E6A23C', marginRight: '10px', cursor: 'pointer' },
auth: '/production_configuration/technology_model/technology_flow_process/result_params',
@@ -354,7 +378,7 @@ export default {
},
{
key: 'temperature',
label: '温度补偿',
label: this.key('temperature'),
icon: 'el-icon-magic-stick',
cssStyle: { color: '#67C23A', marginRight: '10px', cursor: 'pointer' },
auth: '/production_configuration/technology_model/technology_flow_process/temperature',
@@ -362,7 +386,7 @@ export default {
},
{
key: 'calculationScript',
label: '计算脚本',
label: this.key('calculation_script'),
icon: 'el-icon-monitor',
cssStyle: { color: '#67C23A', marginRight: '10px', cursor: 'pointer' },
auth: '/production_configuration/technology_model/technology_flow_process/calculation_script',
@@ -398,10 +422,10 @@ export default {
if (key === 'delete' && !this.isBindingBatch) return true
if (key === 'temperature' &&
['FORMATION', 'YC', 'HC', 'IC', 'FORMATION_SS'].indexOf(row.device_category_code) !== -1) {
return false
return true
}
if (key === 'resultParam') return true
if (key === 'calculationScript') return false
if (key === 'calculationScript') return true
if (key === 'edit') return true
return false
},
@@ -559,13 +583,18 @@ export default {
openResultParamDrawer (row) {
this.resultTitle = this.key('result_param')
this.resultWorkingsubclassId = row.workingsubclass_id || ''
this.resultFlowProcessId = row.id || ''
this.resultVisible = true
},
openTemperatureDrawer (row) {
this.$message.warning('当前项目暂未接入温度补偿功能')
this.temperatureTitle = this.key('temperature')
this.temperatureFlowProcessId = row.id || ''
this.temperatureVisible = true
},
openCalculationScriptDrawer (row) {
this.$message.warning('当前项目暂未接入计算脚本功能')
this.calculationTitle = this.key('calculation_script')
this.calculationFlowProcessId = row.id || ''
this.calculationVisible = true
},
async handleSettingSubmit (data) {
await setSetting({