yyb
2 天以前 b25cddd35f654374d427e775ddbffffe268cffc8
src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -1,503 +1,1024 @@
<template>
  <div class="app-container">
    <div class="operate-button">
      <div style="margin-bottom: 15px;">
        <el-button
            type="primary"
            @click="isShowProductSelectDialog = true"
        >
          选择产品
        </el-button>
        <el-button type="primary" @click="handleSubmit">确认</el-button>
    <PageHeader content="工艺路线项目" />
    <!-- 工艺路线信息展示 -->
    <el-card v-if="routeInfo.processRouteCode"
             class="route-info-card"
             shadow="hover">
      <div class="route-info">
        <div class="info-item">
          <div class="info-label-wrapper">
            <span class="info-label">工艺路线编号</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.processRouteCode }}</span>
          </div>
        </div>
        <div class="info-item">
          <div class="info-label-wrapper">
            <span class="info-label">产品名称</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.productName || '-' }}</span>
          </div>
        </div>
        <div class="info-item">
          <div class="info-label-wrapper">
            <span class="info-label">规格名称</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.model || '-' }}</span>
          </div>
        </div>
        <div class="info-item">
          <div class="info-label-wrapper">
            <span class="info-label">BOM编号</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.bomNo || '-' }}</span>
          </div>
        </div>
        <div class="info-item full-width"
             v-if="routeInfo.description">
          <div class="info-label-wrapper">
            <span class="info-label">描述</span>
          </div>
          <div class="info-value-wrapper">
            <span class="info-value">{{ routeInfo.description }}</span>
          </div>
        </div>
      </div>
      <el-switch
          v-model="isTable"
          inline-prompt
          active-text="表格"
          inactive-text="列表"
          @change="handleViewChange"
      />
    </el-card>
    <!-- bom展示 -->
    <!-- 表格视图 -->
    <div v-if="viewMode === 'table'"
         class="section-header">
      <div class="section-title">工艺路线项目列表</div>
      <div class="section-actions">
        <el-button icon="Grid"
                   @click="toggleView"
                   style="margin-right: 10px;">
          卡片视图
        </el-button>
        <el-button type="primary"
                   @click="handleAdd">新增</el-button>
      </div>
    </div>
    <el-table
        v-if="isTable"
        ref="multipleTable"
        v-loading="tableLoading"
        border
        :data="routeItems"
        :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
        row-key="id"
        tooltip-effect="dark"
        class="lims-table"
        style="cursor: move;"
    >
      <el-table-column align="center" label="序号" width="60">
    <el-table v-if="viewMode === 'table'"
              ref="tableRef"
              v-loading="tableLoading"
              border
              :data="tableData"
              :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
              row-key="id"
              tooltip-effect="dark"
              class="lims-table">
      <el-table-column align="center"
                       label="序号"
                       width="60"
                       type="index" />
      <el-table-column label="工序名称"
                       prop="processId"
                       width="200">
        <template #default="scope">
          {{ scope.$index + 1 }}
          {{ getProcessName(scope.row.processId) || '-' }}
        </template>
      </el-table-column>
      <el-table-column
          v-for="(item, index) in tableColumn"
          :key="index"
          :label="item.label"
          :width="item.width"
          show-overflow-tooltip
      >
        <template #default="scope" v-if="item.dataType === 'action'">
          <el-button
              v-for="(op, opIndex) in item.operation"
              :key="opIndex"
              :type="op.type"
              :link="op.link"
              size="small"
              @click.stop="op.clickFun(scope.row)"
          >
            {{ op.name }}
          </el-button>
      <el-table-column label="产品名称"
                       prop="productName"
                       min-width="160" />
      <el-table-column label="规格名称"
                       prop="model"
                       min-width="140" />
      <el-table-column label="参数列表"
                       min-width="160">
        <template #default="scope">
          <el-button type="primary"
                     link
                     size="small"
                     @click="handleViewParams(scope.row)">参数列表</el-button>
        </template>
        <template #default="scope" v-else>
          <template v-if="item.prop === 'processId'">
            <el-select
                v-model="scope.row[item.prop]"
                style="width: 100%;"
                @mousedown.stop
            >
              <el-option
                  v-for="process in processOptions"
                  :key="process.id"
                  :label="process.name"
                  :value="process.id"
              />
            </el-select>
          </template>
          <template v-else>
            {{ scope.row[item.prop] || '-' }}
          </template>
      </el-table-column>
      <el-table-column label="单位"
                       prop="unit"
                       width="100" />
      <el-table-column label="是否质检"
                       prop="isQuality"
                       width="100">
        <template #default="scope">
          {{scope.row.isQuality ? "是" : "否"}}
        </template>
      </el-table-column>
      <el-table-column label="操作"
                       align="center"
                       fixed="right"
                       width="150">
        <template #default="scope">
          <el-button type="primary"
                     link
                     size="small"
                     @click="handleEdit(scope.row)"
                     :disabled="scope.row.isComplete">编辑</el-button>
          <!-- <el-button type="info"
                     link
                     size="small"
                     @click="handleViewParams(scope.row)">参数列表</el-button> -->
          <el-button type="danger"
                     link
                     size="small"
                     @click="handleDelete(scope.row)"
                     :disabled="scope.row.isComplete">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 使用普通div替代el-steps -->
    <div
        v-else
        ref="stepsContainer"
        class="mb5 custom-steps"
    >
      <div
          v-for="(item, index) in routeItems"
          :key="item.id"
          class="custom-step draggable-step"
          :data-id="item.id"
          style="cursor: move; flex: 0 0 auto; min-width: 220px;"
      >
        <div class="step-content">
          <div class="step-number">{{ index + 1 }}</div>
          <el-card
              :header="item.productName"
              class="step-card"
              style="cursor: move;"
          >
            <div class="step-card-content">
              <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>
            </div>
            <template #footer>
              <div class="step-card-footer">
                <el-button type="danger" link size="small" @click.stop="removeItemByID(item.id)">删除</el-button>
              </div>
            </template>
          </el-card>
    <!-- 卡片视图 -->
    <template v-else>
      <div class="section-header">
        <div class="section-title">工艺路线项目列表</div>
        <div class="section-actions">
          <el-button icon="Menu"
                     @click="toggleView"
                     style="margin-right: 10px;">
            表格视图
          </el-button>
          <el-button type="primary"
                     @click="handleAdd">新增</el-button>
        </div>
      </div>
    </div>
    <ProductSelectDialog
        v-model="isShowProductSelectDialog"
        @confirm="handelSelectProducts"
    />
      <div v-loading="tableLoading"
           class="card-container">
        <div ref="cardsContainer"
             class="cards-wrapper">
          <div v-for="(item, index) in tableData"
               :key="item.id || index"
               class="process-card"
               :data-index="index">
            <!-- 序号圆圈 -->
            <div class="card-header">
              <div class="card-number">{{ index + 1 }}</div>
              <div class="card-process-name">{{ getProcessName(item.processId) || '-' }}</div>
            </div>
            <!-- 产品信息 -->
            <div class="card-content">
              <div v-if="item.productName"
                   class="product-info">
                <div class="product-name">{{ item.productName }}</div>
                <div v-if="item.model"
                     class="product-model">
                  {{ item.model }}
                  <!-- <span v-if="item.unit" class="product-unit">{{ item.unit }}</span> -->
                </div>
                <el-tag type="primary"
                        class="product-tag"
                        v-if="item.isQuality">质检</el-tag>
              </div>
              <div v-else
                   class="product-info empty">暂无产品信息</div>
            </div>
            <!-- 操作按钮 -->
            <div class="card-footer">
              <el-button type="primary"
                         link
                         size="small"
                         @click="handleEdit(item)"
                         :disabled="item.isComplete">编辑</el-button>
              <el-button type="info"
                         link
                         size="small"
                         @click="handleViewParams(item)">参数列表</el-button>
              <el-button type="danger"
                         link
                         size="small"
                         @click="handleDelete(item)"
                         :disabled="item.isComplete">删除</el-button>
            </div>
          </div>
        </div>
      </div>
    </template>
    <!-- 新增/编辑弹窗 -->
    <el-dialog v-model="dialogVisible"
               :title="operationType === 'add' ? '新增工艺路线项目' : '编辑工艺路线项目'"
               width="500px"
               @close="closeDialog">
      <el-form ref="formRef"
               :model="form"
               :rules="rules"
               label-width="120px">
        <el-form-item label="工序"
                      prop="processId">
          <el-select v-model="form.processId"
                     placeholder="请选择工序"
                     clearable
                     style="width: 100%">
            <el-option v-for="process in processOptions"
                       :key="process.id"
                       :label="process.name"
                       :value="process.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="产品名称"
                      prop="productModelId">
          <el-button type="primary"
                     @click="showProductSelectDialog = true">
            {{ form.productName && form.model
              ? `${form.productName} - ${form.model}`
              : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item label="单位"
                      prop="unit">
          <el-input v-model="form.unit"
                    :placeholder="form.productModelId ? '根据选择的产品自动带出' : '请先选择产品'"
                    clearable
                    :disabled="true" />
        </el-form-item>
        <el-form-item label="是否质检"
                      prop="isQuality">
          <el-switch v-model="form.isQuality"
                     :active-value="true"
                     inactive-value="false" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="closeDialog">取消</el-button>
        <el-button type="primary"
                   @click="handleSubmit"
                   :loading="submitLoading">确定</el-button>
      </template>
    </el-dialog>
    <!-- 产品选择对话框 -->
    <ProductSelectDialog v-model="showProductSelectDialog"
                         @confirm="handleProductSelect"
                         single />
    <!-- 参数列表对话框 -->
    <ProcessParamListDialog v-model="showParamListDialog"
                            :title="`${currentProcess ? getProcessName(currentProcess.processId) : ''} - 参数列表`"
                            :route-id="routeId"
                            :editable="false"
                            :process="currentProcess"
                            :param-list="paramList"
                            @refresh="refreshParamList" />
  </div>
</template>
<script setup>
import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import { findProcessRouteItemList, addOrUpdateProcessRouteItem } from "@/api/productionManagement/processRouteItem.js";
import { processList } from "@/api/productionManagement/productionProcess.js";
import Sortable from 'sortablejs';
import { useRoute, useRouter } from 'vue-router'
  import {
    ref,
    computed,
    getCurrentInstance,
    onMounted,
    onUnmounted,
    nextTick,
  } from "vue";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import ProcessParamListDialog from "@/components/ProcessParamListDialog.vue";
  import {
    findProcessRouteItemList,
    addOrUpdateProcessRouteItem,
    sortProcessRouteItem,
    batchDeleteProcessRouteItem,
    getProcessParamList,
  } from "@/api/productionManagement/processRouteItem.js";
  import {
    findProductProcessRouteItemList,
    deleteRouteItem,
    addRouteItem,
    addOrUpdateProductProcessRouteItem,
    sortRouteItem,
  } from "@/api/productionManagement/productProcessRoute.js";
  import { processList } from "@/api/productionManagement/productionProcess.js";
  import { useRoute } from "vue-router";
  import { ElMessageBox, ElMessage } from "element-plus";
  import Sortable from "sortablejs";
const processOptions = ref([]);
const tableLoading = ref(false);
const isShowProductSelectDialog = ref(false);
const routeItems = ref([]);
let tableSortable = null;
let stepsSortable = null;
const multipleTable = ref(null);
const stepsContainer = ref(null);
const isTable = ref(true);
  const route = useRoute();
  const { proxy } = getCurrentInstance() || {};
const route = useRoute()
const router = useRouter()
const routeId = computed({
  get() {
    return route.query.id;
  },
  const routeId = computed(() => route.query.id);
  const orderId = computed(() => route.query.orderId);
  const pageType = computed(() => route.query.type);
  set(val) {
    emit('update:router', val)
  }
});
  const tableLoading = ref(false);
  const tableData = ref([]);
  const dialogVisible = ref(false);
  const operationType = ref("add"); // add | edit
  const formRef = ref(null);
  const submitLoading = ref(false);
  const cardsContainer = ref(null);
  const tableRef = ref(null);
  const viewMode = ref("table"); // table | card
  const routeInfo = ref({
    processRouteCode: "",
    productName: "",
    model: "",
    bomNo: "",
    bomId: null,
    description: "",
  });
  const processOptions = ref([]);
  const showProductSelectDialog = ref(false);
  const showParamListDialog = ref(false);
  const currentProcess = ref(null);
  const paramList = ref([]);
  let tableSortable = null;
  let cardSortable = null;
const tableColumn = ref([
  { label: "产品名称", prop: "productName"},
  { label: "规格名称", prop: "model" },
  { label: "单位", prop: "unit" },
  { label: "工序名称", prop: "processId", width: 200 },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 100,
    operation: [
      {
        name: "删除",
        type: "danger",
        link: true,
        clickFun: (row) => {
          const idx = routeItems.value.findIndex(item => item.id === row.id);
          if (idx > -1) {
            removeItem(idx)
          }
        }
      }
    ]
  }
]);
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 handelSelectProducts = (products) => {
  destroySortable();
  const newData = products.map(({ id, ...product }) => ({
    ...product,
    productModelId: id,
    routeId: routeId.value,
    id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
    processId: undefined
  }));
  console.log('选择产品前数组:', routeItems.value);
  routeItems.value.push(...newData);
  routeItems.value = [...routeItems.value];
  console.log('选择产品后数组:', routeItems.value);
  // 延迟初始化,确保DOM完全渲染
  nextTick(() => {
    // 强制重新渲染组件
    if (proxy?.$forceUpdate) {
      proxy.$forceUpdate();
    }
    const temp = [...routeItems.value];
    routeItems.value = [];
  // 切换视图
  const toggleView = () => {
    viewMode.value = viewMode.value === "table" ? "card" : "table";
    // 切换视图后重新初始化拖拽排序
    nextTick(() => {
      routeItems.value = temp;
      initSortable();
    });
  });
};
  };
const findProcessRouteItems = () => {
  tableLoading.value = true;
  findProcessRouteItemList({ routeId: routeId.value })
  const form = ref({
    id: undefined,
    routeId: routeId.value,
    processId: undefined,
    productModelId: undefined,
    productName: "",
    model: "",
    unit: "",
    isQuality: false,
  });
  const rules = {
    processId: [{ required: true, message: "请选择工序", trigger: "change" }],
    productModelId: [
      { required: true, message: "请选择产品", trigger: "change" },
    ],
  };
  // 根据工序ID获取工序名称
  const getProcessName = processId => {
    if (!processId) return "";
    const process = processOptions.value.find(p => p.id === processId);
    return process ? process.name : "";
  };
  // 获取列表
  const getList = () => {
    tableLoading.value = true;
    const listPromise =
      pageType.value === "order"
        ? findProductProcessRouteItemList({ orderId: orderId.value })
        : findProcessRouteItemList({ routeId: routeId.value });
    listPromise
      .then(res => {
        tableData.value = res.data || [];
        tableLoading.value = false;
        routeItems.value = res.data.map(item => ({
          ...item,
          processId: item.processId === 0 ? undefined : item.processId
        }));
        // 延迟初始化,确保DOM完全渲染
        // 列表加载完成后初始化拖拽排序
        nextTick(() => {
          setTimeout(() => initSortable(), 100);
          initSortable();
        });
      })
      .catch(err => {
        tableLoading.value = false;
        console.error("获取列表失败:", err);
        proxy?.$modal?.msgError("获取列表失败");
      });
};
  };
const findProcessList = () => {
  processList({})
  // 获取工序列表
  const getProcessList = () => {
    processList({})
      .then(res => {
        processOptions.value = res.data;
        processOptions.value = res.data || [];
      })
      .catch(err => {
        console.error("获取工序失败:", err);
      });
};
  };
const { proxy } = getCurrentInstance() || {};
  // 获取工艺路线详情(从路由参数获取)
  const getRouteInfo = () => {
    routeInfo.value = {
      processRouteCode: route.query.processRouteCode || "",
      productName: route.query.productName || "",
      model: route.query.model || "",
      bomNo: route.query.bomNo || "",
      bomId: route.query.bomId || null,
      description: route.query.description || "",
    };
  };
const handleSubmit = () => {
  const hasEmptyProcess = routeItems.value.some(item => !item.processId);
  if (hasEmptyProcess) {
    proxy?.$modal?.msgError("请为所有项目选择工序");
    return;
  }
  // 新增
  const handleAdd = () => {
    operationType.value = "add";
    resetForm();
    dialogVisible.value = true;
  };
  addOrUpdateProcessRouteItem({
    routeId: routeId.value,
    processRouteItem: routeItems.value.map(({ id, ...item }) => item)
  })
  // 编辑
  const handleEdit = row => {
    operationType.value = "edit";
    form.value = {
      id: row.id,
      routeId: routeId.value,
      processId: row.processId,
      productModelId: row.productModelId,
      productName: row.productName || "",
      model: row.model || "",
      unit: row.unit || "",
      isQuality: row.isQuality,
    };
    dialogVisible.value = true;
  };
  // 删除
  const handleDelete = row => {
    ElMessageBox.confirm("确认删除该工艺路线项目?", "提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        // 生产订单下使用 productProcessRoute 的删除接口(路由后拼接 id),其它情况使用工艺路线项目批量删除接口
        const deletePromise =
          pageType.value === "order"
            ? deleteRouteItem(row.id)
            : batchDeleteProcessRouteItem([row.id]);
        deletePromise
          .then(() => {
            proxy?.$modal?.msgSuccess("删除成功");
            getList();
          })
          .catch(() => {
            proxy?.$modal?.msgError("删除失败");
          });
      })
      .catch(() => {});
  };
  // 产品选择
  const handleProductSelect = products => {
    if (products && products.length > 0) {
      const product = products[0];
      form.value.productModelId = product.id;
      form.value.productName = product.productName;
      form.value.model = product.model;
      form.value.unit = product.unit || "";
      showProductSelectDialog.value = false;
      // 触发表单验证
      formRef.value?.validateField("productModelId");
    }
  };
  // 提交
  const handleSubmit = () => {
    formRef.value.validate(valid => {
      if (valid) {
        submitLoading.value = true;
        if (operationType.value === "add") {
          // 新增:传单个对象,包含dragSort字段
          // dragSort = 当前列表长度 + 1,表示新增记录排在最后
          const dragSort = tableData.value.length + 1;
          const isOrderPage = pageType.value === "order";
          const addPromise = isOrderPage
            ? addRouteItem({
                productOrderId: orderId.value,
                productRouteId: routeId.value,
                processId: form.value.processId,
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
                dragSort,
              })
            : addOrUpdateProcessRouteItem({
                routeId: routeId.value,
                processId: form.value.processId,
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
                dragSort,
              });
          addPromise
            .then(() => {
              proxy?.$modal?.msgSuccess("新增成功");
              closeDialog();
              getList();
            })
            .catch(() => {
              proxy?.$modal?.msgError("新增失败");
            })
            .finally(() => {
              submitLoading.value = false;
            });
        } else {
          // 编辑:生产订单下使用 productProcessRoute/updateRouteItem,其它情况使用工艺路线项目更新接口
          const isOrderPage = pageType.value === "order";
          const updatePromise = isOrderPage
            ? addOrUpdateProductProcessRouteItem({
                id: form.value.id,
                processId: form.value.processId,
                productModelId: form.value.productModelId,
                isQuality: form.value.isQuality,
              })
            : addOrUpdateProcessRouteItem({
                routeId: routeId.value,
                processId: form.value.processId,
                productModelId: form.value.productModelId,
                id: form.value.id,
                isQuality: form.value.isQuality,
              });
          updatePromise
            .then(() => {
              proxy?.$modal?.msgSuccess("修改成功");
              closeDialog();
              getList();
            })
            .catch(() => {
              proxy?.$modal?.msgError("修改失败");
            })
            .finally(() => {
              submitLoading.value = false;
            });
        }
      }
    });
  };
  // 重置表单
  const resetForm = () => {
    form.value = {
      id: undefined,
      routeId: routeId.value,
      processId: undefined,
      productModelId: undefined,
      productName: "",
      model: "",
      unit: "",
    };
    formRef.value?.resetFields();
  };
  // 关闭弹窗
  const closeDialog = () => {
    dialogVisible.value = false;
    resetForm();
  };
  // 初始化拖拽排序
  const initSortable = () => {
    destroySortable();
    if (viewMode.value === "table") {
      // 表格视图的拖拽排序
      if (!tableRef.value) return;
      const tbody =
        tableRef.value.$el.querySelector(".el-table__body tbody") ||
        tableRef.value.$el.querySelector(
          ".el-table__body-wrapper > table > tbody"
        );
      if (!tbody) return;
      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 || !tableData.value[evt.oldIndex])
            return;
          // 重新排序数组
          const moveItem = tableData.value.splice(evt.oldIndex, 1)[0];
          tableData.value.splice(evt.newIndex, 0, moveItem);
          // 计算新的序号(dragSort从1开始)
          const newIndex = evt.newIndex;
          const dragSort = newIndex + 1;
          // 调用排序接口
          if (moveItem.id) {
            const isOrderPage = pageType.value === "order";
            const sortPromise = isOrderPage
              ? sortRouteItem({
                  id: moveItem.id,
                  dragSort: dragSort,
                })
              : sortProcessRouteItem({
                  id: moveItem.id,
                  dragSort: dragSort,
                });
            sortPromise
              .then(() => {
                // 更新所有行的dragSort
                tableData.value.forEach((item, index) => {
                  if (item.id) {
                    item.dragSort = index + 1;
                  }
                });
                proxy?.$modal?.msgSuccess("排序成功");
              })
              .catch(err => {
                // 排序失败,恢复原数组
                tableData.value.splice(newIndex, 1);
                tableData.value.splice(evt.oldIndex, 0, moveItem);
                proxy?.$modal?.msgError("排序失败");
                console.error("排序失败:", err);
              });
          }
        },
      });
    } else {
      // 卡片视图的拖拽排序
      if (!cardsContainer.value) return;
      cardSortable = new Sortable(cardsContainer.value, {
        animation: 150,
        ghostClass: "sortable-ghost",
        handle: ".process-card",
        filter: ".el-button",
        onEnd: evt => {
          if (evt.oldIndex === evt.newIndex || !tableData.value[evt.oldIndex])
            return;
          // 重新排序数组
          const moveItem = tableData.value.splice(evt.oldIndex, 1)[0];
          tableData.value.splice(evt.newIndex, 0, moveItem);
          // 计算新的序号(dragSort从1开始)
          const newIndex = evt.newIndex;
          const dragSort = newIndex + 1;
          // 调用排序接口
          if (moveItem.id) {
            const isOrderPage = pageType.value === "order";
            const sortPromise = isOrderPage
              ? sortRouteItem({
                  id: moveItem.id,
                  dragSort: dragSort,
                })
              : sortProcessRouteItem({
                  id: moveItem.id,
                  dragSort: dragSort,
                });
            sortPromise
              .then(() => {
                // 更新所有行的dragSort
                tableData.value.forEach((item, index) => {
                  if (item.id) {
                    item.dragSort = index + 1;
                  }
                });
                proxy?.$modal?.msgSuccess("排序成功");
              })
              .catch(err => {
                // 排序失败,恢复原数组
                tableData.value.splice(newIndex, 1);
                tableData.value.splice(evt.oldIndex, 0, moveItem);
                proxy?.$modal?.msgError("排序失败");
                console.error("排序失败:", err);
              });
          }
        },
      });
    }
  };
  // 销毁拖拽排序
  const destroySortable = () => {
    if (tableSortable) {
      tableSortable.destroy();
      tableSortable = null;
    }
    if (cardSortable) {
      cardSortable.destroy();
      cardSortable = null;
    }
  };
  onMounted(() => {
    getRouteInfo();
    getList();
    getProcessList();
  });
  // 查看参数列表
  const handleViewParams = process => {
    currentProcess.value = process;
    // 调用API获取参数列表
    getProcessParamList({
      routeItemId: process.id,
      pageNum: 1,
      pageSize: 1000,
    })
      .then(res => {
        router.push({
          path: '/productionManagement/processRoute',
        })
        proxy?.$modal?.msgSuccess("提交成功");
        if (res.code === 200) {
          paramList.value = res.data?.records || [];
        } else {
          ElMessage.error(res.msg || "获取参数列表失败");
          paramList.value = [];
        }
        showParamListDialog.value = true;
      })
      .catch(err => {
        proxy?.$modal?.msgError(`提交失败:${err.msg || "网络异常"}`);
        console.error("获取参数列表失败:", err);
        ElMessage.error("获取参数列表失败");
        paramList.value = [];
        showParamListDialog.value = true;
      });
};
  };
