<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="name" min-width="140" show-overflow-tooltip>
|
<template #default="scope">
|
{{ getProcessField(scope.row, 'name') }}
|
</template>
|
</el-table-column>
|
<el-table-column label="产品规格" prop="productModel" min-width="120" show-overflow-tooltip>
|
<template #default="scope">
|
{{ getProcessField(scope.row, 'productModel') }}
|
</template>
|
</el-table-column>
|
<el-table-column label="部件编号" prop="no" width="120" show-overflow-tooltip>
|
<template #default="scope">
|
{{ getProcessField(scope.row, 'no') }}
|
</template>
|
</el-table-column>
|
<el-table-column label="部件类型" prop="typeText" width="120" show-overflow-tooltip>
|
<template #default="scope">
|
{{ getProcessTypeText(getProcessRaw(scope.row)?.type) || '-' }}
|
</template>
|
</el-table-column>
|
<el-table-column label="计划工时(小时)" prop="salaryQuota" width="130" align="center">
|
<template #default="scope">
|
{{ getProcessField(scope.row, 'salaryQuota') }}
|
</template>
|
</el-table-column>
|
<el-table-column label="计划人员" prop="plannerName" width="100" show-overflow-tooltip>
|
<template #default="scope">
|
{{ getProcessField(scope.row, 'plannerName') }}
|
</template>
|
</el-table-column>
|
<el-table-column label="是否质检" prop="isQuality" width="90" align="center">
|
<template #default="scope">
|
{{ scope.row.isQuality ? '是' : '否' }}
|
</template>
|
</el-table-column>
|
<el-table-column label="备注" prop="remark" min-width="100" show-overflow-tooltip>
|
<template #default="scope">
|
{{ getProcessField(scope.row, 'remark') }}
|
</template>
|
</el-table-column>
|
<el-table-column label="更新时间" prop="updateTime" width="160" align="center">
|
<template #default="scope">
|
{{ getProcessField(scope.row, 'updateTime') }}
|
</template>
|
</el-table-column>
|
<el-table-column label="操作" align="center" fixed="right" width="150">
|
<template #default="scope">
|
<el-button type="primary" link size="small" @click="handleEdit(scope.row)" :disabled="scope.row.isComplete">编辑</el-button>
|
<el-button type="danger" link size="small" @click="handleDelete(scope.row)" :disabled="scope.row.isComplete">删除</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">{{ getProcessField(item, 'name') }}</div>
|
</div>
|
|
<!-- 与工序主表一致的简要信息 -->
|
<div class="card-content">
|
<div class="product-info">
|
<div class="product-name">{{ getProcessField(item, 'productModel') }}</div>
|
<div v-if="getProcessRaw(item)?.no" class="product-model">编号 {{ getProcessRaw(item)?.no }}</div>
|
<div v-if="getProcessTypeText(getProcessRaw(item)?.type)" class="product-model">
|
{{ getProcessTypeText(getProcessRaw(item)?.type) }}
|
</div>
|
<el-tag type="primary" class="product-tag" v-if="item.isQuality">质检</el-tag>
|
</div>
|
</div>
|
|
<!-- 操作按钮 -->
|
<div class="card-footer">
|
<el-button type="primary" link size="small" @click="handleEdit(item)" :disabled="item.isComplete">编辑</el-button>
|
<el-button type="danger" link size="small" @click="handleDelete(item)" :disabled="item.isComplete">删除</el-button>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<!-- 新增/编辑弹窗(布局、字段与工序/部件页 New、Edit 一致;提交参数仍为原接口字段) -->
|
<el-dialog
|
v-model="dialogVisible"
|
:title="operationType === 'add' ? '新增工艺路线项目' : '编辑工艺路线项目'"
|
width="760"
|
@close="closeDialog"
|
>
|
<el-form
|
ref="formRef"
|
:model="form"
|
:rules="rules"
|
label-width="140px"
|
label-position="top"
|
>
|
<el-row :gutter="16">
|
<el-col :span="24">
|
<el-form-item label="部件" prop="processId">
|
<el-select
|
v-model="form.processId"
|
placeholder="请选择部件"
|
clearable
|
filterable
|
style="width: 100%"
|
>
|
<el-option
|
v-for="process in processOptions"
|
:key="process.id"
|
:label="formatProcessOptionLabel(process)"
|
:value="process.id"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item
|
label="产品名称:"
|
prop="productId"
|
>
|
<el-tree-select
|
v-model="form.productId"
|
placeholder="请选择产品名称"
|
clearable
|
filterable
|
check-strictly
|
:data="productCategoryOptions"
|
:render-after-expand="false"
|
style="width: 100%"
|
@change="handleProductChange"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item
|
label="产品规格:"
|
prop="productModelId"
|
>
|
<el-select
|
v-model="form.productModelId"
|
placeholder="请选择产品规格"
|
clearable
|
filterable
|
:disabled="!form.productId"
|
style="width: 100%"
|
>
|
<el-option
|
v-for="item in modelOptions"
|
:key="item.id"
|
:label="item.model"
|
:value="item.id"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="部件编号">
|
<el-input :model-value="selectedProcess?.no ?? ''" readonly />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="部件类型">
|
<el-input :model-value="getProcessTypeText(selectedProcess?.type) || '-'" readonly />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="计划工时(小时)">
|
<el-input :model-value="selectedProcess?.salaryQuota != null && selectedProcess?.salaryQuota !== '' ? String(selectedProcess.salaryQuota) : ''" readonly>
|
<template #append>小时</template>
|
</el-input>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="计划人员">
|
<el-input :model-value="selectedProcess?.plannerName ?? ''" readonly />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="是否质检" prop="isQuality">
|
<el-switch v-model="form.isQuality" :active-value="true" inactive-value="false"/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="24">
|
<el-form-item label="备注">
|
<el-input :model-value="selectedProcess?.remark ?? ''" type="textarea" readonly />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
|
<template #footer>
|
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
<el-button @click="closeDialog">取消</el-button>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "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 { modelListPage, productTreeList } from "@/api/basicData/product";
|
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 productCategoryOptions = ref([]);
|
const modelOptions = ref([]);
|
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,
|
productId: undefined,
|
productModelId: undefined,
|
productName: "",
|
model: "",
|
isQuality: false,
|
});
|
|
const rules = {
|
processId: [{ required: true, message: '请选择部件', trigger: 'change' }],
|
productId: [{ required: true, message: '请选择产品名称', trigger: 'change' }],
|
productModelId: [{ required: true, message: '请选择产品规格', trigger: 'change' }],
|
};
|
|
const selectedProcess = computed(() => {
|
if (form.value.processId === undefined || form.value.processId === null || form.value.processId === '') return null;
|
return processOptions.value.find(p => String(p.id) === String(form.value.processId)) || null;
|
});
|
|
const getProcessTypeText = (type) => {
|
if (type === undefined || type === null) return '';
|
const map = {
|
1: '加工',
|
2: '刮板冷芯制作',
|
3: '管路组对',
|
4: '罐体连接及调试',
|
5: '测试打压',
|
6: '其他',
|
};
|
return map[type] || '';
|
};
|
|
const getProcessRaw = (row) => {
|
if (!row?.processId) return null;
|
return processOptions.value.find(p => String(p.id) === String(row.processId)) || null;
|
};
|
|
const getProcessField = (row, key) => {
|
const p = getProcessRaw(row);
|
const fromProcess = p ? p[key] : undefined;
|
if (fromProcess !== undefined && fromProcess !== null && fromProcess !== '') return fromProcess;
|
if (key === 'name' && row.productName) return row.productName;
|
if (key === 'productModel' && row.model) return row.model;
|
const fromRow = row[key];
|
if (fromRow !== undefined && fromRow !== null && fromRow !== '') return fromRow;
|
return '-';
|
};
|
|
const formatProcessOptionLabel = (process) => {
|
if (!process) return '';
|
const no = process.no ? String(process.no).trim() : '';
|
const name = process.name || '';
|
if (no && name) return `${no} ${name}`;
|
return name || no || '';
|
};
|
|
const convertProductTree = (list) => {
|
return (list || []).map(item => {
|
const children = convertProductTree(item.children || item.childList || []);
|
return {
|
...item,
|
value: item.id,
|
label: item.name || item.label,
|
children,
|
disabled: children.length > 0,
|
};
|
});
|
};
|
|
const findNodeById = (nodes, targetId) => {
|
for (const node of nodes || []) {
|
if (String(node.value) === String(targetId)) {
|
return node;
|
}
|
if (node.children && node.children.length > 0) {
|
const found = findNodeById(node.children, targetId);
|
if (found) return found;
|
}
|
}
|
return null;
|
};
|
|
const findNodeIdByLabel = (nodes, targetLabel) => {
|
for (const node of nodes || []) {
|
if (node.label === targetLabel) {
|
return node.value;
|
}
|
if (node.children && node.children.length > 0) {
|
const found = findNodeIdByLabel(node.children, targetLabel);
|
if (found !== null && found !== undefined) return found;
|
}
|
}
|
return undefined;
|
};
|
|
const getProductCategoryOptions = async () => {
|
try {
|
const res = await productTreeList();
|
const list = Array.isArray(res) ? res : res?.data || [];
|
productCategoryOptions.value = convertProductTree(list);
|
} catch (e) {
|
productCategoryOptions.value = [];
|
}
|
};
|
|
const getModelOptions = async (productId) => {
|
if (!productId) {
|
modelOptions.value = [];
|
return;
|
}
|
try {
|
const res = await modelListPage({
|
id: productId,
|
current: 1,
|
size: 999,
|
});
|
const records = res?.records || res?.data?.records || [];
|
modelOptions.value = records;
|
} catch (e) {
|
modelOptions.value = [];
|
}
|
};
|
|
const handleProductChange = async (value) => {
|
const selectedNode = findNodeById(productCategoryOptions.value, value);
|
form.value.productName = selectedNode?.label || '';
|
form.value.productModelId = undefined;
|
await getModelOptions(value);
|
};
|
|
// 获取列表
|
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;
|
getProductCategoryOptions();
|
};
|
|
// 编辑
|
const handleEdit = async (row) => {
|
operationType.value = 'edit';
|
form.value = {
|
id: row.id,
|
routeId: routeId.value,
|
processId: row.processId,
|
productId: row.productId,
|
productModelId: row.productModelId,
|
productName: row.productName || "",
|
model: row.model || "",
|
isQuality: row.isQuality,
|
};
|
dialogVisible.value = true;
|
await getProductCategoryOptions();
|
if (!form.value.productId && form.value.productName) {
|
form.value.productId = findNodeIdByLabel(productCategoryOptions.value, form.value.productName);
|
}
|
await getModelOptions(form.value.productId);
|
};
|
|
// 删除
|
const handleDelete = (row) => {
|
ElMessageBox.confirm('确认删除该工艺路线项目?', '提示', {
|
confirmButtonText: '确认',
|
cancelButtonText: '取消',
|
type: 'warning'
|
})
|
.then(() => {
|
// 生产订单下使用 productProcessRoute 的删除接口(路由后拼接 id),其它情况使用工艺路线项目批量删除接口
|
const deletePromise =
|
pageType.value === 'order'
|
? deleteRouteItem(row.id)
|
: batchDeleteProcessRouteItem([row.id]);
|
|
deletePromise
|
.then(() => {
|
proxy?.$modal?.msgSuccess('删除成功');
|
getList();
|
})
|
.catch(() => {
|
proxy?.$modal?.msgError('删除失败');
|
});
|
})
|
.catch(() => {});
|
};
|
|
// 提交
|
const handleSubmit = () => {
|
formRef.value.validate((valid) => {
|
if (valid) {
|
submitLoading.value = true;
|
|
if (operationType.value === 'add') {
|
// 新增:传单个对象,包含dragSort字段
|
// 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,
|
isQuality: form.value.isQuality,
|
dragSort,
|
})
|
: addOrUpdateProcessRouteItem({
|
routeId: routeId.value,
|
processId: form.value.processId,
|
productModelId: form.value.productModelId,
|
isQuality: form.value.isQuality,
|
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,
|
isQuality: form.value.isQuality,
|
})
|
: addOrUpdateProcessRouteItem({
|
routeId: routeId.value,
|
processId: form.value.processId,
|
productModelId: form.value.productModelId,
|
id: form.value.id,
|
isQuality: form.value.isQuality,
|
});
|
|
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,
|
productId: undefined,
|
productModelId: undefined,
|
productName: "",
|
model: "",
|
isQuality: false,
|
};
|
modelOptions.value = [];
|
nextTick(() => formRef.value?.clearValidate());
|
};
|
|
// 关闭弹窗
|
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);
|
|
// 计算新的序号(dragSort从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(() => {
|
// 更新所有行的dragSort
|
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);
|
|
// 计算新的序号(dragSort从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(() => {
|
// 更新所有行的dragSort
|
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();
|
getProductCategoryOptions();
|
});
|
|
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;
|
}
|
|
.product-tag {
|
margin: 10px 0;
|
}
|
|
.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>
|