mirror of
https://github.com/jumpserver/lina.git
synced 2026-01-13 19:35:24 +00:00
feat: 添加树的title显示模块 (#1975)
* feat: 树组件增加搜索功能 * feat: 添加插入节点 * feat: zTree组件增加搜索功能配置、创建根节点配置 * feat: 添加树的title显示模块 Co-authored-by: “huailei000” <2280131253@qq.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
<script>
|
||||
import DataZTree from '../DataZTree'
|
||||
import $ from '@/utils/jquery-vendor'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'AutoDataZTree',
|
||||
@@ -37,6 +38,10 @@ export default {
|
||||
showCreate: true,
|
||||
showDelete: true,
|
||||
showUpdate: true,
|
||||
showSearch: true,
|
||||
// 自定义header
|
||||
customTreeHeader: true,
|
||||
customTreeHeaderName: this.$t('assets.Asset'),
|
||||
async: {
|
||||
enable: true,
|
||||
url: (process.env.VUE_APP_ENV === 'production') ? (`${this.setting.treeUrl}`) : (`${process.env.VUE_APP_BASE_API}${this.setting.treeUrl}`),
|
||||
@@ -66,6 +71,9 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'currentOrg'
|
||||
]),
|
||||
treeSetting() {
|
||||
this.$log.debug('Settings: ', this.setting)
|
||||
return _.merge(this.defaultSetting, this.setting)
|
||||
@@ -192,7 +200,7 @@ export default {
|
||||
return
|
||||
}
|
||||
// 屏蔽收藏资产
|
||||
if (treeNode.id === '-12') {
|
||||
if (treeNode?.id === '-12') {
|
||||
return
|
||||
}
|
||||
if (!treeNode && event.target.tagName.toLowerCase() !== 'button' && $(event.target).parents('a').length === 0) {
|
||||
|
||||
@@ -1,15 +1,47 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul v-show="loading" class="ztree">
|
||||
{{ this.$t('common.tree.Loading') }}...
|
||||
</ul>
|
||||
<div v-show="!loading" class="treebox">
|
||||
<ul :id="iZTreeID" class="ztree">
|
||||
<div
|
||||
v-if="treeSetting.customTreeHeader"
|
||||
class="tree-header"
|
||||
>
|
||||
<div class="content">
|
||||
<span class="title">
|
||||
{{ treeSetting.customTreeHeaderName }}
|
||||
</span>
|
||||
<span class="tree-banner-icon-zone">
|
||||
<a id="searchIcon" class="tree-search special">
|
||||
<i
|
||||
class="fa fa-search tree-banner-icon"
|
||||
@click.stop="treeSearch"
|
||||
/>
|
||||
<input
|
||||
id="searchInput"
|
||||
v-model="treeSearchValue"
|
||||
type="text"
|
||||
class="tree-input"
|
||||
>
|
||||
</a>
|
||||
<i
|
||||
class="fa fa-refresh tree-banner-icon"
|
||||
style="margin-right: 2px;"
|
||||
@click.stop="refresh"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<ul v-show="loading" class="ztree">
|
||||
{{ this.$t('common.tree.Loading') }}...
|
||||
</ul>
|
||||
<div v-if="treeSetting.treeUrl===''">
|
||||
{{ this.$t('common.tree.Empty') }}<a id="tree-refresh"><i class="fa fa-refresh" /></a>
|
||||
</div>
|
||||
<ul v-show="!loading" :id="iZTreeID" class="ztree" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<ul v-show="loading" class="ztree">
|
||||
{{ this.$t('common.tree.Loading') }}...
|
||||
</ul>
|
||||
<ul v-show="!loading" :id="iZTreeID" class="ztree" />
|
||||
</div>
|
||||
<div v-if="treeSetting.treeUrl===''">
|
||||
{{ this.$t('common.tree.Empty') }}
|
||||
<a id="tree-refresh"><i class="fa fa-refresh" /></a>
|
||||
</div>
|
||||
<div :id="iRMenuID" class="rMenu">
|
||||
<ul class="dropdown-menu menu-actions">
|
||||
@@ -24,6 +56,7 @@
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import $ from '@/utils/jquery-vendor.js'
|
||||
import '@ztree/ztree_v3/js/jquery.ztree.all.min.js'
|
||||
import '@ztree/ztree_v3/js/jquery.ztree.exhide.min.js'
|
||||
import '@/styles/ztree.css'
|
||||
import axiosRetry from 'axios-retry'
|
||||
|
||||
@@ -45,7 +78,8 @@ export default {
|
||||
zTree: '',
|
||||
rMenu: '',
|
||||
init: false,
|
||||
loading: false
|
||||
loading: false,
|
||||
treeSearchValue: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -54,8 +88,9 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.refresh = this.refresh
|
||||
window.treeSearch = this.treeSearch
|
||||
this.initTree()
|
||||
// $('.treebox').css('height', window.innerHeight - 60)
|
||||
},
|
||||
beforeDestroy() {
|
||||
$.fn.zTree.destroy(this.iZTreeID)
|
||||
@@ -82,9 +117,7 @@ export default {
|
||||
retryDelay: () => { return 5000 }
|
||||
}
|
||||
}).then(res => {
|
||||
if (!res) {
|
||||
res = []
|
||||
}
|
||||
if (!res) res = []
|
||||
if (res.length === 0) {
|
||||
res.push({
|
||||
name: this.$t('common.tree.Empty')
|
||||
@@ -94,15 +127,13 @@ export default {
|
||||
if (this.init) {
|
||||
vm.zTree.destroy()
|
||||
}
|
||||
|
||||
this.zTree = $.fn.zTree.init($(`#${this.iZTreeID}`), this.treeSetting, res)
|
||||
if (!this.treeSetting.customTreeHeader) {
|
||||
this.rootNodeAddDom(this.zTree)
|
||||
}
|
||||
// 手动上报事件, Tree加载完成
|
||||
this.$emit('TreeInitFinish', this.zTree)
|
||||
if (this.treeSetting.showRefresh) {
|
||||
this.rootNodeAddDom(
|
||||
this.zTree,
|
||||
this.treeSetting.callback.refresh
|
||||
)
|
||||
}
|
||||
|
||||
if (this.treeSetting.showMenu) {
|
||||
this.rMenu = $(`#${this.iRMenuID}`)
|
||||
@@ -115,43 +146,186 @@ export default {
|
||||
vm.init = true
|
||||
})
|
||||
},
|
||||
rootNodeAddDom: function(ztree, callback) {
|
||||
const vm = this
|
||||
const refreshIcon = "<a id='tree-refresh'><i class='fa fa-refresh'></i></a>"
|
||||
rootNodeAddDom(ztree) {
|
||||
const { showSearch, showRefresh } = this.treeSetting
|
||||
const searchIcon = `<a class="tree-search" id="searchIcon">
|
||||
<i class='fa fa-search tree-banner-icon' onclick="treeSearch()" /></i>
|
||||
<input type="text" id="searchInput" class="tree-input" />
|
||||
</a>`
|
||||
const refreshIcon = "<a id='tree-refresh' onclick='refresh()'><i class='fa fa-refresh'></i></a>"
|
||||
const treeActions = `${showSearch ? searchIcon : ''}${showRefresh ? refreshIcon : ''}`
|
||||
const icons = `<span class="">${treeActions}</span>`
|
||||
const rootNode = ztree.getNodes()[0]
|
||||
let $rootNodeRef
|
||||
if (rootNode) {
|
||||
$rootNodeRef = $('#' + rootNode.tId + '_a')
|
||||
$rootNodeRef.after(refreshIcon)
|
||||
} else {
|
||||
$rootNodeRef = $('#' + ztree.setting.treeId)
|
||||
$rootNodeRef.html(refreshIcon)
|
||||
const $rootNodeRef = $('#' + rootNode.tId + '_a')
|
||||
$rootNodeRef.after(icons)
|
||||
}
|
||||
const refreshIconRef = $('#tree-refresh')
|
||||
refreshIconRef.bind('click', function() {
|
||||
const result = callback()
|
||||
if (result && result.then) {
|
||||
result.finally(() => {
|
||||
vm.initTree()
|
||||
})
|
||||
} else {
|
||||
vm.initTree()
|
||||
}
|
||||
})
|
||||
},
|
||||
refresh: function() {
|
||||
const refreshIconRef = $('#tree-refresh')
|
||||
refreshIconRef.click()
|
||||
refresh() {
|
||||
this.treeSearchValue = ''
|
||||
const result = this.treeSetting?.callback?.refresh()
|
||||
if (result && result.then) {
|
||||
result.finally(() => {
|
||||
this.initTree()
|
||||
})
|
||||
} else {
|
||||
this.initTree()
|
||||
}
|
||||
},
|
||||
treeSearch() {
|
||||
const searchIcon = document.getElementById(`searchIcon`)
|
||||
const searchInput = document.getElementById(`searchInput`)
|
||||
searchIcon.classList.toggle('active')
|
||||
searchInput.focus()
|
||||
searchInput.onclick = (e) => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
searchInput.onblur = (e) => {
|
||||
e.stopPropagation()
|
||||
if (!(e.target.value)) {
|
||||
searchIcon.classList.toggle('active')
|
||||
}
|
||||
}
|
||||
searchInput.oninput = _.debounce((e) => {
|
||||
e.stopPropagation()
|
||||
const value = e.target.value || ''
|
||||
this.filterAssetsServer(value)
|
||||
}, 700)
|
||||
},
|
||||
getCheckedNodes: function() {
|
||||
return this.zTree.getCheckedNodes(true)
|
||||
},
|
||||
recurseParent(node) {
|
||||
const parentNode = node.getParentNode()
|
||||
if (parentNode && parentNode.pId) {
|
||||
return [parentNode, ...this.recurseParent(parentNode)]
|
||||
} else if (parentNode) {
|
||||
return [parentNode]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
},
|
||||
recurseChildren(node) {
|
||||
if (!node.isParent) {
|
||||
return []
|
||||
}
|
||||
const children = node.children
|
||||
if (!children) {
|
||||
return []
|
||||
}
|
||||
let allChildren = []
|
||||
children.forEach((n) => {
|
||||
allChildren = [...children, ...this.recurseChildren(n)]
|
||||
})
|
||||
return allChildren
|
||||
},
|
||||
groupBy(array, filter) {
|
||||
const groups = {}
|
||||
array.forEach(function(o) {
|
||||
const group = JSON.stringify(filter(o))
|
||||
groups[group] = groups[group] || []
|
||||
groups[group].push(o)
|
||||
})
|
||||
return Object.keys(groups).map(function(group) {
|
||||
return groups[group]
|
||||
})
|
||||
},
|
||||
filterTree(keyword, tree) {
|
||||
const searchNode = tree.getNodesByFilter((node) => node.id === 'search')
|
||||
if (searchNode) tree.removeNode(searchNode[0])
|
||||
const nodes = tree.transformToArray(tree.getNodes())
|
||||
if (!keyword) {
|
||||
tree.showNodes(nodes)
|
||||
return
|
||||
}
|
||||
|
||||
if (!keyword) {
|
||||
if (tree.hiddenNodes) {
|
||||
tree.showNodes(tree.hiddenNodes)
|
||||
tree.hiddenNodes = null
|
||||
}
|
||||
if (tree.expandNodes) {
|
||||
tree.expandNodes.forEach((node) => {
|
||||
if (node.id !== nodes[0].id) {
|
||||
tree.expandNode(node, false)
|
||||
}
|
||||
})
|
||||
tree.expandNodes = null
|
||||
}
|
||||
return null
|
||||
}
|
||||
let shouldShow = []
|
||||
const matchedNodes = tree.getNodesByFilter((node) => {
|
||||
return node.name.toLowerCase().indexOf(keyword.toLowerCase()) > -1
|
||||
})
|
||||
|
||||
if (matchedNodes.length < 1) {
|
||||
let name = this.$t('common.Search')
|
||||
const assetsAmount = matchedNodes.length
|
||||
name = `${name} (${assetsAmount})`
|
||||
const newNode = { id: 'search', name: name, isParent: false, open: false }
|
||||
tree.addNodes(null, newNode)
|
||||
}
|
||||
|
||||
matchedNodes.forEach((node) => {
|
||||
const parents = this.recurseParent(node)
|
||||
const children = this.recurseChildren(node)
|
||||
shouldShow = [...shouldShow, ...parents, ...children, node]
|
||||
})
|
||||
|
||||
tree.hiddenNodes = nodes
|
||||
tree.expandNodes = shouldShow
|
||||
tree.hideNodes(nodes)
|
||||
tree.showNodes(shouldShow)
|
||||
for (const node of shouldShow) {
|
||||
if (node.isParent) {
|
||||
tree.expandNode(node, true)
|
||||
}
|
||||
}
|
||||
},
|
||||
filterAssetsServer(keyword) {
|
||||
if (!this.zTree) return
|
||||
let searchNode = this.zTree.getNodesByFilter((node) => node.id === 'search')
|
||||
if (searchNode) {
|
||||
this.zTree.removeChildNodes(searchNode[0])
|
||||
this.zTree.removeNode(searchNode[0])
|
||||
}
|
||||
const treeNodes = this.zTree.getNodes()
|
||||
if (!keyword) {
|
||||
if (treeNodes.length !== 0) {
|
||||
this.zTree.showNodes(treeNodes)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (treeNodes.length !== 0) {
|
||||
this.zTree.hideNodes(treeNodes)
|
||||
}
|
||||
|
||||
const searchUrl = `/api/v1/assets/nodes/children/tree/?search=${keyword}&all=all`
|
||||
this.$axios.get(searchUrl).then(nodes => {
|
||||
let name = this.$t('common.Search')
|
||||
const assetsAmount = nodes.length
|
||||
name = `${name} (${assetsAmount})`
|
||||
const newNode = { id: 'search', name: name, isParent: true, open: true, zAsync: true }
|
||||
searchNode = this.zTree.addNodes(null, newNode)[0]
|
||||
searchNode.zAsync = true
|
||||
const nodesGroupByOrg = this.groupBy(nodes, (node) => {
|
||||
return node.meta.data.org_name
|
||||
})
|
||||
|
||||
for (const item of nodesGroupByOrg) {
|
||||
this.zTree.addNodes(searchNode, item)
|
||||
}
|
||||
searchNode.open = true
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='less' scoped>
|
||||
<style lang='scss' scoped>
|
||||
div.rMenu {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
@@ -191,7 +365,7 @@ export default {
|
||||
top: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
.ztree ::v-deep .fa-refresh {
|
||||
.ztree ::v-deep .fa {
|
||||
font: normal normal normal 14px/1 FontAwesome !important;
|
||||
}
|
||||
.dropdown a:hover {
|
||||
@@ -218,4 +392,103 @@ export default {
|
||||
height: 80vh;
|
||||
overflow: auto;
|
||||
}
|
||||
::v-deep #tree-refresh {
|
||||
margin-left: 3px;
|
||||
}
|
||||
::v-deep .tree-banner-icon-zone {
|
||||
position: absolute;
|
||||
right: 7px;
|
||||
height: 30px;
|
||||
overflow: hidden;
|
||||
.fa {
|
||||
color: #838385!important;;
|
||||
&:hover {
|
||||
color: #606266!important;;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .tree-search {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
border-radius: 12px;
|
||||
vertical-align: sub;
|
||||
transition: .25s;
|
||||
overflow: hidden;
|
||||
.fa {
|
||||
width: 13px!important;
|
||||
}
|
||||
.fa-search {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .tree-search .tree-banner-icon {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 6px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 12px;
|
||||
padding: 10px 6px;
|
||||
overflow: hidden;
|
||||
background-color: transparent!important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::v-deep .tree-search.active {
|
||||
width: 160px;
|
||||
background-color: #ffffff!important;
|
||||
}
|
||||
|
||||
::v-deep .tree-search.active:hover {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
::v-deep .tree-search input {
|
||||
position: relative;
|
||||
left: 20px;
|
||||
width: 133px;
|
||||
height: 100%;
|
||||
background-color: #ffffff!important;
|
||||
color: #606266;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
.tree-header {
|
||||
position: relative;
|
||||
.title {
|
||||
font-weight: 500;
|
||||
}
|
||||
.content {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
border-radius: 3px;
|
||||
padding: 0 5px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background-color: #D7D8DC;
|
||||
.rotate {
|
||||
transition: all .1.8s;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.fa-caret-down {
|
||||
font-size: 16px;
|
||||
}
|
||||
.special {
|
||||
top: 1px!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -119,6 +119,7 @@ export default {
|
||||
border-radius: 3px;
|
||||
line-height: 1.428;
|
||||
cursor:pointer;
|
||||
height: 30px;
|
||||
}
|
||||
.el-tree{
|
||||
background-color: inherit !important;
|
||||
@@ -134,5 +135,8 @@ export default {
|
||||
}
|
||||
.transition-box.left {
|
||||
background: #f3f3f3;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 3px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -52,6 +52,8 @@ export default {
|
||||
showAssets: false,
|
||||
showMenu: false,
|
||||
showRefresh: true,
|
||||
showSearch: false,
|
||||
customTreeHeader: false,
|
||||
treeUrl: '',
|
||||
check: {
|
||||
enable: true
|
||||
|
||||
Reference in New Issue
Block a user