From 7543b32e5c64bb415af4368123f2c1cbefe94549 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期四, 15 一月 2026 14:44:37 +0800
Subject: [PATCH] fix: 完成工艺路线项目重构
---
src/views/productionManagement/processRoute/processRouteItem/index.vue | 546 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 532 insertions(+), 14 deletions(-)
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
index 7e32396..31b4a74 100644
--- a/src/views/productionManagement/processRoute/processRouteItem/index.vue
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -1,12 +1,71 @@
<template>
<div class="app-container">
- <PageHeader content="宸ヨ壓璺嚎椤圭洰">
- <template #right-button>
- <el-button type="primary" @click="handleAdd">鏂板</el-button>
- </template>
- </PageHeader>
+ <PageHeader content="宸ヨ壓璺嚎椤圭洰" />
+ <!-- 宸ヨ壓璺嚎淇℃伅灞曠ず -->
+ <div v-if="routeInfo.processRouteCode" class="section-title" style="margin-bottom: 12px;">宸ヨ壓璺嚎淇℃伅</div>
+ <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"
@@ -31,6 +90,60 @@
</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
@@ -95,12 +208,13 @@
</template>
<script setup>
-import { ref, computed, getCurrentInstance, onMounted } from "vue";
+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 { 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() || {};
@@ -113,9 +227,30 @@
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,
@@ -146,6 +281,10 @@
.then(res => {
tableData.value = res.data || [];
tableLoading.value = false;
+ // 鍒楄〃鍔犺浇瀹屾垚鍚庡垵濮嬪寲鎷栨嫿鎺掑簭
+ nextTick(() => {
+ initSortable();
+ });
})
.catch(err => {
tableLoading.value = false;
@@ -163,6 +302,17 @@
.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 || ''
+ };
};
// 鏂板
@@ -195,11 +345,8 @@
type: 'warning'
})
.then(() => {
- // 璋冪敤鍒犻櫎鎺ュ彛锛屼紶鍗曚釜瀵硅薄锛堝寘鍚玦d锛�
- addOrUpdateProcessRouteItem({
- id: row.id,
- routeId: routeId.value,
- })
+ // 璋冪敤鎵归噺鍒犻櫎鎺ュ彛锛屼紶閫抜d鏁扮粍
+ batchDeleteProcessRouteItem([row.id])
.then(() => {
proxy?.$modal?.msgSuccess('鍒犻櫎鎴愬姛');
getList();
@@ -239,8 +386,13 @@
};
if (operationType.value === 'add') {
- // 鏂板锛氫紶鍗曚釜瀵硅薄
- addOrUpdateProcessRouteItem(submitData)
+ // 鏂板锛氫紶鍗曚釜瀵硅薄锛屽寘鍚玠ragSort瀛楁
+ // dragSort = 褰撳墠鍒楄〃闀垮害 + 1锛岃〃绀烘柊澧炶褰曟帓鍦ㄦ渶鍚�
+ const dragSort = tableData.value.length + 1;
+ addOrUpdateProcessRouteItem({
+ ...submitData,
+ dragSort: dragSort
+ })
.then(() => {
proxy?.$modal?.msgSuccess('鏂板鎴愬姛');
closeDialog();
@@ -294,18 +446,384 @@
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) {
+ sortProcessRouteItem({
+ id: moveItem.id,
+ dragSort: dragSort
+ })
+ .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) {
+ sortProcessRouteItem({
+ id: moveItem.id,
+ dragSort: dragSort
+ })
+ .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