mirror of https://github.com/veops/cmdb.git
Merge pull request #609 from veops/dev_ui_240903
feat: update resource search
This commit is contained in:
commit
d3080bad3c
|
@ -54,93 +54,171 @@
|
|||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-expand</div>
|
||||
<div class="code-name">&#xe9b6;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">公有云</div>
|
||||
<div class="code-name">&#xe9b1;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">操作系统</div>
|
||||
<div class="code-name">&#xe9b2;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">IPAM</div>
|
||||
<div class="code-name">&#xe9b3;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">hyperV</div>
|
||||
<div class="code-name">&#xe9b4;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">数据中心</div>
|
||||
<div class="code-name">&#xe9b5;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">硬件设备</div>
|
||||
<div class="code-name">&#xe9ad;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">计算机</div>
|
||||
<div class="code-name">&#xe9ae;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">网络设备</div>
|
||||
<div class="code-name">&#xe9af;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">存储设备</div>
|
||||
<div class="code-name">&#xe9b0;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">负载均衡</div>
|
||||
<div class="code-name">&#xe9ab;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">消息队列</div>
|
||||
<div class="code-name">&#xe9ac;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">websever</div>
|
||||
<div class="code-name">&#xe9aa;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise-middleware</div>
|
||||
<div class="name">中间件</div>
|
||||
<div class="code-name">&#xe9a9;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise-database</div>
|
||||
<div class="name">数据库</div>
|
||||
<div class="code-name">&#xe9a7;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise-business</div>
|
||||
<div class="name">业务</div>
|
||||
<div class="code-name">&#xe9a8;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise- virtualization</div>
|
||||
<div class="name">虚拟化</div>
|
||||
<div class="code-name">&#xe9a6;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise-storage_pool</div>
|
||||
<div class="name">存储池</div>
|
||||
<div class="code-name">&#xe9a4;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise-storage_volume</div>
|
||||
<div class="name">存储卷</div>
|
||||
<div class="code-name">&#xe9a5;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ciase-aix</div>
|
||||
<div class="name">aix</div>
|
||||
<div class="code-name">&#xe9a3;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise_pool</div>
|
||||
<div class="name">ip池</div>
|
||||
<div class="code-name">&#xe99b;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise-ip_address</div>
|
||||
<div class="name">ip地址</div>
|
||||
<div class="code-name">&#xe99c;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise-computer_room</div>
|
||||
<div class="name">机房</div>
|
||||
<div class="code-name">&#xe99d;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise-rack</div>
|
||||
<div class="name">机柜</div>
|
||||
<div class="code-name">&#xe99e;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise-pc</div>
|
||||
<div class="name">PC</div>
|
||||
<div class="code-name">&#xe99f;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise-bandwidth_line</div>
|
||||
<div class="name">带宽线路</div>
|
||||
<div class="code-name">&#xe9a0;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise-fiber</div>
|
||||
<div class="name">光纤交换机</div>
|
||||
<div class="code-name">&#xe9a1;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">caise-disk_array</div>
|
||||
<div class="name">磁盘阵列</div>
|
||||
<div class="code-name">&#xe9a2;</div>
|
||||
</li>
|
||||
|
||||
|
@ -5622,9 +5700,9 @@
|
|||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.woff2?t=1724834571283') format('woff2'),
|
||||
url('iconfont.woff?t=1724834571283') format('woff'),
|
||||
url('iconfont.ttf?t=1724834571283') format('truetype');
|
||||
src: url('iconfont.woff2?t=1725331691589') format('woff2'),
|
||||
url('iconfont.woff?t=1725331691589') format('woff'),
|
||||
url('iconfont.ttf?t=1725331691589') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
|
@ -5650,10 +5728,127 @@
|
|||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-expand"></span>
|
||||
<div class="name">
|
||||
veops-expand
|
||||
</div>
|
||||
<div class="code-name">.veops-expand
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-public_cloud"></span>
|
||||
<div class="name">
|
||||
公有云
|
||||
</div>
|
||||
<div class="code-name">.caise-public_cloud
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-system"></span>
|
||||
<div class="name">
|
||||
操作系统
|
||||
</div>
|
||||
<div class="code-name">.caise-system
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-IPAM"></span>
|
||||
<div class="name">
|
||||
IPAM
|
||||
</div>
|
||||
<div class="code-name">.caise-IPAM
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-hyperV"></span>
|
||||
<div class="name">
|
||||
hyperV
|
||||
</div>
|
||||
<div class="code-name">.caise-hyperV
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-data_center2"></span>
|
||||
<div class="name">
|
||||
数据中心
|
||||
</div>
|
||||
<div class="code-name">.caise-data_center2
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-hardware"></span>
|
||||
<div class="name">
|
||||
硬件设备
|
||||
</div>
|
||||
<div class="code-name">.caise-hardware
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-computer"></span>
|
||||
<div class="name">
|
||||
计算机
|
||||
</div>
|
||||
<div class="code-name">.caise-computer
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-network_devices"></span>
|
||||
<div class="name">
|
||||
网络设备
|
||||
</div>
|
||||
<div class="code-name">.caise-network_devices
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-storage_device"></span>
|
||||
<div class="name">
|
||||
存储设备
|
||||
</div>
|
||||
<div class="code-name">.caise-storage_device
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-load_balancing"></span>
|
||||
<div class="name">
|
||||
负载均衡
|
||||
</div>
|
||||
<div class="code-name">.caise-load_balancing
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-message_queue"></span>
|
||||
<div class="name">
|
||||
消息队列
|
||||
</div>
|
||||
<div class="code-name">.caise-message_queue
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-websever"></span>
|
||||
<div class="name">
|
||||
websever
|
||||
</div>
|
||||
<div class="code-name">.caise-websever
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-middleware"></span>
|
||||
<div class="name">
|
||||
caise-middleware
|
||||
中间件
|
||||
</div>
|
||||
<div class="code-name">.caise-middleware
|
||||
</div>
|
||||
|
@ -5662,7 +5857,7 @@
|
|||
<li class="dib">
|
||||
<span class="icon iconfont caise-database"></span>
|
||||
<div class="name">
|
||||
caise-database
|
||||
数据库
|
||||
</div>
|
||||
<div class="code-name">.caise-database
|
||||
</div>
|
||||
|
@ -5671,43 +5866,43 @@
|
|||
<li class="dib">
|
||||
<span class="icon iconfont caise-business"></span>
|
||||
<div class="name">
|
||||
caise-business
|
||||
业务
|
||||
</div>
|
||||
<div class="code-name">.caise-business
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont a-caise-virtualization"></span>
|
||||
<span class="icon iconfont caise-virtualization"></span>
|
||||
<div class="name">
|
||||
caise- virtualization
|
||||
虚拟化
|
||||
</div>
|
||||
<div class="code-name">.a-caise-virtualization
|
||||
<div class="code-name">.caise-virtualization
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont caise-storage_pool"></span>
|
||||
<div class="name">
|
||||
caise-storage_pool
|
||||
存储池
|
||||
</div>
|
||||
<div class="code-name">.caise-storage_pool
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont a-caise-storage_volume1"></span>
|
||||
<span class="icon iconfont caise-storage_volume1"></span>
|
||||
<div class="name">
|
||||
caise-storage_volume
|
||||
存储卷
|
||||
</div>
|
||||
<div class="code-name">.a-caise-storage_volume1
|
||||
<div class="code-name">.caise-storage_volume1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont ciase-aix"></span>
|
||||
<div class="name">
|
||||
ciase-aix
|
||||
aix
|
||||
</div>
|
||||
<div class="code-name">.ciase-aix
|
||||
</div>
|
||||
|
@ -5716,7 +5911,7 @@
|
|||
<li class="dib">
|
||||
<span class="icon iconfont caise_pool"></span>
|
||||
<div class="name">
|
||||
caise_pool
|
||||
ip池
|
||||
</div>
|
||||
<div class="code-name">.caise_pool
|
||||
</div>
|
||||
|
@ -5725,7 +5920,7 @@
|
|||
<li class="dib">
|
||||
<span class="icon iconfont caise-ip_address"></span>
|
||||
<div class="name">
|
||||
caise-ip_address
|
||||
ip地址
|
||||
</div>
|
||||
<div class="code-name">.caise-ip_address
|
||||
</div>
|
||||
|
@ -5734,7 +5929,7 @@
|
|||
<li class="dib">
|
||||
<span class="icon iconfont caise-computer_room"></span>
|
||||
<div class="name">
|
||||
caise-computer_room
|
||||
机房
|
||||
</div>
|
||||
<div class="code-name">.caise-computer_room
|
||||
</div>
|
||||
|
@ -5743,7 +5938,7 @@
|
|||
<li class="dib">
|
||||
<span class="icon iconfont caise-rack"></span>
|
||||
<div class="name">
|
||||
caise-rack
|
||||
机柜
|
||||
</div>
|
||||
<div class="code-name">.caise-rack
|
||||
</div>
|
||||
|
@ -5752,7 +5947,7 @@
|
|||
<li class="dib">
|
||||
<span class="icon iconfont caise-pc"></span>
|
||||
<div class="name">
|
||||
caise-pc
|
||||
PC
|
||||
</div>
|
||||
<div class="code-name">.caise-pc
|
||||
</div>
|
||||
|
@ -5761,7 +5956,7 @@
|
|||
<li class="dib">
|
||||
<span class="icon iconfont caise-bandwidth_line"></span>
|
||||
<div class="name">
|
||||
caise-bandwidth_line
|
||||
带宽线路
|
||||
</div>
|
||||
<div class="code-name">.caise-bandwidth_line
|
||||
</div>
|
||||
|
@ -5770,7 +5965,7 @@
|
|||
<li class="dib">
|
||||
<span class="icon iconfont caise-fiber"></span>
|
||||
<div class="name">
|
||||
caise-fiber
|
||||
光纤交换机
|
||||
</div>
|
||||
<div class="code-name">.caise-fiber
|
||||
</div>
|
||||
|
@ -5779,7 +5974,7 @@
|
|||
<li class="dib">
|
||||
<span class="icon iconfont caise-disk_array"></span>
|
||||
<div class="name">
|
||||
caise-disk_array
|
||||
磁盘阵列
|
||||
</div>
|
||||
<div class="code-name">.caise-disk_array
|
||||
</div>
|
||||
|
@ -14002,11 +14197,115 @@
|
|||
<div class="content symbol">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-expand"></use>
|
||||
</svg>
|
||||
<div class="name">veops-expand</div>
|
||||
<div class="code-name">#veops-expand</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-public_cloud"></use>
|
||||
</svg>
|
||||
<div class="name">公有云</div>
|
||||
<div class="code-name">#caise-public_cloud</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-system"></use>
|
||||
</svg>
|
||||
<div class="name">操作系统</div>
|
||||
<div class="code-name">#caise-system</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-IPAM"></use>
|
||||
</svg>
|
||||
<div class="name">IPAM</div>
|
||||
<div class="code-name">#caise-IPAM</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-hyperV"></use>
|
||||
</svg>
|
||||
<div class="name">hyperV</div>
|
||||
<div class="code-name">#caise-hyperV</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-data_center2"></use>
|
||||
</svg>
|
||||
<div class="name">数据中心</div>
|
||||
<div class="code-name">#caise-data_center2</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-hardware"></use>
|
||||
</svg>
|
||||
<div class="name">硬件设备</div>
|
||||
<div class="code-name">#caise-hardware</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-computer"></use>
|
||||
</svg>
|
||||
<div class="name">计算机</div>
|
||||
<div class="code-name">#caise-computer</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-network_devices"></use>
|
||||
</svg>
|
||||
<div class="name">网络设备</div>
|
||||
<div class="code-name">#caise-network_devices</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-storage_device"></use>
|
||||
</svg>
|
||||
<div class="name">存储设备</div>
|
||||
<div class="code-name">#caise-storage_device</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-load_balancing"></use>
|
||||
</svg>
|
||||
<div class="name">负载均衡</div>
|
||||
<div class="code-name">#caise-load_balancing</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-message_queue"></use>
|
||||
</svg>
|
||||
<div class="name">消息队列</div>
|
||||
<div class="code-name">#caise-message_queue</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-websever"></use>
|
||||
</svg>
|
||||
<div class="name">websever</div>
|
||||
<div class="code-name">#caise-websever</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-middleware"></use>
|
||||
</svg>
|
||||
<div class="name">caise-middleware</div>
|
||||
<div class="name">中间件</div>
|
||||
<div class="code-name">#caise-middleware</div>
|
||||
</li>
|
||||
|
||||
|
@ -14014,7 +14313,7 @@
|
|||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-database"></use>
|
||||
</svg>
|
||||
<div class="name">caise-database</div>
|
||||
<div class="name">数据库</div>
|
||||
<div class="code-name">#caise-database</div>
|
||||
</li>
|
||||
|
||||
|
@ -14022,39 +14321,39 @@
|
|||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-business"></use>
|
||||
</svg>
|
||||
<div class="name">caise-business</div>
|
||||
<div class="name">业务</div>
|
||||
<div class="code-name">#caise-business</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#a-caise-virtualization"></use>
|
||||
<use xlink:href="#caise-virtualization"></use>
|
||||
</svg>
|
||||
<div class="name">caise- virtualization</div>
|
||||
<div class="code-name">#a-caise-virtualization</div>
|
||||
<div class="name">虚拟化</div>
|
||||
<div class="code-name">#caise-virtualization</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-storage_pool"></use>
|
||||
</svg>
|
||||
<div class="name">caise-storage_pool</div>
|
||||
<div class="name">存储池</div>
|
||||
<div class="code-name">#caise-storage_pool</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#a-caise-storage_volume1"></use>
|
||||
<use xlink:href="#caise-storage_volume1"></use>
|
||||
</svg>
|
||||
<div class="name">caise-storage_volume</div>
|
||||
<div class="code-name">#a-caise-storage_volume1</div>
|
||||
<div class="name">存储卷</div>
|
||||
<div class="code-name">#caise-storage_volume1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#ciase-aix"></use>
|
||||
</svg>
|
||||
<div class="name">ciase-aix</div>
|
||||
<div class="name">aix</div>
|
||||
<div class="code-name">#ciase-aix</div>
|
||||
</li>
|
||||
|
||||
|
@ -14062,7 +14361,7 @@
|
|||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise_pool"></use>
|
||||
</svg>
|
||||
<div class="name">caise_pool</div>
|
||||
<div class="name">ip池</div>
|
||||
<div class="code-name">#caise_pool</div>
|
||||
</li>
|
||||
|
||||
|
@ -14070,7 +14369,7 @@
|
|||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-ip_address"></use>
|
||||
</svg>
|
||||
<div class="name">caise-ip_address</div>
|
||||
<div class="name">ip地址</div>
|
||||
<div class="code-name">#caise-ip_address</div>
|
||||
</li>
|
||||
|
||||
|
@ -14078,7 +14377,7 @@
|
|||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-computer_room"></use>
|
||||
</svg>
|
||||
<div class="name">caise-computer_room</div>
|
||||
<div class="name">机房</div>
|
||||
<div class="code-name">#caise-computer_room</div>
|
||||
</li>
|
||||
|
||||
|
@ -14086,7 +14385,7 @@
|
|||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-rack"></use>
|
||||
</svg>
|
||||
<div class="name">caise-rack</div>
|
||||
<div class="name">机柜</div>
|
||||
<div class="code-name">#caise-rack</div>
|
||||
</li>
|
||||
|
||||
|
@ -14094,7 +14393,7 @@
|
|||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-pc"></use>
|
||||
</svg>
|
||||
<div class="name">caise-pc</div>
|
||||
<div class="name">PC</div>
|
||||
<div class="code-name">#caise-pc</div>
|
||||
</li>
|
||||
|
||||
|
@ -14102,7 +14401,7 @@
|
|||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-bandwidth_line"></use>
|
||||
</svg>
|
||||
<div class="name">caise-bandwidth_line</div>
|
||||
<div class="name">带宽线路</div>
|
||||
<div class="code-name">#caise-bandwidth_line</div>
|
||||
</li>
|
||||
|
||||
|
@ -14110,7 +14409,7 @@
|
|||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-fiber"></use>
|
||||
</svg>
|
||||
<div class="name">caise-fiber</div>
|
||||
<div class="name">光纤交换机</div>
|
||||
<div class="code-name">#caise-fiber</div>
|
||||
</li>
|
||||
|
||||
|
@ -14118,7 +14417,7 @@
|
|||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#caise-disk_array"></use>
|
||||
</svg>
|
||||
<div class="name">caise-disk_array</div>
|
||||
<div class="name">磁盘阵列</div>
|
||||
<div class="code-name">#caise-disk_array</div>
|
||||
</li>
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1724834571283') format('woff2'),
|
||||
url('iconfont.woff?t=1724834571283') format('woff'),
|
||||
url('iconfont.ttf?t=1724834571283') format('truetype');
|
||||
src: url('iconfont.woff2?t=1725331691589') format('woff2'),
|
||||
url('iconfont.woff?t=1725331691589') format('woff'),
|
||||
url('iconfont.ttf?t=1725331691589') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
@ -13,6 +13,58 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.veops-expand:before {
|
||||
content: "\e9b6";
|
||||
}
|
||||
|
||||
.caise-public_cloud:before {
|
||||
content: "\e9b1";
|
||||
}
|
||||
|
||||
.caise-system:before {
|
||||
content: "\e9b2";
|
||||
}
|
||||
|
||||
.caise-IPAM:before {
|
||||
content: "\e9b3";
|
||||
}
|
||||
|
||||
.caise-hyperV:before {
|
||||
content: "\e9b4";
|
||||
}
|
||||
|
||||
.caise-data_center2:before {
|
||||
content: "\e9b5";
|
||||
}
|
||||
|
||||
.caise-hardware:before {
|
||||
content: "\e9ad";
|
||||
}
|
||||
|
||||
.caise-computer:before {
|
||||
content: "\e9ae";
|
||||
}
|
||||
|
||||
.caise-network_devices:before {
|
||||
content: "\e9af";
|
||||
}
|
||||
|
||||
.caise-storage_device:before {
|
||||
content: "\e9b0";
|
||||
}
|
||||
|
||||
.caise-load_balancing:before {
|
||||
content: "\e9ab";
|
||||
}
|
||||
|
||||
.caise-message_queue:before {
|
||||
content: "\e9ac";
|
||||
}
|
||||
|
||||
.caise-websever:before {
|
||||
content: "\e9aa";
|
||||
}
|
||||
|
||||
.caise-middleware:before {
|
||||
content: "\e9a9";
|
||||
}
|
||||
|
@ -25,7 +77,7 @@
|
|||
content: "\e9a8";
|
||||
}
|
||||
|
||||
.a-caise-virtualization:before {
|
||||
.caise-virtualization:before {
|
||||
content: "\e9a6";
|
||||
}
|
||||
|
||||
|
@ -33,7 +85,7 @@
|
|||
content: "\e9a4";
|
||||
}
|
||||
|
||||
.a-caise-storage_volume1:before {
|
||||
.caise-storage_volume1:before {
|
||||
content: "\e9a5";
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,107 +5,198 @@
|
|||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "41681675",
|
||||
"name": "veops-expand",
|
||||
"font_class": "veops-expand",
|
||||
"unicode": "e9b6",
|
||||
"unicode_decimal": 59830
|
||||
},
|
||||
{
|
||||
"icon_id": "41672951",
|
||||
"name": "公有云",
|
||||
"font_class": "caise-public_cloud",
|
||||
"unicode": "e9b1",
|
||||
"unicode_decimal": 59825
|
||||
},
|
||||
{
|
||||
"icon_id": "41672952",
|
||||
"name": "操作系统",
|
||||
"font_class": "caise-system",
|
||||
"unicode": "e9b2",
|
||||
"unicode_decimal": 59826
|
||||
},
|
||||
{
|
||||
"icon_id": "41673309",
|
||||
"name": "IPAM",
|
||||
"font_class": "caise-IPAM",
|
||||
"unicode": "e9b3",
|
||||
"unicode_decimal": 59827
|
||||
},
|
||||
{
|
||||
"icon_id": "41673312",
|
||||
"name": "hyperV",
|
||||
"font_class": "caise-hyperV",
|
||||
"unicode": "e9b4",
|
||||
"unicode_decimal": 59828
|
||||
},
|
||||
{
|
||||
"icon_id": "41673320",
|
||||
"name": "数据中心",
|
||||
"font_class": "caise-data_center2",
|
||||
"unicode": "e9b5",
|
||||
"unicode_decimal": 59829
|
||||
},
|
||||
{
|
||||
"icon_id": "41669141",
|
||||
"name": "硬件设备",
|
||||
"font_class": "caise-hardware",
|
||||
"unicode": "e9ad",
|
||||
"unicode_decimal": 59821
|
||||
},
|
||||
{
|
||||
"icon_id": "41669249",
|
||||
"name": "计算机",
|
||||
"font_class": "caise-computer",
|
||||
"unicode": "e9ae",
|
||||
"unicode_decimal": 59822
|
||||
},
|
||||
{
|
||||
"icon_id": "41669250",
|
||||
"name": "网络设备",
|
||||
"font_class": "caise-network_devices",
|
||||
"unicode": "e9af",
|
||||
"unicode_decimal": 59823
|
||||
},
|
||||
{
|
||||
"icon_id": "41669278",
|
||||
"name": "存储设备",
|
||||
"font_class": "caise-storage_device",
|
||||
"unicode": "e9b0",
|
||||
"unicode_decimal": 59824
|
||||
},
|
||||
{
|
||||
"icon_id": "41659452",
|
||||
"name": "负载均衡",
|
||||
"font_class": "caise-load_balancing",
|
||||
"unicode": "e9ab",
|
||||
"unicode_decimal": 59819
|
||||
},
|
||||
{
|
||||
"icon_id": "41659446",
|
||||
"name": "消息队列",
|
||||
"font_class": "caise-message_queue",
|
||||
"unicode": "e9ac",
|
||||
"unicode_decimal": 59820
|
||||
},
|
||||
{
|
||||
"icon_id": "41659424",
|
||||
"name": "websever",
|
||||
"font_class": "caise-websever",
|
||||
"unicode": "e9aa",
|
||||
"unicode_decimal": 59818
|
||||
},
|
||||
{
|
||||
"icon_id": "41655608",
|
||||
"name": "caise-middleware",
|
||||
"name": "中间件",
|
||||
"font_class": "caise-middleware",
|
||||
"unicode": "e9a9",
|
||||
"unicode_decimal": 59817
|
||||
},
|
||||
{
|
||||
"icon_id": "41655599",
|
||||
"name": "caise-database",
|
||||
"name": "数据库",
|
||||
"font_class": "caise-database",
|
||||
"unicode": "e9a7",
|
||||
"unicode_decimal": 59815
|
||||
},
|
||||
{
|
||||
"icon_id": "41655591",
|
||||
"name": "caise-business",
|
||||
"name": "业务",
|
||||
"font_class": "caise-business",
|
||||
"unicode": "e9a8",
|
||||
"unicode_decimal": 59816
|
||||
},
|
||||
{
|
||||
"icon_id": "41655550",
|
||||
"name": "caise- virtualization",
|
||||
"font_class": "a-caise-virtualization",
|
||||
"name": "虚拟化",
|
||||
"font_class": "caise-virtualization",
|
||||
"unicode": "e9a6",
|
||||
"unicode_decimal": 59814
|
||||
},
|
||||
{
|
||||
"icon_id": "41654680",
|
||||
"name": "caise-storage_pool",
|
||||
"name": "存储池",
|
||||
"font_class": "caise-storage_pool",
|
||||
"unicode": "e9a4",
|
||||
"unicode_decimal": 59812
|
||||
},
|
||||
{
|
||||
"icon_id": "41654676",
|
||||
"name": "caise-storage_volume",
|
||||
"font_class": "a-caise-storage_volume1",
|
||||
"name": "存储卷",
|
||||
"font_class": "caise-storage_volume1",
|
||||
"unicode": "e9a5",
|
||||
"unicode_decimal": 59813
|
||||
},
|
||||
{
|
||||
"icon_id": "41654608",
|
||||
"name": "ciase-aix",
|
||||
"name": "aix",
|
||||
"font_class": "ciase-aix",
|
||||
"unicode": "e9a3",
|
||||
"unicode_decimal": 59811
|
||||
},
|
||||
{
|
||||
"icon_id": "41654233",
|
||||
"name": "caise_pool",
|
||||
"name": "ip池",
|
||||
"font_class": "caise_pool",
|
||||
"unicode": "e99b",
|
||||
"unicode_decimal": 59803
|
||||
},
|
||||
{
|
||||
"icon_id": "41654237",
|
||||
"name": "caise-ip_address",
|
||||
"name": "ip地址",
|
||||
"font_class": "caise-ip_address",
|
||||
"unicode": "e99c",
|
||||
"unicode_decimal": 59804
|
||||
},
|
||||
{
|
||||
"icon_id": "41654249",
|
||||
"name": "caise-computer_room",
|
||||
"name": "机房",
|
||||
"font_class": "caise-computer_room",
|
||||
"unicode": "e99d",
|
||||
"unicode_decimal": 59805
|
||||
},
|
||||
{
|
||||
"icon_id": "41654271",
|
||||
"name": "caise-rack",
|
||||
"name": "机柜",
|
||||
"font_class": "caise-rack",
|
||||
"unicode": "e99e",
|
||||
"unicode_decimal": 59806
|
||||
},
|
||||
{
|
||||
"icon_id": "41654276",
|
||||
"name": "caise-pc",
|
||||
"name": "PC",
|
||||
"font_class": "caise-pc",
|
||||
"unicode": "e99f",
|
||||
"unicode_decimal": 59807
|
||||
},
|
||||
{
|
||||
"icon_id": "41654305",
|
||||
"name": "caise-bandwidth_line",
|
||||
"name": "带宽线路",
|
||||
"font_class": "caise-bandwidth_line",
|
||||
"unicode": "e9a0",
|
||||
"unicode_decimal": 59808
|
||||
},
|
||||
{
|
||||
"icon_id": "41654323",
|
||||
"name": "caise-fiber",
|
||||
"name": "光纤交换机",
|
||||
"font_class": "caise-fiber",
|
||||
"unicode": "e9a1",
|
||||
"unicode_decimal": 59809
|
||||
},
|
||||
{
|
||||
"icon_id": "41654369",
|
||||
"name": "caise-disk_array",
|
||||
"name": "磁盘阵列",
|
||||
"font_class": "caise-disk_array",
|
||||
"unicode": "e9a2",
|
||||
"unicode_decimal": 59810
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -61,12 +61,12 @@ export default {
|
|||
)
|
||||
|
||||
// 注册富文本自定义元素
|
||||
const resume = {
|
||||
type: 'attachment',
|
||||
attachmentLabel: '',
|
||||
attachmentValue: '',
|
||||
children: [{ text: '' }], // void 元素必须有一个 children ,其中只有一个空字符串,重要!!!
|
||||
}
|
||||
// const resume = {
|
||||
// type: 'attachment',
|
||||
// attachmentLabel: '',
|
||||
// attachmentValue: '',
|
||||
// children: [{ text: '' }], // void 元素必须有一个 children ,其中只有一个空字符串,重要!!!
|
||||
// }
|
||||
|
||||
function withAttachment(editor) {
|
||||
// JS 语法
|
||||
|
|
|
@ -905,6 +905,84 @@ export const multicolorIconList = [
|
|||
value: 'caise-application',
|
||||
label: '应用',
|
||||
list: [{
|
||||
value: 'caise-disk_array',
|
||||
label: '磁盘阵列'
|
||||
}, {
|
||||
value: 'caise-fiber',
|
||||
label: '光纤交换机'
|
||||
}, {
|
||||
value: 'caise-bandwidth_line',
|
||||
label: '带宽线路'
|
||||
}, {
|
||||
value: 'caise-pc',
|
||||
label: 'PC'
|
||||
}, {
|
||||
value: 'caise-rack',
|
||||
label: '机柜'
|
||||
}, {
|
||||
value: 'caise-computer_room',
|
||||
label: '机房'
|
||||
}, {
|
||||
value: 'caise-ip_address',
|
||||
label: 'ip地址'
|
||||
}, {
|
||||
value: 'caise_pool',
|
||||
label: 'ip池'
|
||||
}, {
|
||||
value: 'ciase-aix',
|
||||
label: 'aix'
|
||||
}, {
|
||||
value: 'caise-storage_volume1',
|
||||
label: '存储卷'
|
||||
}, {
|
||||
value: 'caise-virtualization',
|
||||
label: '虚拟化'
|
||||
}, {
|
||||
value: 'caise-business',
|
||||
label: '业务'
|
||||
}, {
|
||||
value: 'caise-database',
|
||||
label: '数据库'
|
||||
}, {
|
||||
value: 'caise-middleware',
|
||||
label: '中间件'
|
||||
}, {
|
||||
value: 'caise-websever',
|
||||
label: 'websever'
|
||||
}, {
|
||||
value: 'caise-message_queue',
|
||||
label: '消息队列'
|
||||
}, {
|
||||
value: 'caise-load_balancing',
|
||||
label: '负载均衡'
|
||||
}, {
|
||||
value: 'caise-storage_device',
|
||||
label: '存储设备'
|
||||
}, {
|
||||
value: 'caise-network_devices',
|
||||
label: '网络设备'
|
||||
}, {
|
||||
value: 'caise-computer',
|
||||
label: '计算机'
|
||||
}, {
|
||||
value: 'caise-hardware',
|
||||
label: '硬件设备'
|
||||
}, {
|
||||
value: 'caise-data_center2',
|
||||
label: '数据中心'
|
||||
}, {
|
||||
value: 'caise-hyperV',
|
||||
label: 'hyperV'
|
||||
}, {
|
||||
value: 'caise-IPAM',
|
||||
label: 'IPAM'
|
||||
}, {
|
||||
value: 'caise-system',
|
||||
label: '操作系统'
|
||||
}, {
|
||||
value: 'caise-public_cloud',
|
||||
label: '公有云'
|
||||
}, {
|
||||
value: 'caise-data_center',
|
||||
label: '数据中心'
|
||||
}, {
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 177 KiB |
Binary file not shown.
After Width: | Height: | Size: 444 KiB |
|
@ -0,0 +1,85 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="icon || title"
|
||||
class="ci-icon"
|
||||
:style="{
|
||||
'--size': size + 'px'
|
||||
}"
|
||||
>
|
||||
<template v-if="icon">
|
||||
<img
|
||||
v-if="icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${icon.split('$$')[3]}`"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else
|
||||
:style="{
|
||||
color: icon.split('$$')[1],
|
||||
}"
|
||||
:type="icon.split('$$')[0]"
|
||||
/>
|
||||
</template>
|
||||
<span
|
||||
class="ci-icon-letter"
|
||||
v-else
|
||||
>
|
||||
<span>
|
||||
{{ title[0].toUpperCase() }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CIIcon',
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 如果没有icon, 默认以title 的第一个字符
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: '12'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-icon {
|
||||
font-size: var(--size);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
& > img {
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
}
|
||||
|
||||
&-letter {
|
||||
background-color: #FFFFFF;
|
||||
color: #2f54eb;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
|
||||
|
||||
& > span {
|
||||
transform-origin: center;
|
||||
transform: scale(0.7);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -179,7 +179,7 @@
|
|||
<a @click="openDetail(row.ci_id || row._id)">
|
||||
<a-icon type="unordered-list" />
|
||||
</a>
|
||||
<a-tooltip :title="$t('cmdb.ci.addRelation')">
|
||||
<a-tooltip :title="$t('cmdb.ci.viewRelation')">
|
||||
<a @click="openDetail(row.ci_id || row._id, 'tab_2', '2')">
|
||||
<a-icon type="retweet" />
|
||||
</a>
|
||||
|
@ -375,7 +375,7 @@ export default {
|
|||
},
|
||||
|
||||
jsonEditorOk(row, column, jsonData) {
|
||||
this.$attrs.data.forEach((item) => {
|
||||
this.data.forEach((item) => {
|
||||
if (item._id === row._id) {
|
||||
item[column.property] = JSON.stringify(jsonData)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import i18n from '@/lang'
|
||||
|
||||
export const ruleTypeList = () => {
|
||||
return [
|
||||
{ value: 'and', label: i18n.t('cmdbFilterComp.and') },
|
||||
{ value: 'or', label: i18n.t('cmdbFilterComp.or') },
|
||||
// { value: 'not', label: '非' },
|
||||
]
|
||||
}
|
||||
|
||||
export const expList = () => {
|
||||
return [
|
||||
{ value: 'is', label: i18n.t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: i18n.t('cmdbFilterComp.~is') },
|
||||
{ value: 'contain', label: i18n.t('cmdbFilterComp.contain') },
|
||||
{ value: '~contain', label: i18n.t('cmdbFilterComp.~contain') },
|
||||
{ value: 'start_with', label: i18n.t('cmdbFilterComp.start_with') },
|
||||
{ value: '~start_with', label: i18n.t('cmdbFilterComp.~start_with') },
|
||||
{ value: 'end_with', label: i18n.t('cmdbFilterComp.end_with') },
|
||||
{ value: '~end_with', label: i18n.t('cmdbFilterComp.~end_with') },
|
||||
{ value: '~value', label: i18n.t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: i18n.t('cmdbFilterComp.value') },
|
||||
]
|
||||
}
|
||||
|
||||
export const advancedExpList = () => {
|
||||
return [
|
||||
{ value: 'in', label: i18n.t('cmdbFilterComp.in') },
|
||||
{ value: '~in', label: i18n.t('cmdbFilterComp.~in') },
|
||||
{ value: 'range', label: i18n.t('cmdbFilterComp.range') },
|
||||
{ value: '~range', label: i18n.t('cmdbFilterComp.~range') },
|
||||
{ value: 'compare', label: i18n.t('cmdbFilterComp.compare') },
|
||||
]
|
||||
}
|
||||
|
||||
export const compareTypeList = [
|
||||
{ value: '1', label: '>' },
|
||||
{ value: '2', label: '>=' },
|
||||
{ value: '3', label: '<' },
|
||||
{ value: '4', label: '<=' },
|
||||
]
|
|
@ -0,0 +1,320 @@
|
|||
<template>
|
||||
<div :style="{ lineHeight: rowHeight }">
|
||||
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
|
||||
<div v-if="ruleList.length > 1" :style="{ width: '60px', height: rowHeight, position: 'relative' }">
|
||||
<treeselect
|
||||
v-if="index !== 0"
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '60px', '--custom-height': rowHeight, position: 'absolute', top: '-24px' }"
|
||||
v-model="item.type"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="ruleTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</treeselect>
|
||||
</div>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '120px', '--custom-height': rowHeight }"
|
||||
v-model="item.property"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="canSearchPreferenceAttrList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.name,
|
||||
label: node.alias || node.name,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<div
|
||||
v-if="node.id !== '$count'"
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
class="property-label"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" />
|
||||
{{ node.label }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
class="property-label"
|
||||
:style="{ borderBottom: '1px solid #E4E7ED', marginBottom: '8px' }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" />
|
||||
{{ node.label }}
|
||||
</div>
|
||||
<div
|
||||
class="property-label"
|
||||
slot="value-label"
|
||||
slot-scope="{ node }"
|
||||
>
|
||||
<ValueTypeMapIcon :attr="node.raw" /> {{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '90px', '--custom-height': rowHeight }"
|
||||
v-model="item.exp"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="getExpListByProperty(item.property)"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
appendToBody
|
||||
@select="(value) => handleChangeExp(value, item, index)"
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
>
|
||||
</treeselect>
|
||||
<ValueControls
|
||||
:rule="ruleList[index]"
|
||||
:attrList="canSearchPreferenceAttrList"
|
||||
:disabled="disabled"
|
||||
:curModelAttrList="curModelAttrList"
|
||||
:rowHeight="rowHeight"
|
||||
@change="(value) => handleChangeValue(value, index)"
|
||||
/>
|
||||
<template v-if="!disabled">
|
||||
<a-tooltip :title="$t('copy')">
|
||||
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="veops-copy"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('delete')">
|
||||
<a class="operation" @click="handleDeleteRule(item)"><a-icon type="minus-circle"/></a>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="$t('cmdbFilterComp.addHere')">
|
||||
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-space>
|
||||
<div class="table-filter-add" v-if="!disabled && ruleList.length === 0">
|
||||
<a @click="handleAddRule">+ {{ $t('new') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants.js'
|
||||
import ValueTypeMapIcon from '@/components/CMDBValueTypeMapIcon'
|
||||
import ValueControls from './valueControls.vue'
|
||||
|
||||
export default {
|
||||
name: 'Expression',
|
||||
components: {
|
||||
ValueTypeMapIcon,
|
||||
ValueControls
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
curModelAttrList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
compareTypeList,
|
||||
rowHeight: '36px',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ruleList: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
return val
|
||||
},
|
||||
},
|
||||
ruleTypeList() {
|
||||
return ruleTypeList()
|
||||
},
|
||||
expList() {
|
||||
return expList()
|
||||
},
|
||||
advancedExpList() {
|
||||
return advancedExpList()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getExpListByProperty(property) {
|
||||
if (property === '$count') {
|
||||
return [
|
||||
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
|
||||
{ value: 'compare', label: this.$t('cmdbFilterComp.compare') }
|
||||
]
|
||||
}
|
||||
if (property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
|
||||
return [
|
||||
{ value: 'is', label: this.$t('cmdbFilterComp.is') },
|
||||
{ value: '~is', label: this.$t('cmdbFilterComp.~is') },
|
||||
{ value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕
|
||||
{ value: 'value', label: this.$t('cmdbFilterComp.value') },
|
||||
...this.advancedExpList
|
||||
]
|
||||
}
|
||||
}
|
||||
return [...this.expList, ...this.advancedExpList]
|
||||
},
|
||||
isChoiceByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.is_choice
|
||||
}
|
||||
return false
|
||||
},
|
||||
handleAddRule() {
|
||||
this.ruleList.push({
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleCopyRule(item) {
|
||||
this.ruleList.push({ ...item, id: uuidv4() })
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleDeleteRule(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx, 1)
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
handleAddRuleAt(item) {
|
||||
const idx = this.ruleList.findIndex((r) => r.id === item.id)
|
||||
if (idx > -1) {
|
||||
this.ruleList.splice(idx + 1, 0, {
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property: this.canSearchPreferenceAttrList[0]?.name,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
})
|
||||
}
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
getChoiceValueByProperty(property) {
|
||||
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
handleChangeExp({ value }, item, index) {
|
||||
const _ruleList = _.cloneDeep(this.ruleList)
|
||||
if (value === 'range') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
min: '',
|
||||
max: '',
|
||||
exp: value,
|
||||
}
|
||||
} else if (value === 'compare') {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
compareType: '1',
|
||||
exp: value,
|
||||
}
|
||||
} else {
|
||||
_ruleList[index] = {
|
||||
..._ruleList[index],
|
||||
exp: value,
|
||||
}
|
||||
}
|
||||
this.ruleList = _ruleList
|
||||
this.$emit('change', this.ruleList)
|
||||
},
|
||||
|
||||
handleChangeValue(value, index) {
|
||||
const _ruleList = _.cloneDeep(this.ruleList)
|
||||
_ruleList[index] = value
|
||||
this.$emit('change', _ruleList)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 150px;
|
||||
|
||||
&-range-icon {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.property-label {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden
|
||||
}
|
||||
|
||||
.attr-filter-tip {
|
||||
color: #86909C;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,211 @@
|
|||
<template>
|
||||
<div>
|
||||
<Expression
|
||||
v-model="ruleList"
|
||||
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
|
||||
:disabled="false"
|
||||
:curModelAttrList="curModelAttrList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { compareTypeList } from './constants.js'
|
||||
|
||||
import Expression from './expression.vue'
|
||||
|
||||
export default {
|
||||
name: 'AttrFilter',
|
||||
components: {
|
||||
Expression
|
||||
},
|
||||
props: {
|
||||
canSearchPreferenceAttrList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [],
|
||||
},
|
||||
expression: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
regQ: {
|
||||
type: String,
|
||||
default: '(?<=q=).+(?=&)|(?<=q=).+$',
|
||||
},
|
||||
CITypeIds: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
curModelAttrList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
compareTypeList,
|
||||
visible: false,
|
||||
ruleList: [],
|
||||
filterExp: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(open, isInitOne = true) {
|
||||
// isInitOne 初始化exp为空时,ruleList是否默认给一条
|
||||
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = this.expression.match(new RegExp(this.regQ, 'g'))
|
||||
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
|
||||
: null
|
||||
if (open && exp) {
|
||||
const expArray = exp.split(',').map((item) => {
|
||||
let has_not = ''
|
||||
const key = item.split(':')[0]
|
||||
const val = item
|
||||
.split(':')
|
||||
.slice(1)
|
||||
.join(':')
|
||||
let type, property, exp, value, min, max, compareType
|
||||
if (key.includes('-')) {
|
||||
type = 'or'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(2)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key.substring(1)
|
||||
}
|
||||
} else {
|
||||
type = 'and'
|
||||
if (key.includes('~')) {
|
||||
property = key.substring(1)
|
||||
has_not = '~'
|
||||
} else {
|
||||
property = key
|
||||
}
|
||||
}
|
||||
|
||||
const in_reg = /(?<=\().+(?=\))/g
|
||||
const range_reg = /(?<=\[).+(?=\])/g
|
||||
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
|
||||
if (val === '*') {
|
||||
exp = has_not + 'value'
|
||||
value = ''
|
||||
} else if (in_reg.test(val)) {
|
||||
exp = has_not + 'in'
|
||||
value = val.match(in_reg)[0]
|
||||
} else if (range_reg.test(val)) {
|
||||
exp = has_not + 'range'
|
||||
value = val.match(range_reg)[0]
|
||||
min = value.split('_TO_')[0]
|
||||
max = value.split('_TO_')[1]
|
||||
} else if (compare_reg.test(val)) {
|
||||
exp = has_not + 'compare'
|
||||
value = val.match(compare_reg)[0]
|
||||
const _compareType = val.substring(0, val.match(compare_reg)['index'])
|
||||
const idx = compareTypeList.findIndex((item) => item.label === _compareType)
|
||||
compareType = compareTypeList[idx].value
|
||||
} else if (!val.includes('*')) {
|
||||
exp = has_not + 'is'
|
||||
value = val
|
||||
} else {
|
||||
const resList = [
|
||||
['contain', /(?<=\*).*(?=\*)/g],
|
||||
['end_with', /(?<=\*).+/g],
|
||||
['start_with', /.+(?=\*)/g],
|
||||
]
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const reg = resList[i]
|
||||
if (reg[1].test(val)) {
|
||||
exp = has_not + reg[0]
|
||||
value = val.match(reg[1])[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: uuidv4(),
|
||||
type,
|
||||
property,
|
||||
exp,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
compareType,
|
||||
}
|
||||
})
|
||||
this.ruleList = [...expArray]
|
||||
} else if (open) {
|
||||
const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password)
|
||||
this.ruleList = isInitOne
|
||||
? [
|
||||
{
|
||||
id: uuidv4(),
|
||||
type: 'and',
|
||||
property:
|
||||
_canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length
|
||||
? _canSearchPreferenceAttrList[0].name
|
||||
: undefined,
|
||||
exp: 'is',
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
},
|
||||
|
||||
handleSubmit() {
|
||||
if (this.ruleList && this.ruleList.length) {
|
||||
this.ruleList[0].type = 'and' // 增删后,以防万一第一个不是and
|
||||
this.filterExp = ''
|
||||
const expList = this.ruleList.filter((rule) => rule?.property).map((rule) => {
|
||||
let singleRuleExp = ''
|
||||
let _exp = rule.exp
|
||||
if (rule.type === 'or') {
|
||||
singleRuleExp += '-'
|
||||
}
|
||||
if (rule.exp.includes('~')) {
|
||||
singleRuleExp += '~'
|
||||
_exp = rule.exp.split('~')[1]
|
||||
}
|
||||
singleRuleExp += `${rule.property}:`
|
||||
if (_exp === 'is') {
|
||||
singleRuleExp += `${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'contain') {
|
||||
singleRuleExp += `*${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'start_with') {
|
||||
singleRuleExp += `${rule.value ?? ''}*`
|
||||
}
|
||||
if (_exp === 'end_with') {
|
||||
singleRuleExp += `*${rule.value ?? ''}`
|
||||
}
|
||||
if (_exp === 'value') {
|
||||
singleRuleExp += `*`
|
||||
}
|
||||
if (_exp === 'in') {
|
||||
singleRuleExp += `(${rule.value ?? ''})`
|
||||
}
|
||||
if (_exp === 'range') {
|
||||
singleRuleExp += `[${rule.min}_TO_${rule.max}]`
|
||||
}
|
||||
if (_exp === 'compare') {
|
||||
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
|
||||
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
|
||||
}
|
||||
return singleRuleExp
|
||||
})
|
||||
this.filterExp = expList.join(',')
|
||||
this.$emit('setExpFromFilter', this.filterExp)
|
||||
} else {
|
||||
this.$emit('setExpFromFilter', '')
|
||||
}
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,267 @@
|
|||
<template>
|
||||
<div class="control-group">
|
||||
<CIReferenceAttr
|
||||
v-if="getAttr(rule.property).is_reference && (rule.exp === 'is' || rule.exp === '~is')"
|
||||
class="select-filter"
|
||||
:referenceTypeId="getAttr(rule.property).reference_type_id"
|
||||
:value="rule.value"
|
||||
:disabled="disabled"
|
||||
@change="(value) => handleChange('value', value)"
|
||||
/>
|
||||
<a-select
|
||||
v-else-if="getAttr(rule.property).is_bool && (rule.exp === 'is' || rule.exp === '~is')"
|
||||
class="select-filter"
|
||||
:disabled="disabled"
|
||||
:placeholder="$t('placeholder2')"
|
||||
:value="rule.value"
|
||||
@change="(value) => handleChange('value', value)"
|
||||
>
|
||||
<a-select-option key="1">
|
||||
true
|
||||
</a-select-option>
|
||||
<a-select-option key="0">
|
||||
false
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<div
|
||||
class="input-group"
|
||||
v-else-if="isChoiceByProperty(rule.property) && (rule.exp === 'is' || rule.exp === '~is')"
|
||||
>
|
||||
<CIBuiltInAttr
|
||||
v-if="getAttr(rule.property).choice_builtin"
|
||||
:choice_builtin="getAttr(rule.property).choice_builtin"
|
||||
class="select-filter"
|
||||
:treeselectCustomStyle="{ width: '136px', '--custom-height': '36px' }"
|
||||
:value="rule.value"
|
||||
@change="(value) => handleChange('value', value)"
|
||||
/>
|
||||
|
||||
<a-select
|
||||
v-else
|
||||
class="select-filter"
|
||||
:style="{ width: '175px' }"
|
||||
showSearch
|
||||
:placeholder="$t('placeholder2')"
|
||||
:disabled="disabled"
|
||||
:value="rule.value"
|
||||
@change="(value) => handleChange('value', value)"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(node) in getChoiceValueByProperty(rule.property)"
|
||||
:key="String(node[0])"
|
||||
:title="node[1] ? node[1].label || node[0] : node[0]"
|
||||
>
|
||||
<a-tooltip placement="topLeft" :title="node[1] ? node[1].label || node[0] : node[0]" >
|
||||
{{ node[1] ? node[1].label || node[0] : node[0] }}
|
||||
</a-tooltip>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<!-- <treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ '--custom-height': rowHeight }"
|
||||
:value="rule.value"
|
||||
@input="(value) => handleChange('value', value)"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="getChoiceValueByProperty(rule.property)"
|
||||
:placeholder="$t('placeholder2')"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node[0],
|
||||
label: node[1] ? node[1].label || node[0] : node[0],
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
appendToBody
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect> -->
|
||||
</div>
|
||||
<div
|
||||
compact
|
||||
v-else-if="rule.exp === 'range' || rule.exp === '~range'"
|
||||
class="input-group"
|
||||
>
|
||||
<a-input
|
||||
class="ops-input"
|
||||
:placeholder="$t('min')"
|
||||
:disabled="disabled"
|
||||
:value="rule.min"
|
||||
@change="(e) => handleChange('min', e.target.value)"
|
||||
/>
|
||||
<span class="input-group-range-icon">~</span>
|
||||
<a-input
|
||||
class="ops-input"
|
||||
v-model="rule.max"
|
||||
:placeholder="$t('max')"
|
||||
:disabled="disabled"
|
||||
:value="rule.max"
|
||||
@change="(e) => handleChange('max', e.target.value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group" compact v-else-if="rule.exp === 'compare'">
|
||||
<treeselect
|
||||
class="custom-treeselect"
|
||||
:style="{ width: '70px', '--custom-height': rowHeight, 'flex-shrink': 0 }"
|
||||
:value="rule.compareType"
|
||||
@input="(value) => handleChange('compareType', value)"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="compareTypeList"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.value,
|
||||
label: node.label,
|
||||
children: node.children,
|
||||
}
|
||||
}
|
||||
"
|
||||
:zIndex="1050"
|
||||
:disabled="disabled"
|
||||
appendToBody
|
||||
>
|
||||
</treeselect>
|
||||
<a-input :value="rule.value" @change="(e) => handleChange('value', e.target.value)" class="ops-input"/>
|
||||
</div>
|
||||
<div class="input-group" v-else-if="rule.exp !== 'value' && rule.exp !== '~value'">
|
||||
<a-input
|
||||
:value="rule.value"
|
||||
@change="(e) => handleChange('value', e.target.value)"
|
||||
:placeholder="rule.exp === 'in' || rule.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
|
||||
class="ops-input"
|
||||
:disabled="disabled"
|
||||
></a-input>
|
||||
</div>
|
||||
<div v-else :style="{ width: '136px' }"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { compareTypeList } from './constants.js'
|
||||
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
|
||||
import CIBuiltInAttr from '@/components/ciBuiltInAttr/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'ValueControls',
|
||||
components: {
|
||||
CIReferenceAttr,
|
||||
CIBuiltInAttr
|
||||
},
|
||||
props: {
|
||||
rule: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
attrList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 当前模型属性
|
||||
curModelAttrList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 行高
|
||||
rowHeight: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
compareTypeList,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
choiceValue() {
|
||||
const val = /\{\{([^}]+)\}\}/g.exec(this?.rule?.value || '')
|
||||
return val ? val?.[1]?.trim() || '' : this?.value?.value
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isChoiceByProperty(property) {
|
||||
const _find = this.attrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.is_choice
|
||||
}
|
||||
return false
|
||||
},
|
||||
getChoiceValueByProperty(property) {
|
||||
const _find = this.attrList.find((item) => item.name === property)
|
||||
if (_find) {
|
||||
return _find.choice_value
|
||||
}
|
||||
return []
|
||||
},
|
||||
handleChange(key, value) {
|
||||
this.$emit('change', {
|
||||
...this.rule,
|
||||
[key]: value
|
||||
})
|
||||
},
|
||||
getAttr(property) {
|
||||
return this.attrList.find((item) => item.name === property) || {}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.control-group {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 136px;
|
||||
|
||||
&-range-icon {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.select-filter {
|
||||
height: 36px;
|
||||
width: 136px;
|
||||
|
||||
/deep/ .ant-select-selection {
|
||||
height: 36px;
|
||||
background: #f7f8fa;
|
||||
line-height: 36px;
|
||||
border: none;
|
||||
|
||||
.ant-select-selection__rendered {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .vue-treeselect__control {
|
||||
background: #f7f8fa;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -307,6 +307,18 @@ const cmdb_en = {
|
|||
filterUsers: 'Filter Users',
|
||||
enum: 'Enum',
|
||||
ciGrantTip: `Filter conditions can be changed dynamically using {{}} referenced variables, currently user variables are supported, such as {{user.uid}},{{user.username}},{{user.email}},{{user.nickname}}`,
|
||||
searchInputTip: 'Please search for resource keywords',
|
||||
resourceSearch: 'Resource Search',
|
||||
recentSearch: 'Recent Search',
|
||||
myCollection: 'My Collection',
|
||||
keyword: 'Keywords',
|
||||
CIType: 'CIType',
|
||||
filterPopoverLabel: 'Filter',
|
||||
conditionFilter: 'Condition Filter',
|
||||
advancedFilter: 'Advanced Filter',
|
||||
saveCondition: 'Save Condition',
|
||||
confirmClear: 'Confirm to clear?',
|
||||
currentPage: 'Current Page'
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: 'Unselected',
|
||||
|
@ -629,6 +641,7 @@ if __name__ == "__main__":
|
|||
attributeDesc: 'Attribute Description',
|
||||
selectRows: 'Select: {rows} items',
|
||||
addRelation: 'Add Relation',
|
||||
viewRelation: 'View Relation',
|
||||
all: 'All',
|
||||
batchUpdate: 'Batch Update',
|
||||
batchUpdateConfirm: 'Are you sure you want to make batch updates?',
|
||||
|
|
|
@ -307,6 +307,18 @@ const cmdb_zh = {
|
|||
filterUsers: '筛选用户',
|
||||
enum: '枚举',
|
||||
ciGrantTip: `筛选条件可使用{{}}引用变量实现动态变化,目前支持用户变量,如{{user.uid}},{{user.username}},{{user.email}},{{user.nickname}}`,
|
||||
searchInputTip: '请搜索资源关键字',
|
||||
resourceSearch: '资源搜索',
|
||||
recentSearch: '最近搜索',
|
||||
myCollection: '我的收藏',
|
||||
keyword: '关键字',
|
||||
CIType: '模型',
|
||||
filterPopoverLabel: '条件过滤',
|
||||
conditionFilter: '条件过滤',
|
||||
advancedFilter: '高级筛选',
|
||||
saveCondition: '保存条件',
|
||||
confirmClear: '确认清空?',
|
||||
currentPage: '当前页'
|
||||
},
|
||||
components: {
|
||||
unselectAttributes: '未选属性',
|
||||
|
@ -628,6 +640,7 @@ if __name__ == "__main__":
|
|||
attributeDesc: '查看属性配置',
|
||||
selectRows: '选取:{rows} 项',
|
||||
addRelation: '添加关系',
|
||||
viewRelation: '查看关系',
|
||||
all: '全部',
|
||||
batchUpdate: '批量修改',
|
||||
batchUpdateConfirm: '确认要批量修改吗?',
|
||||
|
|
|
@ -54,7 +54,7 @@ const genCmdbRoutes = async () => {
|
|||
path: '/cmdb/resourcesearch',
|
||||
name: 'cmdb_resource_search',
|
||||
meta: { title: 'cmdb.menu.ciSearch', icon: 'ops-cmdb-search', selectedIcon: 'ops-cmdb-search', keepAlive: false },
|
||||
component: () => import('../views/resource_search/index.vue')
|
||||
component: () => import('../views/resource_search_2/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/cmdb/adc',
|
||||
|
@ -117,11 +117,10 @@ const genCmdbRoutes = async () => {
|
|||
meta: { title: 'cmdb.menu.serviceTreeDefine', keepAlive: false, icon: 'ops-cmdb-preferencerelation', selectedIcon: 'ops-cmdb-preferencerelation-selected' }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/modelrelation',
|
||||
name: 'model_relation',
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/model_relation/index'),
|
||||
meta: { title: 'cmdb.menu.citypeRelation', keepAlive: false, icon: 'ops-cmdb-modelrelation', selectedIcon: 'ops-cmdb-modelrelation-selected' }
|
||||
path: '/cmdb/discovery',
|
||||
name: 'discovery',
|
||||
component: () => import('../views/discovery/index'),
|
||||
meta: { title: 'cmdb.menu.ad', keepAlive: false, icon: 'ops-cmdb-adr', selectedIcon: 'ops-cmdb-adr-selected' }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/operationhistory',
|
||||
|
@ -130,19 +129,20 @@ const genCmdbRoutes = async () => {
|
|||
component: () => import('../views/operation_history/index'),
|
||||
meta: { title: 'cmdb.menu.operationHistory', keepAlive: false, icon: 'ops-cmdb-operation', selectedIcon: 'ops-cmdb-operation-selected' }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/modelrelation',
|
||||
name: 'model_relation',
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/model_relation/index'),
|
||||
meta: { title: 'cmdb.menu.citypeRelation', keepAlive: false, icon: 'ops-cmdb-modelrelation', selectedIcon: 'ops-cmdb-modelrelation-selected' }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/relationtype',
|
||||
name: 'relation_type',
|
||||
hideChildrenInMenu: true,
|
||||
component: () => import('../views/relation_type/index'),
|
||||
meta: { title: 'cmdb.menu.relationType', keepAlive: false, icon: 'ops-cmdb-relationtype', selectedIcon: 'ops-cmdb-relationtype-selected' }
|
||||
},
|
||||
{
|
||||
path: '/cmdb/discovery',
|
||||
name: 'discovery',
|
||||
component: () => import('../views/discovery/index'),
|
||||
meta: { title: 'cmdb.menu.ad', keepAlive: false, icon: 'ops-cmdb-adr', selectedIcon: 'ops-cmdb-adr-selected' }
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -148,7 +148,6 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
|
||||
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
|
||||
|
|
|
@ -103,7 +103,7 @@ export default {
|
|||
await this.handleLinkAttrToCiType({ attr_id: this.targetKeys.map((i) => Number(i)) })
|
||||
if (this.currentGroup) {
|
||||
await this.updateCurrentGroup()
|
||||
const { id, name, order, attributes } = this.currentGroup
|
||||
const { name, order, attributes } = this.currentGroup
|
||||
const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
|
||||
this.targetKeys.forEach((key) => {
|
||||
attrIds.push(Number(key))
|
||||
|
@ -141,7 +141,7 @@ export default {
|
|||
})
|
||||
if (this.currentGroup) {
|
||||
await this.updateCurrentGroup()
|
||||
const { id, name, order, attributes } = this.currentGroup
|
||||
const { name, order, attributes } = this.currentGroup
|
||||
const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
|
||||
attrIds.push(newAttrId)
|
||||
await createCITypeGroupById(this.CITypeId, { name, order, attributes: attrIds })
|
||||
|
|
|
@ -121,7 +121,6 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { ColorPicker } from 'element-ui'
|
||||
import CustomIconSelect from '@/components/CustomIconSelect'
|
||||
import { defautValueColor, defaultBGColors } from '@/modules/cmdb/utils/const.js'
|
||||
|
|
|
@ -407,7 +407,7 @@ export default {
|
|||
this.visible = true
|
||||
this.type = type
|
||||
this.item = item
|
||||
const { category = 0, name, type_id, attr_id, level } = item
|
||||
const { category = 0, name, type_id, level } = item
|
||||
const chartType = (item.options || {}).chartType || 'count'
|
||||
const fontColor = (item.options || {}).fontColor || '#ffffff'
|
||||
const bgColor = (item.options || {}).bgColor || ['#6ABFFE', '#5375EB']
|
||||
|
|
|
@ -131,7 +131,7 @@ import ModelRelationTable from './modules/modelRelationTable.vue'
|
|||
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||
import { getCITypeGroupsConfig } from '@/modules/cmdb/api/ciTypeGroup'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { createRelation, deleteRelation, getCITypeChildren, getRelationTypes } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { createRelation, deleteRelation, getRelationTypes } from '@/modules/cmdb/api/CITypeRelation'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
|
|
|
@ -588,7 +588,7 @@ export default {
|
|||
this.calcColumns()
|
||||
}
|
||||
if (refreshType === 'refreshNumber') {
|
||||
const promises = this.treeKeys.map((key, index) => {
|
||||
this.treeKeys.map((key, index) => {
|
||||
let ancestor_ids
|
||||
if (
|
||||
Object.keys(this.level2constraint).some(
|
||||
|
@ -1432,7 +1432,7 @@ export default {
|
|||
content: (h) => <div>{that.$t('confirmDelete')}</div>,
|
||||
async onOk() {
|
||||
for (let i = 0; i < that.batchTreeKey.length; i++) {
|
||||
const { splitTreeKey, firstCIObj, firstCIId, _tempTree, ancestor_ids } = that.calculateParamsFromTreeKey(
|
||||
const { splitTreeKey, _tempTree, ancestor_ids } = that.calculateParamsFromTreeKey(
|
||||
that.batchTreeKey[i],
|
||||
'delete'
|
||||
)
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { ciTypeFilterPermissions, getCIType } from '../../../api/CIType'
|
||||
import { ciTypeFilterPermissions } from '../../../api/CIType'
|
||||
import FilterComp from '@/components/CMDBFilterComp'
|
||||
import { searchRole } from '@/modules/acl/api/role'
|
||||
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
<template>
|
||||
<div :class="['attr-display', isEllipsis ? 'attr-display-ellipsis' : '']">
|
||||
<template v-if="attr.is_reference && ci[attr.name]" >
|
||||
<a
|
||||
v-for="(ciId) in (attr.is_list ? ci[attr.name] : [ci[attr.name]])"
|
||||
:key="ciId"
|
||||
:href="`/cmdb/cidetail/${attr.reference_type_id}/${ciId}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ getReferenceAttrValue(ciId) }}
|
||||
</a>
|
||||
</template>
|
||||
<span v-else-if="attr.value_type === '6' && ci[attr.name]">{{ JSON.stringify(ci[attr.name]) }}</span>
|
||||
<template v-else-if="attr.is_link && ci[attr.name]">
|
||||
<a
|
||||
v-for="(item, linkIndex) in (attr.is_list ? ci[attr.name] : [ci[attr.name]])"
|
||||
:key="linkIndex"
|
||||
:href="
|
||||
item.startsWith('http') || item.startsWith('https')
|
||||
? `${item}`
|
||||
: `http://${item}`
|
||||
"
|
||||
target="_blank"
|
||||
>
|
||||
{{ getChoiceValueLabel(item) || item }}
|
||||
</a>
|
||||
</template>
|
||||
<PasswordField
|
||||
v-else-if="attr.is_password && ci[attr.name]"
|
||||
:ci_id="ci._id"
|
||||
:attr_id="attr.id"
|
||||
></PasswordField>
|
||||
<template v-else-if="attr.is_choice">
|
||||
<span
|
||||
v-for="value in (attr.is_list ? ci[attr.name] : [ci[attr.name]])"
|
||||
:key="value"
|
||||
:style="{
|
||||
borderRadius: '4px',
|
||||
padding: '1px 5px',
|
||||
margin: '2px',
|
||||
...getChoiceValueStyle(value),
|
||||
}"
|
||||
>
|
||||
<ops-icon
|
||||
:style="{ color: getChoiceValueIcon(attr, value).color }"
|
||||
:type="getChoiceValueIcon(attr, value).name"
|
||||
/>
|
||||
<span
|
||||
v-html="markSearchValue(getChoiceValueLabel(value) || value)"
|
||||
></span>
|
||||
</span>
|
||||
</template>
|
||||
<span
|
||||
v-else
|
||||
v-html="markSearchValue((attr.is_list && Array.isArray(ci[attr.name])) ? ci[attr.name].join(',') : ci[attr.name])"
|
||||
></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PasswordField from '@/modules/cmdb/components/passwordField/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'AttrDisplay',
|
||||
components: {
|
||||
PasswordField
|
||||
},
|
||||
props: {
|
||||
attr: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
ci: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
isEllipsis: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
referenceShowAttrNameMap: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
referenceCIIdMap: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
searchValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
markSearchValue(text) {
|
||||
if (!text || !this.searchValue) {
|
||||
return text
|
||||
}
|
||||
const regex = new RegExp(`(${this.searchValue})`, 'gi')
|
||||
return String(text).replace(
|
||||
regex,
|
||||
`<span style="background-color: #D3EEFE; padding: 0 2px;">$1</span>`
|
||||
)
|
||||
},
|
||||
getChoiceValueStyle(attrValue) {
|
||||
const _find = this?.attr?.choice_value?.find?.((item) => String(item?.[0]) === String(attrValue))
|
||||
if (_find) {
|
||||
return _find?.[1]?.style || {}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
getChoiceValueIcon(attrValue) {
|
||||
const _find = this?.attr?.choice_value?.find((item) => String(item?.[0]) === String(attrValue))
|
||||
if (_find) {
|
||||
return _find?.[1]?.icon || {}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
getChoiceValueLabel(attrValue) {
|
||||
const _find = this?.attr?.choice_value?.find((item) => String(item?.[0]) === String(attrValue))
|
||||
if (_find) {
|
||||
return _find?.[1]?.label || ''
|
||||
}
|
||||
return ''
|
||||
},
|
||||
getReferenceAttrValue(id) {
|
||||
if (this.attr.referenceShowAttrNameMap?.[id]) {
|
||||
return this.attr.referenceShowAttrNameMap[id]
|
||||
}
|
||||
|
||||
const ci = this?.referenceCIIdMap?.[this?.attr?.reference_type_id]?.[id]
|
||||
if (!ci) {
|
||||
return id
|
||||
}
|
||||
|
||||
const attrName = this.referenceShowAttrNameMap?.[this?.attr.reference_type_id]
|
||||
return ci?.[attrName] || id
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.attr-display {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
word-break: break-all;
|
||||
|
||||
&-ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,235 @@
|
|||
<template>
|
||||
<a-popover
|
||||
v-model="visible"
|
||||
trigger="click"
|
||||
placement="bottom"
|
||||
@visibleChange="handleVisibleChange"
|
||||
>
|
||||
<div class="filter-btn">
|
||||
<a-icon class="filter-btn-icon" type="filter" />
|
||||
<span class="filter-btn-title">{{ $t('cmdb.ciType.advancedFilter') }}</span>
|
||||
</div>
|
||||
<template slot="content">
|
||||
<div class="filter-content">
|
||||
<a-form :form="form">
|
||||
<a-form-item
|
||||
:label="$t('cmdb.ciType.ciType')"
|
||||
:label-col="formLayout.labelCol"
|
||||
:wrapper-col="formLayout.wrapperCol"
|
||||
>
|
||||
<treeselect
|
||||
:value="selectCITypeIds"
|
||||
class="custom-treeselect custom-treeselect-bgcAndBorder filter-content-ciTypes"
|
||||
:style="{
|
||||
width: '400px',
|
||||
zIndex: '1000',
|
||||
'--custom-height': '32px',
|
||||
'--custom-bg-color': '#FFF',
|
||||
'--custom-border': '1px solid #d9d9d9',
|
||||
'--custom-multiple-lineHeight': '32px',
|
||||
}"
|
||||
:multiple="true"
|
||||
:clearable="true"
|
||||
searchable
|
||||
:options="CITypeGroup"
|
||||
:limit="1"
|
||||
:limitText="(count) => `+ ${count}`"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
:placeholder="$t('cmdb.ciType.ciType')"
|
||||
@close="closeCiTypeGroup"
|
||||
@open="openCiTypeGroup"
|
||||
@input="inputCiTypeGroup"
|
||||
:normalizer="
|
||||
(node) => {
|
||||
return {
|
||||
id: node.id || -1,
|
||||
label: node.alias || node.name || $t('other'),
|
||||
title: node.alias || node.name || $t('other'),
|
||||
children: node.ci_types,
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div
|
||||
:title="node.label"
|
||||
slot="option-label"
|
||||
slot-scope="{ node }"
|
||||
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||
>
|
||||
{{ node.label }}
|
||||
</div>
|
||||
</treeselect>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="$t('cmdb.ciType.filterPopoverLabel')"
|
||||
:label-col="formLayout.labelCol"
|
||||
:wrapper-col="formLayout.wrapperCol"
|
||||
class="filter-content-condition-filter"
|
||||
>
|
||||
<ConditionFilter
|
||||
ref="conditionFilterRef"
|
||||
:canSearchPreferenceAttrList="allAttributesList"
|
||||
:expression="expression"
|
||||
:CITypeIds="selectCITypeIds"
|
||||
:isDropdown="false"
|
||||
@setExpFromFilter="setExpFromFilter"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<div class="filter-content-action">
|
||||
<a-button
|
||||
size="small"
|
||||
@click="saveCondition(false)"
|
||||
>
|
||||
{{ $t('cmdb.ciType.saveCondition') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="saveCondition(true)"
|
||||
>
|
||||
{{ $t('confirm') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import ConditionFilter from '@/modules/cmdb/components/conditionFilter/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'FilterPopover',
|
||||
components: {
|
||||
ConditionFilter
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
form: this.$form.createForm(this),
|
||||
formLayout: {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 15 },
|
||||
},
|
||||
lastCiType: [],
|
||||
}
|
||||
},
|
||||
props: {
|
||||
expression: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
selectCITypeIds: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
CITypeGroup: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
allAttributesList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleVisibleChange(open) {
|
||||
if (open) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.conditionFilterRef.init(true, false)
|
||||
})
|
||||
}
|
||||
},
|
||||
openCiTypeGroup() {
|
||||
this.lastCiType = _.cloneDeep(this.selectCITypeIds)
|
||||
},
|
||||
closeCiTypeGroup(value) {
|
||||
if (!_.isEqual(value, this.lastCiType)) {
|
||||
this.$emit('updateAllAttributesList', value)
|
||||
}
|
||||
},
|
||||
inputCiTypeGroup(value) {
|
||||
if (!value || !value.length) {
|
||||
this.$emit('updateAllAttributesList', value)
|
||||
}
|
||||
this.$emit('changeFilter', {
|
||||
name: 'selectCITypeIds',
|
||||
value
|
||||
})
|
||||
},
|
||||
setExpFromFilter(filterExp) {
|
||||
const regSort = /(?<=sort=).+/g
|
||||
const expSort = this.expression.match(regSort) ? this.expression.match(regSort)[0] : undefined
|
||||
let expression = ''
|
||||
if (filterExp) {
|
||||
expression = `q=${filterExp}`
|
||||
}
|
||||
if (expSort) {
|
||||
expression += `&sort=${expSort}`
|
||||
}
|
||||
this.$emit('changeFilter', {
|
||||
name: 'expression',
|
||||
value: expression
|
||||
})
|
||||
},
|
||||
|
||||
saveCondition(isSubmit) {
|
||||
this.$refs.conditionFilterRef.handleSubmit()
|
||||
this.$nextTick(() => {
|
||||
this.$emit('saveCondition', isSubmit)
|
||||
this.visible = false
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.filter-btn {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 13px;
|
||||
cursor: pointer;
|
||||
|
||||
&-icon {
|
||||
color: #2F54EB;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #2F54EB;
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
.filter-content {
|
||||
width: 600px;
|
||||
|
||||
&-ciTypes {
|
||||
/deep/ .vue-treeselect__value-container {
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
&-condition-filter {
|
||||
max-height: 250px;
|
||||
// overflow-y: auto;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
&-action {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
column-gap: 21px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,310 @@
|
|||
<template>
|
||||
<div class="history-list">
|
||||
<div
|
||||
class="history-recent"
|
||||
v-if="recentList.length"
|
||||
>
|
||||
<div class="history-title">
|
||||
<a-icon type="eye" class="history-title-icon" />
|
||||
<div class="history-title-text">{{ $t('cmdb.ciType.recentSearch') }}</div>
|
||||
|
||||
<a-popconfirm
|
||||
:title="$t('cmdb.ciType.confirmClear')"
|
||||
placement="topRight"
|
||||
@confirm="clearRecent"
|
||||
>
|
||||
<a-tooltip :title="$t('clear')" >
|
||||
<a-icon
|
||||
type="delete"
|
||||
class="history-title-clear"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
<div class="recent-list">
|
||||
<div
|
||||
v-for="(item) in recentList.slice(0, 10)"
|
||||
:key="item.id"
|
||||
class="recent-list-item"
|
||||
@click="clickRecent(item.option)"
|
||||
>
|
||||
<div class="recent-list-item-text">
|
||||
{{ getRecentSearchText(item.option) }}
|
||||
</div>
|
||||
<a-icon
|
||||
type="close"
|
||||
class="recent-list-item-close"
|
||||
@click.stop="deleteRecent(item.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="history-favor"
|
||||
v-if="favorList.length"
|
||||
>
|
||||
<div class="history-title">
|
||||
<ops-icon type="veops-collect" class="history-title-icon" />
|
||||
<div class="history-title-text">{{ $t('cmdb.ciType.myCollection') }}</div>
|
||||
<div class="history-title-count">({{ favorList.length }})</div>
|
||||
|
||||
<ops-icon
|
||||
type="veops-expand"
|
||||
class="history-title-expand"
|
||||
:style="{
|
||||
transform: `rotate(${isExpand ? '180deg' : '0deg'})`
|
||||
}"
|
||||
@click="isExpand = !isExpand"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="favor-list"
|
||||
:style="{ height: isExpand ? 'auto' : '30px' }"
|
||||
>
|
||||
<div
|
||||
v-for="(item) in favorList"
|
||||
:key="item.id"
|
||||
:class="['favor-list-item', detailCIId === item.option.CIId ? 'favor-list-item-selected' : '']"
|
||||
@click="showDetail(item.option)"
|
||||
>
|
||||
<CIIcon
|
||||
:icon="item.option.icon"
|
||||
:title="item.option.CITypeTitle"
|
||||
/>
|
||||
<div class="favor-list-item-title">
|
||||
{{ item.option.title }}
|
||||
</div>
|
||||
<ops-icon
|
||||
type="veops-collected"
|
||||
class="favor-list-item-collected"
|
||||
@click.stop="deleteCollect(item.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CIIcon from '@/modules/cmdb/components/ciIcon/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'HistoryList',
|
||||
components: {
|
||||
CIIcon
|
||||
},
|
||||
props: {
|
||||
recentList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
favorList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
detailCIId: {
|
||||
type: [String, Number],
|
||||
default: -1
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isExpand: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRecentSearchText(option) {
|
||||
const textArray = []
|
||||
if (option.searchValue) {
|
||||
textArray.push(`${this.$t('cmdb.ciType.keyword')}: ${option.searchValue}`)
|
||||
}
|
||||
|
||||
if (option?.ciTypeNames?.length) {
|
||||
textArray.push(`${this.$t('cmdb.ciType.CIType')}: ${option.ciTypeNames.join(',')}`)
|
||||
}
|
||||
|
||||
if (option.expression) {
|
||||
textArray.push(`${this.$t('cmdb.ciType.conditionFilter')}: ${option.expression}`)
|
||||
}
|
||||
|
||||
return textArray.join('; ')
|
||||
},
|
||||
|
||||
clickRecent(data) {
|
||||
this.$emit('clickRecent', data)
|
||||
},
|
||||
|
||||
deleteRecent(id) {
|
||||
this.$emit('deleteRecent', id)
|
||||
},
|
||||
|
||||
deleteCollect(id) {
|
||||
this.$emit('deleteCollect', id)
|
||||
},
|
||||
|
||||
showDetail(data) {
|
||||
this.$emit('showDetail', {
|
||||
id: data.CIId,
|
||||
ciTypeId: data.CITypeId
|
||||
})
|
||||
},
|
||||
|
||||
clearRecent() {
|
||||
this.$emit('clearRecent')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.history-list {
|
||||
width: 100%;
|
||||
|
||||
.history-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-icon {
|
||||
font-size: 12px;
|
||||
color: #2F54EB;
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #4E5969;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
&-count {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #86909C;
|
||||
}
|
||||
|
||||
&-clear {
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-expand {
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.history-recent {
|
||||
width: 100%;
|
||||
margin-top: 15px;
|
||||
|
||||
.recent-list {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 16px;
|
||||
row-gap: 8px;
|
||||
|
||||
&-item {
|
||||
flex-shrink: 0;
|
||||
padding: 4px 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 22px;
|
||||
background: rgba(255, 255, 255, 0.50);
|
||||
cursor: pointer;
|
||||
max-width: calc((100% - 16px) / 2);
|
||||
|
||||
&-text {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #1D2129;
|
||||
|
||||
max-width: 100%;
|
||||
text-wrap: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&-close {
|
||||
font-size: 12px;
|
||||
margin-left: 4px;
|
||||
color: #A5A9BC;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.recent-list-item-text {
|
||||
color: #2F54EB;
|
||||
}
|
||||
|
||||
.recent-list-item-close {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.history-favor {
|
||||
width: 100%;
|
||||
margin-top: 15px;
|
||||
|
||||
.favor-list {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 16px;
|
||||
row-gap: 8px;
|
||||
overflow: hidden;
|
||||
min-height: 30px;
|
||||
|
||||
&-item {
|
||||
flex-shrink: 0;
|
||||
padding: 4px 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 22px;
|
||||
background: rgba(255, 255, 255, 0.90);
|
||||
cursor: pointer;
|
||||
max-width: calc((100% - 16px) / 2);
|
||||
|
||||
&-title {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-left: 4px;
|
||||
color: #1D2129;
|
||||
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&-collected {
|
||||
font-size: 14px;
|
||||
margin-left: 4px;
|
||||
color: #FAD337;
|
||||
}
|
||||
|
||||
&-selected {
|
||||
border: 1px solid #7F97FA;
|
||||
background-color: rgba(255, 255, 255, 0.90);
|
||||
|
||||
.favor-list-item-title {
|
||||
color: #2F54EB;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.favor-list-item-title {
|
||||
color: #2F54EB;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,438 @@
|
|||
<template>
|
||||
<div class="instance-detail">
|
||||
<div
|
||||
class="instance-detail-hide"
|
||||
@click="hideDetail"
|
||||
>
|
||||
<a-icon class="instance-detail-hide-icon" type="right" />
|
||||
</div>
|
||||
|
||||
<div class="instance-detail-null" v-if="!ci._id" >
|
||||
<img
|
||||
:src="require('@/modules/cmdb/assets/no_permission.png')"
|
||||
class="instance-detail-null-img"
|
||||
/>
|
||||
<span class="instance-detail-null-text" >{{ $t('noData') }}</span>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div
|
||||
class="instance-detail-header"
|
||||
>
|
||||
<div class="instance-detail-header-line-1"></div>
|
||||
<div class="instance-detail-header-line-2"></div>
|
||||
<div class="instance-detail-header-row">
|
||||
<CIIcon
|
||||
:icon="ciType.icon"
|
||||
:title="ciType.name || ''"
|
||||
:size="20"
|
||||
/>
|
||||
<div class="instance-detail-header-title">
|
||||
{{ detailTitle }}
|
||||
</div>
|
||||
|
||||
<ops-icon
|
||||
:type="favorId ? 'veops-collected' : 'veops-collect'"
|
||||
:style="{ color: favorId ? '#FAD337' : '#A5A9BC' }"
|
||||
class="instance-detail-header-collect"
|
||||
@click="clickCollect"
|
||||
/>
|
||||
|
||||
<a class="instance-detail-header-share" @click="shareCi">
|
||||
<a-icon type="share-alt" />
|
||||
{{ $t('cmdb.ci.share') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="instance-detail-attr">
|
||||
<div
|
||||
v-for="(group) in attributeGroups"
|
||||
:key="group.id"
|
||||
class="instance-detail-attr-group"
|
||||
>
|
||||
<span class="instance-detail-attr-group-name">{{ group.name || $t('other') }}</span>
|
||||
|
||||
<div class="instance-detail-attr-list">
|
||||
<div
|
||||
v-for="(attr) in group.attributes"
|
||||
:key="attr.id"
|
||||
class="instance-detail-attr-item"
|
||||
>
|
||||
<a-tooltip :title="attr.alias || attr.name || ''">
|
||||
<div class="instance-detail-attr-item-label">
|
||||
<span class="instance-detail-attr-item-label-text">
|
||||
{{ attr.alias || attr.name || '' }}
|
||||
</span>
|
||||
<span class="instance-detail-attr-item-label-colon">:</span>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
|
||||
<div class="instance-detail-attr-item-value">
|
||||
<AttrDisplay
|
||||
:attr="attr"
|
||||
:ci="ci"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { getCIById, searchCI } from '@/modules/cmdb/api/ci'
|
||||
import { getCITypeGroupById, getCITypes, getCIType } from '@/modules/cmdb/api/CIType'
|
||||
|
||||
import AttrDisplay from './attrDisplay.vue'
|
||||
import CIIcon from '@/modules/cmdb/components/ciIcon/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'InstanceDetail',
|
||||
components: {
|
||||
AttrDisplay,
|
||||
CIIcon
|
||||
},
|
||||
props: {
|
||||
CIId: {
|
||||
type: [String, Number],
|
||||
default: -1
|
||||
},
|
||||
CITypeId: {
|
||||
type: [String, Number],
|
||||
default: -1
|
||||
},
|
||||
favorList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ci: {},
|
||||
ciType: {},
|
||||
attributeGroups: [],
|
||||
isNullData: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
watchParams() {
|
||||
return {
|
||||
CIId: this.CIId,
|
||||
CITypeId: this.CITypeId
|
||||
}
|
||||
},
|
||||
detailTitle() {
|
||||
const attrName = this?.ciType?.show_name || this?.ciType?.unique_name || ''
|
||||
return attrName ? (this?.ci?.[attrName] || '') : ''
|
||||
},
|
||||
favorId() {
|
||||
const id = this.favorList.find((item) => item?.option?.CIId === this.CIId)?.id
|
||||
return id ?? null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
watchParams: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler(newVal) {
|
||||
if (newVal?.CIId !== -1 && newVal?.CITypeId !== -1) {
|
||||
this.initData()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async initData() {
|
||||
const ci = await this.getCI()
|
||||
if (!ci) {
|
||||
this.isNullData = true
|
||||
return
|
||||
}
|
||||
await this.getCIType()
|
||||
await this.getAttributes()
|
||||
},
|
||||
|
||||
async getCI() {
|
||||
const res = await getCIById(this.CIId)
|
||||
const ci = res.result?.[0] || {}
|
||||
this.ci = ci
|
||||
return ci
|
||||
},
|
||||
|
||||
async getCIType() {
|
||||
const res = await getCIType(this.CITypeId)
|
||||
this.ciType = res?.ci_types?.[0] || {}
|
||||
},
|
||||
|
||||
async getAttributes() {
|
||||
const res = await getCITypeGroupById(this.CITypeId, { need_other: 1 })
|
||||
this.attributeGroups = res
|
||||
this.handleReferenceAttr()
|
||||
},
|
||||
|
||||
async handleReferenceAttr() {
|
||||
const map = {}
|
||||
this.attributeGroups.forEach((group) => {
|
||||
group.attributes.forEach((attr) => {
|
||||
if (attr?.is_reference && attr?.reference_type_id && this.ci[attr.name]) {
|
||||
const ids = Array.isArray(this.ci[attr.name]) ? this.ci[attr.name] : this.ci[attr.name] ? [this.ci[attr.name]] : []
|
||||
if (ids.length) {
|
||||
if (!map?.[attr.reference_type_id]) {
|
||||
map[attr.reference_type_id] = {}
|
||||
}
|
||||
ids.forEach((id) => {
|
||||
map[attr.reference_type_id][id] = {}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (!Object.keys(map).length) {
|
||||
return
|
||||
}
|
||||
|
||||
const ciTypesRes = await getCITypes({
|
||||
type_ids: Object.keys(map).join(',')
|
||||
})
|
||||
const showAttrNameMap = {}
|
||||
ciTypesRes.ci_types.forEach((ciType) => {
|
||||
showAttrNameMap[ciType.id] = ciType?.show_name || ciType?.unique_name || ''
|
||||
})
|
||||
|
||||
const allRes = await Promise.all(
|
||||
Object.keys(map).map((key) => {
|
||||
return searchCI({
|
||||
q: `_type:${key},_id:(${Object.keys(map[key]).join(';')})`,
|
||||
count: 9999
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
const ciNameMap = {}
|
||||
allRes.forEach((res) => {
|
||||
res.result.forEach((item) => {
|
||||
ciNameMap[item._id] = item
|
||||
})
|
||||
})
|
||||
|
||||
const newAttrGroups = _.cloneDeep(this.attributeGroups)
|
||||
|
||||
newAttrGroups.forEach((group) => {
|
||||
group.attributes.forEach((attr) => {
|
||||
if (attr?.is_reference && attr?.reference_type_id) {
|
||||
attr.showAttrName = showAttrNameMap?.[attr?.reference_type_id] || ''
|
||||
|
||||
const referenceShowAttrNameMap = {}
|
||||
const referenceCIIds = this.ci[attr.name];
|
||||
(Array.isArray(referenceCIIds) ? referenceCIIds : referenceCIIds ? [referenceCIIds] : []).forEach((id) => {
|
||||
referenceShowAttrNameMap[id] = ciNameMap?.[id]?.[attr.showAttrName] ?? id
|
||||
})
|
||||
attr.referenceShowAttrNameMap = referenceShowAttrNameMap
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.$set(this, 'attributeGroups', newAttrGroups)
|
||||
},
|
||||
|
||||
shareCi() {
|
||||
const text = `${document.location.host}/cmdb/cidetail/${this.CITypeId}/${this.CIId}`
|
||||
this.$copyText(text)
|
||||
.then(() => {
|
||||
this.$message.success(this.$t('copySuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error(this.$t('cmdb.ci.copyFailed'))
|
||||
})
|
||||
},
|
||||
|
||||
clickCollect() {
|
||||
if (this.favorId) {
|
||||
this.$emit('deleteCollect', this.favorId)
|
||||
} else {
|
||||
this.$emit('addCollect', {
|
||||
CIId: this.CIId,
|
||||
CITypeId: this.CITypeId,
|
||||
title: this.detailTitle,
|
||||
icon: this.ciType?.icon,
|
||||
CITypeTitle: this.ciType?.name || ''
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
hideDetail() {
|
||||
this.$emit('hideDetail')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.instance-detail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #E4E7ED;
|
||||
background-color: #FFFFFF;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
&-hide {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
margin-top: -21px;
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
background-color: #2f54eb;
|
||||
width: 13px;
|
||||
height: 43px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
|
||||
&-icon {
|
||||
color: #FFFFFF;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #597ef7;
|
||||
}
|
||||
}
|
||||
|
||||
&-null {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding-top: 100px;
|
||||
|
||||
&-img {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
color: #86909C;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&-header {
|
||||
width: 100%;
|
||||
height: 75px;
|
||||
background-color: #EBF0F9;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&-line-1 {
|
||||
height: 44px;
|
||||
width: 300px;
|
||||
position: absolute;
|
||||
right: -20px;
|
||||
top: 0px;
|
||||
transform: rotate(40deg);
|
||||
background: rgba(248, 249, 253, 0.60);
|
||||
}
|
||||
|
||||
&-line-2 {
|
||||
height: 44px;
|
||||
width: 300px;
|
||||
position: absolute;
|
||||
right: -110px;
|
||||
top: 0px;
|
||||
transform: rotate(40deg);
|
||||
background: rgba(248, 249, 253, 0.60);
|
||||
}
|
||||
|
||||
&-row {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #1D2129;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
margin-left: 9px;
|
||||
}
|
||||
|
||||
&-collect {
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&-share {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-attr {
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
|
||||
&-group {
|
||||
&:not(:first-child) {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
&-name {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #1D2129;
|
||||
}
|
||||
}
|
||||
|
||||
&-item {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
&-label {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #86909C;
|
||||
width: 25%;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
&-colon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-value {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,443 @@
|
|||
<template>
|
||||
<div class="list-wrap">
|
||||
<div class="list-wrap-bg" v-if="!filterList.length">
|
||||
<img :src="require('@/modules/cmdb/assets/resourceSearch/resource_search_bg_2.png')" />
|
||||
</div>
|
||||
|
||||
<div v-if="tabList.length" class="list-tab">
|
||||
<div class="list-tab-left">
|
||||
<div class="list-tab-label">{{ $t('cmdb.ciType.currentPage') }}: </div>
|
||||
<div
|
||||
v-for="(tab) in tabList"
|
||||
:key="tab.id"
|
||||
:class="['list-tab-item', tab.id === currentTab ? 'list-tab-item-active' : '']"
|
||||
@click="clickTab(tab.id)"
|
||||
>
|
||||
<span class="list-tab-item-title">{{ tab.title }}</span>
|
||||
(<span class="list-tab-item-count">{{ tab.count }}</span>)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-button
|
||||
icon="download"
|
||||
type="primary"
|
||||
class="ops-button-ghost list-tab-export"
|
||||
ghost
|
||||
@click="handleExport"
|
||||
>
|
||||
{{ $t('download') }}
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div v-if="filterList.length" class="list-container">
|
||||
<div
|
||||
v-for="(item) in filterList"
|
||||
:key="item._id"
|
||||
:class="['list-card', detailCIId === item.ci._id ? 'list-card-selected' : '']"
|
||||
@click="clickInstance(item.ci._id, item.ciTypeObj.id)"
|
||||
>
|
||||
<div class="list-card-header">
|
||||
<div class="list-card-model">
|
||||
<CIIcon
|
||||
:icon="item.ciTypeObj.icon"
|
||||
:title="item.ciTypeObj.name"
|
||||
/>
|
||||
<span class="list-card-model-title">{{ item.ciTypeObj.title }}</span>
|
||||
</div>
|
||||
<div class="list-card-title">{{ item.ci[item.ciTypeObj.showAttrName] }}</div>
|
||||
|
||||
<ops-icon
|
||||
v-if="getFavorId(item.ci._id)"
|
||||
type="veops-collected"
|
||||
class="list-card-collect"
|
||||
:style="{ color: '#FAD337' }"
|
||||
@click.stop="deleteCollect(item.ci._id)"
|
||||
/>
|
||||
|
||||
<ops-icon
|
||||
v-else
|
||||
type="veops-collect"
|
||||
class="list-card-collect"
|
||||
:style="{ color: '#A5A9BC' }"
|
||||
@click.stop="addCollect(item)"
|
||||
/>
|
||||
</div>
|
||||
<div class="list-card-attr">
|
||||
<div
|
||||
v-for="(attr) in item.attributes"
|
||||
:key="attr.name"
|
||||
class="list-card-attr-item"
|
||||
>
|
||||
<div class="list-card-attr-item-label">{{ attr.alias || attr.name || '' }}: </div>
|
||||
<div class="list-card-attr-item-value">
|
||||
<AttrDisplay
|
||||
:attr="attr"
|
||||
:ci="item.ci"
|
||||
:referenceShowAttrNameMap="referenceShowAttrNameMap"
|
||||
:referenceCIIdMap="referenceCIIdMap"
|
||||
:isEllipsis="true"
|
||||
:searchValue="searchValue"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ExcelJS from 'exceljs'
|
||||
import FileSaver from 'file-saver'
|
||||
import moment from 'moment'
|
||||
|
||||
import AttrDisplay from './attrDisplay.vue'
|
||||
import CIIcon from '@/modules/cmdb/components/ciIcon/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'InstanceList',
|
||||
components: {
|
||||
AttrDisplay,
|
||||
CIIcon
|
||||
},
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
tabList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
referenceShowAttrNameMap: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
referenceCIIdMap: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
favorList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
detailCIId: {
|
||||
type: [String, Number],
|
||||
default: -1
|
||||
},
|
||||
searchValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentTab: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filterList() {
|
||||
if (!this.currentTab || this.currentTab === -1) {
|
||||
return this.list
|
||||
}
|
||||
|
||||
return this.list.filter((item) => item.ciTypeObj.id === this.currentTab)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
tabList: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler(newVal) {
|
||||
this.currentTab = newVal?.[0]?.id ?? ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickTab(id) {
|
||||
this.currentTab = id
|
||||
},
|
||||
|
||||
getAttrLabel(attrName, attributes) {
|
||||
const label = attributes.find((attr) => attr.name === attrName)?.alias
|
||||
return label || attrName
|
||||
},
|
||||
|
||||
clickInstance(id, ciTypeId) {
|
||||
this.$emit('showDetail', {
|
||||
id,
|
||||
ciTypeId,
|
||||
})
|
||||
},
|
||||
getFavorId(ciId) {
|
||||
const id = this.favorList.find((item) => item?.option?.CIId === ciId)?.id
|
||||
return id ?? null
|
||||
},
|
||||
|
||||
addCollect(data) {
|
||||
this.$emit('addCollect', {
|
||||
CIId: data.ci._id,
|
||||
CITypeId: data.ciTypeObj.id,
|
||||
title: data.ci[data.ciTypeObj.showAttrName],
|
||||
icon: data.ciTypeObj.icon,
|
||||
CITypeTitle: data.ciTypeObj.name
|
||||
})
|
||||
},
|
||||
|
||||
deleteCollect(ciId) {
|
||||
const favorId = this.getFavorId(ciId)
|
||||
if (favorId) {
|
||||
this.$emit('deleteCollect', favorId)
|
||||
}
|
||||
},
|
||||
|
||||
handleExport() {
|
||||
const excel_name = `cmdb-${this.$t('cmdb.ciType.resourceSearch')}-${moment().format('YYYYMMDDHHmmss')}.xlsx`
|
||||
const wb = new ExcelJS.Workbook()
|
||||
|
||||
this.tabList.map((sheet) => {
|
||||
if (sheet.id === -1) {
|
||||
return
|
||||
}
|
||||
|
||||
const ws = wb.addWorksheet(sheet.title)
|
||||
this.handleSheetData({
|
||||
ws,
|
||||
sheet
|
||||
})
|
||||
})
|
||||
|
||||
wb.xlsx.writeBuffer().then((buffer) => {
|
||||
const file = new Blob([buffer], {
|
||||
type: 'application/octet-stream',
|
||||
})
|
||||
FileSaver.saveAs(file, excel_name)
|
||||
})
|
||||
},
|
||||
|
||||
handleSheetData({
|
||||
ws,
|
||||
sheet
|
||||
}) {
|
||||
const listData = this.list.filter((item) => item.ciTypeObj.id === sheet.id)
|
||||
if (!listData.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const columnMap = new Map()
|
||||
const columns = listData[0].attributes.filter((attr) => !attr.is_password).map((attr) => {
|
||||
columnMap.set(attr.name, attr)
|
||||
|
||||
return {
|
||||
header: attr.alias || attr.name || '',
|
||||
key: attr.name,
|
||||
width: 20,
|
||||
}
|
||||
})
|
||||
|
||||
ws.columns = columns
|
||||
|
||||
listData.forEach((data) => {
|
||||
const row = {}
|
||||
columns.forEach(({ key }) => {
|
||||
const value = data?.ci?.[key] ?? null
|
||||
const attr = columnMap.get(key)
|
||||
if (attr.valueType === '6') {
|
||||
row[key] = value ? JSON.stringify(value) : value
|
||||
} else if (attr.is_list && Array.isArray(value)) {
|
||||
row[key] = value.join(',')
|
||||
} else {
|
||||
row[key] = value
|
||||
}
|
||||
})
|
||||
ws.addRow(row)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.list-wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-shrink: 1 !important;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&-bg {
|
||||
width: 100%;
|
||||
padding-top: 90px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.list-tab {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
column-gap: 14px;
|
||||
|
||||
&-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 14px;
|
||||
row-gap: 7px;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&-label {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #4E5969;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #4E5969;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
|
||||
&-count {
|
||||
color: #2F54EB;
|
||||
}
|
||||
|
||||
&-active {
|
||||
color: #2F54EB;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #2F54EB;
|
||||
}
|
||||
}
|
||||
|
||||
&-export {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.list-container {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
flex-shrink: 1;
|
||||
flex-grow: 0;
|
||||
|
||||
.list-card {
|
||||
width: 100%;
|
||||
background-color: #FFF;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&-selected {
|
||||
border: 1px solid #7F97FA;
|
||||
background-color: #F9FBFF;
|
||||
}
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-model {
|
||||
border-radius: 24px;
|
||||
border: 1px solid #E4E7ED;
|
||||
background-color: #FFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 24px;
|
||||
padding: 0 13px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&-title {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
color: #1D2129;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
margin-left: 11px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #1D2129;
|
||||
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
&-collect {
|
||||
font-size: 12px;
|
||||
margin-left: 9px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-attr {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
height: 25px;
|
||||
column-gap: 40px;
|
||||
row-gap: 20px;
|
||||
margin-top: 12px;
|
||||
|
||||
&-item {
|
||||
flex-shrink: 0;
|
||||
max-width: calc((100% - 160px) / 5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
|
||||
&-label {
|
||||
color: #86909C;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&-value {
|
||||
color: #1D2129;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
margin-left: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 2px 12px 0px rgba(147, 168, 223, 0.20);
|
||||
|
||||
.list-card-collect {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,201 @@
|
|||
<template>
|
||||
<div :class="['search-input', classType ? 'search-input-' + classType : '']">
|
||||
<a-input
|
||||
:value="searchValue"
|
||||
class="search-input-component"
|
||||
:placeholder="$t('cmdb.ciType.searchInputTip')"
|
||||
@change="handleChangeSearchValue"
|
||||
@pressEnter="saveCondition(true)"
|
||||
>
|
||||
<a-icon
|
||||
class="search-input-component-icon"
|
||||
slot="prefix"
|
||||
type="search"
|
||||
@click="saveCondition(true)"
|
||||
/>
|
||||
</a-input>
|
||||
<FilterPopover
|
||||
ref="filterPpoverRef"
|
||||
:CITypeGroup="CITypeGroup"
|
||||
:allAttributesList="allAttributesList"
|
||||
:expression="expression"
|
||||
:selectCITypeIds="selectCITypeIds"
|
||||
@changeFilter="changeFilter"
|
||||
@updateAllAttributesList="updateAllAttributesList"
|
||||
@saveCondition="saveCondition"
|
||||
/>
|
||||
|
||||
<div v-if="copyText" class="expression-display">
|
||||
<span class="expression-display-text">{{ copyText }}</span>
|
||||
<a-icon
|
||||
slot="suffix"
|
||||
type="check-circle"
|
||||
class="expression-display-icon"
|
||||
@click="handleCopyExpression"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FilterPopover from './filterPopover.vue'
|
||||
|
||||
export default {
|
||||
name: 'SearchInput',
|
||||
components: {
|
||||
FilterPopover
|
||||
},
|
||||
props: {
|
||||
searchValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
expression: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
selectCITypeIds: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
CITypeGroup: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
allAttributesList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
classType: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
// 复制文字展示,与实际文本复制内容区别在于,未选择模型时不展示所有模型拼接数据
|
||||
copyText() {
|
||||
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = this.expression.match(regQ) ? this.expression.match(regQ)[0] : null
|
||||
|
||||
const textArray = []
|
||||
if (this.selectCITypeIds?.length) {
|
||||
textArray.push(`_type:(${this.selectCITypeIds.join(';')})`)
|
||||
}
|
||||
if (exp) {
|
||||
textArray.push(exp)
|
||||
}
|
||||
if (this.searchValue) {
|
||||
textArray.push(`*${this.searchValue}*`)
|
||||
}
|
||||
|
||||
return textArray.length ? `q=${textArray.join(',')}` : ''
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateAllAttributesList(value) {
|
||||
this.$emit('updateAllAttributesList', value)
|
||||
},
|
||||
saveCondition(isSubmit) {
|
||||
this.$emit('saveCondition', isSubmit)
|
||||
},
|
||||
handleChangeSearchValue(e) {
|
||||
const value = e.target.value
|
||||
this.changeFilter({
|
||||
name: 'searchValue',
|
||||
value
|
||||
})
|
||||
},
|
||||
|
||||
changeFilter(data) {
|
||||
this.$emit('changeFilter', data)
|
||||
},
|
||||
|
||||
handleCopyExpression() {
|
||||
const { selectCITypeIds, expression, searchValue } = this
|
||||
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||
|
||||
const ciTypeIds = [...selectCITypeIds]
|
||||
if (!ciTypeIds.length) {
|
||||
this.CITypeGroup.forEach((item) => {
|
||||
const ids = item.ci_types.map((ci_type) => ci_type.id)
|
||||
ciTypeIds.push(...ids)
|
||||
})
|
||||
}
|
||||
const copyText = `${ciTypeIds?.length ? `_type:(${ciTypeIds.join(';')})` : ''}${exp ? `,${exp}` : ''}${searchValue ? `,*${searchValue}*` : ''}`
|
||||
|
||||
this.$copyText(copyText)
|
||||
.then(() => {
|
||||
this.$message.success(this.$t('copySuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error(this.$t('cmdb.ci.copyFailed'))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search-input {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&-component {
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
background-color: #FFFFFF;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
border-radius: 48px;
|
||||
overflow: hidden;
|
||||
|
||||
&-icon {
|
||||
color: #2F54EB;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/deep/ & > input {
|
||||
height: 100%;
|
||||
margin-left: 10px;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-after {
|
||||
height: 38px;
|
||||
justify-content: flex-start;
|
||||
|
||||
.search-input-component {
|
||||
max-width: 524px;
|
||||
}
|
||||
}
|
||||
|
||||
.expression-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 20px;
|
||||
max-width: 30%;
|
||||
|
||||
&-text {
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
margin-left: 8px;
|
||||
color: #00b42a;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,615 @@
|
|||
<template>
|
||||
<div
|
||||
class="resource-search"
|
||||
:style="{ height: `${windowHeight - 93}px` }"
|
||||
>
|
||||
<div v-if="!isSearch" class="resource-search-before">
|
||||
<div class="resource-search-title">
|
||||
<ops-icon class="resource-search-title-icon" type="veops-resource11" />
|
||||
<span class="resource-search-title-text">{{ $t('cmdb.ciType.resourceSearch') }}</span>
|
||||
</div>
|
||||
<SearchInput
|
||||
ref="searchInputRef"
|
||||
:CITypeGroup="CITypeGroup"
|
||||
:allAttributesList="allAttributesList"
|
||||
:searchValue="searchValue"
|
||||
:selectCITypeIds="selectCITypeIds"
|
||||
:expression="expression"
|
||||
@changeFilter="changeFilter"
|
||||
@updateAllAttributesList="updateAllAttributesList"
|
||||
@saveCondition="saveCondition"
|
||||
/>
|
||||
<HistoryList
|
||||
:recentList="recentList"
|
||||
:favorList="favorList"
|
||||
:detailCIId="detailCIId"
|
||||
@clickRecent="clickRecent"
|
||||
@deleteRecent="deleteRecent"
|
||||
@clearRecent="clearRecent"
|
||||
@deleteCollect="deleteCollect"
|
||||
@showDetail="clickFavor"
|
||||
/>
|
||||
|
||||
<img class="resource-search-before-bg" :src="require('@/modules/cmdb/assets/resourceSearch/resource_search_bg_1.png')" />
|
||||
</div>
|
||||
|
||||
<div class="resource-search-after" v-else>
|
||||
<div
|
||||
class="resource-search-after-left"
|
||||
:style="{ width: showInstanceDetail ? '70%' : '100%' }"
|
||||
>
|
||||
<SearchInput
|
||||
ref="searchInputRef"
|
||||
classType="after"
|
||||
:CITypeGroup="CITypeGroup"
|
||||
:allAttributesList="allAttributesList"
|
||||
:searchValue="searchValue"
|
||||
:selectCITypeIds="selectCITypeIds"
|
||||
:expression="expression"
|
||||
@changeFilter="changeFilter"
|
||||
@updateAllAttributesList="updateAllAttributesList"
|
||||
@saveCondition="saveCondition"
|
||||
/>
|
||||
<HistoryList
|
||||
:recentList="recentList"
|
||||
:favorList="favorList"
|
||||
:detailCIId="detailCIId"
|
||||
@clickRecent="clickRecent"
|
||||
@deleteRecent="deleteRecent"
|
||||
@clearRecent="clearRecent"
|
||||
@deleteCollect="deleteCollect"
|
||||
@showDetail="clickFavor"
|
||||
/>
|
||||
<div class="resource-search-divider"></div>
|
||||
<InstanceList
|
||||
:list="instanceList"
|
||||
:tabList="ciTabList"
|
||||
:referenceShowAttrNameMap="referenceShowAttrNameMap"
|
||||
:referenceCIIdMap="referenceCIIdMap"
|
||||
:favorList="favorList"
|
||||
:detailCIId="detailCIId"
|
||||
:searchValue="currentSearchValue"
|
||||
@showDetail="showDetail"
|
||||
@addCollect="addCollect"
|
||||
@deleteCollect="deleteCollect"
|
||||
/>
|
||||
|
||||
<div class="resource-search-pagination">
|
||||
<a-pagination
|
||||
:showSizeChanger="true"
|
||||
:current="currentPage"
|
||||
size="small"
|
||||
:total="totalNumber"
|
||||
show-quick-jumper
|
||||
:page-size="pageSize"
|
||||
:page-size-options="pageSizeOptions"
|
||||
@showSizeChange="handlePageSizeChange"
|
||||
:show-total="
|
||||
(total, range) =>
|
||||
$t('pagination.total', {
|
||||
range0: range[0],
|
||||
range1: range[1],
|
||||
total,
|
||||
})
|
||||
"
|
||||
@change="changePage"
|
||||
>
|
||||
<template slot="buildOptionText" slot-scope="props">
|
||||
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
|
||||
<span v-if="props.value === '100000'">{{ $t('all') }}</span>
|
||||
</template>
|
||||
</a-pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showInstanceDetail" class="resource-search-after-right">
|
||||
<InstanceDetail
|
||||
:CIId="detailCIId"
|
||||
:CITypeId="detailCITypeId"
|
||||
:favorList="favorList"
|
||||
@addCollect="addCollect"
|
||||
@deleteCollect="deleteCollect"
|
||||
@hideDetail="hideDetail"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { getPreferenceSearch, savePreferenceSearch, getSubscribeAttributes, deletePreferenceSearch } from '@/modules/cmdb/api/preference'
|
||||
import { getCITypeGroups } from '@/modules/cmdb/api/ciTypeGroup'
|
||||
import { searchAttributes, getCITypeAttributesByTypeIds } from '@/modules/cmdb/api/CITypeAttr'
|
||||
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
|
||||
import SearchInput from './components/searchInput.vue'
|
||||
import HistoryList from './components/historyList.vue'
|
||||
import InstanceList from './components/instanceList.vue'
|
||||
import InstanceDetail from './components/instanceDetail.vue'
|
||||
|
||||
export default {
|
||||
name: 'ResourceSearch',
|
||||
components: {
|
||||
SearchInput,
|
||||
HistoryList,
|
||||
InstanceList,
|
||||
InstanceDetail
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 筛选条件
|
||||
searchValue: '', // 搜索框
|
||||
selectCITypeIds: [], // 已选模型
|
||||
expression: '', // 筛选语句
|
||||
currentSearchValue: '', // 当前已搜索语句
|
||||
|
||||
recentList: [], // 最近搜索
|
||||
favorList: [], // 我的收藏
|
||||
CITypeGroup: [], // CIType 分组
|
||||
CITypes: [],
|
||||
allAttributesList: [],
|
||||
|
||||
isSearch: false, // 是否搜索过
|
||||
currentPage: 1,
|
||||
pageSizeOptions: ['50', '100', '200', '100000'],
|
||||
pageSize: 50,
|
||||
totalNumber: 0,
|
||||
ciTabList: [],
|
||||
instanceList: [],
|
||||
referenceShowAttrNameMap: {},
|
||||
referenceCIIdMap: {},
|
||||
|
||||
showInstanceDetail: false,
|
||||
detailCIId: -1,
|
||||
detailCITypeId: -1,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initData()
|
||||
},
|
||||
methods: {
|
||||
async initData() {
|
||||
await this.getRecentList()
|
||||
await this.getFavorList()
|
||||
await this.getCITypeGroups()
|
||||
await this.getAllCITypes()
|
||||
await this.getAllAttr()
|
||||
},
|
||||
|
||||
async getRecentList() {
|
||||
const recentList = await getPreferenceSearch({
|
||||
name: '__recent__'
|
||||
})
|
||||
recentList.sort((a, b) => b.id - a.id)
|
||||
this.recentList = recentList
|
||||
},
|
||||
|
||||
async getFavorList() {
|
||||
const favorList = await getPreferenceSearch({
|
||||
name: '__favor__'
|
||||
})
|
||||
favorList.sort((a, b) => b.id - a.id)
|
||||
this.favorList = favorList
|
||||
},
|
||||
|
||||
async getCITypeGroups() {
|
||||
const res = await getCITypeGroups({ need_other: true })
|
||||
|
||||
this.CITypeGroup = res
|
||||
.filter((item) => item.ci_types && item.ci_types.length)
|
||||
.map((item) => {
|
||||
item.id = `parent_${item.id || -1}`
|
||||
return { ..._.cloneDeep(item) }
|
||||
})
|
||||
},
|
||||
|
||||
async getAllCITypes() {
|
||||
const res = await getCITypes()
|
||||
this.CITypes = res?.ci_types
|
||||
},
|
||||
|
||||
async getAllAttr() {
|
||||
const res = await searchAttributes({ page_size: 9999 })
|
||||
this.allAttributesList = res.attributes
|
||||
this.originAllAttributesList = res.attributes
|
||||
},
|
||||
|
||||
async updateAllAttributesList(value) {
|
||||
if (value && value.length) {
|
||||
const res = await getCITypeAttributesByTypeIds({ type_ids: value.join(',') })
|
||||
this.allAttributesList = res.attributes
|
||||
} else {
|
||||
this.allAttributesList = this.originAllAttributesList
|
||||
}
|
||||
},
|
||||
|
||||
async saveCondition(isSubmit) {
|
||||
if (
|
||||
this.searchValue ||
|
||||
this.expression ||
|
||||
this.selectCITypeIds.length
|
||||
) {
|
||||
const needDeleteList = []
|
||||
const differentList = []
|
||||
this.recentList.forEach((item) => {
|
||||
const option = item.option
|
||||
if (
|
||||
option.searchValue === this.searchValue &&
|
||||
option.expression === this.expression &&
|
||||
_.isEqual(option.ciTypeIds, this.selectCITypeIds)
|
||||
) {
|
||||
needDeleteList.push(item.id)
|
||||
} else {
|
||||
differentList.push(item.id)
|
||||
}
|
||||
})
|
||||
if (differentList.length >= 10) {
|
||||
needDeleteList.push(...differentList.slice(9))
|
||||
}
|
||||
if (needDeleteList.length) {
|
||||
await Promise.all(
|
||||
needDeleteList.map((id) => deletePreferenceSearch(id))
|
||||
)
|
||||
}
|
||||
|
||||
const ciTypeNames = this.selectCITypeIds.map((id) => {
|
||||
const ciType = this.CITypes.find((item) => item.id === id)
|
||||
return ciType?.alias || ciType?.name || id
|
||||
})
|
||||
|
||||
await savePreferenceSearch({
|
||||
option: {
|
||||
searchValue: this.searchValue,
|
||||
expression: this.expression,
|
||||
ciTypeIds: this.selectCITypeIds,
|
||||
ciTypeNames
|
||||
},
|
||||
name: '__recent__'
|
||||
})
|
||||
this.getRecentList()
|
||||
}
|
||||
|
||||
if (isSubmit) {
|
||||
this.isSearch = true
|
||||
this.currentPage = 1
|
||||
this.hideDetail()
|
||||
this.loadInstance()
|
||||
}
|
||||
},
|
||||
|
||||
async deleteRecent(id) {
|
||||
await deletePreferenceSearch(id)
|
||||
this.getRecentList()
|
||||
},
|
||||
|
||||
async clearRecent() {
|
||||
const deletePromises = this.recentList.map((item) => {
|
||||
return deletePreferenceSearch(item.id)
|
||||
})
|
||||
await Promise.all(deletePromises)
|
||||
this.getRecentList()
|
||||
},
|
||||
|
||||
async loadInstance() {
|
||||
const { selectCITypeIds, expression, searchValue } = this
|
||||
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||
|
||||
const ciTypeIds = [...selectCITypeIds]
|
||||
if (!ciTypeIds.length) {
|
||||
this.CITypeGroup.forEach((item) => {
|
||||
const ids = item.ci_types.map((ci_type) => ci_type.id)
|
||||
ciTypeIds.push(...ids)
|
||||
})
|
||||
}
|
||||
|
||||
const res = await searchCI({
|
||||
q: `${ciTypeIds?.length ? `_type:(${ciTypeIds.join(';')})` : ''}${exp ? `,${exp}` : ''}${
|
||||
searchValue ? `,*${searchValue}*` : ''
|
||||
}`,
|
||||
count: this.pageSize,
|
||||
page: this.currentPage,
|
||||
sort: '_type'
|
||||
})
|
||||
this.currentSearchValue = searchValue
|
||||
|
||||
this.totalNumber = res?.numfound ?? 0
|
||||
if (!res?.result?.length) {
|
||||
this.ciTabList = []
|
||||
this.instanceList = []
|
||||
}
|
||||
|
||||
const ciTabMap = new Map()
|
||||
|
||||
let list = res.result
|
||||
list.forEach((item) => {
|
||||
const ciType = this.CITypes.find((type) => type.id === item._type)
|
||||
if (ciTabMap.has(item._type)) {
|
||||
ciTabMap.get(item._type).count++
|
||||
} else {
|
||||
ciTabMap.set(item._type, {
|
||||
id: item._type,
|
||||
count: 1,
|
||||
title: ciType?.alias || ciType?.name || '',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const mapEntries = [...ciTabMap.entries()]
|
||||
const subscribedPromises = mapEntries.map((item) => {
|
||||
return getSubscribeAttributes(item[0])
|
||||
})
|
||||
const subscribedRes = await Promise.all(subscribedPromises)
|
||||
list = list.map((item) => {
|
||||
const subscribedIndex = mapEntries.findIndex((mapValue) => mapValue[0] === item._type)
|
||||
const subscribedAttr = subscribedRes?.[subscribedIndex]?.attributes || []
|
||||
const obj = {
|
||||
ci: item,
|
||||
ciTypeObj: {},
|
||||
attributes: subscribedAttr
|
||||
}
|
||||
|
||||
const ciType = this.CITypes.find((type) => type.id === item._type)
|
||||
obj.ciTypeObj = {
|
||||
showAttrName: ciType?.show_name || ciType?.unique_key || '',
|
||||
icon: ciType?.icon || '',
|
||||
title: ciType?.alias || ciType?.name || '',
|
||||
name: ciType?.name || '',
|
||||
id: ciType.id
|
||||
}
|
||||
|
||||
return obj
|
||||
})
|
||||
|
||||
this.instanceList = list
|
||||
const ciTabList = [...ciTabMap.values()]
|
||||
if (list?.length) {
|
||||
ciTabList.unshift({
|
||||
id: -1,
|
||||
title: this.$t('all'),
|
||||
count: list?.length
|
||||
})
|
||||
}
|
||||
this.ciTabList = ciTabList
|
||||
|
||||
// 处理引用属性
|
||||
const allAttr = []
|
||||
subscribedRes.map((item) => {
|
||||
allAttr.push(...item.attributes)
|
||||
})
|
||||
this.handlePerference(_.uniqBy(allAttr, 'id'))
|
||||
},
|
||||
|
||||
handlePerference(allAttr) {
|
||||
let needRequiredCIType = []
|
||||
allAttr.forEach((attr) => {
|
||||
if (attr?.is_reference && attr?.reference_type_id) {
|
||||
needRequiredCIType.push(attr)
|
||||
}
|
||||
})
|
||||
needRequiredCIType = _.uniq(needRequiredCIType, 'id')
|
||||
|
||||
if (!needRequiredCIType.length) {
|
||||
this.referenceShowAttrNameMap = {}
|
||||
this.referenceCIIdMap = {}
|
||||
return
|
||||
}
|
||||
|
||||
this.handleReferenceShowAttrName(needRequiredCIType)
|
||||
this.handleReferenceCIIdMap(needRequiredCIType)
|
||||
},
|
||||
|
||||
async handleReferenceShowAttrName(needRequiredCIType) {
|
||||
const res = await getCITypes({
|
||||
type_ids: needRequiredCIType.map((col) => col.reference_type_id).join(',')
|
||||
})
|
||||
|
||||
const map = {}
|
||||
res.ci_types.forEach((ciType) => {
|
||||
map[ciType.id] = ciType?.show_name || ciType?.unique_name || ''
|
||||
})
|
||||
|
||||
this.referenceShowAttrNameMap = map
|
||||
},
|
||||
|
||||
async handleReferenceCIIdMap(needRequiredCIType) {
|
||||
const map = {}
|
||||
this.instanceList.forEach(({ ci }) => {
|
||||
needRequiredCIType.forEach((col) => {
|
||||
const ids = Array.isArray(ci[col.name]) ? ci[col.name] : ci[col.name] ? [ci[col.name]] : []
|
||||
if (ids.length) {
|
||||
if (!map?.[col.reference_type_id]) {
|
||||
map[col.reference_type_id] = {}
|
||||
}
|
||||
ids.forEach((id) => {
|
||||
map[col.reference_type_id][id] = {}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (!Object.keys(map).length) {
|
||||
this.referenceCIIdMap = {}
|
||||
return
|
||||
}
|
||||
|
||||
const allRes = await Promise.all(
|
||||
Object.keys(map).map((key) => {
|
||||
return searchCI({
|
||||
q: `_type:${key},_id:(${Object.keys(map[key]).join(';')})`,
|
||||
count: 9999
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
allRes.forEach((res) => {
|
||||
res.result.forEach((item) => {
|
||||
if (map?.[item._type]?.[item._id]) {
|
||||
map[item._type][item._id] = item
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.referenceCIIdMap = map
|
||||
},
|
||||
|
||||
clickRecent(data) {
|
||||
this.updateAllAttributesList(data.ciTypeIds || [])
|
||||
this.isSearch = true
|
||||
this.currentPage = 1
|
||||
this.searchValue = data?.searchValue || ''
|
||||
this.expression = data?.expression || ''
|
||||
this.selectCITypeIds = data?.ciTypeIds || []
|
||||
|
||||
this.hideDetail()
|
||||
this.loadInstance()
|
||||
},
|
||||
|
||||
handlePageSizeChange(_, pageSize) {
|
||||
this.pageSize = pageSize
|
||||
this.currentPage = 1
|
||||
this.loadInstance()
|
||||
},
|
||||
|
||||
changePage(page) {
|
||||
this.currentPage = page
|
||||
this.loadInstance()
|
||||
},
|
||||
|
||||
changeFilter(data) {
|
||||
this[data.name] = data.value
|
||||
},
|
||||
|
||||
showDetail(data) {
|
||||
this.detailCIId = data.id
|
||||
this.detailCITypeId = data.ciTypeId
|
||||
this.showInstanceDetail = true
|
||||
},
|
||||
|
||||
hideDetail() {
|
||||
this.detailCIId = -1
|
||||
this.detailCITypeId = -1
|
||||
this.showInstanceDetail = false
|
||||
},
|
||||
|
||||
async addCollect(data) {
|
||||
if (this?.favorList?.length >= 10) {
|
||||
const deletePromises = this.favorList.slice(9).map((item) => {
|
||||
return deletePreferenceSearch(item.id)
|
||||
})
|
||||
await Promise.all(deletePromises)
|
||||
}
|
||||
await savePreferenceSearch({
|
||||
option: {
|
||||
...data
|
||||
},
|
||||
name: '__favor__'
|
||||
})
|
||||
this.getFavorList()
|
||||
},
|
||||
|
||||
async deleteCollect(id) {
|
||||
await deletePreferenceSearch(id)
|
||||
this.getFavorList()
|
||||
},
|
||||
|
||||
clickFavor(data) {
|
||||
this.isSearch = true
|
||||
this.showDetail(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.resource-search {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
&-before {
|
||||
width: 100%;
|
||||
max-width: 725px;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
padding-top: 100px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
& > div {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&-bg {
|
||||
position: absolute;
|
||||
left: -24px;
|
||||
bottom: -24px;
|
||||
width: calc(100% + 48px);
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 25px;
|
||||
|
||||
&-icon {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
margin-left: 10px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #1D2129;
|
||||
}
|
||||
}
|
||||
|
||||
&-after {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&-left {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > div {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-right {
|
||||
margin-left: 20px;
|
||||
width: calc(30% - 20px);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #E4E7ED;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
&-pagination {
|
||||
text-align: right;
|
||||
margin: 12px 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue