| | |
| | | <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">规格名称</span> |
| | | <span class="info-label">规格型号</span> |
| | | </div> |
| | | <div class="info-value-wrapper"> |
| | | <span class="info-value">{{ routeInfo.model || '-' }}</span> |
| | | <span class="info-value">{{ routeInfo.drawingNumber || '-' }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-item"> |
| | |
| | | <el-button |
| | | icon="Grid" |
| | | @click="toggleView" |
| | | style="margin-right: 10px;" |
| | | > |
| | | 卡片视图 |
| | | </el-button> |
| | | <el-button type="primary" @click="handleAdd">新增</el-button> |
| | | </div> |
| | | </div> |
| | | <el-table |
| | |
| | | {{ getProcessName(scope.row.processId) || '-' }} |
| | | </template> |
| | | </el-table-column> |
| | | <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="是否质检" prop="isQuality" width="100"> |
| | | <template #default="scope"> |
| | | {{scope.row.isQuality ? "是" : "否"}} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="报工权限" prop="userPower" min-width="200"> |
| | | <template #default="scope"> |
| | | {{ scope.row.userPower || '-' }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" align="center" fixed="right" width="150"> |
| | |
| | | <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 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> |
| | | <el-tag type="primary" class="product-tag" v-if="item.isQuality">质检</el-tag> |
| | | </div> |
| | | <div v-else class="product-info empty">暂无产品信息</div> |
| | | <el-tag type="primary" class="product-tag" v-if="item.isQuality">质检</el-tag> |
| | | </div> |
| | | |
| | | <!-- 操作按钮 --> |
| | |
| | | </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-item label="是否质检" prop="isQuality"> |
| | | <el-switch v-model="form.isQuality" :active-value="true" inactive-value="false"/> |
| | | <el-switch v-model="form.isQuality" :active-value="true" :inactive-value="false"/> |
| | | </el-form-item> |
| | | <el-form-item label="报工权限" prop="userPower" :rules="[{ required: true, message: '请选择报工权限', trigger: 'change' }]"> |
| | | <el-tree-select |
| | | v-model="form.userPower" |
| | | :data="staffList" |
| | | :props="treeProps" |
| | | placeholder="请选择人员" |
| | | multiple |
| | | show-checkbox |
| | | collapse-tags |
| | | collapse-tags-tooltip |
| | | style="width: 100%" |
| | | node-key="id" |
| | | :render-after-expand="false" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 产品选择对话框 --> |
| | | <ProductSelectDialog |
| | | v-model="showProductSelectDialog" |
| | | @confirm="handleProductSelect" |
| | | single |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, getCurrentInstance, onMounted, onUnmounted, nextTick } from "vue"; |
| | | import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.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 { listDeptUserTree } from "@/api/basicData/productProcess.js"; |
| | | import { useRoute } from 'vue-router' |
| | | import { ElMessageBox } from 'element-plus' |
| | | import Sortable from 'sortablejs' |
| | |
| | | const routeInfo = ref({ |
| | | processRouteCode: '', |
| | | productName: '', |
| | | drawingNumber: '', |
| | | model: '', |
| | | bomNo: '', |
| | | description: '' |
| | | }); |
| | | |
| | | const processOptions = ref([]); |
| | | const showProductSelectDialog = ref(false); |
| | | const staffList = ref([]); |
| | | |
| | | const treeProps = { |
| | | label: 'label', |
| | | children: 'children', |
| | | }; |
| | | |
| | | let tableSortable = null; |
| | | let cardSortable = null; |
| | | |
| | |
| | | id: undefined, |
| | | routeId: routeId.value, |
| | | processId: undefined, |
| | | productModelId: undefined, |
| | | productName: "", |
| | | model: "", |
| | | unit: "", |
| | | isQuality: false, |
| | | userPower: [], |
| | | }); |
| | | |
| | | const rules = { |
| | | processId: [{ required: true, message: '请选择工序', trigger: 'change' }], |
| | | productModelId: [{ required: true, message: '请选择产品', trigger: 'change' }], |
| | | }; |
| | | |
| | | // 根据工序ID获取工序名称 |
| | |
| | | .then(res => { |
| | | tableData.value = res.data || []; |
| | | tableLoading.value = false; |
| | | routeInfo.value = tableData.value[0] || {} |
| | | // 列表加载完成后初始化拖拽排序 |
| | | nextTick(() => { |
| | | initSortable(); |
| | |
| | | routeInfo.value = { |
| | | processRouteCode: route.query.processRouteCode || '', |
| | | productName: route.query.productName || '', |
| | | drawingNumber: route.query.drawingNumber || '', |
| | | model: route.query.model || '', |
| | | bomNo: route.query.bomNo || '', |
| | | description: route.query.description || '' |
| | |
| | | // 编辑 |
| | | const handleEdit = (row) => { |
| | | operationType.value = 'edit'; |
| | | const userPowerNames = row.userPower ? row.userPower.split(',') : []; |
| | | const userPowerIds = userPowerNames.map(name => { |
| | | const user = findUserByName(name); |
| | | return user ? user.id : null; |
| | | }).filter(id => id !== null); |
| | | |
| | | form.value = { |
| | | id: row.id, |
| | | routeId: routeId.value, |
| | | processId: row.processId, |
| | | productModelId: row.productModelId, |
| | | productName: row.productName || "", |
| | | model: row.model || "", |
| | | unit: row.unit || "", |
| | | isQuality: row.isQuality, |
| | | userPower: userPowerIds, |
| | | }; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const findUserByName = (name) => { |
| | | const findInTree = (nodes) => { |
| | | for (const node of nodes) { |
| | | if (node.isUser && node.label === name) { |
| | | return node; |
| | | } |
| | | if (node.children && node.children.length > 0) { |
| | | const found = findInTree(node.children); |
| | | if (found) return found; |
| | | } |
| | | } |
| | | return null; |
| | | }; |
| | | return findInTree(staffList.value); |
| | | }; |
| | | |
| | | // 删除 |
| | |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // 产品选择 |
| | | 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; |
| | | |
| | | const userPowerNames = getAllUserNamesFromSelection(form.value.userPower); |
| | | |
| | | if (operationType.value === 'add') { |
| | | // 新增:传单个对象,包含dragSort字段 |
| | |
| | | productOrderId: orderId.value, |
| | | productRouteId: routeId.value, |
| | | processId: form.value.processId, |
| | | productModelId: form.value.productModelId, |
| | | isQuality: form.value.isQuality, |
| | | userPower: userPowerNames.join(','), |
| | | dragSort, |
| | | }) |
| | | : addOrUpdateProcessRouteItem({ |
| | | routeId: routeId.value, |
| | | processId: form.value.processId, |
| | | productModelId: form.value.productModelId, |
| | | isQuality: form.value.isQuality, |
| | | userPower: userPowerNames.join(','), |
| | | dragSort, |
| | | }); |
| | | |
| | |
| | | ? addOrUpdateProductProcessRouteItem({ |
| | | id: form.value.id, |
| | | processId: form.value.processId, |
| | | productModelId: form.value.productModelId, |
| | | isQuality: form.value.isQuality, |
| | | userPower: userPowerNames.join(','), |
| | | }) |
| | | : addOrUpdateProcessRouteItem({ |
| | | routeId: routeId.value, |
| | | processId: form.value.processId, |
| | | productModelId: form.value.productModelId, |
| | | id: form.value.id, |
| | | isQuality: form.value.isQuality, |
| | | userPower: userPowerNames.join(','), |
| | | }); |
| | | |
| | | updatePromise |
| | |
| | | id: undefined, |
| | | routeId: routeId.value, |
| | | processId: undefined, |
| | | productModelId: undefined, |
| | | productName: "", |
| | | model: "", |
| | | unit: "", |
| | | isQuality: false, |
| | | userPower: [], |
| | | }; |
| | | formRef.value?.resetFields(); |
| | | }; |
| | |
| | | getRouteInfo(); |
| | | getList(); |
| | | getProcessList(); |
| | | getStaffList(); |
| | | }); |
| | | |
| | | const getStaffList = () => { |
| | | listDeptUserTree().then(res => { |
| | | const buildTree = (nodes) => { |
| | | return nodes.map(node => { |
| | | const deptNode = { |
| | | id: `dept_${node.deptId}`, |
| | | label: node.deptName, |
| | | isUser: false, |
| | | children: [] |
| | | }; |
| | | |
| | | if (node.userList && node.userList.length > 0) { |
| | | node.userList.forEach(user => { |
| | | deptNode.children.push({ |
| | | id: user.userId, |
| | | label: user.nickName || user.userName, |
| | | isUser: true, |
| | | userName: user.userName, |
| | | nickName: user.nickName |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | if (node.childrenList && node.childrenList.length > 0) { |
| | | const childNodes = buildTree(node.childrenList); |
| | | deptNode.children = deptNode.children.concat(childNodes); |
| | | } |
| | | |
| | | return deptNode; |
| | | }); |
| | | }; |
| | | staffList.value = buildTree(res.data || []); |
| | | }).catch(() => { |
| | | staffList.value = []; |
| | | }); |
| | | }; |
| | | |
| | | const getAllUserNamesFromSelection = (selectedIds) => { |
| | | const names = []; |
| | | const processNode = (node) => { |
| | | if (selectedIds.includes(node.id)) { |
| | | if (node.isUser) { |
| | | names.push(node.label); |
| | | } else { |
| | | if (node.children && node.children.length > 0) { |
| | | node.children.forEach(child => { |
| | | if (child.isUser) { |
| | | names.push(child.label); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | if (node.children && node.children.length > 0) { |
| | | node.children.forEach(child => processNode(child)); |
| | | } |
| | | }; |
| | | staffList.value.forEach(node => processNode(node)); |
| | | return [...new Set(names)]; |
| | | }; |
| | | |
| | | onUnmounted(() => { |
| | | destroySortable(); |
| | |
| | | |
| | | .process-card { |
| | | flex-shrink: 0; |
| | | width: 220px; |
| | | width: 160px; |
| | | background: #fff; |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); |
| | | padding: 16px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | cursor: move; |
| | | transition: all 0.3s; |
| | | transition: all 0.2s; |
| | | } |
| | | |
| | | .process-card:hover { |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); |
| | | } |
| | | |
| | | .card-header { |
| | |
| | | } |
| | | |
| | | .card-number { |
| | | width: 36px; |
| | | height: 36px; |
| | | line-height: 36px; |
| | | width: 28px; |
| | | height: 28px; |
| | | line-height: 28px; |
| | | border-radius: 50%; |
| | | background: #409eff; |
| | | color: #fff; |
| | | font-weight: bold; |
| | | font-size: 16px; |
| | | font-size: 13px; |
| | | margin: 0 auto 8px; |
| | | } |
| | | |
| | | .card-process-name { |
| | | font-size: 14px; |
| | | color: #333; |
| | | color: #303133; |
| | | font-weight: 500; |
| | | word-break: break-all; |
| | | } |
| | |
| | | .card-content { |
| | | flex: 1; |
| | | margin-bottom: 12px; |
| | | min-height: 60px; |
| | | min-height: 40px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |