spring
43 分钟以前 b3af089315a98903163a394a3b1ca0e4c634b9ab
src/views/qualityManagement/metricMaintenance/index.vue
@@ -1,308 +1,761 @@
<template>
  <div class="app-container product-view">
    <div class="left">
      <div>
        <el-input
            v-model="search"
            style="width: 210px"
            placeholder="输入关键字进行搜索"
            @change="searchFilter"
            @clear="searchFilter"
  <div class="app-container metric-maintenance">
    <!-- 左侧:检测标准列表 -->
    <div class="left-panel">
      <div class="toolbar">
        <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>
      <PIMTable
        rowKey="id"
        :column="standardColumns"
        :tableData="standardTableData"
        :page="page"
        :isSelection="true"
        :tableLoading="tableLoading"
        :rowClassName="rowClassNameCenter"
        @selection-change="handleSelectionChange"
        @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
            prefix-icon="Search"
        />
      </div>
      <div ref="containerRef">
        <el-tree
            ref="tree"
            v-loading="treeLoad"
            :data="list"
            @node-click="handleNodeClick"
            :expand-on-click-node="false"
            default-expand-all
            :default-expanded-keys="expandedKeys"
            :draggable="true"
            :filter-node-method="filterNode"
            :props="{ children: 'children', label: 'label' }"
            highlight-current
            node-key="id"
            style="
            height: calc(100vh - 190px);
            overflow-y: scroll;
            scrollbar-width: none;
          "
        >
          <template #default="{ node, data }">
            <div class="custom-tree-node">
              <span class="tree-node-content">
                <el-icon class="orange-icon">
                  <component :is="data.children && data.children.length > 0
                  ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" />
                </el-icon>
                {{ data.label }}
              </span>
            </div>
          </template>
        </el-tree>
      </div>
            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">
      <div style="margin-bottom: 10px">
        <el-button type="primary" @click="openModelDia('add')">
          新增检测指标
    <!-- 右侧:标准参数列表 -->
    <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 || isStandardReadonly" @click="openParamDialog('add')">
          新增
        </el-button>
        <ImportExcel @uploadSuccess="getModelList" />
        <el-button
            type="danger"
            @click="handleDelete"
            style="margin-left: 10px"
            plain
        >
        <el-button type="danger" plain :disabled="!currentStandard || isStandardReadonly" @click="handleParamBatchDelete">
          删除
        </el-button>
      </div>
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
          :total="page.total"
      ></PIMTable>
      <el-table
        v-loading="detailLoading"
        :data="detailTableData"
        border
        :row-class-name="() => 'row-center'"
        class="center-table"
        style="width: 100%"
        height="calc(100vh - 220px)"
        @selection-change="handleParamSelectionChange"
      >
        <el-table-column type="selection" width="48" align="center" />
        <el-table-column type="index" label="序号" width="60" align="center" />
        <el-table-column prop="parameterItem" label="参数项" min-width="120" />
        <el-table-column prop="unit" label="单位" width="80" />
        <el-table-column prop="standardValue" label="标准值" min-width="120" />
        <el-table-column prop="controlValue" label="内控值" min-width="120" />
        <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" :disabled="isStandardReadonly" @click="openParamDialog('edit', row)">
              编辑
            </el-button>
            <el-button link type="danger" size="small" :disabled="isStandardReadonly" @click="handleParamDelete(row)">
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <!-- 新增 / 编辑检测标准 -->
    <StandardFormDialog
      ref="standardFormDialogRef"
      v-model="standardDialogVisible"
      :operation-type="standardOperationType"
      :form="standardForm"
      :rules="standardRules"
      :process-options="processOptions"
      @confirm="submitStandardForm"
      @close="closeStandardDialog"
      @cancel="closeStandardDialog"
    />
    <ParamFormDialog
      ref="paramFormDialogRef"
      v-model="paramDialogVisible"
      :operation-type="paramOperationType"
      :form="paramForm"
      @confirm="submitParamForm"
      @close="closeParamDialog"
      @cancel="closeParamDialog"
    />
  </div>
