diff --git a/src/api/production-master-data/optional-params.js b/src/api/production-master-data/optional-params.js index 86161c2a..21aaf67b 100644 --- a/src/api/production-master-data/optional-params.js +++ b/src/api/production-master-data/optional-params.js @@ -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 + } }) } diff --git a/src/api/production-master-data/process-step.js b/src/api/production-master-data/process-step.js index 31884731..b03711c0 100644 --- a/src/api/production-master-data/process-step.js +++ b/src/api/production-master-data/process-step.js @@ -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) }) } diff --git a/src/locales/en.json b/src/locales/en.json index 898e2e9a..b6410559 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -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", diff --git a/src/locales/zh-chs.json b/src/locales/zh-chs.json index 620cc109..56566da6 100644 --- a/src/locales/zh-chs.json +++ b/src/locales/zh-chs.json @@ -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": "查询", diff --git a/src/views/production-master-data/process-model/process-step/components/result-param.vue b/src/views/production-master-data/process-model/process-step/components/result-param.vue index bb947916..a9ffbecb 100644 --- a/src/views/production-master-data/process-model/process-step/components/result-param.vue +++ b/src/views/production-master-data/process-model/process-step/components/result-param.vue @@ -1,6 +1,6 @@ {{ $t(key('confirm')) }} + + + + + + + {{ $t(key('select_file')) }} + + {{ $t(key('download_template')) }} + + + + + + + + + + + + + + + + {{ $t(key('cancel')) }} + {{ $t(key('confirm')) }} + + @@ -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; +} diff --git a/src/views/production-master-data/process-model/process-step/components/technology-flow-model.vue b/src/views/production-master-data/process-model/process-step/components/technology-flow-model.vue index dd3f461f..e2b3ddfa 100644 --- a/src/views/production-master-data/process-model/process-step/components/technology-flow-model.vue +++ b/src/views/production-master-data/process-model/process-step/components/technology-flow-model.vue @@ -1,6 +1,6 @@ + + + + + {{ $t(key('search')) }} @@ -46,7 +64,12 @@ auto-height @page-change="onPageChange" @selection-change="onSelect" - /> + > + + {{ $t(key('yes')) }} + {{ $t(key('no')) }} + + !!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 } }