diff --git a/package.json b/package.json
index a411807..0f0f9ac 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
"pubsub-js": "^1.9.3",
"qrcode": "^1.5.3",
"sass-loader": "6.0.6",
+ "sortablejs": "^1.15.0",
"svg-sprite-loader": "3.7.3",
"v-viewer": "^1.6.4",
"vue": "2.6.10",
diff --git a/src/views/modules/part/bom_create.vue b/src/views/modules/part/bom_create.vue
index 7f1f8ec..096fece 100644
--- a/src/views/modules/part/bom_create.vue
+++ b/src/views/modules/part/bom_create.vue
@@ -92,6 +92,7 @@
{
+ this.initTableRowDrag()
+ })
+ },
+ updated() {
+ // 组件更新后重新初始化拖拽功能
+ // 只在非拖拽状态下初始化,避免拖拽操作触发重复初始化
+ if (!this.isDragging) {
+ this.$nextTick(() => {
+ this.initTableRowDrag()
+ })
+ }
+ },
methods: {
+ /**
+ * 初始化表格行拖拽排序功能
+ * 拖拽后自动重新计算序号并保存到数据库
+ */
+ initTableRowDrag() {
+ // 如果正在拖拽,不重新初始化
+ if (this.isDragging) {
+ return
+ }
+
+ // 销毁旧的 Sortable 实例
+ if (this.sortableInstance) {
+ this.sortableInstance.destroy()
+ this.sortableInstance = null
+ }
+
+ const el = document.querySelector('.rq .el-table__body-wrapper tbody')
+ if (!el) {
+ console.warn('未找到表格DOM元素,拖拽功能初始化失败')
+ return
+ }
+
+ // 创建新的 Sortable 实例
+ this.sortableInstance = Sortable.create(el, {
+ animation: 150,
+ handle: 'tr', // 整行可拖拽
+ ghostClass: 'sortable-ghost',
+ chosenClass: 'sortable-chosen',
+ dragClass: 'sortable-drag',
+ onStart: () => {
+ this.isDragging = true
+ },
+ onEnd: (evt) => {
+ const { oldIndex, newIndex } = evt
+ this.isDragging = false
+
+ if (oldIndex === newIndex) return
+
+ console.log(`🔄 拖拽: 从位置 ${oldIndex} (序号${this.subDetailList[oldIndex].lineSequence}) 移动到位置 ${newIndex}`)
+
+ // 1. 更新本地数据顺序
+ const movedItem = this.subDetailList.splice(oldIndex, 1)[0]
+ this.subDetailList.splice(newIndex, 0, movedItem)
+
+ // 2. 重新计算所有序号(从1开始)- 使用 Vue.set 确保响应式更新
+ this.subDetailList.forEach((item, index) => {
+ this.$set(item, 'lineSequence', index + 1)
+ })
+
+ console.log('📝 拖拽后新序号:', this.subDetailList.map((item, idx) =>
+ `位置${idx}: ${item.componentPart} (序号${item.lineSequence})`
+ ).join(', '))
+
+ // 3. 强制刷新视图 - 使用多种方式确保更新
+ this.tableKey++ // 强制刷新表格
+ this.$forceUpdate()
+
+ // 4. 使用 nextTick 确保 DOM 更新后再保存
+ this.$nextTick(() => {
+ // 5. 保存到数据库
+ this.saveBomComponentSequence()
+ })
+ }
+ })
+
+ console.log('✅ 拖拽功能初始化成功')
+ },
+
+ /**
+ * 保存BOM子物料序号到数据库
+ * 将整个列表的数据(包含新序号)提交到后端保存
+ */
+ saveBomComponentSequence() {
+ if (this.subDetailList.length === 0) {
+ return
+ }
+
+ console.log('💾 === 拖拽排序保存开始 ===')
+ console.log('准备保存的数据:')
+ this.subDetailList.forEach((item, index) => {
+ console.log(` 位置${index}: ${item.componentPart} → 新序号 ${item.lineSequence}`)
+ })
+
+ // 构建保存数据
+ const tempData = {
+ site: this.modalData.site,
+ buNo: this.modalData.buNo,
+ partNo: this.modalData.partNo,
+ partDesc: this.modalData.partDesc,
+ engChgLevel: this.modalData.engChgLevel,
+ bomType: this.modalData.bomType,
+ noteText: this.modalData.noteText,
+ effPhaseInDate: this.modalData.effPhaseInDate,
+ effPhaseOutDate: this.modalData.effPhaseOutDate,
+ engRevision: this.modalData.engRevision,
+ typeFlag: this.modalData.typeFlag,
+ netWeight: this.modalData.netWeight,
+ alternativeNo: this.detailData.alternativeNo,
+ alternativeDescription: this.detailData.alternativeDescription,
+ minLotQty: this.detailData.minLotQty,
+ defaultFlag: this.detailData.defaultFlag,
+ detailNoteText: this.detailData.detailNoteText,
+ status: this.detailData.status,
+ createBy: this.$store.state.user.name,
+ updateBy: this.$store.state.user.name,
+ informationList: this.subDetailList,
+ processUnit: this.modalData.processUnit
+ }
+
+ // 显示loading
+ const loadingInstance = this.$loading({
+ lock: true,
+ text: '正在保存序号排序...',
+ spinner: 'el-icon-loading',
+ background: 'rgba(0, 0, 0, 0.3)'
+ })
+
+ // 调用保存API
+ bomManagementSave(tempData).then(({data}) => {
+ loadingInstance.close()
+ if (data && data.code === 0) {
+ this.$message({
+ message: '序号排序保存成功',
+ type: 'success',
+ duration: 1500
+ })
+ // ✅ 保存成功后,保持前端已排序的数据,不使用后端返回的数据
+ // 因为后端返回的数据是按旧的 line_sequence 排序的,会覆盖我们的新顺序
+ console.log('✅ 序号保存成功,当前顺序:', this.subDetailList.map(item => ({
+ componentPart: item.componentPart,
+ lineSequence: item.lineSequence
+ })))
+
+ // 如果需要更新其他字段(如计算字段),可以从后端数据中提取并合并
+ if (data.rows && data.rows.subDetailList) {
+ const backendList = data.rows.subDetailList
+ // 按照前端的顺序,更新后端返回的其他字段
+ this.subDetailList = this.subDetailList.map(frontItem => {
+ const backendItem = backendList.find(b =>
+ b.componentPart === frontItem.componentPart &&
+ b.lineItemNo === frontItem.lineItemNo
+ )
+ // 保留前端的 lineSequence,更新其他字段
+ return backendItem ? {
+ ...backendItem,
+ lineSequence: frontItem.lineSequence
+ } : frontItem
+ })
+ }
+
+ // 强制刷新表格显示
+ this.tableKey++
+ this.$forceUpdate()
+ } else {
+ this.$message.error(data.msg || '保存失败')
+ // 保存失败时刷新数据,恢复原序号
+ this.alternativeChange()
+ }
+ }).catch((error) => {
+ loadingInstance.close()
+ console.error('保存失败:', error)
+ this.$message.error('保存失败,请重试')
+ // 保存失败时刷新数据,恢复原序号
+ this.alternativeChange()
+ })
+ },
+
// 初始化组件的参数
async init (tempData) {
await getBomInformationByPartNo(tempData).then(({data}) => {
@@ -4066,13 +4254,13 @@ export default {
// 然后只更新该行的计算字段,保留用户在弹窗中修改的其他行数据
const returnedList = data.rows.subDetailList || []
const currentRow = this.batchUpdateList[i]
-
+
// 在返回的列表中找到对应的行
- const updatedRow = returnedList.find(item =>
- item.componentPart === currentRow.componentPart &&
+ const updatedRow = returnedList.find(item =>
+ item.componentPart === currentRow.componentPart &&
item.lineSequence === currentRow.lineSequence
)
-
+
if (updatedRow) {
// 只更新计算相关的字段,保留用户修改的其他字段
this.$set(this.batchUpdateList, i, {
@@ -4105,7 +4293,7 @@ export default {
if(flag) {
// 保存成功后,将修改后的数据同步回原数据
this.subDetailList = JSON.parse(JSON.stringify(this.batchUpdateList))
-
+
this.$message({
message: '批量修改并计算完成',
type: 'success',
@@ -4277,6 +4465,32 @@ export default {