</template>
<script setup>
import {ref} from "vue";
import {delProductModel, modelListPage, productTreeList} from "@/api/basicData/product.js";
import ImportExcel from "@/views/basicData/product/ImportExcel/index.vue";
import {ElMessageBox} from "element-plus";
import { Search } from '@element-plus/icons-vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance, computed } from 'vue'
import { ElMessageBox } from 'element-plus'
import {
  qualityTestStandardListPage,
  qualityTestStandardAdd,
  qualityTestStandardUpdate,
  qualityTestStandardDel,
  qualityTestStandardCopyParam,
  qualityTestStandardAudit,
  qualityTestStandardParamList,
  qualityTestStandardParamAdd,
  qualityTestStandardParamUpdate,
  qualityTestStandardParamDel
} from '@/api/qualityManagement/metricMaintenance.js'
import { productProcessListPage } from '@/api/basicData/productProcess.js'
import StandardFormDialog from './StandardFormDialog.vue'
import ParamFormDialog from './ParamFormDialog.vue'
// 树
const search = ref("");
const treeLoad = ref(false);
const list = ref([]);
const expandedKeys = ref([]);
const currentId = ref("");
const currentParentId = ref("");
// 指标表格
const tableData = ref([]);
const tableLoading = ref(false);
const { proxy } = getCurrentInstance()
// 左侧标准列表:整行内容居中(配合样式)
const rowClassNameCenter = () => 'row-center'
// 标准状态为“通过(1)”时,右侧参数禁止增删改
const isStandardReadonly = computed(() => {
  const state = currentStandard.value?.state
  return state === 1 || state === '1'
})
// 搜索条件
const data = reactive({
  searchForm: {
    standardNo: '',
    standardName: '',
    remark: '',
    state: '',
    inspectType: '',
    processId: ''
  },
  standardForm: {
    id: undefined,
    standardNo: '',
    standardName: '',
    remark: '',
    state: '0',
    inspectType: '',
    processId: ''
  },
  standardRules: {
    standardNo: [{ required: true, message: '请输入标准编号', trigger: 'blur' }],
    standardName: [{ required: true, message: '请输入标准名称', trigger: 'blur' }],
    inspectType: [{ required: true, message: '请选择检测类型', trigger: 'change' }],
    processId: [{ required: true, message: '请选择工序', trigger: 'change' }]
  }
})
const { searchForm, standardForm, standardRules } = toRefs(data)
// 左侧表格
const standardTableData = ref([])
const selectedRows = ref([])
const tableLoading = ref(false)
const page = reactive({
  current: 1,
  size: 10,
});
const tableColumn = ref([
  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 currentStandard = ref(null)
const detailTableData = ref([])
const detailLoading = ref(false)
const paramSelectedRows = ref([])
const paramDialogVisible = ref(false)
const paramOperationType = ref('add') // add | edit
const paramFormDialogRef = ref(null)
const paramForm = reactive({
  id: undefined,
  parameterItem: '',
  unit: '',
  standardValue: '',
  controlValue: '',
  defaultValue: ''
})
// 弹窗
const standardDialogVisible = ref(false)
const standardOperationType = ref('add') // add | edit | copy
const standardFormDialogRef = ref(null)
// 列定义
const standardColumns = ref([
  {
    label: "指标",
    prop: "parameterItem",
    label: '标准编号',
    prop: 'standardNo',
    dataType: 'slot',
    slot: 'standardNoCell',
    minWidth: 160,
    headerSlot: 'standardNoHeader'
  },
  {
    label: "单位",
    prop: "unit",
    label: '标准名称',
    prop: 'standardName',
    minWidth: 180,
    headerSlot: 'standardNameHeader'
  },
  {
    label: "标准值",
    prop: "unit",
    label: '类别',
    prop: 'inspectType',
    headerSlot: 'inspectTypeHeader',
    dataType: 'tag',
    formatData: (val) => {
      const map = {
        0: '原材料检验',
        1: '过程检验',
        2: '出厂检验'
      }
      return map[val] || val
    }
  },
  {
    label: "内控值",
    prop: "unit",
    label: '工序',
    prop: 'processId',
    dataType: 'tag',
    formatData: (val) => {
      const target = processOptions.value.find(
        (item) => String(item.value) === String(val)
      )
      return target?.label || val
    }
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    label: '状态',
    prop: 'state',
    headerSlot: 'stateHeader',
    dataType: 'tag',
    formatData: (val) => {
      const map = {
        0: '草稿',
        1: '通过',
        2: '撤销'
      }
      return map[val] || val
    },
    formatType: (val) => {
      if (val === '1' || val === 1) return 'success'
      if (val === '2' || val === 2) return 'warning'
      return 'info'
    }
  },
  {
    label: '备注',
    prop: 'remark',
    minWidth: 160
  },
  {
    dataType: 'action',
    label: '操作',
    align: 'center',
    fixed: 'right',
    width: 220,
    operation: [
      {
        name: "编辑",
        type: "text",
        name: '编辑',
        type: 'text',
        clickFun: (row) => {
          openModelDia("edit", row);
        },
          openStandardDialog('edit', row)
        }
      },
    ],
  },
]);
// 指标弹框
const modelDia = ref(false);
const modelOperationType = ref("");
const data = reactive({
  form: {
    productName: "",
  },
  rules: {
    productName: [{ required: true, message: "请输入", trigger: "blur" }],
  },
  modelForm: {
    model: "",
    unit: "",
  },
  modelRules: {
    model: [{ required: true, message: "请输入", trigger: "blur" }],
    unit: [{ required: true, message: "请输入", trigger: "blur" }],
  },
});
const { form, rules, modelForm, modelRules } = toRefs(data);
      {
        name: '复制',
        type: 'text',
        clickFun: async (row) => {
          if (!row?.id) return
          try {
            await ElMessageBox.confirm('确认复制该标准参数?', '提示', { type: 'warning' })
          } catch {
            return
          }
          await qualityTestStandardCopyParam(row.id)
          proxy.$message.success('复制成功')
          getStandardList()
          if (currentStandard.value?.id === row.id) {
            loadDetail(row.id)
          }
        }
      },
      {
        name: '删除',
        type: 'text',
        clickFun: (row) => {
          handleDelete(row)
        }
      }
    ]
  }
])
// 查询产品树
const getProductTreeList = () => {
  treeLoad.value = true;
  productTreeList()
      .then((res) => {
        list.value = res;
        list.value.forEach((a) => {
          expandedKeys.value.push(a.label);
        });
        treeLoad.value = false;
      })
      .catch((err) => {
        treeLoad.value = false;
      });
};
// 过滤产品树
const searchFilter = () => {
  proxy.$refs.tree.filter(search.value);
};
// 选择产品
const handleNodeClick = (val, node, el) => {
  // 只有叶子节点才执行以下逻辑
  currentId.value = val.id;
  currentParentId.value = val.parentId;
  getModelList();
};
// 表格选择数据
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// 查询指标数据
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getModelList();
};
const getModelList = () => {
  tableLoading.value = true;
  modelListPage({
    id: currentId.value,
// 查询列表
const getStandardList = () => {
  tableLoading.value = true
  const params = {
    ...searchForm.value,
    current: page.current,
    size: page.size,
  }).then((res) => {
    tableData.value = res.records;
    page.total = res.total;
    tableLoading.value = false;
  });
};
// 调用tree过滤方法 中文英过滤
const filterNode = (value, data, node) => {
  if (!value) {
    //如果数据为空,则返回true,显示所有的数据项
    return true;
    size: page.size
  }
  // 查询列表是否有匹配数据,将值小写,匹配英文数据
  let val = value.toLowerCase();
  return chooseNode(val, data, node); // 调用过滤二层方法
};
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配,则返回该节点以及其下的所有子节点;如果参数是子节点,则返回该节点的父节点。name是中文字符,enName是英文字符.
const chooseNode = (value, data, node) => {
  if (data.label.indexOf(value) !== -1) {
    return true;
  qualityTestStandardListPage(params)
    .then((res) => {
      const records = res?.data?.records || []
      standardTableData.value = records
      page.total = res?.data?.total || records.length
    })
    .finally(() => {
      tableLoading.value = false
    })
}
const handleQuery = () => {
  page.current = 1
  getStandardList()
}
const resetQuery = () => {
  searchForm.value.standardNo = ''
  searchForm.value.standardName = ''
  searchForm.value.remark = ''
  searchForm.value.state = ''
  searchForm.value.inspectType = ''
  searchForm.value.processId = ''
  handleQuery()
}
const handlePagination = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getStandardList()
}
const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}
// 批量审核:状态 1=批准,2=撤销
const handleBatchAudit = async (state) => {
  if (!selectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const level = node.level;
  // 如果传入的节点本身就是一级节点就不用校验了
  if (level === 1) {
    return false;
  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
  }
  // 先取当前节点的父节点
  let parentData = node.parent;
  // 遍历当前节点的父节点
  let index = 0;
  while (index < level - 1) {
    // 如果匹配到直接返回,此处name值是中文字符,enName是英文字符。判断匹配中英文过滤
    if (parentData.data.label.indexOf(value) !== -1) {
      return true;
    }
    // 否则的话再往上一层做匹配
    parentData = parentData.parent;
    index++;
  try {
    await ElMessageBox.confirm(`确认${text}选中的标准?`, '提示', { type: 'warning' })
  } catch {
    return
  }
  // 没匹配到返回false
  return false;
};
// 打开指标弹框
const openModelDia = (type, data) => {
  modelOperationType.value = type;
  modelDia.value = true;
  modelForm.value.model = "";
  modelForm.value.model = "";
  modelForm.value.id = "";
  if (type === "edit") {
    modelForm.value = { ...data };
  await qualityTestStandardAudit(payload)
  proxy.$message.success(`${text}成功`)
  getStandardList()
}
// 左侧行点击,加载右侧参数
const handleStandardRowClick = (row) => {
  currentStandard.value = row
  loadDetail(row.id)
}
const loadDetail = (standardId) => {
  if (!standardId) {
    detailTableData.value = []
    return
  }
};
// 删除指标
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  detailLoading.value = true
  qualityTestStandardParamList({ testStandardId: standardId }).then((res) => {
    detailTableData.value = res?.data || []
  })
    .finally(() => {
      detailLoading.value = false
    })
}
const handleParamSelectionChange = (selection) => {
  paramSelectedRows.value = selection
}
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, {
      id: undefined,
      parameterItem: '',
      unit: '',
      standardValue: '',
      controlValue: '',
      defaultValue: ''
    })
  } else if (type === 'edit' && row) {
    Object.assign(paramForm, row)
  }
  paramDialogVisible.value = true
}
const closeParamDialog = () => {
  paramDialogVisible.value = false
  paramFormDialogRef.value?.resetFields?.()
}
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)
    proxy.$message.success('提交成功')
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
    await qualityTestStandardParamAdd(payload)
    proxy.$message.success('提交成功')
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    tableLoading.value = true;
    delProductModel(ids).then((res) => {
      proxy.$modal.msgSuccess("删除成功");
      getModelList();
    }).finally(() => {
      tableLoading.value = false;
    });
  }).catch(() => {
    proxy.$modal.msg("已取消");
  });
};
getProductTreeList();
  closeParamDialog()
  loadDetail(testStandardId)
}
const handleParamDelete = async (row) => {
  if (!row?.id) return
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  try {
    await ElMessageBox.confirm('确认删除该参数?', '提示', { type: 'warning' })
  } catch {
    return
  }
  await qualityTestStandardParamDel([row.id])
  proxy.$message.success('删除成功')
  loadDetail(currentStandard.value?.id)
}
const handleParamBatchDelete = async () => {
  if (isStandardReadonly.value) {
    proxy.$message.warning('该标准已通过,参数不可编辑')
    return
  }
  if (!paramSelectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const ids = paramSelectedRows.value.map((i) => i.id)
  try {
    await ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', { type: 'warning' })
  } catch {
    return
  }
  await qualityTestStandardParamDel(ids)
  proxy.$message.success('删除成功')
  loadDetail(currentStandard.value?.id)
}
// 新增 / 编辑 / 复制
const openStandardDialog = (type, row) => {
  standardOperationType.value = type
  if (type === 'add') {
    Object.assign(standardForm.value, {
      id: undefined,
      standardNo: '',
      standardName: '',
      remark: '',
      state: '0',
      inspectType: '',
      processId: ''
    })
  } else if (type === 'edit' && row) {
    Object.assign(standardForm.value, row)
  } else if (type === 'copy' && row) {
    const { id, ...rest } = row
    Object.assign(standardForm.value, {
      ...rest,
      id: undefined,
      standardNo: '',
      state: '0'
    })
  }
  standardDialogVisible.value = true
}
const closeStandardDialog = () => {
  standardDialogVisible.value = false
  standardFormDialogRef.value?.resetFields?.()
}
const submitStandardForm = () => {
  const payload = { ...standardForm.value }
  const isEdit = standardOperationType.value === 'edit'
  if (isEdit) {
    qualityTestStandardUpdate(payload).then(() => {
      proxy.$message.success('提交成功')
      standardDialogVisible.value = false
      getStandardList()
    })
  } else {
    qualityTestStandardAdd(payload).then(() => {
       proxy.$message.success('提交成功')
      standardDialogVisible.value = false
      getStandardList()
    })
  }
}
// 删除(单条)
const handleDelete = (row) => {
  const ids = [row.id]
  ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      tableLoading.value = true
      qualityTestStandardDel(ids)
        .then(() => {
          proxy.$message.success('删除成功')
          getStandardList()
          if (currentStandard.value && currentStandard.value.id === row.id) {
            currentStandard.value = null
            detailTableData.value = []
          }
        })
        .finally(() => {
          tableLoading.value = false
        })
    })
    .catch(() => {
      proxy.$modal?.msg('已取消')
    })
}
// 批量删除
const handleBatchDelete = () => {
  if (!selectedRows.value.length) {
    proxy.$message.warning('请选择数据')
    return
  }
  const ids = selectedRows.value.map((item) => item.id)
  ElMessageBox.confirm('选中的内容将被删除,是否确认删除?', '删除提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      tableLoading.value = true
      qualityTestStandardDel(ids)
        .then(() => {
           proxy.$message.success('删除成功')
          getStandardList()
          if (currentStandard.value && ids.includes(currentStandard.value.id)) {
            currentStandard.value = null
            detailTableData.value = []
          }
        })
        .finally(() => {
          tableLoading.value = false
        })
    })
    .catch(() => {
      proxy.$modal?.msg('已取消')
    })
}
onMounted(() => {
  getProcessList()
  getStandardList()
})
</script>
<style scoped>
.product-view {
.metric-maintenance {
  display: flex;
  gap: 16px;
}
.left {
  width: 380px;
  padding: 16px;
  background: #ffffff;
}
.right {
  width: calc(100% - 380px);
  padding: 16px;
  margin-left: 20px;
  background: #ffffff;
}
.custom-tree-node {
.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-left {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding-right: 8px;
  flex-wrap: wrap;
  gap: 4px;
}
.tree-node-content {
.toolbar-right {
  flex-shrink: 0;
}
.search-label {
  margin: 0 4px 0 12px;
}
.search-label:first-of-type {
  margin-left: 0;
}
.right-header {
  display: flex;
  align-items: center; /* 垂直居中 */
  height: 100%;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 10px;
}
.orange-icon {
  color: orange;
  font-size: 18px;
  margin-right: 8px; /* 图标与文字之间加点间距 */
.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>