fix: 参考中天的[检测标准绑定]页面,再新增一个分表,表示检测标准汇总表应用于哪些产品,是一对多的关系,这个维护好以后,后面的来料检,过程检,出厂检可以根据对应检验类型和产品id去获取具体的检测参数信息
已添加2个文件
已修改2个文件
545 ■■■■■ 文件已修改
src/api/qualityManagement/metricMaintenance.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/qualityManagement/qualityTestStandardBinding.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricBinding/index.vue 428 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/index.vue 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/qualityManagement/metricMaintenance.js
@@ -53,6 +53,16 @@
  });
}
// æ‰¹é‡å®¡æ ¸ï¼ˆçŠ¶æ€ï¼š1=通过/批准,2=撤销)
// ä¼ å‚:[{ id, state }]
export function qualityTestStandardAudit(data) {
  return request({
    url: "/qualityTestStandard/qualityTestStandardAudit",
    method: "post",
    data,
  });
}
// æ ‡å‡†å‚数:列表(不分页)
export function qualityTestStandardParamList(query) {
  return request({
src/api/qualityManagement/qualityTestStandardBinding.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
import request from "@/utils/request";
// ç»‘定列表(不分页)
export function qualityTestStandardBindingList(query) {
  return request({
    url: "/qualityTestStandardBinding/list",
    method: "get",
    params: query,
  });
}
// æ–°å¢žç»‘定(支持批量)
export function qualityTestStandardBindingAdd(data) {
  return request({
    url: "/qualityTestStandardBinding/add",
    method: "post",
    data,
  });
}
// åˆ é™¤ç»‘定(传 id æ•°ç»„)
export function qualityTestStandardBindingDel(ids) {
  return request({
    url: "/qualityTestStandardBinding/del",
    method: "delete",
    data: ids,
  });
}
src/views/qualityManagement/metricBinding/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,428 @@
<template>
  <div class="app-container metric-binding">
    <!-- å·¦ä¾§ï¼šæ£€æµ‹æ ‡å‡†åˆ—表(只读) -->
    <div class="left-panel">
      <PIMTable
        rowKey="id"
        :column="standardColumns"
        :tableData="standardTableData"
        :page="page"
        :isSelection="false"
        :tableLoading="tableLoading"
        @pagination="handlePagination"
        :total="page.total"
      >
        <template #standardNoCell="{ row }">
          <span class="clickable-link" @click="handleStandardRowClick(row)">
            {{ row.standardNo }}
          </span>
        </template>
        <!-- è¡¨å¤´æœç´¢ -->
        <template #standardNoHeader>
          <el-input
            v-model="searchForm.standardNo"
            placeholder="标准编号"
            clearable
            size="small"
            @change="handleQuery"
            @clear="handleQuery"
          />
        </template>
        <template #standardNameHeader>
          <el-input
            v-model="searchForm.standardName"
            placeholder="标准名称"
            clearable
            size="small"
            @change="handleQuery"
            @clear="handleQuery"
          />
        </template>
        <template #inspectTypeHeader>
          <el-select
            v-model="searchForm.inspectType"
            placeholder="类别"
            clearable
            size="small"
            style="width: 120px"
            @change="handleQuery"
            @clear="handleQuery"
          >
            <el-option label="原材料检验" value="0" />
            <el-option label="过程检验" value="1" />
            <el-option label="出厂检验" value="2" />
          </el-select>
        </template>
        <template #stateHeader>
          <el-select
            v-model="searchForm.state"
            placeholder="状态"
            clearable
            size="small"
            style="width: 110px"
            @change="handleQuery"
            @clear="handleQuery"
          >
            <el-option label="草稿" value="0" />
            <el-option label="通过" value="1" />
            <el-option label="撤销" value="2" />
          </el-select>
        </template>
      </PIMTable>
    </div>
    <!-- å³ä¾§ï¼šç»‘定列表 -->
    <div class="right-panel">
      <div class="right-header">
        <div class="title">绑定关系</div>
        <div class="desc" v-if="currentStandard">
          å½“前检测标准编号:<span class="link-text">{{ currentStandard.standardNo }}</span>
        </div>
        <div class="desc" v-else>请选择左侧检测标准</div>
      </div>
      <div class="right-toolbar">
        <el-button type="primary" :disabled="!currentStandard" @click="openBindingDialog">添加绑定</el-button>
        <el-button type="danger" plain :disabled="!currentStandard" @click="handleBatchUnbind">删除</el-button>
      </div>
      <el-table
        v-loading="bindingLoading"
        :data="bindingTableData"
        border
        :row-class-name="() => 'row-center'"
        class="center-table"
        style="width: 100%"
        height="calc(100vh - 220px)"
        @selection-change="handleBindingSelectionChange"
      >
        <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 label="操作" width="120" fixed="right" align="center">
          <template #default="{ row }">
            <el-button link type="danger" size="small" @click="handleUnbind(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <!-- æ·»åŠ ç»‘å®šå¼¹æ¡† -->
    <el-dialog
      v-model="bindingDialogVisible"
      title="添加绑定"
      width="520px"
      @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>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="closeBindingDialog">取消</el-button>
          <el-button type="primary" @click="submitBinding">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { Search } from '@element-plus/icons-vue'
import { ref, reactive, toRefs, 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 {
  qualityTestStandardBindingList,
  qualityTestStandardBindingAdd,
  qualityTestStandardBindingDel
} from '@/api/qualityManagement/qualityTestStandardBinding.js'
const { proxy } = getCurrentInstance()
const data = reactive({
  searchForm: {
    standardNo: '',
    standardName: '',
    state: '',
    inspectType: ''
  }
})
const { searchForm } = toRefs(data)
// å·¦ä¾§
const standardTableData = ref([])
const tableLoading = ref(false)
const page = reactive({ current: 1, size: 10, total: 0 })
const standardColumns = ref([
  { label: '标准编号', prop: 'standardNo', dataType: 'slot', slot: 'standardNoCell', minWidth: 160, headerSlot: 'standardNoHeader' },
  { label: '标准名称', prop: 'standardName', minWidth: 180, headerSlot: 'standardNameHeader' },
  {
    label: '检测类型',
    prop: 'inspectType',
    headerSlot: 'inspectTypeHeader',
    dataType: 'tag',
    formatData: (val) => {
      const map = { 0: '原材料检验', 1: '过程检验', 2: '出厂检验' }
      return map[val] || val
    }
  },
  // {
  //   label: '状态',
  //   prop: 'state',
  //   headerSlot: 'stateHeader',
  //   dataType: 'tag',
  //   formatData: (val) => {
  //     const map = { 0: '草稿', 1: '通过', 2: '撤销' }
  //     return map[val] || val
  //   },
  //   formatType: (val) => {
  //     if (val == 1) return 'success'
  //     if (val == 2) return 'warning'
  //     return 'info'
  //   }
  // }
])
const currentStandard = ref(null)
// å³ä¾§ç»‘定
const bindingTableData = ref([])
const bindingLoading = ref(false)
const bindingSelectedRows = ref([])
const bindingDialogVisible = ref(false)
// äº§å“æ ‘(用于绑定选择)
const productOptions = ref([])
const selectedProductIds = ref([])
const getProductOptions = async () => {
  // é¿å…é‡å¤è¯·æ±‚
  if (productOptions.value?.length) return
  const res = await productTreeList()
  productOptions.value = convertIdToValue(Array.isArray(res) ? res : [])
}
function convertIdToValue(data) {
  return (data || []).map((item) => {
    const { id, children, ...rest } = item
    const newItem = {
      ...rest,
      value: id
    }
    if (children && children.length > 0) {
      newItem.children = convertIdToValue(children)
    }
    return newItem
  })
}
const handleQuery = () => {
  page.current = 1
  getStandardList()
}
const handlePagination = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getStandardList()
}
const getStandardList = () => {
  tableLoading.value = true
  qualityTestStandardListPage({
    ...searchForm.value,
    current: page.current,
    size: page.size,
    state: 1
  })
    .then((res) => {
      const records = res?.data?.records || []
      standardTableData.value = records
      page.total = res?.data?.total || records.length
    })
    .finally(() => {
      tableLoading.value = false
    })
}
const handleStandardRowClick = (row) => {
  currentStandard.value = row
  loadBindingList()
}
const loadBindingList = () => {
  if (!currentStandard.value?.id) {
    bindingTableData.value = []
    return
  }
  bindingLoading.value = true
  qualityTestStandardBindingList({ testStandardId: currentStandard.value.id })
    .then((res) => {
      bindingTableData.value = res?.data || []
    })
    .finally(() => {
      bindingLoading.value = false
    })
}
const handleBindingSelectionChange = (selection) => {
  bindingSelectedRows.value = selection
}
const openBindingDialog = () => {
  if (!currentStandard.value?.id) return
  selectedProductIds.value = []
  getProductOptions()
  bindingDialogVisible.value = true
}
const closeBindingDialog = () => {
  bindingDialogVisible.value = false
}
const submitBinding = async () => {
  const testStandardId = currentStandard.value?.id
  if (!testStandardId) return
  const ids = (selectedProductIds.value || []).filter(Boolean)
  if (!ids.length) {
    proxy.$message.warning('请选择产品')
    return
  }
  const payload = ids.map((pid) => ({
    productId: pid,
    testStandardId
  }))
  await qualityTestStandardBindingAdd(payload)
  proxy.$message.success('添加成功')
  bindingDialogVisible.value = false
  loadBindingList()
}
const handleUnbind = async (row) => {
  if (!row?.id) return
  try {
    await ElMessageBox.confirm('确认删除该绑定?', '提示', { type: 'warning' })
  } catch {
    return
  }
  await qualityTestStandardBindingDel([row.qualityTestStandardBindingId])
  proxy.$message.success('删除成功')
  loadBindingList()
}
const handleBatchUnbind = async () => {
  if (!bindingSelectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const ids = bindingSelectedRows.value.map((i) => i.qualityTestStandardBindingId)
  try {
    await ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', { type: 'warning' })
  } catch {
    return
  }
  await qualityTestStandardBindingDel(ids)
  proxy.$message.success('删除成功')
  loadBindingList()
}
onMounted(() => {
  getStandardList()
})
</script>
<style scoped>
.metric-binding {
  display: flex;
  gap: 16px;
}
.left-panel,
.right-panel {
  flex: 1;
  background: #ffffff;
  padding: 16px;
  box-sizing: border-box;
}
.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}
.toolbar-right {
  flex-shrink: 0;
}
.right-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 10px;
}
.right-header .title {
  font-size: 16px;
  font-weight: 600;
}
.right-header .desc {
  font-size: 13px;
  color: #666;
}
.right-toolbar {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-bottom: 10px;
}
.link-text {
  color: #409eff;
  cursor: default;
}
.clickable-link {
  color: #409eff;
  cursor: pointer;
}
.clickable-link:hover {
  text-decoration: underline;
}
:deep(.row-center td) {
  text-align: center !important;
}
/* el-table è¡¨å¤´/内容统一居中(row-class-name ä¸ä½œç”¨äºŽè¡¨å¤´ï¼‰ */
:deep(.center-table .el-table__header-wrapper th .cell) {
  text-align: center !important;
}
:deep(.center-table .el-table__body-wrapper td .cell) {
  text-align: center !important;
}
</style>
src/views/qualityManagement/metricMaintenance/index.vue
@@ -6,6 +6,8 @@
        <div class="toolbar-left"></div>
        <div class="toolbar-right">
          <el-button type="primary" @click="openStandardDialog('add')">新增</el-button>
          <el-button type="success" plain @click="handleBatchAudit(1)">批准</el-button>
          <el-button type="warning" plain @click="handleBatchAudit(2)">撤销</el-button>
          <el-button type="danger" plain @click="handleBatchDelete">删除</el-button>
        </div>
      </div>
@@ -16,6 +18,7 @@
        :page="page"
        :isSelection="true"
        :tableLoading="tableLoading"
        :rowClassName="rowClassNameCenter"
        @selection-change="handleSelectionChange"
        @pagination="handlePagination"
        :total="page.total"
@@ -92,10 +95,10 @@
      </div>
      <div class="right-toolbar">
        <el-button type="primary" :disabled="!currentStandard" @click="openParamDialog('add')">
        <el-button type="primary" :disabled="!currentStandard || isStandardReadonly" @click="openParamDialog('add')">
          æ–°å¢ž
        </el-button>
        <el-button type="danger" plain :disabled="!currentStandard" @click="handleParamBatchDelete">
        <el-button type="danger" plain :disabled="!currentStandard || isStandardReadonly" @click="handleParamBatchDelete">
          åˆ é™¤
        </el-button>
      </div>
@@ -104,6 +107,8 @@
        v-loading="detailLoading"
        :data="detailTableData"
        border
        :row-class-name="() => 'row-center'"
        class="center-table"
        style="width: 100%"
        height="calc(100vh - 220px)"
        @selection-change="handleParamSelectionChange"
@@ -117,10 +122,10 @@
        <el-table-column prop="defaultValue" label="默认值" min-width="120" />
        <el-table-column label="操作" width="140" fixed="right" align="center">
          <template #default="{ row }">
            <el-button link type="primary" size="small" @click="openParamDialog('edit', row)">
            <el-button link type="primary" size="small" :disabled="isStandardReadonly" @click="openParamDialog('edit', row)">
              ç¼–辑
            </el-button>
            <el-button link type="danger" size="small" @click="handleParamDelete(row)">
            <el-button link type="danger" size="small" :disabled="isStandardReadonly" @click="handleParamDelete(row)">
              åˆ é™¤
            </el-button>
          </template>
@@ -155,7 +160,7 @@
<script setup>
import { Search } from '@element-plus/icons-vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance, computed } from 'vue'
import { ElMessageBox } from 'element-plus'
import {
  qualityTestStandardListPage,
@@ -163,6 +168,7 @@
  qualityTestStandardUpdate,
  qualityTestStandardDel,
  qualityTestStandardCopyParam,
  qualityTestStandardAudit,
  qualityTestStandardParamList,
  qualityTestStandardParamAdd,
  qualityTestStandardParamUpdate,
@@ -173,6 +179,15 @@
import ParamFormDialog from './ParamFormDialog.vue'
const { proxy } = getCurrentInstance()
// å·¦ä¾§æ ‡å‡†åˆ—表:整行内容居中(配合样式)
const rowClassNameCenter = () => 'row-center'
// æ ‡å‡†çŠ¶æ€ä¸ºâ€œé€šè¿‡(1)”时,右侧参数禁止增删改
const isStandardReadonly = computed(() => {
  const state = currentStandard.value?.state
  return state === 1 || state === '1'
})
// æœç´¢æ¡ä»¶
const data = reactive({
@@ -399,6 +414,32 @@
  selectedRows.value = selection
}
// æ‰¹é‡å®¡æ ¸ï¼šçŠ¶æ€ 1=批准,2=撤销
const handleBatchAudit = async (state) => {
  if (!selectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const text = state === 1 ? '批准' : '撤销'
  const payload = selectedRows.value
    .filter(i => i?.id)
    .map((item) => ({ id: item.id, state }))
  if (!payload.length) {
    proxy.$message.warning('请选择有效数据')
    return
  }
  try {
    await ElMessageBox.confirm(`确认${text}选中的标准?`, '提示', { type: 'warning' })
  } catch {
    return
  }
  await qualityTestStandardAudit(payload)
  proxy.$message.success(`${text}成功`)
  getStandardList()
}
// å·¦ä¾§è¡Œç‚¹å‡»ï¼ŒåŠ è½½å³ä¾§å‚æ•°
const handleStandardRowClick = (row) => {
  currentStandard.value = row
@@ -425,6 +466,10 @@
const openParamDialog = (type, row) => {
  if (!currentStandard.value?.id) return
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  paramOperationType.value = type
  if (type === 'add') {
    Object.assign(paramForm, {
@@ -449,6 +494,10 @@
const submitParamForm = async () => {
  const testStandardId = currentStandard.value?.id
  if (!testStandardId) return
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  const payload = { ...paramForm, testStandardId }
  if (paramOperationType.value === 'edit') {
    await qualityTestStandardParamUpdate(payload)
@@ -463,6 +512,10 @@
const handleParamDelete = async (row) => {
  if (!row?.id) return
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  try {
    await ElMessageBox.confirm('确认删除该参数?', '提示', { type: 'warning' })
  } catch {
@@ -474,6 +527,10 @@
}
const handleParamBatchDelete = async () => {
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  if (!paramSelectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
@@ -682,4 +739,16 @@
.clickable-link:hover {
  text-decoration: underline;
}
:deep(.row-center td) {
  text-align: center !important;
}
/* el-table è¡¨å¤´/内容统一居中(row-class-name ä¸ä½œç”¨äºŽè¡¨å¤´ï¼‰ */
:deep(.center-table .el-table__header-wrapper th .cell) {
  text-align: center !important;
}
:deep(.center-table .el-table__body-wrapper td .cell) {
  text-align: center !important;
}
</style>