From 17bff385d517ada8dd3ac8fc4b81c4c163a17aa2 Mon Sep 17 00:00:00 2001
From: yaowanxin <3588231647@qq.com>
Date: 星期三, 14 一月 2026 13:34:32 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_天津军泰伟业' into dev_天津军泰伟业
---
src/views/productionManagement/processRoute/index.vue | 150 +++++-----
src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue | 171 ++++++-----
src/views/productionManagement/processRoute/processRouteItem/index.vue | 503 +++++++++++++++++++++++++++++++++++
3 files changed, 675 insertions(+), 149 deletions(-)
diff --git a/src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue b/src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue
index e26d73c..85f83ff 100644
--- a/src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue
+++ b/src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue
@@ -28,12 +28,22 @@
</el-col>
<el-col :span="12">
<el-form-item label="鏉ョエ鏁帮細">
- <el-input-number :step="0.1" :min="0" style="width: 100%" v-model="form.ticketsNum" @change="inputTicketsNum" :precision="2"/>
+ <el-input-number :step="0.1"
+ :min="0"
+ style="width: 100%"
+ v-model="form.ticketsNum"
+ @change="inputTicketsNum"
+ :precision="2" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="鏈鏉ョエ閲戦(鍏�)锛�">
- <el-input-number :step="0.1" :min="0" style="width: 100%" v-model="form.ticketsAmount" @change="inputTicketsAmount" :precision="2"/>
+ <el-input-number :step="0.1"
+ :min="0"
+ style="width: 100%"
+ v-model="form.ticketsAmount"
+ @change="inputTicketsAmount"
+ :precision="2" />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -46,85 +56,92 @@
</template>
<script setup>
-import useFormData from "@/hooks/useFormData";
-import { getProductRecordById } from "@/api/procurementManagement/procurementInvoiceLedger";
-const { proxy } = getCurrentInstance()
+ import useFormData from "@/hooks/useFormData";
+ import { getProductRecordById } from "@/api/procurementManagement/procurementInvoiceLedger";
+ const { proxy } = getCurrentInstance();
-defineOptions({
- name: "鏉ョエ鍙拌处琛ㄥ崟",
-});
-const temFutureTickets = ref(0)
-const { form, resetForm } = useFormData({
- id: undefined,
- purchaseContractNumber: undefined, // 閲囪喘鍚堝悓鍙�
- salesContractNo: undefined, // 閿�鍞悎鍚屽彿
- createdAt: undefined, // 鍒涘缓鏃堕棿
- invoiceNumber: undefined, // 鍙戠エ鍙�
- ticketsNum: undefined, // 鏉ョエ鏁�
- ticketsAmount: undefined, // 鏉ョエ閲戦
- taxInclusiveUnitPrice: undefined, // 鍚◣鍗曚环
- ticketRegistrationId: undefined, // 鍚◣鍗曚环
-});
+ defineOptions({
+ name: "鏉ョエ鍙拌处琛ㄥ崟",
+ });
+ const temFutureTickets = ref(0);
+ const { form, resetForm } = useFormData({
+ id: undefined,
+ purchaseContractNumber: undefined, // 閲囪喘鍚堝悓鍙�
+ salesContractNo: undefined, // 閿�鍞悎鍚屽彿
+ createdAt: undefined, // 鍒涘缓鏃堕棿
+ invoiceNumber: undefined, // 鍙戠エ鍙�
+ ticketsNum: undefined, // 鏉ョエ鏁�
+ ticketsAmount: undefined, // 鏉ョエ閲戦
+ taxInclusiveUnitPrice: undefined, // 鍚◣鍗曚环
+ ticketRegistrationId: undefined, // 鍚◣鍗曚环
+ });
-const load = async (id) => {
- const { code, data } = await getProductRecordById({ id });
- if (code === 200) {
- form.id = data.id;
- form.purchaseContractNumber = data.purchaseContractNumber;
- form.salesContractNo = data.salesContractNo;
- form.createdAt = data.createdAt;
- form.invoiceNumber = data.invoiceNumber;
- form.ticketsNum = data.ticketsNum;
- form.ticketsAmount = data.ticketsAmount.toFixed(2);
- form.taxInclusiveUnitPrice = data.taxInclusiveUnitPrice;
- form.futureTickets = data.futureTickets;
- temFutureTickets.value = data.futureTickets;
- form.ticketRegistrationId = data.ticketRegistrationId;
- }
-};
+ const load = async id => {
+ const { code, data } = await getProductRecordById({ id });
+ if (code === 200) {
+ form.id = data.id;
+ form.purchaseContractNumber = data.purchaseContractNumber;
+ form.salesContractNo = data.salesContractNo;
+ form.createdAt = data.createdAt;
+ form.invoiceNumber = data.invoiceNumber;
+ form.ticketsNum = data.ticketsNum;
+ form.ticketsAmount = data.ticketsAmount.toFixed(2);
+ form.taxInclusiveUnitPrice = data.taxInclusiveUnitPrice;
+ form.futureTickets = data.futureTickets;
+ // temFutureTickets.value = data.futureTickets;
+ temFutureTickets.value = data.quantity;
-const inputTicketsNum = (val) => {
- // 纭繚鍚◣鍗曚环瀛樺湪涓斾笉涓洪浂
- if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) {
- proxy.$modal.msgWarning("鍚◣鍗曚环涓嶈兘涓洪浂鎴栨湭瀹氫箟");
- return;
- }
- if (Number(form.ticketsNum) > Number(temFutureTickets.value)) {
- proxy.$modal.msgWarning("寮�绁ㄦ暟涓嶅緱澶т簬鏈紑绁ㄦ暟");
- form.ticketsNum = temFutureTickets.value
- }
-
- // 纭繚鎵�鏈夋暟鍊奸兘杞崲涓烘暟瀛楃被鍨嬭繘琛岃绠�
- const ticketsAmount = Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice);
- const futureTickets = Number(temFutureTickets.value) - Number(form.ticketsNum);
- form.futureTickets = Number(futureTickets.toFixed(2));
- form.ticketsAmount = Number(ticketsAmount.toFixed(2));
-};
-const inputTicketsAmount = (val) => {
- // 纭繚鍚◣鍗曚环瀛樺湪涓斾笉涓洪浂
- if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) {
- proxy.$modal.msgWarning("鍚◣鍗曚环涓嶈兘涓洪浂鎴栨湭瀹氫箟");
- return;
- }
-
- if (Number(val) > Number(form.futureTickets*form.taxInclusiveUnitPrice)) {
- proxy.$modal.msgWarning("鏈鏉ョエ閲戦涓嶅緱澶т簬鎬婚噾棰�");
- form.ticketsAmount = (form.futureTickets*form.taxInclusiveUnitPrice).toFixed(2)
- const ticketsNum = Number(form.ticketsAmount) / Number(form.taxInclusiveUnitPrice);
- form.ticketsNum = Number(ticketsNum.toFixed(2))
- return;
- }
-
- // 纭繚鎵�鏈夋暟鍊奸兘杞崲涓烘暟瀛楃被鍨嬭繘琛岃绠�
- const ticketsNum = Number(val) / Number(form.taxInclusiveUnitPrice);
- form.ticketsNum = Number(ticketsNum.toFixed(2));
-};
+ form.ticketRegistrationId = data.ticketRegistrationId;
+ }
+ };
-defineExpose({
- load,
- form,
- resetForm,
-});
+ const inputTicketsNum = val => {
+ // 纭繚鍚◣鍗曚环瀛樺湪涓斾笉涓洪浂
+ if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) {
+ proxy.$modal.msgWarning("鍚◣鍗曚环涓嶈兘涓洪浂鎴栨湭瀹氫箟");
+ return;
+ }
+ if (Number(form.ticketsNum) > Number(temFutureTickets.value)) {
+ proxy.$modal.msgWarning("寮�绁ㄦ暟涓嶅緱澶т簬鏈紑绁ㄦ暟");
+ form.ticketsNum = temFutureTickets.value;
+ }
+
+ // 纭繚鎵�鏈夋暟鍊奸兘杞崲涓烘暟瀛楃被鍨嬭繘琛岃绠�
+ const ticketsAmount =
+ Number(form.ticketsNum) * Number(form.taxInclusiveUnitPrice);
+ const futureTickets =
+ Number(temFutureTickets.value) - Number(form.ticketsNum);
+ form.futureTickets = Number(futureTickets.toFixed(2));
+ form.ticketsAmount = Number(ticketsAmount.toFixed(2));
+ };
+ const inputTicketsAmount = val => {
+ // 纭繚鍚◣鍗曚环瀛樺湪涓斾笉涓洪浂
+ if (!form.taxInclusiveUnitPrice || Number(form.taxInclusiveUnitPrice) === 0) {
+ proxy.$modal.msgWarning("鍚◣鍗曚环涓嶈兘涓洪浂鎴栨湭瀹氫箟");
+ return;
+ }
+
+ if (Number(val) > Number(form.futureTickets * form.taxInclusiveUnitPrice)) {
+ proxy.$modal.msgWarning("鏈鏉ョエ閲戦涓嶅緱澶т簬鎬婚噾棰�");
+ form.ticketsAmount = (
+ form.futureTickets * form.taxInclusiveUnitPrice
+ ).toFixed(2);
+ const ticketsNum =
+ Number(form.ticketsAmount) / Number(form.taxInclusiveUnitPrice);
+ form.ticketsNum = Number(ticketsNum.toFixed(2));
+ return;
+ }
+
+ // 纭繚鎵�鏈夋暟鍊奸兘杞崲涓烘暟瀛楃被鍨嬭繘琛岃绠�
+ const ticketsNum = Number(val) / Number(form.taxInclusiveUnitPrice);
+ form.ticketsNum = Number(ticketsNum.toFixed(2));
+ };
+
+ defineExpose({
+ load,
+ form,
+ resetForm,
+ });
</script>
<style lang="scss" scoped></style>
diff --git a/src/views/productionManagement/processRoute/index.vue b/src/views/productionManagement/processRoute/index.vue
index 10bd0d9..7d5ab5d 100644
--- a/src/views/productionManagement/processRoute/index.vue
+++ b/src/views/productionManagement/processRoute/index.vue
@@ -1,45 +1,45 @@
<template>
- <div class="app-container">
- <div class="search_form">
- <el-form :model="searchForm" :inline="true">
- <el-form-item label="瑙勬牸鍚嶇О:">
- <el-input v-model="searchForm.model" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
- style="width: 200px;"
- @change="handleQuery" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
- </el-form-item>
- </el-form>
- </div>
- <div class="table_list">
- <div style="text-align: right" class="mb10">
- <el-button type="primary" @click="showNewModal">鏂板宸ヨ壓璺嚎</el-button>
- <el-button type="danger" @click="handleDelete" :disabled="selectedRows.length === 0" plain>鍒犻櫎宸ヨ壓璺嚎</el-button>
- </div>
- <PIMTable
- rowKey="id"
- :column="tableColumn"
- :tableData="tableData"
- :page="page"
- :isSelection="true"
- @selection-change="handleSelectionChange"
- :tableLoading="tableLoading"
- @pagination="pagination"
- :total="page.total"
- />
- </div>
- <new-process
- v-if="isShowNewModal"
- v-model:visible="isShowNewModal"
- @completed="getList"
+ <div class="app-container">
+ <div class="search_form">
+ <el-form :model="searchForm" :inline="true">
+ <el-form-item label="瑙勬牸鍚嶇О:">
+ <el-input v-model="searchForm.model" placeholder="璇疯緭鍏�" clearable prefix-icon="Search"
+ style="width: 200px;"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+ </el-form-item>
+ </el-form>
+ </div>
+ <div class="table_list">
+ <div style="text-align: right" class="mb10">
+ <el-button type="primary" @click="showNewModal">鏂板宸ヨ壓璺嚎</el-button>
+ <el-button type="danger" @click="handleDelete" :disabled="selectedRows.length === 0" plain>鍒犻櫎宸ヨ壓璺嚎</el-button>
+ </div>
+ <PIMTable
+ rowKey="id"
+ :column="tableColumn"
+ :tableData="tableData"
+ :page="page"
+ :isSelection="true"
+ @selection-change="handleSelectionChange"
+ :tableLoading="tableLoading"
+ @pagination="pagination"
+ :total="page.total"
+ />
+ </div>
+ <new-process
+ v-if="isShowNewModal"
+ v-model:visible="isShowNewModal"
+ @completed="getList"
/>
<edit-process
- v-if="isShowEditModal"
- v-model:visible="isShowEditModal"
- :record="record"
- @completed="getList"
+ v-if="isShowEditModal"
+ v-model:visible="isShowEditModal"
+ :record="record"
+ @completed="getList"
/>
<route-item-form
@@ -48,7 +48,7 @@
:record="record"
@completed="getList"
/>
- </div>
+ </div>
</template>
<script setup>
@@ -57,11 +57,13 @@
import EditProcess from "@/views/productionManagement/processRoute/Edit.vue";
import RouteItemForm from "@/views/productionManagement/processRoute/ItemsForm.vue";
import {listPage, del} from "@/api/productionManagement/processRoute.js";
+import { useRouter } from 'vue-router'
+const router = useRouter()
const data = reactive({
- searchForm: {
+ searchForm: {
model: "",
- },
+ },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
@@ -73,14 +75,14 @@
label: "浜у搧鍚嶇О",
prop: "productName",
},
- {
- label: "瑙勬牸鍚嶇О",
- prop: "model",
- },
- {
- label: "鎻忚堪",
- prop: "description",
- },
+ {
+ label: "瑙勬牸鍚嶇О",
+ prop: "model",
+ },
+ {
+ label: "鎻忚堪",
+ prop: "description",
+ },
{
dataType: "action",
label: "鎿嶄綔",
@@ -113,41 +115,41 @@
const isShowItemModal = ref(false);
const record = ref({});
const page = reactive({
- current: 1,
- size: 100,
- total: 0,
+ current: 1,
+ size: 100,
+ total: 0,
});
const { proxy } = getCurrentInstance()
// 鏌ヨ鍒楄〃
/** 鎼滅储鎸夐挳鎿嶄綔 */
const handleQuery = () => {
- page.current = 1;
- getList();
+ page.current = 1;
+ getList();
};
const pagination = (obj) => {
- page.current = obj.page;
- page.size = obj.limit;
- getList();
+ page.current = obj.page;
+ page.size = obj.limit;
+ getList();
};
const getList = () => {
- tableLoading.value = true;
- const params = { ...searchForm.value, ...page };
- params.entryDate = undefined
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined
listPage(params).then(res => {
- tableLoading.value = false;
- tableData.value = res.data.records.map(item => ({
- ...item,
- }));
- page.total = res.data.total;
- }).catch(err => {
- tableLoading.value = false;
- })
+ tableLoading.value = false;
+ tableData.value = res.data.records.map(item => ({
+ ...item,
+ }));
+ page.total = res.data.total;
+ }).catch(err => {
+ tableLoading.value = false;
+ })
};
// 琛ㄦ牸閫夋嫨鏁版嵁
const handleSelectionChange = (selection) => {
- selectedRows.value = selection;
+ selectedRows.value = selection;
};
// 鎵撳紑鏂板寮规
@@ -161,8 +163,12 @@
};
const showItemModal = (row) => {
- isShowItemModal.value = true
- record.value = row
+ router.push({
+ path: '/productionManagement/processRouteItem',
+ query: {
+ id: row.id
+ }
+ })
};
// 鍒犻櫎
@@ -181,7 +187,7 @@
}
onMounted(() => {
- getList();
+ getList();
});
</script>
diff --git a/src/views/productionManagement/processRoute/processRouteItem/index.vue b/src/views/productionManagement/processRoute/processRouteItem/index.vue
new file mode 100644
index 0000000..641ffff
--- /dev/null
+++ b/src/views/productionManagement/processRoute/processRouteItem/index.vue
@@ -0,0 +1,503 @@
+<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>
+ </div>
+
+ <el-switch
+ v-model="isTable"
+ inline-prompt
+ active-text="琛ㄦ牸"
+ inactive-text="鍒楄〃"
+ @change="handleViewChange"
+ />
+ </div>
+ <el-table
+ v-if="isTable"
+ ref="multipleTable"
+ v-loading="tableLoading"
+ border
+ :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">
+ <template #default="scope">
+ {{ scope.$index + 1 }}
+ </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>
+ </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;"
+ >
+ <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>
+ </div>
+ </div>
+ </div>
+
+ <ProductSelectDialog
+ v-model="isShowProductSelectDialog"
+ @confirm="handelSelectProducts"
+ />
+ </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'
+
+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 router = useRouter()
+const routeId = computed({
+ get() {
+ return route.query.id;
+ },
+
+ set(val) {
+ emit('update:router', val)
+ }
+});
+
+
+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 = [];
+ nextTick(() => {
+ routeItems.value = temp;
+ initSortable();
+ });
+ });
+};
+
+const findProcessRouteItems = () => {
+ 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 findProcessList = () => {
+ processList({})
+ .then(res => {
+ processOptions.value = res.data;
+ })
+ .catch(err => {
+ console.error("鑾峰彇宸ュ簭澶辫触锛�", err);
+ });
+};
+
+const { proxy } = getCurrentInstance() || {};
+
+const handleSubmit = () => {
+ const hasEmptyProcess = routeItems.value.some(item => !item.processId);
+ if (hasEmptyProcess) {
+ proxy?.$modal?.msgError("璇蜂负鎵�鏈夐」鐩�夋嫨宸ュ簭");
+ return;
+ }
+
+ addOrUpdateProcessRouteItem({
+ routeId: routeId.value,
+ processRouteItem: routeItems.value.map(({ id, ...item }) => item)
+ })
+ .then(res => {
+ router.push({
+ path: '/productionManagement/processRoute',
+ })
+ proxy?.$modal?.msgSuccess("鎻愪氦鎴愬姛");
+ })
+ .catch(err => {
+ proxy?.$modal?.msgError(`鎻愪氦澶辫触锛�${err.msg || "缃戠粶寮傚父"}`);
+ });
+};
+
+const destroySortable = () => {
+ if (tableSortable) {
+ tableSortable.destroy();
+ tableSortable = null;
+ }
+ if (stepsSortable) {
+ stepsSortable.destroy();
+ stepsSortable = null;
+ }
+};
+
+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;
+
+ // 淇敼锛氱洿鎺ヤ娇鐢╯tepsContainer.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();
+ // 寤惰繜鍒濆鍖栵紝纭繚瑙嗗浘鍒囨崲鍚嶥OM瀹屽叏娓叉煋
+ nextTick(() => {
+ setTimeout(() => initSortable(), 100);
+ });
+};
+
+onMounted(() => {
+ findProcessRouteItems();
+ findProcessList();
+});
+
+onUnmounted(() => {
+ destroySortable();
+});
+
+defineExpose({
+ handleSubmit,
+});
+</script>
+
+<style scoped>
+:deep(.sortable-ghost) {
+ opacity: 0.6;
+ background-color: #f5f7fa !important;
+}
+
+:deep(.el-table__row) {
+ transition: background-color 0.2s;
+}
+
+:deep(.el-table__row:hover) {
+ background-color: #f9fafc !important;
+}
+
+:deep(.el-card__footer){
+ padding: 0 !important;
+}
+
+.operate-button {
+ 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;
+}
+
+/* 淇敼锛氳嚜瀹氫箟姝ラ椤规牱寮� */
+.custom-step {
+ cursor: move !important;
+ padding: 8px;
+ position: relative;
+ transition: all 0.2s ease;
+ flex: 0 0 auto;
+ min-width: 220px;
+ touch-action: none;
+}
+
+/* 鎷栨嫿鎮诞鏍峰紡锛屾彁绀哄彲鎷栨嫿 */
+.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 {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ height: 140px;
+}
+
+.step-card-footer {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ padding: 10px;
+}
+
+/* 鑷畾涔夊簭鍙锋牱寮忎紭鍖� */
+.step-number {
+ font-weight: bold;
+ text-align: center;
+ width: 36px;
+ height: 36px;
+ line-height: 36px;
+ margin: 0 auto 10px;
+ background: #409eff;
+ color: #fff;
+ border-radius: 50%;
+ font-size: 14px;
+}
+</style>
--
Gitblit v1.9.3