huminmin
6 天以前 79dbd82e9d31e659a5ecb58da7fd011c03a8d58f
修改工艺路线拖拽排序
已修改1个文件
324 ■■■■ 文件已修改
src/views/productionManagement/processRoute/ItemsForm.vue 324 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/processRoute/ItemsForm.vue
@@ -6,16 +6,27 @@
        width="800px"
        @close="closeModal"
    >
      <el-button
          type="primary"
          @click="isShowProductSelectDialog = true"
          class="mb5"
          style="margin-bottom: 10px;"
      >
        选择产品
      </el-button>
      <div class="operate-button">
        <el-button
            type="primary"
            @click="isShowProductSelectDialog = true"
            class="mb5"
            style="margin-bottom: 10px;"
        >
          选择产品
        </el-button>
        <el-switch
            v-model="isTable"
            inline-prompt
            active-text="表格"
            inactive-text="列表"
            @change="handleViewChange"
        />
      </div>
      <el-table
          v-if="isTable"
          ref="multipleTable"
          v-loading="tableLoading"
          border
@@ -26,7 +37,11 @@
          class="lims-table"
          style="cursor: move;"
      >
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column align="center" label="序号" width="60">
          <template #default="scope">
            {{ scope.$index + 1 }}
          </template>
        </el-table-column>
        <el-table-column
            v-for="(item, index) in tableColumn"
@@ -70,6 +85,54 @@
        </el-table-column>
      </el-table>
      <!-- 简化容器结构,直接给el-steps加ref -->
      <el-steps
          v-else
          ref="stepsContainer"
          class="mb5 custom-steps"
          :active="routeItems.length"
          align-center
          style="padding: 10px 0;"
      >
        <!-- 关键:给el-step添加data-id,而非内部卡片 -->
        <el-step
            v-for="(item, index) in routeItems"
            :key="item.id"
            class="custom-step draggable-step"
            :data-id="item.id"
            style="cursor: move;"
        >
          <template #title>
            <div class="step-content">
              <div class="step-number">{{ index + 1 }}</div>
              <el-card
                  :header="item.productName"
                  class="step-card"
                  style="cursor: move;"
              >
                <p>{{ item.model }}</p>
                <p>{{ item.unit }}</p>
                <el-select
                    v-model="item.processId"
                    style="width: 100%;"
                    @mousedown.stop
                >
                  <el-option
                      v-for="process in processOptions"
                      :key="process.id"
                      :label="process.name"
                      :value="process.id"
                  />
                </el-select>
                <template #footer>
                  <el-button type="danger" link size="small" @click.stop="removeItemByID(item.id)">删除</el-button>
                </template>
              </el-card>
            </div>
          </template>
        </el-step>
      </el-steps>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
