From 9ea5ff67655241271bdf04bb50ca3a8d60c4e22a Mon Sep 17 00:00:00 2001
From: yuan <123@>
Date: 星期二, 11 八月 2026 15:07:02 +0800
Subject: [PATCH] fix: 用河南鹤壁代码覆盖山西长治

---
 src/views/qualityManagement/metricBinding/index.vue |  337 ++++++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 277 insertions(+), 60 deletions(-)

diff --git a/src/views/qualityManagement/metricBinding/index.vue b/src/views/qualityManagement/metricBinding/index.vue
index 616a5e5..ba989ea 100644
--- a/src/views/qualityManagement/metricBinding/index.vue
+++ b/src/views/qualityManagement/metricBinding/index.vue
@@ -1,14 +1,18 @@
 <template>
   <div class="app-container metric-binding">
-    <!-- 宸︿晶锛氭娴嬫爣鍑嗗垪琛紙鍙锛� -->
-    <div class="left-panel">
+    <el-row :gutter="16" class="metric-binding-row">
+      <!-- 宸︿晶锛氭娴嬫爣鍑嗗垪琛� -->
+      <el-col :xs="24" :sm="24" :md="12" :lg="14" :xl="14" class="left-col">
+        <div class="panel left-panel">
       <PIMTable
         rowKey="id"
         :column="standardColumns"
         :tableData="standardTableData"
         :page="page"
         :isSelection="false"
+        :rowClassName="rowClassNameCenter"
         :tableLoading="tableLoading"
+        :rowClick="handleTableRowClick"
         @pagination="handlePagination"
         :total="page.total"
       >
@@ -70,10 +74,12 @@
           </el-select>
         </template>
       </PIMTable>
-    </div>
+        </div>
+      </el-col>
 
-    <!-- 鍙充晶锛氱粦瀹氬垪琛� -->
-    <div class="right-panel">
+      <!-- 鍙充晶锛氱粦瀹氬垪琛� -->
+      <el-col :xs="24" :sm="24" :md="12" :lg="10" :xl="10" class="right-col">
+        <div class="panel right-panel">
       <div class="right-header">
         <div class="title">缁戝畾鍏崇郴</div>
         <div class="desc" v-if="currentStandard">
@@ -106,31 +112,53 @@
           </template>
         </el-table-column>
       </el-table>
-    </div>
+        </div>
+      </el-col>
+    </el-row>
 
     <!-- 娣诲姞缁戝畾寮规 -->
     <el-dialog
       v-model="bindingDialogVisible"
       title="娣诲姞缁戝畾"
-      width="520px"
+      width="640px"
       @close="closeBindingDialog"
     >
-      <el-form label-width="100px">
-        <el-form-item label="浜у搧">
-          <el-tree-select
-            v-model="selectedProductIds"
-            multiple
-            collapse-tags
-            collapse-tags-tooltip
-            placeholder="璇烽�夋嫨浜у搧锛堝彲澶氶�夛級"
-            clearable
-            check-strictly
-            :data="productOptions"
-            :render-after-expand="false"
-            style="width: 100%"
-          />
-        </el-form-item>
-      </el-form>
+      <div class="binding-dialog">
+        <el-input
+          v-model="productSearchKeyword"
+          placeholder="鎼滅储浜у搧"
+          clearable
+          prefix-icon="Search"
+          class="binding-search"
+        />
+        <el-tree
+          ref="productTreeRef"
+          :key="productTreeKey"
+          v-loading="productTreeLoading"
+          :data="productTreeData"
+          show-checkbox
+          check-strictly
+          node-key="id"
+          :props="{ label: 'label', children: 'children', disabled: 'disabled' }"
+          :filter-node-method="filterProductNode"
+          class="product-binding-tree"
+          @check="handleProductTreeCheck"
+        />
+        <div v-if="selectedProductLabels.length" class="selected-products">
+          <div class="selected-title">宸查�夋嫨锛坽{ selectedProductLabels.length }}锛�</div>
+          <div class="selected-tags">
+            <el-tag
+              v-for="item in selectedProductLabels"
+              :key="item.id"
+              closable
+              type="info"
+              @close="removeSelectedProduct(item.id)"
+            >
+              {{ item.label }}
+            </el-tag>
+          </div>
+        </div>
+      </div>
       <template #footer>
         <span class="dialog-footer">
           <el-button @click="closeBindingDialog">鍙栨秷</el-button>
