| src/views/procurementManagement/procurementInvoiceLedger/Form/EditForm.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/processRoute/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/productionManagement/processRoute/processRouteItem/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
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> 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> 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> <!-- ä½¿ç¨æ®édivæ¿ä»£el-steps --> <div v-else ref="stepsContainer" class="mb5 custom-steps" > <div v-for="(item, index) in routeItems" :key="item.id" class="custom-step draggable-step" :data-id="item.id" style="cursor: move; flex: 0 0 auto; min-width: 220px;" > <div class="step-content"> <div class="step-number">{{ index + 1 }}</div> <el-card :header="item.productName" class="step-card" style="cursor: move;" > <div class="step-card-content"> <p>{{ item.model }}</p> <p>{{ item.unit }}</p> <el-select v-model="item.processId" style="width: 100%;" @mousedown.stop > <el-option v-for="process in processOptions" :key="process.id" :label="process.name" :value="process.id" /> </el-select> </div> <template #footer> <div class="step-card-footer"> <el-button type="danger" link size="small" @click.stop="removeItemByID(item.id)">å é¤</el-button> </div> </template> </el-card> </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; // ä¿®æ¹ï¼ç´æ¥ä½¿ç¨stepsContainer.valueä½ä¸ºææ½å®¹å¨ const stepsList = stepsContainer.value; if (!stepsList) { console.warn('æªæ¾å°æ¥éª¤æ¡ææ½å®¹å¨'); return; } // ä¿®æ¹ï¼ç®åææ½é ç½® stepsSortable = new Sortable(stepsList, { animation: 150, ghostClass: 'sortable-ghost', draggable: '.draggable-step', // 坿æ½å ç´ handle: '.draggable-step, .step-card', // ææ½ææ filter: '.el-button, .el-select, .el-input', // è¿æ»¤æé®/éæ©å¨ forceFallback: true, fallbackClass: 'sortable-fallback', preventOnFilter: true, scroll: true, scrollSensitivity: 30, scrollSpeed: 10, bubbleScroll: true, onEnd: (evt) => { if (evt.oldIndex === evt.newIndex || !routeItems.value[evt.oldIndex]) return; // ä½¿ç¨æ°ç» splice æ¹æ³éæ°æåº const moveItem = routeItems.value.splice(evt.oldIndex, 1)[0]; routeItems.value.splice(evt.newIndex, 0, moveItem); routeItems.value = [...routeItems.value]; } }); // è°è¯ï¼æå°å®¹å¨åå®ä¾ï¼ç¡®è®¤ç»å®æå console.log('æ¥éª¤æ¡ææ½å®¹å¨:', stepsList); console.log('Sortableå®ä¾:', stepsSortable); } }; const handleViewChange = () => { destroySortable(); // å»¶è¿åå§åï¼ç¡®ä¿è§å¾åæ¢åDOMå®å ¨æ¸²æ nextTick(() => { setTimeout(() => initSortable(), 100); }); }; 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>