2025-12-09 18:07:12 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="app-container">
|
|
|
|
|
|
<el-row :gutter="20" class="main-row">
|
|
|
|
|
|
<!-- 左侧树区域 -->
|
|
|
|
|
|
<el-col :span="4" :xs="24" class="tree-col">
|
2025-12-11 18:03:51 +08:00
|
|
|
|
<el-input v-model="deptName" placeholder="请输入仪器名称" clearable prefix-icon="Search" class="tree-search-input" />
|
|
|
|
|
|
<el-radio-group v-model="radiotype" @change="radioChange">
|
|
|
|
|
|
<el-radio value="1" size="large">仪器组</el-radio>
|
|
|
|
|
|
<el-radio value="2" size="large">所有仪器</el-radio>
|
|
|
|
|
|
</el-radio-group>
|
2025-12-09 18:07:12 +08:00
|
|
|
|
<div class="tree-container">
|
2025-12-11 18:03:51 +08:00
|
|
|
|
<el-tree :data="deptOptions" :props="treeProps" expand-on-click-node :filter-node-method="filterNode"
|
|
|
|
|
|
ref="deptTreeRef" node-key="id" highlight-current :expanded-keys="expandedKeys"
|
|
|
|
|
|
@node-click="handleNodeClick">
|
2025-12-09 18:07:12 +08:00
|
|
|
|
<!-- 自定义树节点模板:新增newflag徽章 -->
|
|
|
|
|
|
<template #default="{ node, data }">
|
|
|
|
|
|
<span class="tree-node-content">
|
|
|
|
|
|
{{ node.label }}
|
|
|
|
|
|
<!-- 新节点徽章:newflag=1时显示黄色"新"标 -->
|
2025-12-11 18:03:51 +08:00
|
|
|
|
<el-badge v-if="data.newflag === '1'" value="new" class="new-node-badge" />
|
2025-12-09 18:07:12 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-tree>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
2025-12-11 18:03:51 +08:00
|
|
|
|
<!-- 右侧列表区域 -->
|
2025-12-09 18:07:12 +08:00
|
|
|
|
<el-col :span="20" :xs="24" class="table-col">
|
|
|
|
|
|
<el-row :gutter="10" class="yq-title-row">
|
|
|
|
|
|
<h1 class="yq-title">仪器:{{ yqmc }}</h1>
|
|
|
|
|
|
</el-row>
|
2025-12-11 18:03:51 +08:00
|
|
|
|
<el-tabs v-model="activeName" type="card" class="tabs_box">
|
|
|
|
|
|
<el-tab-pane v-for="tab in tabList" :key="tab.key" :label="tab.label" :name="tab.key" />
|
|
|
|
|
|
</el-tabs>
|
|
|
|
|
|
<!-- 动态组件渲染 -->
|
|
|
|
|
|
<component :is="currentComponent" :key="activeName" v-model="queryParams.yq" @updateTree="getDeptTree" />
|
2025-12-09 18:07:12 +08:00
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup name="ComOpt" lang="ts">
|
|
|
|
|
|
import { ref, watch, nextTick, computed } from 'vue'
|
2025-12-11 18:03:51 +08:00
|
|
|
|
import { ElMessage, ElTree, TreeNode, ElMessageBox, ElBadge } from 'element-plus'
|
2025-12-09 18:07:12 +08:00
|
|
|
|
import { paramList, paramTree, getdef, updateParam } from '@/api/liswork/xtwh/ComOpt'
|
2025-12-11 18:03:51 +08:00
|
|
|
|
// @ts-ignore
|
2025-12-09 18:07:12 +08:00
|
|
|
|
import { getFirstLetter } from '@/utils/pinyin.js'
|
2025-12-11 18:03:51 +08:00
|
|
|
|
|
|
|
|
|
|
import SysGlobal from './components/sysGlobal.vue'
|
|
|
|
|
|
import SysReport from './components/sysReport.vue'
|
2025-12-09 18:07:12 +08:00
|
|
|
|
|
|
|
|
|
|
// ========== TypeScript 类型定义 ==========
|
|
|
|
|
|
interface InstrTreeNode {
|
|
|
|
|
|
id: string
|
|
|
|
|
|
label: string
|
|
|
|
|
|
lx: string
|
2025-12-11 18:03:51 +08:00
|
|
|
|
newflag: string // 新增newflag字段
|
2025-12-09 18:07:12 +08:00
|
|
|
|
children?: InstrTreeNode[]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface ParamTreeResponse {
|
|
|
|
|
|
code: string
|
|
|
|
|
|
msg: string
|
|
|
|
|
|
data: InstrTreeNode[]
|
|
|
|
|
|
url: null | string
|
|
|
|
|
|
method: null | string
|
|
|
|
|
|
params: null | any
|
|
|
|
|
|
problemId: null | string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface QueryParams {
|
|
|
|
|
|
yq?: string | undefined
|
|
|
|
|
|
xxdh?: string | undefined
|
|
|
|
|
|
xxqz?: string | undefined
|
|
|
|
|
|
groupId?: string | undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-11 18:03:51 +08:00
|
|
|
|
const radiotype = ref<string>("1")
|
2025-12-09 18:07:12 +08:00
|
|
|
|
// ========== 响应式变量 ==========
|
|
|
|
|
|
// 树相关
|
|
|
|
|
|
const deptTreeRef = ref<InstanceType<typeof ElTree>>()
|
|
|
|
|
|
const deptOptions = ref<InstrTreeNode[]>([])
|
|
|
|
|
|
const deptName = ref<string>("")
|
|
|
|
|
|
const yqmc = ref<string>("")
|
|
|
|
|
|
const expandedKeys = ref<string[]>([])
|
|
|
|
|
|
const isFiltering = ref<boolean>(false)
|
|
|
|
|
|
const treeProps = ref({
|
|
|
|
|
|
label: 'label',
|
|
|
|
|
|
children: 'children',
|
2025-12-11 18:03:51 +08:00
|
|
|
|
isLeaf: (data: Record<string, any>) => !data.children || data.children.length === 0
|
2025-12-09 18:07:12 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 查询参数
|
|
|
|
|
|
const queryParams = ref<QueryParams>({
|
|
|
|
|
|
yq: undefined,
|
|
|
|
|
|
xxdh: undefined,
|
|
|
|
|
|
xxqz: undefined,
|
|
|
|
|
|
groupId: undefined
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-11 18:03:51 +08:00
|
|
|
|
const tabList = [
|
|
|
|
|
|
{ key: 'SysGlobal', label: '系统全局参数', component: SysGlobal },
|
|
|
|
|
|
{ key: 'SysReport', label: '报告界面', component: SysReport },
|
|
|
|
|
|
]
|
|
|
|
|
|
const activeName = ref('SysGlobal')
|
|
|
|
|
|
|
|
|
|
|
|
// 计算当前要渲染的组件
|
|
|
|
|
|
const currentComponent = computed(() => {
|
|
|
|
|
|
return tabList.find(tab => tab.key === activeName.value)?.component || 'SysGlobal'
|
2025-12-09 18:07:12 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-11 18:03:51 +08:00
|
|
|
|
|
|
|
|
|
|
const treeData = ref<InstrTreeNode[]>([])
|
|
|
|
|
|
const radioChange = (val: string) => {
|
|
|
|
|
|
if (val == "1") {
|
|
|
|
|
|
deptOptions.value = treeData.value
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const result = [];
|
|
|
|
|
|
// 遍历外层数组的每个分组
|
|
|
|
|
|
for (let i = 0; i < treeData.value.length; i++) {
|
|
|
|
|
|
const group = treeData.value[i];
|
|
|
|
|
|
if (group.children && Array.isArray(group.children)) {
|
|
|
|
|
|
for (let j = 0; j < group.children.length; j++) {
|
|
|
|
|
|
result.push(group.children[j]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
deptOptions.value = result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 默认展开第一个一级节点的子级
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
if (deptOptions.value.length > 0) {
|
|
|
|
|
|
const firstLevelNode = deptOptions.value[0]
|
|
|
|
|
|
if (firstLevelNode && firstLevelNode.children && firstLevelNode.children.length > 0) {
|
|
|
|
|
|
expandedKeys.value = [firstLevelNode.id]
|
|
|
|
|
|
const firstChildNode = firstLevelNode.children[0]
|
|
|
|
|
|
queryParams.value.yq = firstChildNode.id
|
|
|
|
|
|
yqmc.value = firstChildNode.label
|
|
|
|
|
|
|
|
|
|
|
|
deptTreeRef.value?.setCurrentKey(firstChildNode.id)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-09 18:07:12 +08:00
|
|
|
|
// ========== 树操作核心方法 ==========
|
|
|
|
|
|
const forceExpandSingleNode = (nodeId: string): void => {
|
|
|
|
|
|
expandedKeys.value = []
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
expandedKeys.value = [nodeId]
|
2025-12-11 18:03:51 +08:00
|
|
|
|
// deptTreeRef.value?.expand(nodeId, false)
|
2025-12-09 18:07:12 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleNodeClick = (data: InstrTreeNode): void => {
|
|
|
|
|
|
if (isFiltering.value) {
|
|
|
|
|
|
queryParams.value.groupId = data.id === "-1" ? undefined : data.id
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (data.children && data.children.length > 0) {
|
|
|
|
|
|
const isExpanded = expandedKeys.value.includes(data.id)
|
|
|
|
|
|
expandedKeys.value = isExpanded ? [] : [data.id]
|
|
|
|
|
|
!isExpanded && forceExpandSingleNode(data.id)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
queryParams.value.yq = data.id
|
2025-12-11 18:03:51 +08:00
|
|
|
|
yqmc.value = data.label
|
2025-12-09 18:07:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// ========== 检索过滤逻辑 ==========
|
2025-12-11 18:03:51 +08:00
|
|
|
|
const filterNode = (value: string, data: any, _node: TreeNode): boolean => {
|
2025-12-09 18:07:12 +08:00
|
|
|
|
if (!value) return true
|
|
|
|
|
|
|
|
|
|
|
|
const keyword = value.trim().toUpperCase()
|
|
|
|
|
|
const nodeLabel = data.label?.toLowerCase() || ''
|
|
|
|
|
|
const nodePinyinFirst = data.label ? getFirstLetter(data.label).toUpperCase() : ''
|
|
|
|
|
|
|
|
|
|
|
|
const currentMatch = nodeLabel.includes(keyword.toLowerCase()) || nodePinyinFirst.includes(keyword)
|
2025-12-11 18:03:51 +08:00
|
|
|
|
const childrenMatch = (data.children || []).some((child: any) => filterNode(value, child, _node))
|
2025-12-09 18:07:12 +08:00
|
|
|
|
return currentMatch || childrenMatch
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const debounceFilter = (() => {
|
|
|
|
|
|
let timer: number | undefined
|
|
|
|
|
|
return (val: string): void => {
|
|
|
|
|
|
clearTimeout(timer)
|
|
|
|
|
|
timer = setTimeout(() => {
|
|
|
|
|
|
isFiltering.value = true
|
|
|
|
|
|
deptTreeRef.value?.filter(val)
|
|
|
|
|
|
|
|
|
|
|
|
if (val) {
|
|
|
|
|
|
expandedKeys.value = []
|
|
|
|
|
|
const expandMatched = (nodes: InstrTreeNode[]) => {
|
|
|
|
|
|
nodes.forEach(node => {
|
2025-12-11 18:03:51 +08:00
|
|
|
|
if (filterNode(val, node, {} as TreeNode) && node.children?.length) {
|
2025-12-09 18:07:12 +08:00
|
|
|
|
expandedKeys.value.push(node.id)
|
|
|
|
|
|
}
|
|
|
|
|
|
node.children && expandMatched(node.children)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
expandMatched(deptOptions.value)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
expandedKeys.value = []
|
|
|
|
|
|
}
|
|
|
|
|
|
isFiltering.value = false
|
|
|
|
|
|
}, 300)
|
|
|
|
|
|
}
|
|
|
|
|
|
})()
|
|
|
|
|
|
|
|
|
|
|
|
watch(deptName, debounceFilter, { immediate: false })
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 业务逻辑 ==========
|
|
|
|
|
|
const getDeptTree = async (): Promise<void> => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await paramTree() as ParamTreeResponse
|
|
|
|
|
|
if (response.code === "0" && response.data) {
|
2025-12-11 18:03:51 +08:00
|
|
|
|
treeData.value = response.data
|
|
|
|
|
|
|
|
|
|
|
|
radioChange('1')
|
2025-12-09 18:07:12 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
ElMessage.warning(response.msg || "获取树数据失败")
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
ElMessage.error(`获取树数据异常:${(error as Error).message}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 初始化 ==========
|
|
|
|
|
|
getDeptTree()
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.app-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 90vh;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.main-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tree-col {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.tree-container {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 1px solid #e6e6e6;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
// 核心:树选中节点样式
|
|
|
|
|
|
:deep(.el-tree) {
|
|
|
|
|
|
--el-tree-node-transition-duration: 0.2s;
|
|
|
|
|
|
|
|
|
|
|
|
.el-tree-node__children {
|
|
|
|
|
|
transition: all var(--el-tree-node-transition-duration) ease-in-out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.el-tree-node__expand-icon.is-leaf {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 选中节点的背景色(蓝色)+ 文字白色
|
2025-12-11 18:03:51 +08:00
|
|
|
|
.el-tree-node.is-current>.el-tree-node__content {
|
2025-12-09 18:07:12 +08:00
|
|
|
|
background-color: #409eff !important;
|
|
|
|
|
|
color: #ffffff !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 选中节点hover样式(加深蓝色)
|
2025-12-11 18:03:51 +08:00
|
|
|
|
.el-tree-node.is-current>.el-tree-node__content:hover {
|
2025-12-09 18:07:12 +08:00
|
|
|
|
background-color: #1e88e5 !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 树节点内容容器样式
|
|
|
|
|
|
.tree-node-content {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 4px; // 徽章和文字间距
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 新节点徽章样式
|
|
|
|
|
|
.new-node-badge {
|
|
|
|
|
|
--el-badge-font-size: 10px !important;
|
|
|
|
|
|
--el-badge-height: 16px !important;
|
|
|
|
|
|
--el-badge-padding: 0 4px !important;
|
|
|
|
|
|
--el-badge-warning-color: #FFEC24 !important; // 黄色徽章
|
|
|
|
|
|
--el-badge-warning-bg-color: #fff7e6 !important; // 浅黄色背景
|
|
|
|
|
|
--el-badge-border-radius: 2px !important; // 圆角优化
|
|
|
|
|
|
|
|
|
|
|
|
// 徽章文字样式
|
|
|
|
|
|
.el-badge__content {
|
|
|
|
|
|
font-size: 10px !important;
|
|
|
|
|
|
line-height: 16px !important;
|
|
|
|
|
|
padding: 0 4px !important;
|
|
|
|
|
|
height: 16px !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.table-col {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
// 仪器标题行样式
|
|
|
|
|
|
.yq-title-row {
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
.yq-title {
|
|
|
|
|
|
font-size: 16px; // 缩小h1字体(默认24px→16px)
|
|
|
|
|
|
color: #409eff; // 蓝色(Element Plus主题色)
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
margin: 0; // 清除默认margin
|
|
|
|
|
|
line-height: 1.2;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|