diff --git a/src/api/sampleTracking/sampleTracking.js b/src/api/sampleTracking/sampleTracking.js index c2ec7e1..b3a5d0e 100644 --- a/src/api/sampleTracking/sampleTracking.js +++ b/src/api/sampleTracking/sampleTracking.js @@ -25,4 +25,8 @@ export const deleteProofTracking = data => createAPI('/sampleTracking/proofTrack export const queryProofTrackingProcessHistory = data => createAPI('/sampleTracking/proofTracking/processHistory', 'POST', data) export const queryProofTrackingBuProcessConfig = data => createAPI('/sampleTracking/proofTracking/buProcessConfig/list', 'POST', data) export const saveProofTrackingBuProcessConfig = data => createAPI('/sampleTracking/proofTracking/buProcessConfig/save', 'POST', data) +export const deleteProofTrackingBuProcessConfig = data => createAPI('/sampleTracking/proofTracking/buProcessConfig/delete', 'POST', data) export const queryProofTrackingProcessColumns = data => createAPI('/sampleTracking/proofTracking/processColumns/list', 'POST', data) +export const queryProofTrackingProcessColumnsManage = data => createAPI('/sampleTracking/proofTracking/processColumns/manageList', 'POST', data) +export const saveProofTrackingProcessColumnsManage = data => createAPI('/sampleTracking/proofTracking/processColumns/manageSave', 'POST', data) +export const deleteProofTrackingProcessColumnsManage = data => createAPI('/sampleTracking/proofTracking/processColumns/manageDelete', 'POST', data) diff --git a/src/views/modules/sampleTracking/projectProofTracking.vue b/src/views/modules/sampleTracking/projectProofTracking.vue index 143aaeb..85da95b 100644 --- a/src/views/modules/sampleTracking/projectProofTracking.vue +++ b/src/views/modules/sampleTracking/projectProofTracking.vue @@ -59,6 +59,7 @@ 同步项目物料到NPI 同步打样到NPI 事业部进度设置 + 默认进度设置 @@ -285,6 +286,71 @@ + +
+ 新增一行 + 删除选中 +
+ + + + + + + + + + + + + + + +
+ 当前没有默认行,请点击“新增一行”后填写并保存。 +
+ +
+ 关闭 + 保存设置 +
+
+ @@ -868,8 +934,11 @@ import { oneKeyUpdateProofTracking, queryProofTrackingBuProcessConfig, queryProofTrackingProcessColumns, + deleteProofTrackingProcessColumnsManage, + queryProofTrackingProcessColumnsManage, queryProofTrackingProcessHistory, saveProofTrackingBuProcessConfig, + saveProofTrackingProcessColumnsManage, searchProjectInfoTracking, searchProjectPartTracking, searchProofTracking, @@ -894,6 +963,7 @@ import { } from '@/api/factory/site.js' import { queryCustomerList } from '@/api/customer/customer' import Chooselist from '@/views/modules/common/Chooselist_eam' +import Sortable from 'sortablejs' function focusCommentsNativeInput (el, vnode) { const doFocus = () => { @@ -978,6 +1048,12 @@ export default { buProcessConfigDialogVisible: false, buProcessConfigRows: [], buProcessConfigMap: {}, + defaultProcessConfigDialogVisible: false, + defaultProcessConfigRows: [], + defaultProcessConfigSelectionRows: [], + defaultProcessConfigDeleteLoading: false, + defaultProcessConfigRowSeed: 0, + defaultProcessConfigSortable: null, processOrderDragState: { buNo: '', fromIndex: -1 @@ -1137,6 +1213,9 @@ export default { }) this.getDataList() }, + deactivated () { + this.destroyDefaultProcessConfigSortable() + }, computed: { visibleProcessColumns () { const searchBuNo = this.getSelectedSearchBuNo() @@ -1440,7 +1519,7 @@ export default { site: this.$store.state.user.site, userName: this.$store.state.user.name } - queryProofTrackingBuProcessConfig(inData).then(({ data }) => { + return queryProofTrackingBuProcessConfig(inData).then(({ data }) => { if (!(data && data.code === 0)) { this.$message.error((data && data.msg) || '加载事业部进度展示配置失败') return @@ -1528,6 +1607,345 @@ export default { } this.buProcessConfigDialogVisible = true }, + buildDefaultProcessConfigRow (item) { + const parsedSortNo = parseInt(item && item.sortNo, 10) + const activeFlag = item && item.activeFlag ? String(item.activeFlag).trim().toUpperCase() : 'Y' + const code = item && item.code ? String(item.code).trim() : '' + return { + _rowKey: `default_process_${Date.now()}_${this.defaultProcessConfigRowSeed++}`, + // 记录后端原始编码,删除时可避免“修改了编码但尚未保存”导致删不到原记录。 + _persistedCode: code, + sortNo: Number.isNaN(parsedSortNo) || parsedSortNo <= 0 ? this.getNextDefaultProcessSortNo() : parsedSortNo, + code: code, + label: item && item.label ? String(item.label).trim() : '', + activeFlag: activeFlag === 'N' ? 'N' : 'Y' + } + }, + getNextDefaultProcessSortNo () { + if (!Array.isArray(this.defaultProcessConfigRows) || this.defaultProcessConfigRows.length === 0) { + return 10 + } + let maxSortNo = 0 + this.defaultProcessConfigRows.forEach(row => { + const parsedSortNo = parseInt(row && row.sortNo, 10) + if (!Number.isNaN(parsedSortNo) && parsedSortNo > maxSortNo) { + maxSortNo = parsedSortNo + } + }) + return maxSortNo > 0 ? maxSortNo + 10 : (this.defaultProcessConfigRows.length + 1) * 10 + }, + resetDefaultProcessConfigSortNoByOrder () { + if (!Array.isArray(this.defaultProcessConfigRows)) { + return + } + this.defaultProcessConfigRows.forEach((row, index) => { + if (!row) { + return + } + this.$set(row, 'sortNo', (index + 1) * 10) + }) + }, + clearDefaultProcessConfigSelection () { + this.defaultProcessConfigSelectionRows = [] + this.$nextTick(() => { + const tableRef = this.$refs.defaultProcessConfigTable + if (tableRef && tableRef.clearSelection) { + tableRef.clearSelection() + } + }) + }, + destroyDefaultProcessConfigSortable () { + if (this.defaultProcessConfigSortable && typeof this.defaultProcessConfigSortable.destroy === 'function') { + this.defaultProcessConfigSortable.destroy() + } + this.defaultProcessConfigSortable = null + }, + initDefaultProcessConfigSortable () { + this.$nextTick(() => { + this.destroyDefaultProcessConfigSortable() + if (!this.defaultProcessConfigDialogVisible || !Array.isArray(this.defaultProcessConfigRows) || this.defaultProcessConfigRows.length === 0) { + return + } + const tableRef = this.$refs.defaultProcessConfigTable + const tableBody = tableRef && tableRef.$el + ? tableRef.$el.querySelector('.el-table__body-wrapper tbody') + : null + if (!tableBody) { + return + } + this.defaultProcessConfigSortable = Sortable.create(tableBody, { + animation: 150, + // 整行可拖拽,避免必须精确悬浮到小图标才能排序。 + handle: 'tr', + draggable: 'tr', + ghostClass: 'default-process-sortable-ghost', + chosenClass: 'default-process-sortable-chosen', + dragClass: 'default-process-sortable-drag', + onEnd: (evt) => { + const oldIndex = evt && typeof evt.oldIndex === 'number' ? evt.oldIndex : -1 + const newIndex = evt && typeof evt.newIndex === 'number' ? evt.newIndex : -1 + if (oldIndex < 0 || newIndex < 0 || oldIndex === newIndex) { + return + } + const movedRow = this.defaultProcessConfigRows.splice(oldIndex, 1)[0] + this.defaultProcessConfigRows.splice(newIndex, 0, movedRow) + this.resetDefaultProcessConfigSortNoByOrder() + this.clearDefaultProcessConfigSelection() + } + }) + }) + }, + handleDefaultProcessConfigDialogClosed () { + this.defaultProcessConfigDeleteLoading = false + this.clearDefaultProcessConfigSelection() + this.destroyDefaultProcessConfigSortable() + }, + handleDefaultProcessConfigSelectionChange (rows) { + this.defaultProcessConfigSelectionRows = Array.isArray(rows) ? rows : [] + }, + normalizeDefaultProcessFieldBaseByCode (code) { + const rawCode = code == null ? '' : String(code).trim() + if (!rawCode) { + return '' + } + if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(rawCode)) { + return rawCode + } + const parts = rawCode.split(/[^A-Za-z0-9]+/).filter(item => !!item) + if (parts.length === 0) { + return '' + } + const firstPart = parts[0].toLowerCase() + const remained = parts.slice(1).map(item => { + const lower = item.toLowerCase() + return `${lower.charAt(0).toUpperCase()}${lower.substring(1)}` + }).join('') + let fieldBase = `${firstPart}${remained}` + if (/^[0-9]/.test(fieldBase)) { + fieldBase = `p${fieldBase}` + } + return fieldBase + }, + buildDefaultProcessFieldsByCode (code) { + const fieldBase = this.normalizeDefaultProcessFieldBaseByCode(code) + if (!fieldBase) { + return { + planField: '', + actualField: '', + statusField: '' + } + } + return { + planField: `${fieldBase}PlanDate`, + actualField: `${fieldBase}ActualDate`, + statusField: `${fieldBase}Status` + } + }, + queryDefaultProcessConfig () { + const inData = { + site: '*', + buNo: '*', + userName: this.$store.state.user.name + } + return queryProofTrackingProcessColumnsManage(inData).then(({ data }) => { + if (!(data && data.code === 0)) { + this.$message.error((data && data.msg) || '加载默认工序配置失败') + this.defaultProcessConfigRows = [] + this.defaultProcessConfigSelectionRows = [] + return [] + } + const rows = Array.isArray(data.rows) ? data.rows : [] + const sortedRows = rows + .map((item, index) => { + const parsedSortNo = parseInt(item && item.sortNo, 10) + return { + sourceIndex: index, + sortNo: Number.isNaN(parsedSortNo) ? Number.MAX_SAFE_INTEGER : parsedSortNo, + item: item + } + }) + .sort((a, b) => { + if (a.sortNo !== b.sortNo) { + return a.sortNo - b.sortNo + } + return a.sourceIndex - b.sourceIndex + }) + this.defaultProcessConfigRows = sortedRows.map(({ item }) => this.buildDefaultProcessConfigRow(item)) + this.resetDefaultProcessConfigSortNoByOrder() + this.clearDefaultProcessConfigSelection() + this.initDefaultProcessConfigSortable() + return this.defaultProcessConfigRows + }).catch(() => { + this.$message.error('加载默认工序配置异常') + this.defaultProcessConfigRows = [] + this.defaultProcessConfigSelectionRows = [] + this.destroyDefaultProcessConfigSortable() + return [] + }) + }, + openDefaultProcessConfigDialog () { + this.defaultProcessConfigDeleteLoading = false + this.defaultProcessConfigDialogVisible = true + this.queryDefaultProcessConfig() + }, + addDefaultProcessConfigRow () { + this.defaultProcessConfigRows.push(this.buildDefaultProcessConfigRow({ + sortNo: this.getNextDefaultProcessSortNo(), + activeFlag: 'Y' + })) + this.resetDefaultProcessConfigSortNoByOrder() + this.initDefaultProcessConfigSortable() + }, + deleteDefaultProcessConfigRow () { + if (!this.defaultProcessConfigSelectionRows || this.defaultProcessConfigSelectionRows.length === 0) { + this.$message.warning('请先勾选要删除的行') + return + } + const selectedRows = this.defaultProcessConfigSelectionRows.slice() + this.$confirm(`确认删除选中的 ${selectedRows.length} 条默认工序吗?`, '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + const selectedKeyMap = {} + const persistedCodeMap = {} + selectedRows.forEach(item => { + if (item && item._rowKey) { + selectedKeyMap[item._rowKey] = true + } + const persistedCode = item && item._persistedCode ? String(item._persistedCode).trim() : '' + const persistedCodeKey = persistedCode.toLowerCase() + if (persistedCode && !persistedCodeMap[persistedCodeKey]) { + persistedCodeMap[persistedCodeKey] = persistedCode + } + }) + const nextRows = this.defaultProcessConfigRows.filter(row => { + return !(row && row._rowKey && selectedKeyMap[row._rowKey]) + }) + const applyDeletedRowsToUi = () => { + this.defaultProcessConfigRows = nextRows + this.resetDefaultProcessConfigSortNoByOrder() + this.clearDefaultProcessConfigSelection() + this.initDefaultProcessConfigSortable() + } + const persistedCodes = Object.keys(persistedCodeMap).map(key => persistedCodeMap[key]) + if (persistedCodes.length === 0) { + applyDeletedRowsToUi() + this.$message.success('删除成功') + return + } + const inData = { + site: '*', + buNo: '*', + updateBy: this.$store.state.user.name, + processCodes: persistedCodes + } + this.defaultProcessConfigDeleteLoading = true + deleteProofTrackingProcessColumnsManage(inData).then(({ data }) => { + if (!(data && data.code === 0)) { + this.$message.error((data && data.msg) || '删除默认工序失败') + return + } + applyDeletedRowsToUi() + this.$message.success(data.msg || '删除成功') + // 删除动作实时落库后立即刷新主列表列定义,避免页面继续展示已删工序。 + this.queryProcessColumns().then(() => { + this.queryBuProcessConfig() + this.getDataList() + this.refreshTrackingTableLayout() + }) + }).catch(() => { + this.$message.error('删除默认工序异常') + }).finally(() => { + this.defaultProcessConfigDeleteLoading = false + }) + }).catch(() => { + // 用户取消删除时无需额外提示。 + }) + }, + normalizeDefaultProcessConfigRowsForSave () { + const sourceRows = Array.isArray(this.defaultProcessConfigRows) ? this.defaultProcessConfigRows : [] + const normalizedRows = [] + const usedCodeMap = {} + for (let i = 0; i < sourceRows.length; i++) { + const row = sourceRows[i] || {} + const rowNo = i + 1 + const code = row.code == null ? '' : String(row.code).trim() + const codeKey = code.toLowerCase() + const label = row.label == null ? '' : String(row.label).trim() + if (!code) { + this.$message.warning(`第${rowNo}行的工序编码不能为空`) + return null + } + if (usedCodeMap[codeKey]) { + this.$message.warning(`工序编码【${code}】重复,请检查`) + return null + } + if (!label) { + this.$message.warning(`第${rowNo}行的工序名称不能为空`) + return null + } + const fieldData = this.buildDefaultProcessFieldsByCode(code) + if (!fieldData.planField || !fieldData.actualField || !fieldData.statusField) { + this.$message.warning(`第${rowNo}行的工序编码格式无法生成字段,请调整编码`) + return null + } + usedCodeMap[codeKey] = true + normalizedRows.push({ + code: code, + label: label, + planField: fieldData.planField, + actualField: fieldData.actualField, + statusField: fieldData.statusField, + sortNo: rowNo * 10, + activeFlag: String(row.activeFlag || 'Y').toUpperCase() === 'N' ? 'N' : 'Y' + }) + } + return normalizedRows + }, + saveDefaultProcessConfig () { + const submitRows = this.normalizeDefaultProcessConfigRowsForSave() + if (!submitRows) { + return + } + const executeSave = () => { + const inData = { + site: '*', + buNo: '*', + updateBy: this.$store.state.user.name, + rows: submitRows + } + saveProofTrackingProcessColumnsManage(inData).then(({ data }) => { + if (!(data && data.code === 0)) { + this.$message.error((data && data.msg) || '默认工序配置保存失败') + return + } + this.$message.success(data.msg || '默认工序配置已保存') + this.queryDefaultProcessConfig() + // 保存后刷新页面列定义,确保新增/删除工序立即反映到主表列。 + this.queryProcessColumns().then(() => { + this.queryBuProcessConfig() + this.getDataList() + this.refreshTrackingTableLayout() + }) + }).catch(() => { + this.$message.error('默认工序配置保存异常') + }) + } + if (submitRows.length === 0) { + this.$confirm('当前将清空默认工序配置,保存后页面会回退到系统兜底配置,是否继续?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + executeSave() + }).catch(() => { + // 用户取消清空时无需额外提示。 + }) + return + } + executeSave() + }, refreshTrackingTableLayout () { this.$nextTick(() => { const tableRef = this.$refs.trackingTable @@ -3607,6 +4025,68 @@ export default { color: #409eff; } +.default-process-config-dialog .default-process-config-actions { + margin-bottom: 8px; +} + +.default-process-config-dialog .bu-process-config-tip { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 8px; + padding: 6px 10px; + border-radius: 4px; + background: #f4f8ff; + color: #5f6b7a; + font-size: 12px; +} + +.default-process-config-dialog .bu-process-config-tip i { + color: #409eff; +} + +.default-process-config-dialog .default-process-config-empty { + margin: 12px 0; + padding: 12px; + border: 1px dashed #dcdfe6; + border-radius: 4px; + color: #909399; + text-align: center; +} + +.default-process-config-dialog .default-process-drag-handle { + color: #909399; + cursor: move; + font-size: 15px; + margin-right: 6px; +} + +.default-process-config-dialog .default-process-order-index { + color: #606266; + font-size: 12px; +} + +.default-process-config-dialog .el-table__body-wrapper tbody tr { + cursor: move; +} + +.default-process-config-dialog .el-table__body-wrapper tbody tr .el-input__inner { + cursor: text; +} + +.default-process-config-dialog .el-table__body-wrapper tbody tr .el-checkbox__inner, +.default-process-config-dialog .el-table__body-wrapper tbody tr .el-select .el-input__inner { + cursor: pointer; +} + +.default-process-sortable-ghost > td { + background: #f0f9ff !important; +} + +.default-process-sortable-chosen > td { + background: #ecf5ff !important; +} + .button-col { margin-bottom: 8px; } @@ -3834,5 +4314,11 @@ export default { .end-link { color: #a303ff; } + +.zxClass .cell { + line-height: 24px; + font-size: 12px; + height: 24px; +}