完善工序单元功能迁移
Some checks failed
Release pipeline / publish (push) Has been cancelled
Release pipeline / Always run job (push) Has been cancelled

This commit is contained in:
sheng
2026-06-25 00:14:40 +08:00
parent d40b7a88b2
commit 46e4c2643b
7 changed files with 344 additions and 45 deletions

View File

@@ -4,12 +4,20 @@ const BASE = 'production_configuration/technology_model/optional_params/'
function apiParams (method, data = {}) {
return {
method: `production_master_data_process_model_optional_params_${method}`,
method: `production_configuration_technology_model_optional_params_${method}`,
platform: 'background',
...data
}
}
export function getOptionalParamsALL (data) {
return request({
url: BASE + 'all',
method: 'get',
params: apiParams('all', data)
})
}
export function getOptionalParamsList (data) {
return request({
url: BASE + 'list',
@@ -47,11 +55,7 @@ export function getImportTemplate (data) {
url: BASE + 'get_import_template',
method: 'post',
responseType: 'blob',
data: {
method: 'production_master_data_process_model_optional_params_get_import_template',
platform: 'background',
...data
}
data: apiParams('get_import_template', data)
})
}
@@ -59,6 +63,10 @@ export function importDataCreate (data) {
return request({
url: BASE + 'import_data_create',
method: 'post',
data: apiParams('import_data_create', data)
data: {
method: 'production_configuration_technology_model_optional_params_import_data_create',
platform: 'background',
...data
}
})
}

View File

@@ -4,7 +4,7 @@ const BASE = 'production_configuration/technology_model/technology_flow_workings
function apiParams (method, data = {}) {
return {
method: `production_master_data_process_model_process_step_${method}`,
method: `production_configuration_technology_model_technology_flow_workingsubclass_${method}`,
platform: 'background',
...data
}
@@ -30,6 +30,14 @@ export function getWorkingsubclassAll (data) {
})
}
export function getWorkSubClassByDeviceCode (data) {
return request({
url: BASE + 'get_worksubclass_by_device_code',
method: 'get',
params: apiParams('get_worksubclass_by_device_code', data)
})
}
export function createProcessStep (data) {
return request({
url: BASE + 'create',
@@ -58,10 +66,6 @@ export function settingSubmit (data) {
return request({
url: BASE + 'setting_submit',
method: 'post',
data: {
method: 'production_master_data_process_model_process_step_setting_submit',
platform: 'background',
...data
}
data: apiParams('setting_submit', data)
})
}

View File

@@ -126,6 +126,8 @@
"enter_process_unit_code": "Enter process unit code",
"enter_process_unit_name": "Enter process unit name",
"select_device_category": "Please select device category",
"login_process": "Login Process",
"select_login_process": "Please select whether this is a login process",
"remark_required": "Please enter remark",
"preset_setting": "Preset Settings",
"add_process_unit": "Add Process Unit",
@@ -145,6 +147,7 @@
"no": "No",
"is_upload": "Is Upload Required",
"need_device_upload": "Requires Device Upload",
"multi_step_not_unique": "Set to No when parameters with the same name exist across steps",
"add_row": "Add Row",
"import": "Import",
"close": "Close",
@@ -158,7 +161,18 @@
"delete_success": "Deleted successfully",
"setting_placeholder": "Preset setting configuration area",
"setting_tip": "Configure preset settings for this process unit here",
"import_tip": "Import function is under development"
"preset_result_param_import": "Preset Result Parameter Import",
"file_suffix_rule": "Only xlsx or xls files following the template can be uploaded",
"import_table": "Import Table",
"select_file": "Select File",
"download_template": "Download Template",
"preview": "Preview",
"upload_format_error": "Please upload xls or xlsx file",
"please_import_data": "Please import data first",
"optional_param_import_template": "Optional Parameter Import Template",
"import_missing_column": "Column [{name}] does not exist in the file. Please check.",
"import_duplicate_name": "Name [{name}] is duplicated. Remove duplicates and upload again.",
"import_duplicate_param": "Parameter [{param}] is duplicated. Remove duplicates and upload again."
},
"process_routing": {
"search": "Search",

View File

@@ -126,6 +126,8 @@
"enter_process_unit_code": "请输入工序单元编码",
"enter_process_unit_name": "请输入工序单元名称",
"select_device_category": "请选择设备类别",
"login_process": "是否登录工序",
"select_login_process": "请选择是否登录工序",
"remark_required": "请输入备注",
"preset_setting": "预设设定值",
"add_process_unit": "新增工序单元",
@@ -145,6 +147,7 @@
"no": "否",
"is_upload": "是否上传",
"need_device_upload": "是否需要设备上传",
"multi_step_not_unique": "多工步同名称参数时设置为否",
"add_row": "新增一行",
"import": "导入",
"close": "关闭",
@@ -158,7 +161,18 @@
"delete_success": "删除成功",
"setting_placeholder": "预设设定值配置区域",
"setting_tip": "在此配置工序单元的预设设定值",
"import_tip": "导入功能开发中"
"preset_result_param_import": "预设结果参数导入",
"file_suffix_rule": "上传的文件后缀必须是 xlsx 或 xls且根据模板上传否则不能上传成功",
"import_table": "导入表格",
"select_file": "选择文件",
"download_template": "下载模板",
"preview": "预览",
"upload_format_error": "请上传 xls 或 xlsx 文件",
"please_import_data": "请先导入数据",
"optional_param_import_template": "可选参数导入模板",
"import_missing_column": "文件不存在【{name}】数据列,请检查",
"import_duplicate_name": "【{name}】名称存在重复值,请去掉重复值并重新上传",
"import_duplicate_param": "【{param}】参数存在重复值,请去掉重复值并重新上传"
},
"process_routing": {
"search": "查询",

View File

@@ -1,6 +1,6 @@
<template>
<el-dialog
:title="$t(title)"
:title="title"
:visible.sync="visible"
:append-to-body="true"
:close-on-click-modal="false"
@@ -129,6 +129,66 @@
<el-button type="primary" @click="handleSubmit">{{ $t(key('confirm')) }}</el-button>
</div>
</el-dialog>
<el-dialog
:title="$t(key('preset_result_param_import'))"
:visible.sync="importVisible"
append-to-body
width="75%"
:close-on-click-modal="false"
:before-close="handleImportClose"
>
<el-alert
:title="$t(key('file_suffix_rule'))"
:closable="false"
type="warning"
show-icon
/>
<el-form label-width="90px" class="import-form">
<el-form-item :label="$t(key('import_table'))">
<el-upload
action=""
:multiple="false"
:auto-upload="false"
:show-file-list="true"
:file-list="importFileList"
accept=".xls,.xlsx"
:on-change="onImportFileChange"
>
<el-button slot="trigger" size="mini" type="success">{{ $t(key('select_file')) }}</el-button>
<el-button
style="margin-left:10px"
size="mini"
type="primary"
:loading="downloadImportLoading"
@click.stop="downloadTemplate"
>
{{ $t(key('download_template')) }}
</el-button>
</el-upload>
</el-form-item>
<el-form-item :label="$t(key('preview'))">
<el-table
v-loading="importTableLoading"
:data="importRows"
border
height="360"
style="width:100%"
>
<el-table-column prop="name" :label="$t(key('name'))" min-width="140" show-overflow-tooltip />
<el-table-column prop="code" :label="$t(key('param'))" min-width="140" show-overflow-tooltip />
<el-table-column prop="field_type" :label="$t(key('category'))" min-width="120" />
<el-table-column prop="remark" :label="$t(key('remark'))" min-width="160" show-overflow-tooltip />
<el-table-column prop="is_only" :label="$t(key('is_unique'))" width="110" />
<el-table-column prop="is_upload" :label="$t(key('is_upload'))" width="110" />
</el-table>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="handleImportClose">{{ $t(key('cancel')) }}</el-button>
<el-button type="primary" :loading="importSubmitting" @click="submitImport">{{ $t(key('confirm')) }}</el-button>
</div>
</el-dialog>
</el-dialog>
</template>
@@ -138,8 +198,11 @@ import {
getOptionalParamsList,
createOptionalParams,
editOptionalParams,
deleteOptionalParams
deleteOptionalParams,
getImportTemplate,
importDataCreate
} from '@/api/production-master-data/optional-params'
import { downloadRename, readExcel } from '@/utils/file'
export default {
name: 'ProcessStepResultParam',
@@ -167,6 +230,12 @@ export default {
pagination: { current: 1, size: 10, total: 0 },
innerVisible: false,
dialogTitle: '',
importVisible: false,
downloadImportLoading: false,
importFileList: [],
importTableLoading: false,
importSubmitting: false,
importRows: [],
formData: {
id: '',
code: '',
@@ -201,6 +270,25 @@ export default {
this.innerVisible = false
this.$refs.form && this.$refs.form.resetFields()
},
normalizeResponse (res) {
const getTotal = (source, fallback) => {
if (!source) return fallback
const total = source.count ?? source.total ?? source.total_count ?? source.record_count
const value = Number(total)
return Number.isNaN(value) ? fallback : value
}
const containers = [res, res && res.data, res && res.data && res.data.data].filter(Boolean)
for (const item of containers) {
if (Array.isArray(item)) {
return { list: item, total: getTotal(res, item.length) }
}
const list = item.data || item.list || item.rows || item.records || item.items
if (Array.isArray(list)) {
return { list, total: getTotal(item, getTotal(res, list.length)) }
}
}
return { list: [], total: 0 }
},
onSearch () {
this.pagination.current = 1
this.fetchData()
@@ -214,10 +302,9 @@ export default {
page_size: this.pagination.size,
...this.searchForm
}).then(res => {
const list = Array.isArray(res) ? res : (res.data || [])
const total = Array.isArray(res) ? res.length : (res.count || 0)
this.tableData = list
this.pagination.total = total
const data = this.normalizeResponse(res)
this.tableData = data.list
this.pagination.total = data.total
}).finally(() => {
this.loading = false
})
@@ -265,6 +352,7 @@ export default {
}).then(() => {
deleteOptionalParams({ workingsubclass_id: this.workingsubclass_id, id: row.id }).then(() => {
this.$message.success(this.$t(this.key('operation_success')))
this.pagination.current = Math.min(this.pagination.current, Math.ceil((this.pagination.total - 1) / this.pagination.size) || 1)
this.fetchData()
})
}).catch(() => {})
@@ -283,7 +371,109 @@ export default {
})
},
handleImport () {
this.$message.info(this.$t(this.key('import_tip')))
this.importFileList = []
this.importRows = []
this.importVisible = true
},
handleImportClose () {
this.importVisible = false
this.importFileList = []
this.importRows = []
},
async downloadTemplate () {
this.downloadImportLoading = true
try {
const res = await getImportTemplate({})
downloadRename(res, 'xlsx', this.$t(this.key('optional_param_import_template')))
} finally {
this.downloadImportLoading = false
}
},
getImportValue (row, keys) {
for (const key of keys) {
if (row[key] !== undefined && row[key] !== null) return row[key]
}
return ''
},
normalizeFlag (value) {
if (value === 1 || value === '1') return 1
if (value === 0 || value === '0') return 0
const text = String(value || '').trim().toLowerCase()
if ([this.$t(this.key('yes')).toLowerCase(), '是', 'yes', 'true'].includes(text)) return 1
if ([this.$t(this.key('no')).toLowerCase(), '否', 'no', 'false'].includes(text)) return 0
return value
},
async onImportFileChange (file) {
if (!file || !/\.(xls|xlsx)$/i.test(file.name)) {
this.$message.error(this.$t(this.key('upload_format_error')))
this.importFileList = []
return
}
this.importFileList = [file]
this.importTableLoading = true
try {
const rows = await readExcel(file.raw)
const required = [
{ label: this.key('name'), aliases: [this.$t(this.key('name')), '名称', 'Name'] },
{ label: this.key('param'), aliases: [this.$t(this.key('param')), '参数', 'Parameter'] },
{ label: this.key('category'), aliases: [this.$t(this.key('category')), '类别', 'Category'] }
]
const first = rows[0] || {}
const missing = required.find(field => !field.aliases.some(alias => Object.prototype.hasOwnProperty.call(first, alias)))
if (missing) {
this.$message.error(this.$t(this.key('import_missing_column'), { name: this.$t(missing.label) }))
this.importRows = []
return
}
const nameSet = new Set()
const codeSet = new Set()
const result = []
for (const row of rows) {
const name = this.getImportValue(row, [this.$t(this.key('name')), '名称', 'Name'])
const code = this.getImportValue(row, [this.$t(this.key('param')), '参数', 'Parameter'])
if (nameSet.has(name)) {
this.$message.error(this.$t(this.key('import_duplicate_name'), { name }))
this.importRows = []
return
}
if (codeSet.has(code)) {
this.$message.error(this.$t(this.key('import_duplicate_param'), { param: code }))
this.importRows = []
return
}
nameSet.add(name)
codeSet.add(code)
result.push({
name,
code,
field_type: this.getImportValue(row, [this.$t(this.key('category')), '类别', 'Category']),
remark: this.getImportValue(row, [this.$t(this.key('remark')), '备注', 'Remark']),
is_only: this.normalizeFlag(this.getImportValue(row, [this.$t(this.key('is_unique')), '是否唯一', 'Is Unique'])),
is_upload: this.normalizeFlag(this.getImportValue(row, [this.$t(this.key('is_upload')), '是否上传', 'Is Upload Required']))
})
}
this.importRows = result
} finally {
this.importTableLoading = false
}
},
async submitImport () {
if (!this.importRows.length) {
this.$message.error(this.$t(this.key('please_import_data')))
return
}
this.importSubmitting = true
try {
await importDataCreate({
workingsubclass_id: this.workingsubclass_id,
import_data: JSON.stringify(this.importRows)
})
this.$message.success(this.$t(this.key('operation_success')))
this.handleImportClose()
this.fetchData()
} finally {
this.importSubmitting = false
}
}
}
}
@@ -296,4 +486,7 @@ export default {
.box-card {
width: 100%;
}
.import-form {
margin-top: 16px;
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<el-dialog
:title="$t(title)"
:title="title"
:visible.sync="visible"
:append-to-body="true"
:close-on-click-modal="false"

View File

@@ -21,6 +21,24 @@
@keyup.enter.native="onSearch"
/>
</el-form-item>
<el-form-item :label="$t(key('device_category'))">
<el-select
v-model="search.device_category_id"
:placeholder="$t(key('select_device_category'))"
clearable
filterable
style="width:200px"
@focus="loadDeviceCategoryOptions"
@change="onSearch"
>
<el-option
v-for="item in deviceCategoryOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="onSearch">
{{ $t(key('search')) }}
@@ -46,7 +64,12 @@
auto-height
@page-change="onPageChange"
@selection-change="onSelect"
/>
>
<template #col-is_denglu_process="{ row }">
<el-tag v-if="String(row.is_denglu_process) === '1'" size="mini">{{ $t(key('yes')) }}</el-tag>
<el-tag v-else type="warning" size="mini">{{ $t(key('no')) }}</el-tag>
</template>
</page-table>
<page-dialog-form
ref="dialogForm"
@@ -116,9 +139,10 @@ export default {
dialogTitle: '',
editId: '',
handleType: 'create',
search: { code: '', name: '' },
search: { code: '', name: '', device_category_id: '' },
pagination: { current: 1, size: 10, total: 0 },
formData: { code: '', name: '', device_category_id: '', remark: '' },
deviceCategoryOptions: [],
formData: { code: '', name: '', device_category_id: '', is_denglu_process: '0', remark: '' },
settingVisible: false,
settingTitle: '',
settingType: '',
@@ -139,6 +163,9 @@ export default {
],
device_category_id: [
{ required: true, message: this.key('select_device_category'), trigger: 'change' }
],
is_denglu_process: [
{ required: true, message: this.key('select_login_process'), trigger: 'change' }
]
},
columns: [],
@@ -175,7 +202,23 @@ export default {
clearable: true,
filterable: true,
style: { width: '90%' },
options: []
options: [],
onFocus: this.loadDeviceCategoryOptions
}
],
[
{
type: 'select',
prop: 'is_denglu_process',
label: this.key('login_process'),
placeholder: this.key('select_login_process'),
clearable: false,
filterable: true,
style: { width: '90%' },
options: [
{ label: this.$t(this.key('yes')), value: '1' },
{ label: this.$t(this.key('no')), value: '0' }
]
}
],
[
@@ -198,8 +241,9 @@ export default {
{ prop: 'code', label: this.key('process_unit_code'), minWidth: 120 },
{ prop: 'name', label: this.key('process_unit_name'), minWidth: 120 },
{ prop: 'device_category_name', label: this.key('device_category'), minWidth: 120 },
{ prop: 'is_denglu_process', label: this.key('login_process'), width: 120, slot: 'is_denglu_process' },
{ prop: 'remark', label: this.key('remark') },
{ prop: '_actions', label: this.key('operation'), width: 160, fixed: 'right' }
{ prop: '_actions', label: this.key('operation'), width: 350, fixed: 'right' }
])
const btns = useTableButtons({
toolbar: [
@@ -217,7 +261,7 @@ export default {
key: 'edit',
label: this.key('edit'),
icon: 'el-icon-edit',
auth: '/production_configuration/technology_model/technology_flow_process/edit',
auth: '/production_configuration/technology_model/technology_flow_workingsubclass/edit',
onClick: this.openEdit
},
{
@@ -226,7 +270,8 @@ export default {
icon: 'el-icon-setting',
color: 'warning',
auth: '/production_configuration/technology_model/technology_flow_workingsubclass/setting',
onClick: this.openSetting
onClick: this.openSetting,
visible: row => !!row.setting_plugin
},
{
key: 'result',
@@ -248,17 +293,38 @@ export default {
}, this.$permission)
this.toolbarButtons = btns.toolbarButtons
this.rowButtons = btns.rowButtons
this.loadDeviceCategoryOptions()
this.fetchData()
},
methods: {
normalizeResponse (res) {
const getTotal = (source, fallback) => {
if (!source) return fallback
const total = source.count ?? source.total ?? source.total_count ?? source.record_count
const value = Number(total)
return Number.isNaN(value) ? fallback : value
}
const containers = [res, res && res.data, res && res.data && res.data.data].filter(Boolean)
for (const item of containers) {
if (Array.isArray(item)) {
return { list: item, total: getTotal(res, item.length) }
}
const list = item.data || item.list || item.rows || item.records || item.items
if (Array.isArray(list)) {
return { list, total: getTotal(item, getTotal(res, list.length)) }
}
}
return { list: [], total: 0 }
},
loadDeviceCategoryOptions () {
if (this.formCols[2][0].options.length > 0) return Promise.resolve()
if (this.deviceCategoryOptions.length > 0) return Promise.resolve()
return getDeviceCategoryAll().then(res => {
const list = res.data || res || []
this.formCols[2][0].options = list.map(item => ({
const list = this.normalizeResponse(res).list
this.deviceCategoryOptions = list.map(item => ({
label: item.name,
value: item.id
}))
this.formCols[2][0].options = this.deviceCategoryOptions
})
},
async fetchData () {
@@ -269,10 +335,9 @@ export default {
page_no: this.pagination.current,
page_size: this.pagination.size
})
const list = Array.isArray(res) ? res : (res.data || [])
const total = Array.isArray(res) ? res.length : (res.count || 0)
this.tableData = list
this.pagination.total = total
const data = this.normalizeResponse(res)
this.tableData = data.list
this.pagination.total = data.total
} finally {
this.loading = false
}
@@ -282,7 +347,7 @@ export default {
this.fetchData()
},
onReset () {
this.search = { code: '', name: '' }
this.search = { code: '', name: '', device_category_id: '' }
this.pagination.current = 1
this.fetchData()
},
@@ -295,7 +360,7 @@ export default {
this.selectedRows = rows
},
resetForm () {
this.formData = { code: '', name: '', device_category_id: '', remark: '' }
this.formData = { code: '', name: '', device_category_id: '', is_denglu_process: '0', remark: '' }
this.editId = ''
this.$nextTick(() => {
this.formCols[0][0].disabled = false
@@ -320,6 +385,7 @@ export default {
code: row.code,
name: row.name,
device_category_id: row.device_category_id || '',
is_denglu_process: String(row.is_denglu_process || '0'),
remark: row.remark || ''
}
this.$nextTick(() => {
@@ -365,17 +431,17 @@ export default {
},
openSetting (row) {
this.currentRowId = row.id
this.settingTitle = this.key('preset_setting')
this.settingType = row.device_category_code || row.device_category_name || ''
this.settingTitle = `${row.name}${this.$t(this.key('preset_setting'))}`
this.settingType = row.setting_plugin || ''
this.settingCode = row.code
this.settingWidth = '60%'
this.settingPluginData = row.setting || {}
this.settingWidth = row.setting_plugin_width ? `${row.setting_plugin_width}%` : '35%'
this.settingPluginData = row.default_setting || {}
this.settingVisible = true
},
async handleSettingSubmit (data) {
await settingSubmit({
id: this.currentRowId,
setting: JSON.stringify(data)
default_setting: JSON.stringify(data)
})
this.$message.success(this.$t(this.key('operation_success')))
this.settingVisible = false
@@ -383,7 +449,7 @@ export default {
},
openResult (row) {
this.currentRowId = row.id
this.resultTitle = this.key('preset_result_param')
this.resultTitle = `${row.name}${this.$t(this.key('preset_result_param'))}`
this.resultVisible = true
}
}