| | |
| | | <el-table-column type="selection" width="48" align="center" /> |
| | | <el-table-column type="index" label="序号" width="60" align="center" /> |
| | | <el-table-column prop="productName" label="产品名称" min-width="140" /> |
| | | <el-table-column prop="model" label="规格型号" min-width="120"> |
| | | <template #default="{ row }"> |
| | | {{ row.model || '—' }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="120" fixed="right" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button link type="danger" size="small" @click="handleUnbind(row)">删除</el-button> |
| | |
| | | <div class="binding-dialog"> |
| | | <el-input |
| | | v-model="productSearchKeyword" |
| | | placeholder="搜索产品" |
| | | placeholder="搜索产品或规格型号" |
| | | clearable |
| | | prefix-icon="Search" |
| | | class="binding-search" |
| | | /> |
| | | <div class="binding-tip"> |
| | | 勾选产品(大类或具体产品)表示该类下所有规格通用;勾选规格型号表示只对该型号生效,两种可以同时选择。 |
| | | </div> |
| | | <el-tree |
| | | ref="productTreeRef" |
| | | :key="productTreeKey" |
| | |
| | | :data="productTreeData" |
| | | show-checkbox |
| | | check-strictly |
| | | node-key="id" |
| | | node-key="treeKey" |
| | | :props="{ label: 'label', children: 'children', disabled: 'disabled' }" |
| | | :filter-node-method="filterProductNode" |
| | | class="product-binding-tree" |
| | |
| | | import { ref, reactive, toRefs, computed, watch, nextTick, onMounted, getCurrentInstance } from 'vue' |
| | | import { ElMessageBox } from 'element-plus' |
| | | import PIMTable from '@/components/PIMTable/PIMTable.vue' |
| | | import { productTreeList } from '@/api/basicData/product.js' |
| | | import { productTreeList, modelList } from '@/api/basicData/product.js' |
| | | import { |
| | | qualityTestStandardListPage |
| | | } from '@/api/qualityManagement/metricMaintenance.js' |
| | |
| | | const productSearchKeyword = ref('') |
| | | const selectedProductIds = ref([]) |
| | | |
| | | const markParentNodesDisabled = (nodes) => { |
| | | return (nodes || []).map((node) => { |
| | | const children = node.children?.length ? markParentNodesDisabled(node.children) : [] |
| | | return { |
| | | ...node, |
| | | children, |
| | | disabled: children.length > 0 |
| | | } |
| | | }) |
| | | } |
| | | // 树节点唯一 key:产品节点用 id,规格型号节点用 model-{id},避免与产品 id 冲突 |
| | | const nodeKey = (node) => (node.isModel ? `model-${node.id}` : node.id) |
| | | |
| | | const buildProductLabelMap = (nodes, map = {}) => { |
| | | ;(nodes || []).forEach((node) => { |
| | | if (node.id != null) { |
| | | map[node.id] = node.label |
| | | const key = nodeKey(node) |
| | | if (key != null) { |
| | | map[key] = node.label |
| | | } |
| | | if (node.children?.length) { |
| | | buildProductLabelMap(node.children, map) |
| | |
| | | productTreeRef.value?.filter(val) |
| | | }) |
| | | |
| | | // 为产品节点挂载规格型号子节点;无规格型号的产品保持可选 |
| | | const attachProductModels = async (nodes) => { |
| | | if (!Array.isArray(nodes)) return [] |
| | | const list = await Promise.all( |
| | | nodes.map(async (node) => { |
| | | const children = await attachProductModels(node.children) |
| | | let modelChildren = [] |
| | | try { |
| | | const res = await modelList({ id: node.id }) |
| | | modelChildren = (res || []).map((model) => ({ |
| | | id: model.id, |
| | | label: model.model || model.productCode || `型号${model.id}`, |
| | | isModel: true, |
| | | productId: node.id, |
| | | treeKey: `model-${model.id}` |
| | | })) |
| | | } catch (error) { |
| | | console.error('获取产品规格型号失败:', error) |
| | | } |
| | | return { |
| | | ...node, |
| | | treeKey: nodeKey(node), |
| | | children: [...children, ...modelChildren] |
| | | } |
| | | }) |
| | | ) |
| | | return list |
| | | } |
| | | |
| | | const getProductTreeData = async () => { |
| | | if (productTreeData.value?.length) return |
| | | productTreeLoading.value = true |
| | | try { |
| | | const res = await productTreeList() |
| | | productTreeData.value = markParentNodesDisabled(Array.isArray(res) ? res : []) |
| | | const tree = Array.isArray(res) ? res : [] |
| | | productTreeData.value = await attachProductModels(tree) |
| | | } catch (error) { |
| | | console.error('获取产品树失败:', error) |
| | | } finally { |
| | |
| | | bindingSelectedRows.value = selection |
| | | } |
| | | |
| | | // 勾选节点 -> 绑定项:产品节点绑定整个产品,规格型号节点绑定到具体型号 |
| | | const checkedNodesToBindings = () => { |
| | | const nodes = productTreeRef.value?.getCheckedNodes(true) || [] |
| | | const seen = new Set() |
| | | return nodes |
| | | .map((node) => (node.isModel |
| | | ? { productId: node.productId, productModelId: node.id } |
| | | : { productId: node.id, productModelId: null })) |
| | | .filter((item) => item.productId != null) |
| | | .filter((item) => { |
| | | const key = `${item.productId}_${item.productModelId ?? ''}` |
| | | if (seen.has(key)) return false |
| | | seen.add(key) |
| | | return true |
| | | }) |
| | | } |
| | | |
| | | const openBindingDialog = () => { |
| | | if (!currentStandard.value?.id) return |
| | | selectedProductIds.value = [] |
| | |
| | | const submitBinding = async () => { |
| | | const testStandardId = currentStandard.value?.id |
| | | if (!testStandardId) return |
| | | const ids = (selectedProductIds.value || []).filter(Boolean) |
| | | if (!ids.length) { |
| | | proxy.$message.warning('请选择产品') |
| | | const items = checkedNodesToBindings() |
| | | if (!items.length) { |
| | | proxy.$message.warning('请选择产品或规格型号') |
| | | return |
| | | } |
| | | const payload = ids.map((pid) => ({ |
| | | productId: pid, |
| | | const payload = items.map((item) => ({ |
| | | productId: item.productId, |
| | | productModelId: item.productModelId ?? null, |
| | | testStandardId |
| | | })) |
| | | await qualityTestStandardBindingAdd(payload) |
| | |
| | | width: 100%; |
| | | } |
| | | |
| | | .binding-tip { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | line-height: 18px; |
| | | } |
| | | |
| | | .product-binding-tree { |
| | | max-height: 360px; |
| | | overflow-y: auto; |