From 187041ecb09b7b1e911e12423e4db90b5d1d2d28 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期一, 19 一月 2026 14:53:38 +0800
Subject: [PATCH] 生产管控迁移遗漏

---
 src/views/productionManagement/processRoute/processRouteItem/index.vue |  911 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 911 insertions(+), 0 deletions(-)

diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
new file mode 100644
index 0000000..2472bee
--- /dev/null
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -0,0 +1,911 @@
+<template>
+  <div class="app-container">
+    <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-card>
+    <!-- 琛ㄦ牸瑙嗗浘 -->
+    <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="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">
+          {{ getProcessName(scope.row.processId) || '-' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="浜у搧鍚嶇О"
+                       prop="productName"
+                       min-width="160" />
+      <el-table-column label="瑙勬牸鍚嶇О"
+                       prop="model"
+                       min-width="140" />
+      <el-table-column label="鍗曚綅"
+                       prop="unit"
+                       width="100" />
+      <el-table-column label="鎿嶄綔"
+                       align="center"
+                       fixed="right"
+                       width="150">
+        <template #default="scope">
+          <el-button type="primary"
+                     link
+                     size="small"
+                     @click="handleEdit(scope.row)">缂栬緫</el-button>
+          <el-button type="danger"
+                     link
+                     size="small"
+                     @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 鍗$墖瑙嗗浘 -->
+    <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 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>
+              </div>
+              <div v-else
+                   class="product-info empty">鏆傛棤浜у搧淇℃伅</div>
+            </div>
+            <!-- 鎿嶄綔鎸夐挳 -->
+            <div class="card-footer">
+              <el-button type="primary"
+                         link
+                         size="small"
+                         @click="handleEdit(item)">缂栬緫</el-button>
+              <el-button type="danger"
+                         link
+                         size="small"
+                         @click="handleDelete(item)">鍒犻櫎</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>
+      <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 />
+  </div>
+</template>
+
+<script setup>
+  import {
+    ref,
+    computed,
+    getCurrentInstance,
+    onMounted,
+    onUnmounted,
+    nextTick,
+  } from "vue";
+  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+  import {
+    findProcessRouteItemList,
+    addOrUpdateProcessRouteItem,
+    sortProcessRouteItem,
+    batchDeleteProcessRouteItem,
+  } 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 } from "element-plus";
+  import Sortable from "sortablejs";
+
+  const route = useRoute();
+  const { proxy } = getCurrentInstance() || {};
+
+  const routeId = computed(() => route.query.id);
+  const orderId = computed(() => route.query.orderId);
+  const pageType = computed(() => route.query.type);
+
+  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: "",
+    description: "",
+  });
+
+  const processOptions = ref([]);
+  const showProductSelectDialog = ref(false);
+  let tableSortable = null;
+  let cardSortable = null;
+
+  // 鍒囨崲瑙嗗浘
+  const toggleView = () => {
+    viewMode.value = viewMode.value === "table" ? "card" : "table";
+    // 鍒囨崲瑙嗗浘鍚庨噸鏂板垵濮嬪寲鎷栨嫿鎺掑簭
+    nextTick(() => {
+      initSortable();
+    });
+  };
+
+  const form = ref({
+    id: undefined,
+    routeId: routeId.value,
+    processId: undefined,
+    productModelId: undefined,
+    productName: "",
+    model: "",
+    unit: "",
+  });
+
+  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;
+        // 鍒楄〃鍔犺浇瀹屾垚鍚庡垵濮嬪寲鎷栨嫿鎺掑簭
+        nextTick(() => {
+          initSortable();
+        });
+      })
+      .catch(err => {
+        tableLoading.value = false;
+        console.error("鑾峰彇鍒楄〃澶辫触锛�", err);
+        proxy?.$modal?.msgError("鑾峰彇鍒楄〃澶辫触");
+      });
+  };
+
+  // 鑾峰彇宸ュ簭鍒楄〃
+  const getProcessList = () => {
+    processList({})
+      .then(res => {
+        processOptions.value = res.data || [];
+      })
+      .catch(err => {
+        console.error("鑾峰彇宸ュ簭澶辫触锛�", err);
+      });
+  };
+
+  // 鑾峰彇宸ヨ壓璺嚎璇︽儏锛堜粠璺敱鍙傛暟鑾峰彇锛�
+  const getRouteInfo = () => {
+    routeInfo.value = {
+      processRouteCode: route.query.processRouteCode || "",
+      productName: route.query.productName || "",
+      model: route.query.model || "",
+      bomNo: route.query.bomNo || "",
+      description: route.query.description || "",
+    };
+  };
+
+  // 鏂板
+  const handleAdd = () => {
+    operationType.value = "add";
+    resetForm();
+    dialogVisible.value = true;
+  };
+
+  // 缂栬緫
+  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 || "",
+    };
+    dialogVisible.value = true;
+  };
+
+  // 鍒犻櫎
+  const handleDelete = row => {
+    ElMessageBox.confirm("纭鍒犻櫎璇ュ伐鑹鸿矾绾块」鐩紵", "鎻愮ず", {
+      confirmButtonText: "纭",
+      cancelButtonText: "鍙栨秷",
+      type: "warning",
+    })
+      .then(() => {
+        // 鐢熶骇璁㈠崟涓嬩娇鐢� productProcessRoute 鐨勫垹闄ゆ帴鍙o紙璺敱鍚庢嫾鎺� 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") {
+          // 鏂板锛氫紶鍗曚釜瀵硅薄锛屽寘鍚玠ragSort瀛楁
+          // 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,
+                dragSort,
+              })
+            : addOrUpdateProcessRouteItem({
+                routeId: routeId.value,
+                processId: form.value.processId,
+                productModelId: form.value.productModelId,
+                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,
+              })
+            : addOrUpdateProcessRouteItem({
+                routeId: routeId.value,
+                processId: form.value.processId,
+                productModelId: form.value.productModelId,
+                id: form.value.id,
+              });
+
+          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);
+
+          // 璁$畻鏂扮殑搴忓彿锛坉ragSort浠�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(() => {
+                // 鏇存柊鎵�鏈夎鐨刣ragSort
+                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);
+
+          // 璁$畻鏂扮殑搴忓彿锛坉ragSort浠�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(() => {
+                // 鏇存柊鎵�鏈夎鐨刣ragSort
+                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();
+  });
+
+  onUnmounted(() => {
+    destroySortable();
+  });
+</script>
+
+<style scoped>
+  .card-container {
+    padding: 20px 0;
+  }
+
+  .cards-wrapper {
+    display: flex;
+    gap: 16px;
+    overflow-x: auto;
+    padding: 10px 0;
+    min-height: 200px;
+  }
+
+  .cards-wrapper::-webkit-scrollbar {
+    height: 8px;
+  }
+
+  .cards-wrapper::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 4px;
+  }
+
+  .cards-wrapper::-webkit-scrollbar-thumb {
+    background: #c1c1c1;
+    border-radius: 4px;
+  }
+
+  .cards-wrapper::-webkit-scrollbar-thumb:hover {
+    background: #a8a8a8;
+  }
+
+  .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;
+  }
+
+  .process-card:hover {
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+    transform: translateY(-2px);
+  }
+
+  .card-header {
+    text-align: center;
+    margin-bottom: 12px;
+  }
+
+  .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;
+  }
+
+  .card-process-name {
+    font-size: 14px;
+    color: #333;
+    font-weight: 500;
+    word-break: break-all;
+  }
+
+  .card-content {
+    flex: 1;
+    margin-bottom: 12px;
+    min-height: 60px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .product-info {
+    font-size: 13px;
+    color: #666;
+    text-align: center;
+    width: 100%;
+  }
+
+  .product-info.empty {
+    color: #999;
+    text-align: center;
+    padding: 20px 0;
+  }
+
+  .product-name {
+    margin-bottom: 6px;
+    word-break: break-all;
+    line-height: 1.5;
+    text-align: center;
+  }
+
+  .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;
+  }
+
+  .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>

--
Gitblit v1.9.3