const destroySortable = () => {
  if (tableSortable) {
    tableSortable.destroy();
    tableSortable = null;
  }
  if (stepsSortable) {
    stepsSortable.destroy();
    stepsSortable = null;
  }
};
  // 刷新参数列表
  const refreshParamList = () => {
    if (!currentProcess.value) return;
    // 重新调用API获取参数列表
    getProcessParamList({
      routeItemId: currentProcess.value.id,
      pageNum: 1,
      pageSize: 1000,
    })
      .then(res => {
        if (res.code === 200) {
          paramList.value = res.data?.records || [];
        } else {
          ElMessage.error(res.msg || "获取参数列表失败");
          paramList.value = [];
        }
      })
      .catch(err => {
        console.error("获取参数列表失败:", err);
        ElMessage.error("获取参数列表失败");
        paramList.value = [];
      });
  };
const initSortable = () => {
  destroySortable();
  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;
    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;
    // 修改:直接使用stepsContainer.value作为拖拽容器
    const stepsList = stepsContainer.value;
    if (!stepsList) {
      console.warn('未找到步骤条拖拽容器');
      return;
    }
    // 修改:简化拖拽配置
    stepsSortable = new Sortable(stepsList, {
      animation: 150,
      ghostClass: 'sortable-ghost',
      draggable: '.draggable-step', // 可拖拽元素
      handle: '.draggable-step, .step-card', // 拖拽手柄
      filter: '.el-button, .el-select, .el-input', // 过滤按钮/选择器
      forceFallback: true,
      fallbackClass: 'sortable-fallback',
      preventOnFilter: true,
      scroll: true,
      scrollSensitivity: 30,
      scrollSpeed: 10,
      bubbleScroll: true,
      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);
  onUnmounted(() => {
    destroySortable();
  });
};
onMounted(() => {
  findProcessRouteItems();
  findProcessList();
});
onUnmounted(() => {
  destroySortable();
});
defineExpose({
  handleSubmit,
});
</script>
<style scoped>
:deep(.sortable-ghost) {
  opacity: 0.6;
  background-color: #f5f7fa !important;
}
  .card-container {
    padding: 20px 0;
  }
:deep(.el-table__row) {
  transition: background-color 0.2s;
}
  .cards-wrapper {
    display: flex;
    gap: 16px;
    overflow-x: auto;
    padding: 10px 0;
    min-height: 200px;
  }
:deep(.el-table__row:hover) {
  background-color: #f9fafc !important;
}
  .cards-wrapper::-webkit-scrollbar {
    height: 8px;
  }
:deep(.el-card__footer){
  padding: 0 !important;
}
  .cards-wrapper::-webkit-scrollbar-track {
    background: #f1f1f1;
    border-radius: 4px;
  }
.operate-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
  .cards-wrapper::-webkit-scrollbar-thumb {
    background: #c1c1c1;
    border-radius: 4px;
  }
/* 修改:自定义步骤条容器样式 */
.custom-steps {
  min-height: 100px;
  padding: 10px 0;
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  align-items: flex-start;
}
  .cards-wrapper::-webkit-scrollbar-thumb:hover {
    background: #a8a8a8;
  }
/* 修改:自定义步骤项样式 */
.custom-step {
  cursor: move !important;
  padding: 8px;
  position: relative;
  transition: all 0.2s ease;
  flex: 0 0 auto;
  min-width: 220px;
  touch-action: none;
}
  .process-card {
    flex-shrink: 0;
    width: 220px;
    background: #fff;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    padding: 16px;
    display: flex;
    flex-direction: column;
    cursor: move;
    transition: all 0.3s;
  }
/* 拖拽悬浮样式,提示可拖拽 */
.custom-step:hover {
  background-color: rgba(64, 158, 255, 0.05);
  transform: translateY(-2px);
}
  .process-card:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    transform: translateY(-2px);
  }
