From 3de8b71ee1c6be3e41b77d6633c3f1a1b66c40f2 Mon Sep 17 00:00:00 2001
From: 张诺 <zhang_12370@163.com>
Date: 星期五, 16 一月 2026 10:59:33 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_天津军泰伟业' into dev_天津军泰伟业
---
src/views/productionManagement/processRoute/processRouteItem/index.vue | 1133 +++++++++++++++++++++++++++++++++++++++--------------------
1 files changed, 753 insertions(+), 380 deletions(-)
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index 641ffff..18e21e8 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -1,133 +1,207 @@
<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>
+
+ <!-- 琛ㄦ牸瑙嗗浘 -->
+ <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-if="viewMode === 'table'"
+ ref="tableRef"
v-loading="tableLoading"
border
- :data="routeItems"
+ :data="tableData"
: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-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>
- </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 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>
-
- <!-- 浣跨敤鏅�歞iv鏇夸唬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;"
+
+ <!-- 鍗$墖瑙嗗浘 -->
+ <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;"
>
- <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>
+ 琛ㄦ牸瑙嗗浘
+ </el-button>
+ <el-button type="primary" @click="handleAdd">鏂板</el-button>
</div>
</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="isShowProductSelectDialog"
- @confirm="handelSelectProducts"
+ v-model="showProductSelectDialog"
+ @confirm="handleProductSelect"
+ single
/>
</div>
</template>
@@ -135,178 +209,285 @@
<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 { 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 Sortable from 'sortablejs';
-import { useRoute, useRouter } from 'vue-router'
-
-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);
+import { useRoute } from 'vue-router'
+import { ElMessageBox } from 'element-plus'
+import Sortable from 'sortablejs'
const route = useRoute()
-const router = useRouter()
-const routeId = computed({
- get() {
- return route.query.id;
- },
+const { proxy } = getCurrentInstance() || {};
- set(val) {
- emit('update:router', val)
- }
+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 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瀹屽叏娓叉煋
+// 鍒囨崲瑙嗗浘
+const toggleView = () => {
+ viewMode.value = viewMode.value === 'table' ? 'card' : 'table';
+ // 鍒囨崲瑙嗗浘鍚庨噸鏂板垵濮嬪寲鎷栨嫿鎺掑簭
nextTick(() => {
- // 寮哄埗閲嶆柊娓叉煋缁勪欢
- if (proxy?.$forceUpdate) {
- proxy.$forceUpdate();
- }
-
- const temp = [...routeItems.value];
- routeItems.value = [];
- nextTick(() => {
- routeItems.value = temp;
- initSortable();
- });
+ initSortable();
});
};
-const findProcessRouteItems = () => {
+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;
- findProcessRouteItemList({ routeId: routeId.value })
- .then(res => {
- tableLoading.value = false;
- routeItems.value = res.data.map(item => ({
- ...item,
- processId: item.processId === 0 ? undefined : item.processId
- }));
- // 寤惰繜鍒濆鍖栵紝纭繚DOM瀹屽叏娓叉煋
- nextTick(() => {
- setTimeout(() => initSortable(), 100);
- });
- })
- .catch(err => {
- tableLoading.value = false;
- console.error("鑾峰彇鍒楄〃澶辫触锛�", err);
+ 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 findProcessList = () => {
+// 鑾峰彇宸ュ簭鍒楄〃
+const getProcessList = () => {
processList({})
- .then(res => {
- processOptions.value = res.data;
- })
- .catch(err => {
- console.error("鑾峰彇宸ュ簭澶辫触锛�", err);
- });
+ .then(res => {
+ 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 || '',
+ 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({
+// 缂栬緫
+const handleEdit = (row) => {
+ operationType.value = 'edit';
+ form.value = {
+ id: row.id,
routeId: routeId.value,
- processRouteItem: routeItems.value.map(({ id, ...item }) => item)
+ 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(res => {
- router.push({
- path: '/productionManagement/processRoute',
+ .then(() => {
+ // 鐢熶骇璁㈠崟涓嬩娇鐢� productProcessRoute 鐨勫垹闄ゆ帴鍙o紙璺敱鍚庢嫾鎺� id锛夛紝鍏跺畠鎯呭喌浣跨敤宸ヨ壓璺嚎椤圭洰鎵归噺鍒犻櫎鎺ュ彛
+ const deletePromise =
+ pageType.value === 'order'
+ ? deleteRouteItem(row.id)
+ : batchDeleteProcessRouteItem([row.id]);
+
+ deletePromise
+ .then(() => {
+ proxy?.$modal?.msgSuccess('鍒犻櫎鎴愬姛');
+ getList();
})
- proxy?.$modal?.msgSuccess("鎻愪氦鎴愬姛");
- })
- .catch(err => {
- proxy?.$modal?.msgError(`鎻愪氦澶辫触锛�${err.msg || "缃戠粶寮傚父"}`);
- });
+ .catch(() => {
+ proxy?.$modal?.msgError('鍒犻櫎澶辫触');
+ });
+ })
+ .catch(() => {});
};
-const destroySortable = () => {
- if (tableSortable) {
- tableSortable.destroy();
- tableSortable = null;
- }
- if (stepsSortable) {
- stepsSortable.destroy();
- stepsSortable = null;
+// 浜у搧閫夋嫨
+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 (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 (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, {
@@ -315,189 +496,381 @@
handle: '.el-table__row',
filter: '.el-button, .el-select',
onEnd: (evt) => {
- if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
+ if (evt.oldIndex === evt.newIndex || !tableData.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);
+ // 閲嶆柊鎺掑簭鏁扮粍
+ 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 (!stepsContainer.value) return;
+ // 鍗$墖瑙嗗浘鐨勬嫋鎷芥帓搴�
+ if (!cardsContainer.value) return;
- // 淇敼锛氱洿鎺ヤ娇鐢╯tepsContainer.value浣滀负鎷栨嫿瀹瑰櫒
- const stepsList = stepsContainer.value;
- if (!stepsList) {
- console.warn('鏈壘鍒版楠ゆ潯鎷栨嫿瀹瑰櫒');
- return;
- }
-
- // 淇敼锛氱畝鍖栨嫋鎷介厤缃�
- stepsSortable = new Sortable(stepsList, {
+ cardSortable = new Sortable(cardsContainer.value, {
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,
+ handle: '.process-card',
+ filter: '.el-button',
onEnd: (evt) => {
- if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return;
+ if (evt.oldIndex === evt.newIndex || !tableData.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];
+ // 閲嶆柊鎺掑簭鏁扮粍
+ 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);
+ });
+ }
}
});
-
- // 璋冭瘯锛氭墦鍗板鍣ㄥ拰瀹炰緥锛岀‘璁ょ粦瀹氭垚鍔�
- console.log('姝ラ鏉℃嫋鎷藉鍣�:', stepsList);
- console.log('Sortable瀹炰緥:', stepsSortable);
}
};
-const handleViewChange = () => {
- destroySortable();
- // 寤惰繜鍒濆鍖栵紝纭繚瑙嗗浘鍒囨崲鍚嶥OM瀹屽叏娓叉煋
- nextTick(() => {
- setTimeout(() => initSortable(), 100);
- });
+// 閿�姣佹嫋鎷芥帓搴�
+const destroySortable = () => {
+ if (tableSortable) {
+ tableSortable.destroy();
+ tableSortable = null;
+ }
+ if (cardSortable) {
+ cardSortable.destroy();
+ cardSortable = null;
+ }
};
onMounted(() => {
- findProcessRouteItems();
- findProcessList();
+ getRouteInfo();
+ getList();
+ getProcessList();
});
onUnmounted(() => {
destroySortable();
});
-
-defineExpose({
- handleSubmit,
-});
</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.6;
+ 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;
}
-:deep(.el-card__footer){
- padding: 0 !important;
+/* 鍖哄煙鏍囬鏍峰紡 */
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
}
-.operate-button {
+.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;
- justify-content: space-between;
}
-/* 淇敼锛氳嚜瀹氫箟姝ラ鏉″鍣ㄦ牱寮� */
-.custom-steps {
- min-height: 100px;
- padding: 10px 0;
- display: flex;
- flex-wrap: wrap;
- gap: 20px;
- align-items: flex-start;
+/* 宸ヨ壓璺嚎淇℃伅鍗$墖鏍峰紡 */
+.route-info-card {
+ margin-bottom: 20px;
+ border: 1px solid #e4e7ed;
+ background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
+ border-radius: 8px;
+ overflow: hidden;
}
-/* 淇敼锛氳嚜瀹氫箟姝ラ椤规牱寮� */
-.custom-step {
- cursor: move !important;
- padding: 8px;
- position: relative;
- transition: all 0.2s ease;
- flex: 0 0 auto;
- min-width: 220px;
- touch-action: none;
+.route-info {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 16px;
+ padding: 4px;
}
-/* 鎷栨嫿鎮诞鏍峰紡锛屾彁绀哄彲鎷栨嫿 */
-.custom-step:hover {
- background-color: rgba(64, 158, 255, 0.05);
- transform: translateY(-2px);
-}
-
-.sortable-ghost {
- opacity: 0.4;
- background-color: #f5f7fa !important;
- border: 2px dashed #409eff;
- margin: 10px;
- transform: scale(1.02);
-}
-
-.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;
-}
-
-.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;
-}
-
-.step-card:hover {
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
-}
-
-.step-content {
- width: 245px;
- user-select: none;
-}
-
-.step-card-content {
+.info-item {
display: flex;
flex-direction: column;
- align-items: center;
- height: 140px;
+ 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);
}
-.step-card-footer {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- padding: 10px;
+.info-item:hover {
+ border-color: #409eff;
+ box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
+ transform: translateY(-1px);
}
-/* 鑷畾涔夊簭鍙锋牱寮忎紭鍖� */
-.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;
+.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