@@ -111,8 +174,11 @@
const tableLoading = ref(false);
const isShowProductSelectDialog = ref(false);
const routeItems = ref([]);
let sortable = null;
let tableSortable = null;
let stepsSortable = null;
const multipleTable = ref(null);
const stepsContainer = ref(null);
const isTable = ref(true);
const isShow = computed({
  get() {
@@ -142,7 +208,7 @@
        clickFun: (row) => {
          const idx = routeItems.value.findIndex(item => item.id === row.id);
          if (idx > -1) {
            routeItems.value.splice(idx, 1);
            removeItem(idx)
          }
        }
      }
@@ -150,21 +216,51 @@
  }
]);
const removeItem = (index) => {
  routeItems.value.splice(index, 1);
  nextTick(() => initSortable());
};
const removeItemByID = (id) => {
  const idx = routeItems.value.findIndex(item => item.id === id);
  if (idx > -1) {
    routeItems.value.splice(idx, 1);
    nextTick(() => initSortable());
  }
};
const closeModal = () => {
  isShow.value = false;
};
const handelSelectProducts = (products) => {
  destroySortable();
  const newData = products.map(({ id, ...product }) => ({
    ...product,
    productModelId: id,
    routeId: props.record.id,
    id: `${Date.now()}-${Math.random().toString(36).slice(2)}`, // 生成无特殊字符的ID
    id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
    processId: undefined
  }));
  routeItems.value.push(...newData);
  nextTick(() => initSortable());
  routeItems.value.push(...newData);
  routeItems.value = [...routeItems.value];
  // 延迟初始化,确保DOM完全渲染
  nextTick(() => {
    // 强制重新渲染组件
    if (proxy?.$forceUpdate) {
      proxy.$forceUpdate();
    }
    const temp = [...routeItems.value];
    routeItems.value = [];
    nextTick(() => {
      routeItems.value = temp;
      initSortable();
    });
  });
};
const findProcessRouteItems = () => {
@@ -176,7 +272,10 @@
          ...item,
          processId: item.processId === 0 ? undefined : item.processId
        }));
        nextTick(() => initSortable());
        // 延迟初始化,确保DOM完全渲染
        nextTick(() => {
          setTimeout(() => initSortable(), 100);
        });
      })
      .catch(err => {
        tableLoading.value = false;
@@ -222,27 +321,89 @@
      });
};
const initSortable = () => {
  if (sortable) {
    sortable.destroy();
    sortable = null;
const destroySortable = () => {
  if (tableSortable) {
    tableSortable.destroy();
    tableSortable = null;
  }
  if (stepsSortable) {
    stepsSortable.destroy();
    stepsSortable = null;
  }
};
  if (!multipleTable.value) return;
const initSortable = () => {
  destroySortable();
  const tbody = multipleTable.value.$el.querySelector('.el-table__body tbody') ||
      multipleTable.value.$el.querySelector('.el-table__body-wrapper > table > tbody');
  if (!tbody) return;
  if (isTable.value) {
    if (!multipleTable.value) return;
    const tbody = multipleTable.value.$el.querySelector('.el-table__body tbody') ||
        multipleTable.value.$el.querySelector('.el-table__body-wrapper > table > tbody');
    if (!tbody) return;
  sortable = new Sortable(tbody, {
    animation: 150,
    ghostClass: 'sortable-ghost',
    handle: '.el-table__row',
    filter: '.el-button, .el-select',
    onEnd: (evt) => {
      const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
      routeItems.value.splice(evt.newIndex, 0, moveItem);
    tableSortable = new Sortable(tbody, {
      animation: 150,
      ghostClass: 'sortable-ghost',
      handle: '.el-table__row',
      filter: '.el-button, .el-select',
      onEnd: (evt) => {
        if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
        // 使用数组 splice 方法重新排序,与表格模式保持一致
        const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
        routeItems.value.splice(evt.newIndex, 0, moveItem);
        routeItems.value = [...routeItems.value];
        console.log('排序后数组:', routeItems.value);
      }
    });
  } else {
    if (!stepsContainer.value) return;
    // 关键修复1:精准定位步骤条列表容器(兼容Element Plus不同版本)
    const stepsList = stepsContainer.value.$el.querySelector('.el-steps__items') ||
        stepsContainer.value.$el ||
        stepsContainer.value;
    if (!stepsList) {
      console.warn('未找到步骤条拖拽容器');
      return;
    }
    // 关键修复2:放宽拖拽触发条件,恢复拖拽功能
    stepsSortable = new Sortable(stepsList, {
      animation: 150,
      ghostClass: 'sortable-ghost',
      draggable: '.draggable-step', // 可拖拽元素:el-step
      handle: '.draggable-step, .step-card', // 拖拽手柄:step本身 + 卡片(扩大触发区域)
      filter: '.el-button, .el-select, .el-input', // 过滤按钮/选择器,避免误触发
      forceFallback: true, // 强制使用fallback模式,避免原生拖拽冲突
      fallbackClass: 'sortable-fallback',
      preventOnFilter: true, // 过滤元素阻止拖拽
      scroll: true,
      scrollSensitivity: 30,
      scrollSpeed: 10,
      bubbleScroll: true,
      // 统一使用数组 splice 方法重新排序,与表格模式保持一致
      onEnd: (evt) => {
        if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
        // 使用数组 splice 方法重新排序,与表格模式保持一致
        const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0];
        routeItems.value.splice(evt.newIndex, 0, moveItem);
        routeItems.value = [...routeItems.value];
      }
    });
    // 调试:打印容器和实例,确认绑定成功
    console.log('步骤条拖拽容器:', stepsList);
    console.log('Sortable实例:', stepsSortable);
  }
};
const handleViewChange = () => {
  destroySortable();
  // 延迟初始化,确保视图切换后DOM完全渲染
  nextTick(() => {
    setTimeout(() => initSortable(), 100);
  });
};
@@ -252,12 +413,9 @@
});
onUnmounted(() => {
  if (sortable) {
    sortable.destroy();
  }
  destroySortable();
});
// 修复:暴露方法时避免语法错误
defineExpose({
  closeModal,
  handleSubmit,
@@ -279,7 +437,99 @@
  background-color: #f9fafc !important;
}
.mb5 {
  margin-bottom: 5px;
:deep(.el-card__footer){
  padding: 0 !important;
}
.operate-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
/* 关键修复:优化步骤条拖拽样式,确保可点击区域 */
:deep(.el-steps__items) {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 20px;
  min-height: 100px; /* 确保容器有高度 */
}
:deep(.draggable-step) {
  cursor: move !important; /* 强制显示拖拽光标 */
  padding: 8px;
  position: relative;
  transition: all 0.2s ease;
  flex: 0 0 auto;
  min-width: 220px;
  touch-action: none; /* 禁用触摸动作,避免移动端冲突 */
}
/* 拖拽悬浮样式,提示可拖拽 */
:deep(.draggable-step:hover) {
  background-color: rgba(64, 158, 255, 0.05);
  transform: translateY(-2px);
}
:deep(.sortable-ghost) {
  opacity: 0.4;
  background-color: #f5f7fa !important;
  border: 2px dashed #409eff;
  margin: 10px;
  transform: scale(1.02);
}
:deep(.sortable-fallback) {
  opacity: 0.9;
  background-color: #f5f7fa;
  border: 1px solid #409eff;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  transform: rotate(2deg);
  margin: 10px;
}
:deep(.step-card) {
  cursor: move !important;
  transition: box-shadow 0.2s ease;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: auto; /* 确保卡片可触发鼠标事件 */
  margin: 10px;
  height: 300px;
}
:deep(.step-card:hover) {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.step-content {
  width: 220px;
  user-select: none;
}
/* 禁用步骤条默认的头部样式干扰 */
:deep(.el-step__head) {
  display: none; /* 隐藏默认的步骤圆圈和序号 */
}
/* 隐藏Element Plus自动生成的连接线 */
:deep(.el-step__main::before) {
  display: none; /* 隐藏连接线 */
}
/* 自定义序号样式优化 */
.step-number {
  font-weight: bold;
  text-align: center;
  width: 36px;
  height: 36px;
  line-height: 36px;
  margin: 0 auto 10px;
  background: #409eff;
  color: #fff;
  border-radius: 50%;
  font-size: 14px;
}
</style>