.sortable-ghost {
  opacity: 0.4;
  background-color: #f5f7fa !important;
  border: 2px dashed #409eff;
  margin: 10px;
  transform: scale(1.02);
}
  .card-header {
    text-align: center;
    margin-bottom: 12px;
  }
.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;
}
  .card-number {
    width: 36px;
    height: 36px;
    line-height: 36px;
    border-radius: 50%;
    background: #409eff;
    color: #fff;
    font-weight: bold;
    font-size: 16px;
    margin: 0 auto 8px;
  }
.step-card {
  cursor: move !important;
  transition: box-shadow 0.2s ease;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: auto;
  margin: 10px;
  height: 260px;
}
  .card-process-name {
    font-size: 14px;
    color: #333;
    font-weight: 500;
    word-break: break-all;
  }
.step-card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
  .card-content {
    flex: 1;
    margin-bottom: 12px;
    min-height: 60px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
.step-content {
  width: 245px;
  user-select: none;
}
  .product-info {
    font-size: 13px;
    color: #666;
    text-align: center;
    width: 100%;
  }
.step-card-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  height: 140px;
}
  .product-info.empty {
    color: #999;
    text-align: center;
    padding: 20px 0;
  }
.step-card-footer {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding: 10px;
}
  .product-name {
    margin-bottom: 6px;
    word-break: break-all;
    line-height: 1.5;
    text-align: center;
  }