@@ -142,14 +170,14 @@
 </template>
 
 <script setup>
-import { Search } from '@element-plus/icons-vue'
-import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
+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 {
   qualityTestStandardListPage
 } from '@/api/qualityManagement/metricMaintenance.js'
+import { productProcessListPage } from '@/api/basicData/productProcess.js'
 import {
   qualityTestStandardBindingList,
   qualityTestStandardBindingAdd,
@@ -157,6 +185,9 @@
 } from '@/api/qualityManagement/qualityTestStandardBinding.js'
 
 const { proxy } = getCurrentInstance()
+
+// 宸︿晶鏍囧噯鍒楄〃锛氭暣琛屽唴瀹瑰眳涓紙閰嶅悎鏍峰紡锛�
+const rowClassNameCenter = () => 'row-center'
 
 const data = reactive({
   searchForm: {
@@ -173,19 +204,56 @@
 const tableLoading = ref(false)
 const page = reactive({ current: 1, size: 10, total: 0 })
 
+// 宸ュ簭涓嬫媺锛堢敤浜庡垪琛ㄥ洖鏄撅級
+const processOptions = ref([])
+
+const getProcessList = async () => {
+  try {
+    const res = await productProcessListPage({ current: 1, size: 1000 })
+    if (res?.code === 200) {
+      const records = res?.data?.records || []
+      processOptions.value = records.map((item) => ({
+        label: item.processName || item.name || item.label,
+        value: item.id || item.processId || item.value
+      }))
+    }
+  } catch (error) {
+    console.error('鑾峰彇宸ュ簭鍒楄〃澶辫触:', error)
+  }
+}
+
 const standardColumns = ref([
-  { label: '鏍囧噯缂栧彿', prop: 'standardNo', dataType: 'slot', slot: 'standardNoCell', minWidth: 160, headerSlot: 'standardNoHeader' },
-  { label: '鏍囧噯鍚嶇О', prop: 'standardName', minWidth: 180, headerSlot: 'standardNameHeader' },
+  { label: '鏍囧噯缂栧彿', prop: 'standardNo', dataType: 'slot', slot: 'standardNoCell', minWidth: 160, align: 'center', headerSlot: 'standardNoHeader' },
+  { label: '鏍囧噯鍚嶇О', prop: 'standardName', minWidth: 180, align: 'center', headerSlot: 'standardNameHeader' },
   {
-    label: '妫�娴嬬被鍨�',
+    label: '绫诲埆',
     prop: 'inspectType',
     headerSlot: 'inspectTypeHeader',
+    align: 'center',
     dataType: 'tag',
     formatData: (val) => {
       const map = { 0: '鍘熸潗鏂欐楠�', 1: '杩囩▼妫�楠�', 2: '鍑哄巶妫�楠�' }
       return map[val] || val
     }
   },
+  {
+    label: '宸ュ簭',
+    prop: 'processId',
+    align: 'center',
+    dataType: 'tag',
+    formatData: (val) => {
+      const target = processOptions.value.find(
+        (item) => String(item.value) === String(val)
+      )
+      return target?.label || val
+    }
+  },
+  {
+    label: '澶囨敞',
+    prop: 'remark',
+    minWidth: 160,
+    align: 'center'
+  }
   // {
   //   label: '鐘舵��',
   //   prop: 'state',
@@ -212,28 +280,74 @@
 const bindingDialogVisible = ref(false)
 
 // 浜у搧鏍戯紙鐢ㄤ簬缁戝畾閫夋嫨锛�
-const productOptions = ref([])
+const productTreeData = ref([])
+const productTreeLoading = ref(false)
+const productTreeRef = ref(null)
+const productTreeKey = ref(0)
+const productSearchKeyword = ref('')
 const selectedProductIds = ref([])
 
-const getProductOptions = async () => {
-  // 閬垮厤閲嶅璇锋眰
-  if (productOptions.value?.length) return
-  const res = await productTreeList()
-  productOptions.value = convertIdToValue(Array.isArray(res) ? res : [])
+const markParentNodesDisabled = (nodes) => {
+  return (nodes || []).map((node) => {
+    const children = node.children?.length ? markParentNodesDisabled(node.children) : []
+    return {
+      ...node,
+      children,
+      disabled: children.length > 0
+    }
+  })
 }
 
-function convertIdToValue(data) {
-  return (data || []).map((item) => {
-    const { id, children, ...rest } = item
-    const newItem = {
-      ...rest,
-      value: id
+const buildProductLabelMap = (nodes, map = {}) => {
+  ;(nodes || []).forEach((node) => {
+    if (node.id != null) {
+      map[node.id] = node.label
     }
-    if (children && children.length > 0) {
-      newItem.children = convertIdToValue(children)
+    if (node.children?.length) {
+      buildProductLabelMap(node.children, map)
     }
-    return newItem
   })
+  return map
+}
+
+const productLabelMap = computed(() => buildProductLabelMap(productTreeData.value))
+
+const selectedProductLabels = computed(() =>
+  selectedProductIds.value.map((id) => ({
+    id,
+    label: productLabelMap.value[id] || id
+  }))
+)
+
+const filterProductNode = (value, data) => {
+  if (!value) return true
+  return String(data.label || '').includes(value)
+}
+
+watch(productSearchKeyword, (val) => {
+  productTreeRef.value?.filter(val)
+})
+
+const getProductTreeData = async () => {
+  if (productTreeData.value?.length) return
+  productTreeLoading.value = true
+  try {
+    const res = await productTreeList()
+    productTreeData.value = markParentNodesDisabled(Array.isArray(res) ? res : [])
+  } catch (error) {
+    console.error('鑾峰彇浜у搧鏍戝け璐�:', error)
+  } finally {
+    productTreeLoading.value = false
+  }
+}
+
+const handleProductTreeCheck = () => {
+  selectedProductIds.value = productTreeRef.value?.getCheckedKeys(true) || []
+}
+
+const removeSelectedProduct = (id) => {
+  selectedProductIds.value = selectedProductIds.value.filter((item) => item !== id)
+  productTreeRef.value?.setChecked(id, false, false)
 }
 
 const handleQuery = () => {
@@ -265,6 +379,13 @@
     })
 }
 
+// 琛ㄦ牸琛岀偣鍑伙紝鍔犺浇鍙充晶缁戝畾鍒楄〃
+const handleTableRowClick = (row) => {
+  currentStandard.value = row
+  loadBindingList()
+}
+
+// 宸︿晶琛岀偣鍑伙紝鍔犺浇鍙充晶缁戝畾鍒楄〃锛堜繚鐣欑敤浜庢爣鍑嗙紪鍙峰垪鐨勭偣鍑伙級
 const handleStandardRowClick = (row) => {
   currentStandard.value = row
   loadBindingList()
@@ -278,7 +399,13 @@
   bindingLoading.value = true
   qualityTestStandardBindingList({ testStandardId: currentStandard.value.id })
     .then((res) => {
-      bindingTableData.value = res?.data || []
+      const base = res?.data || []
+      // 灏嗗綋鍓嶆爣鍑嗙殑宸ュ簭鍜屽娉ㄥ甫鍒扮粦瀹氬垪琛ㄤ腑灞曠ず
+      bindingTableData.value = base.map((item) => ({
+        ...item,
+        processId: currentStandard.value?.processId,
+        remark: currentStandard.value?.remark
+      }))
     })
     .finally(() => {
       bindingLoading.value = false
@@ -292,12 +419,20 @@
 const openBindingDialog = () => {
   if (!currentStandard.value?.id) return
   selectedProductIds.value = []
-  getProductOptions()
+  productSearchKeyword.value = ''
+  productTreeKey.value += 1
   bindingDialogVisible.value = true
+  nextTick(() => {
+    productTreeRef.value?.setCheckedKeys([])
+    productTreeRef.value?.filter('')
+  })
 }
 
 const closeBindingDialog = () => {
   bindingDialogVisible.value = false
+  selectedProductIds.value = []
+  productSearchKeyword.value = ''
+  productTreeRef.value?.setCheckedKeys([])
 }
 
 const submitBinding = async () => {
@@ -319,15 +454,21 @@
 }
 
 const handleUnbind = async (row) => {
-  if (!row?.id) return
+  const id = row?.id ?? row?.qualityTestStandardBindingId
+  if (id == null || id === '') return
   try {
     await ElMessageBox.confirm('纭鍒犻櫎璇ョ粦瀹氾紵', '鎻愮ず', { type: 'warning' })
   } catch {
     return
   }
-  await qualityTestStandardBindingDel([row.qualityTestStandardBindingId])
-  proxy.$message.success('鍒犻櫎鎴愬姛')
-  loadBindingList()
+  try {
+    await qualityTestStandardBindingDel([id])
+    proxy.$message.success('鍒犻櫎鎴愬姛')
+    loadBindingList()
+  } catch (err) {
+    console.error('鍒犻櫎缁戝畾澶辫触:', err)
+    proxy.$message?.error(err?.message || '鍒犻櫎澶辫触')
+  }
 }
 
 const handleBatchUnbind = async () => {
@@ -335,34 +476,56 @@
     proxy.$message.warning('璇烽�夋嫨鏁版嵁')
     return
   }
-  const ids = bindingSelectedRows.value.map((i) => i.qualityTestStandardBindingId)
+  const ids = bindingSelectedRows.value
+    .map((i) => i?.id ?? i?.qualityTestStandardBindingId)
+    .filter((id) => id != null && id !== '')
+  if (!ids.length) {
+    proxy.$message.warning('閫変腑鏁版嵁缂哄皯鏈夋晥 id')
+    return
+  }
   try {
     await ElMessageBox.confirm('閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�', '鍒犻櫎鎻愮ず', { type: 'warning' })
   } catch {
     return
   }
-  await qualityTestStandardBindingDel(ids)
-  proxy.$message.success('鍒犻櫎鎴愬姛')
-  loadBindingList()
+  try {
+    await qualityTestStandardBindingDel(ids)
+    proxy.$message.success('鍒犻櫎鎴愬姛')
+    loadBindingList()
+  } catch (err) {
+    console.error('鎵归噺鍒犻櫎缁戝畾澶辫触:', err)
+    proxy.$message?.error(err?.message || '鍒犻櫎澶辫触')
+  }
 }
 
 onMounted(() => {
   getStandardList()
+  getProcessList()
+  getProductTreeData()
 })
 </script>
 
 <style scoped>
-.metric-binding {
-  display: flex;
-  gap: 16px;
+.metric-binding-row {
+  width: 100%;
+}
+
+.metric-binding-row .left-col,
+.metric-binding-row .right-col {
+  margin-bottom: 16px;
+}
+
+.metric-binding-row .panel {
+  background: #ffffff;
+  padding: 16px;
+  box-sizing: border-box;
+  height: 100%;
+  min-height: 400px;
 }
 
 .left-panel,
 .right-panel {
-  flex: 1;
-  background: #ffffff;
-  padding: 16px;
-  box-sizing: border-box;
+  height: 100%;
 }
 
 .toolbar {
@@ -425,4 +588,58 @@
 :deep(.center-table .el-table__body-wrapper td .cell) {
   text-align: center !important;
 }
+
+/* PIMTable 琛ㄥご灞呬腑 */
+:deep(.lims-table .pim-table-header-cell) {
+  text-align: center;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+:deep(.lims-table .pim-table-header-title) {
+  text-align: center;
+  width: 100%;
+}
+
+:deep(.lims-table .pim-table-header-extra) {
+  width: 100%;
+  margin-top: 4px;
+}
+
+.binding-dialog {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.binding-search {
+  width: 100%;
+}
+
+.product-binding-tree {
+  max-height: 360px;
+  overflow-y: auto;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  padding: 8px;
+}
+
+.selected-products {
+  border-top: 1px solid #ebeef5;
+  padding-top: 12px;
+}
+
+.selected-title {
+  font-size: 13px;
+  color: #606266;
+  margin-bottom: 8px;
+}
+
+.selected-tags {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+}
 </style>

--
Gitblit v1.9.3