/* 自定义序号样式优化 */
.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;
}
  .product-model {
    color: #909399;
    font-size: 12px;
    word-break: break-all;
    line-height: 1.5;
    text-align: center;
  }
  .product-unit {
    margin-left: 4px;
    color: #409eff;
  }
  .product-tag {
    margin: 10px 0;
  }
  .card-footer {
    display: flex;
    justify-content: space-around;
    padding-top: 12px;
    border-top: 1px solid #f0f0f0;
  }
  .card-footer .el-button {
    padding: 0;
    font-size: 12px;
  }
  :deep(.sortable-ghost) {
    opacity: 0.5;
    background-color: #f5f7fa !important;
  }
  :deep(.sortable-drag) {
    opacity: 0.8;
  }
  /* 表格视图样式 */
  :deep(.el-table__row) {
    transition: background-color 0.2s;
    cursor: move;
  }
  :deep(.el-table__row:hover) {
    background-color: #f9fafc !important;
  }
  /* 区域标题样式 */
  .section-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 12px;
  }
  .section-title {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
    padding-left: 12px;
    position: relative;
    margin-bottom: 0;
  }
  .section-title::before {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 16px;
    background: #409eff;
    border-radius: 2px;
  }
  .section-actions {
    display: flex;
    align-items: center;
  }
  /* 工艺路线信息卡片样式 */
  .route-info-card {
    margin-bottom: 20px;
    border: 1px solid #e4e7ed;
    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
    border-radius: 8px;
    overflow: hidden;
  }
  .route-info {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 16px;
    padding: 4px;
  }
  .info-item {
    display: flex;
    flex-direction: column;
    background: #ffffff;
    border-radius: 6px;
    padding: 14px 16px;
    border: 1px solid #f0f2f5;
    transition: all 0.3s ease;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  }
  .info-item:hover {
    border-color: #409eff;
    box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
    transform: translateY(-1px);
  }
  .info-item.full-width {
    grid-column: 1 / -1;
  }
  .info-label-wrapper {
    margin-bottom: 8px;
  }
  .info-label {
    display: inline-block;
    color: #909399;
    font-size: 12px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    padding: 2px 0;
    position: relative;
  }
  .info-label::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 20px;
    height: 2px;
    background: linear-gradient(90deg, #409eff, transparent);
    border-radius: 1px;
  }
  .info-value-wrapper {
    flex: 1;
  }
  .info-value {
    display: block;
    color: #303133;
    font-size: 15px;
    font-weight: 500;
    line-height: 1.5;
    word-break: break-all;
  }
</style>