Merge pull request #609 from veops/dev_ui_240903

feat: update resource search
This commit is contained in:
Leo Song 2024-09-03 11:30:08 +08:00 committed by GitHub
commit d3080bad3c
35 changed files with 4079 additions and 213 deletions

View File

@ -54,93 +54,171 @@
<div class="content unicode" style="display: block;"> <div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe9b6;</span>
<div class="name">veops-expand</div>
<div class="code-name">&amp;#xe9b6;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9b1;</span>
<div class="name">公有云</div>
<div class="code-name">&amp;#xe9b1;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9b2;</span>
<div class="name">操作系统</div>
<div class="code-name">&amp;#xe9b2;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9b3;</span>
<div class="name">IPAM</div>
<div class="code-name">&amp;#xe9b3;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9b4;</span>
<div class="name">hyperV</div>
<div class="code-name">&amp;#xe9b4;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9b5;</span>
<div class="name">数据中心</div>
<div class="code-name">&amp;#xe9b5;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9ad;</span>
<div class="name">硬件设备</div>
<div class="code-name">&amp;#xe9ad;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9ae;</span>
<div class="name">计算机</div>
<div class="code-name">&amp;#xe9ae;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9af;</span>
<div class="name">网络设备</div>
<div class="code-name">&amp;#xe9af;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9b0;</span>
<div class="name">存储设备</div>
<div class="code-name">&amp;#xe9b0;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9ab;</span>
<div class="name">负载均衡</div>
<div class="code-name">&amp;#xe9ab;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9ac;</span>
<div class="name">消息队列</div>
<div class="code-name">&amp;#xe9ac;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9aa;</span>
<div class="name">websever</div>
<div class="code-name">&amp;#xe9aa;</div>
</li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe9a9;</span> <span class="icon iconfont">&#xe9a9;</span>
<div class="name">caise-middleware</div> <div class="name">中间件</div>
<div class="code-name">&amp;#xe9a9;</div> <div class="code-name">&amp;#xe9a9;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe9a7;</span> <span class="icon iconfont">&#xe9a7;</span>
<div class="name">caise-database</div> <div class="name">数据库</div>
<div class="code-name">&amp;#xe9a7;</div> <div class="code-name">&amp;#xe9a7;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe9a8;</span> <span class="icon iconfont">&#xe9a8;</span>
<div class="name">caise-business</div> <div class="name">业务</div>
<div class="code-name">&amp;#xe9a8;</div> <div class="code-name">&amp;#xe9a8;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe9a6;</span> <span class="icon iconfont">&#xe9a6;</span>
<div class="name">caise- virtualization</div> <div class="name">虚拟化</div>
<div class="code-name">&amp;#xe9a6;</div> <div class="code-name">&amp;#xe9a6;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe9a4;</span> <span class="icon iconfont">&#xe9a4;</span>
<div class="name">caise-storage_pool</div> <div class="name">存储池</div>
<div class="code-name">&amp;#xe9a4;</div> <div class="code-name">&amp;#xe9a4;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe9a5;</span> <span class="icon iconfont">&#xe9a5;</span>
<div class="name">caise-storage_volume</div> <div class="name">存储卷</div>
<div class="code-name">&amp;#xe9a5;</div> <div class="code-name">&amp;#xe9a5;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe9a3;</span> <span class="icon iconfont">&#xe9a3;</span>
<div class="name">ciase-aix</div> <div class="name">aix</div>
<div class="code-name">&amp;#xe9a3;</div> <div class="code-name">&amp;#xe9a3;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe99b;</span> <span class="icon iconfont">&#xe99b;</span>
<div class="name">caise_pool</div> <div class="name">ip池</div>
<div class="code-name">&amp;#xe99b;</div> <div class="code-name">&amp;#xe99b;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe99c;</span> <span class="icon iconfont">&#xe99c;</span>
<div class="name">caise-ip_address</div> <div class="name">ip地址</div>
<div class="code-name">&amp;#xe99c;</div> <div class="code-name">&amp;#xe99c;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe99d;</span> <span class="icon iconfont">&#xe99d;</span>
<div class="name">caise-computer_room</div> <div class="name">机房</div>
<div class="code-name">&amp;#xe99d;</div> <div class="code-name">&amp;#xe99d;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe99e;</span> <span class="icon iconfont">&#xe99e;</span>
<div class="name">caise-rack</div> <div class="name">机柜</div>
<div class="code-name">&amp;#xe99e;</div> <div class="code-name">&amp;#xe99e;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe99f;</span> <span class="icon iconfont">&#xe99f;</span>
<div class="name">caise-pc</div> <div class="name">PC</div>
<div class="code-name">&amp;#xe99f;</div> <div class="code-name">&amp;#xe99f;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe9a0;</span> <span class="icon iconfont">&#xe9a0;</span>
<div class="name">caise-bandwidth_line</div> <div class="name">带宽线路</div>
<div class="code-name">&amp;#xe9a0;</div> <div class="code-name">&amp;#xe9a0;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe9a1;</span> <span class="icon iconfont">&#xe9a1;</span>
<div class="name">caise-fiber</div> <div class="name">光纤交换机</div>
<div class="code-name">&amp;#xe9a1;</div> <div class="code-name">&amp;#xe9a1;</div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe9a2;</span> <span class="icon iconfont">&#xe9a2;</span>
<div class="name">caise-disk_array</div> <div class="name">磁盘阵列</div>
<div class="code-name">&amp;#xe9a2;</div> <div class="code-name">&amp;#xe9a2;</div>
</li> </li>
@ -5622,9 +5700,9 @@
<pre><code class="language-css" <pre><code class="language-css"
>@font-face { >@font-face {
font-family: 'iconfont'; font-family: 'iconfont';
src: url('iconfont.woff2?t=1724834571283') format('woff2'), src: url('iconfont.woff2?t=1725331691589') format('woff2'),
url('iconfont.woff?t=1724834571283') format('woff'), url('iconfont.woff?t=1725331691589') format('woff'),
url('iconfont.ttf?t=1724834571283') format('truetype'); url('iconfont.ttf?t=1725331691589') format('truetype');
} }
</code></pre> </code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3> <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@ -5650,10 +5728,127 @@
<div class="content font-class"> <div class="content font-class">
<ul class="icon_lists dib-box"> <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"> <li class="dib">
<span class="icon iconfont caise-middleware"></span> <span class="icon iconfont caise-middleware"></span>
<div class="name"> <div class="name">
caise-middleware 中间件
</div> </div>
<div class="code-name">.caise-middleware <div class="code-name">.caise-middleware
</div> </div>
@ -5662,7 +5857,7 @@
<li class="dib"> <li class="dib">
<span class="icon iconfont caise-database"></span> <span class="icon iconfont caise-database"></span>
<div class="name"> <div class="name">
caise-database 数据库
</div> </div>
<div class="code-name">.caise-database <div class="code-name">.caise-database
</div> </div>
@ -5671,43 +5866,43 @@
<li class="dib"> <li class="dib">
<span class="icon iconfont caise-business"></span> <span class="icon iconfont caise-business"></span>
<div class="name"> <div class="name">
caise-business 业务
</div> </div>
<div class="code-name">.caise-business <div class="code-name">.caise-business
</div> </div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont a-caise-virtualization"></span> <span class="icon iconfont caise-virtualization"></span>
<div class="name"> <div class="name">
caise- virtualization 虚拟化
</div> </div>
<div class="code-name">.a-caise-virtualization <div class="code-name">.caise-virtualization
</div> </div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont caise-storage_pool"></span> <span class="icon iconfont caise-storage_pool"></span>
<div class="name"> <div class="name">
caise-storage_pool 存储池
</div> </div>
<div class="code-name">.caise-storage_pool <div class="code-name">.caise-storage_pool
</div> </div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont a-caise-storage_volume1"></span> <span class="icon iconfont caise-storage_volume1"></span>
<div class="name"> <div class="name">
caise-storage_volume 存储卷
</div> </div>
<div class="code-name">.a-caise-storage_volume1 <div class="code-name">.caise-storage_volume1
</div> </div>
</li> </li>
<li class="dib"> <li class="dib">
<span class="icon iconfont ciase-aix"></span> <span class="icon iconfont ciase-aix"></span>
<div class="name"> <div class="name">
ciase-aix aix
</div> </div>
<div class="code-name">.ciase-aix <div class="code-name">.ciase-aix
</div> </div>
@ -5716,7 +5911,7 @@
<li class="dib"> <li class="dib">
<span class="icon iconfont caise_pool"></span> <span class="icon iconfont caise_pool"></span>
<div class="name"> <div class="name">
caise_pool ip池
</div> </div>
<div class="code-name">.caise_pool <div class="code-name">.caise_pool
</div> </div>
@ -5725,7 +5920,7 @@
<li class="dib"> <li class="dib">
<span class="icon iconfont caise-ip_address"></span> <span class="icon iconfont caise-ip_address"></span>
<div class="name"> <div class="name">
caise-ip_address ip地址
</div> </div>
<div class="code-name">.caise-ip_address <div class="code-name">.caise-ip_address
</div> </div>
@ -5734,7 +5929,7 @@
<li class="dib"> <li class="dib">
<span class="icon iconfont caise-computer_room"></span> <span class="icon iconfont caise-computer_room"></span>
<div class="name"> <div class="name">
caise-computer_room 机房
</div> </div>
<div class="code-name">.caise-computer_room <div class="code-name">.caise-computer_room
</div> </div>
@ -5743,7 +5938,7 @@
<li class="dib"> <li class="dib">
<span class="icon iconfont caise-rack"></span> <span class="icon iconfont caise-rack"></span>
<div class="name"> <div class="name">
caise-rack 机柜
</div> </div>
<div class="code-name">.caise-rack <div class="code-name">.caise-rack
</div> </div>
@ -5752,7 +5947,7 @@
<li class="dib"> <li class="dib">
<span class="icon iconfont caise-pc"></span> <span class="icon iconfont caise-pc"></span>
<div class="name"> <div class="name">
caise-pc PC
</div> </div>
<div class="code-name">.caise-pc <div class="code-name">.caise-pc
</div> </div>
@ -5761,7 +5956,7 @@
<li class="dib"> <li class="dib">
<span class="icon iconfont caise-bandwidth_line"></span> <span class="icon iconfont caise-bandwidth_line"></span>
<div class="name"> <div class="name">
caise-bandwidth_line 带宽线路
</div> </div>
<div class="code-name">.caise-bandwidth_line <div class="code-name">.caise-bandwidth_line
</div> </div>
@ -5770,7 +5965,7 @@
<li class="dib"> <li class="dib">
<span class="icon iconfont caise-fiber"></span> <span class="icon iconfont caise-fiber"></span>
<div class="name"> <div class="name">
caise-fiber 光纤交换机
</div> </div>
<div class="code-name">.caise-fiber <div class="code-name">.caise-fiber
</div> </div>
@ -5779,7 +5974,7 @@
<li class="dib"> <li class="dib">
<span class="icon iconfont caise-disk_array"></span> <span class="icon iconfont caise-disk_array"></span>
<div class="name"> <div class="name">
caise-disk_array 磁盘阵列
</div> </div>
<div class="code-name">.caise-disk_array <div class="code-name">.caise-disk_array
</div> </div>
@ -14002,11 +14197,115 @@
<div class="content symbol"> <div class="content symbol">
<ul class="icon_lists dib-box"> <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"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-middleware"></use> <use xlink:href="#caise-middleware"></use>
</svg> </svg>
<div class="name">caise-middleware</div> <div class="name">中间件</div>
<div class="code-name">#caise-middleware</div> <div class="code-name">#caise-middleware</div>
</li> </li>
@ -14014,7 +14313,7 @@
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-database"></use> <use xlink:href="#caise-database"></use>
</svg> </svg>
<div class="name">caise-database</div> <div class="name">数据库</div>
<div class="code-name">#caise-database</div> <div class="code-name">#caise-database</div>
</li> </li>
@ -14022,39 +14321,39 @@
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-business"></use> <use xlink:href="#caise-business"></use>
</svg> </svg>
<div class="name">caise-business</div> <div class="name">业务</div>
<div class="code-name">#caise-business</div> <div class="code-name">#caise-business</div>
</li> </li>
<li class="dib"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#a-caise-virtualization"></use> <use xlink:href="#caise-virtualization"></use>
</svg> </svg>
<div class="name">caise- virtualization</div> <div class="name">虚拟化</div>
<div class="code-name">#a-caise-virtualization</div> <div class="code-name">#caise-virtualization</div>
</li> </li>
<li class="dib"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-storage_pool"></use> <use xlink:href="#caise-storage_pool"></use>
</svg> </svg>
<div class="name">caise-storage_pool</div> <div class="name">存储池</div>
<div class="code-name">#caise-storage_pool</div> <div class="code-name">#caise-storage_pool</div>
</li> </li>
<li class="dib"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#a-caise-storage_volume1"></use> <use xlink:href="#caise-storage_volume1"></use>
</svg> </svg>
<div class="name">caise-storage_volume</div> <div class="name">存储卷</div>
<div class="code-name">#a-caise-storage_volume1</div> <div class="code-name">#caise-storage_volume1</div>
</li> </li>
<li class="dib"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#ciase-aix"></use> <use xlink:href="#ciase-aix"></use>
</svg> </svg>
<div class="name">ciase-aix</div> <div class="name">aix</div>
<div class="code-name">#ciase-aix</div> <div class="code-name">#ciase-aix</div>
</li> </li>
@ -14062,7 +14361,7 @@
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise_pool"></use> <use xlink:href="#caise_pool"></use>
</svg> </svg>
<div class="name">caise_pool</div> <div class="name">ip池</div>
<div class="code-name">#caise_pool</div> <div class="code-name">#caise_pool</div>
</li> </li>
@ -14070,7 +14369,7 @@
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-ip_address"></use> <use xlink:href="#caise-ip_address"></use>
</svg> </svg>
<div class="name">caise-ip_address</div> <div class="name">ip地址</div>
<div class="code-name">#caise-ip_address</div> <div class="code-name">#caise-ip_address</div>
</li> </li>
@ -14078,7 +14377,7 @@
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-computer_room"></use> <use xlink:href="#caise-computer_room"></use>
</svg> </svg>
<div class="name">caise-computer_room</div> <div class="name">机房</div>
<div class="code-name">#caise-computer_room</div> <div class="code-name">#caise-computer_room</div>
</li> </li>
@ -14086,7 +14385,7 @@
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-rack"></use> <use xlink:href="#caise-rack"></use>
</svg> </svg>
<div class="name">caise-rack</div> <div class="name">机柜</div>
<div class="code-name">#caise-rack</div> <div class="code-name">#caise-rack</div>
</li> </li>
@ -14094,7 +14393,7 @@
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-pc"></use> <use xlink:href="#caise-pc"></use>
</svg> </svg>
<div class="name">caise-pc</div> <div class="name">PC</div>
<div class="code-name">#caise-pc</div> <div class="code-name">#caise-pc</div>
</li> </li>
@ -14102,7 +14401,7 @@
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-bandwidth_line"></use> <use xlink:href="#caise-bandwidth_line"></use>
</svg> </svg>
<div class="name">caise-bandwidth_line</div> <div class="name">带宽线路</div>
<div class="code-name">#caise-bandwidth_line</div> <div class="code-name">#caise-bandwidth_line</div>
</li> </li>
@ -14110,7 +14409,7 @@
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-fiber"></use> <use xlink:href="#caise-fiber"></use>
</svg> </svg>
<div class="name">caise-fiber</div> <div class="name">光纤交换机</div>
<div class="code-name">#caise-fiber</div> <div class="code-name">#caise-fiber</div>
</li> </li>
@ -14118,7 +14417,7 @@
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-disk_array"></use> <use xlink:href="#caise-disk_array"></use>
</svg> </svg>
<div class="name">caise-disk_array</div> <div class="name">磁盘阵列</div>
<div class="code-name">#caise-disk_array</div> <div class="code-name">#caise-disk_array</div>
</li> </li>

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 3857903 */ font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1724834571283') format('woff2'), src: url('iconfont.woff2?t=1725331691589') format('woff2'),
url('iconfont.woff?t=1724834571283') format('woff'), url('iconfont.woff?t=1725331691589') format('woff'),
url('iconfont.ttf?t=1724834571283') format('truetype'); url('iconfont.ttf?t=1725331691589') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,58 @@
-moz-osx-font-smoothing: grayscale; -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 { .caise-middleware:before {
content: "\e9a9"; content: "\e9a9";
} }
@ -25,7 +77,7 @@
content: "\e9a8"; content: "\e9a8";
} }
.a-caise-virtualization:before { .caise-virtualization:before {
content: "\e9a6"; content: "\e9a6";
} }
@ -33,7 +85,7 @@
content: "\e9a4"; content: "\e9a4";
} }
.a-caise-storage_volume1:before { .caise-storage_volume1:before {
content: "\e9a5"; content: "\e9a5";
} }

File diff suppressed because one or more lines are too long

View File

@ -5,107 +5,198 @@
"css_prefix_text": "", "css_prefix_text": "",
"description": "", "description": "",
"glyphs": [ "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", "icon_id": "41655608",
"name": "caise-middleware", "name": "中间件",
"font_class": "caise-middleware", "font_class": "caise-middleware",
"unicode": "e9a9", "unicode": "e9a9",
"unicode_decimal": 59817 "unicode_decimal": 59817
}, },
{ {
"icon_id": "41655599", "icon_id": "41655599",
"name": "caise-database", "name": "数据库",
"font_class": "caise-database", "font_class": "caise-database",
"unicode": "e9a7", "unicode": "e9a7",
"unicode_decimal": 59815 "unicode_decimal": 59815
}, },
{ {
"icon_id": "41655591", "icon_id": "41655591",
"name": "caise-business", "name": "业务",
"font_class": "caise-business", "font_class": "caise-business",
"unicode": "e9a8", "unicode": "e9a8",
"unicode_decimal": 59816 "unicode_decimal": 59816
}, },
{ {
"icon_id": "41655550", "icon_id": "41655550",
"name": "caise- virtualization", "name": "虚拟化",
"font_class": "a-caise-virtualization", "font_class": "caise-virtualization",
"unicode": "e9a6", "unicode": "e9a6",
"unicode_decimal": 59814 "unicode_decimal": 59814
}, },
{ {
"icon_id": "41654680", "icon_id": "41654680",
"name": "caise-storage_pool", "name": "存储池",
"font_class": "caise-storage_pool", "font_class": "caise-storage_pool",
"unicode": "e9a4", "unicode": "e9a4",
"unicode_decimal": 59812 "unicode_decimal": 59812
}, },
{ {
"icon_id": "41654676", "icon_id": "41654676",
"name": "caise-storage_volume", "name": "存储卷",
"font_class": "a-caise-storage_volume1", "font_class": "caise-storage_volume1",
"unicode": "e9a5", "unicode": "e9a5",
"unicode_decimal": 59813 "unicode_decimal": 59813
}, },
{ {
"icon_id": "41654608", "icon_id": "41654608",
"name": "ciase-aix", "name": "aix",
"font_class": "ciase-aix", "font_class": "ciase-aix",
"unicode": "e9a3", "unicode": "e9a3",
"unicode_decimal": 59811 "unicode_decimal": 59811
}, },
{ {
"icon_id": "41654233", "icon_id": "41654233",
"name": "caise_pool", "name": "ip池",
"font_class": "caise_pool", "font_class": "caise_pool",
"unicode": "e99b", "unicode": "e99b",
"unicode_decimal": 59803 "unicode_decimal": 59803
}, },
{ {
"icon_id": "41654237", "icon_id": "41654237",
"name": "caise-ip_address", "name": "ip地址",
"font_class": "caise-ip_address", "font_class": "caise-ip_address",
"unicode": "e99c", "unicode": "e99c",
"unicode_decimal": 59804 "unicode_decimal": 59804
}, },
{ {
"icon_id": "41654249", "icon_id": "41654249",
"name": "caise-computer_room", "name": "机房",
"font_class": "caise-computer_room", "font_class": "caise-computer_room",
"unicode": "e99d", "unicode": "e99d",
"unicode_decimal": 59805 "unicode_decimal": 59805
}, },
{ {
"icon_id": "41654271", "icon_id": "41654271",
"name": "caise-rack", "name": "机柜",
"font_class": "caise-rack", "font_class": "caise-rack",
"unicode": "e99e", "unicode": "e99e",
"unicode_decimal": 59806 "unicode_decimal": 59806
}, },
{ {
"icon_id": "41654276", "icon_id": "41654276",
"name": "caise-pc", "name": "PC",
"font_class": "caise-pc", "font_class": "caise-pc",
"unicode": "e99f", "unicode": "e99f",
"unicode_decimal": 59807 "unicode_decimal": 59807
}, },
{ {
"icon_id": "41654305", "icon_id": "41654305",
"name": "caise-bandwidth_line", "name": "带宽线路",
"font_class": "caise-bandwidth_line", "font_class": "caise-bandwidth_line",
"unicode": "e9a0", "unicode": "e9a0",
"unicode_decimal": 59808 "unicode_decimal": 59808
}, },
{ {
"icon_id": "41654323", "icon_id": "41654323",
"name": "caise-fiber", "name": "光纤交换机",
"font_class": "caise-fiber", "font_class": "caise-fiber",
"unicode": "e9a1", "unicode": "e9a1",
"unicode_decimal": 59809 "unicode_decimal": 59809
}, },
{ {
"icon_id": "41654369", "icon_id": "41654369",
"name": "caise-disk_array", "name": "磁盘阵列",
"font_class": "caise-disk_array", "font_class": "caise-disk_array",
"unicode": "e9a2", "unicode": "e9a2",
"unicode_decimal": 59810 "unicode_decimal": 59810

Binary file not shown.

View File

@ -61,12 +61,12 @@ export default {
) )
// 注册富文本自定义元素 // 注册富文本自定义元素
const resume = { // const resume = {
type: 'attachment', // type: 'attachment',
attachmentLabel: '', // attachmentLabel: '',
attachmentValue: '', // attachmentValue: '',
children: [{ text: '' }], // void 元素必须有一个 children 其中只有一个空字符串重要 // children: [{ text: '' }], // void 元素必须有一个 children 其中只有一个空字符串重要
} // }
function withAttachment(editor) { function withAttachment(editor) {
// JS 语法 // JS 语法

View File

@ -905,6 +905,84 @@ export const multicolorIconList = [
value: 'caise-application', value: 'caise-application',
label: '应用', label: '应用',
list: [{ 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', value: 'caise-data_center',
label: '数据中心' 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

View File

@ -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>

View File

@ -179,7 +179,7 @@
<a @click="openDetail(row.ci_id || row._id)"> <a @click="openDetail(row.ci_id || row._id)">
<a-icon type="unordered-list" /> <a-icon type="unordered-list" />
</a> </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 @click="openDetail(row.ci_id || row._id, 'tab_2', '2')">
<a-icon type="retweet" /> <a-icon type="retweet" />
</a> </a>
@ -375,7 +375,7 @@ export default {
}, },
jsonEditorOk(row, column, jsonData) { jsonEditorOk(row, column, jsonData) {
this.$attrs.data.forEach((item) => { this.data.forEach((item) => {
if (item._id === row._id) { if (item._id === row._id) {
item[column.property] = JSON.stringify(jsonData) item[column.property] = JSON.stringify(jsonData)
} }

View File

@ -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: '<=' },
]

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -307,6 +307,18 @@ const cmdb_en = {
filterUsers: 'Filter Users', filterUsers: 'Filter Users',
enum: 'Enum', 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}}`, 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: { components: {
unselectAttributes: 'Unselected', unselectAttributes: 'Unselected',
@ -629,6 +641,7 @@ if __name__ == "__main__":
attributeDesc: 'Attribute Description', attributeDesc: 'Attribute Description',
selectRows: 'Select: {rows} items', selectRows: 'Select: {rows} items',
addRelation: 'Add Relation', addRelation: 'Add Relation',
viewRelation: 'View Relation',
all: 'All', all: 'All',
batchUpdate: 'Batch Update', batchUpdate: 'Batch Update',
batchUpdateConfirm: 'Are you sure you want to make batch updates?', batchUpdateConfirm: 'Are you sure you want to make batch updates?',

View File

@ -307,6 +307,18 @@ const cmdb_zh = {
filterUsers: '筛选用户', filterUsers: '筛选用户',
enum: '枚举', enum: '枚举',
ciGrantTip: `筛选条件可使用{{}}引用变量实现动态变化,目前支持用户变量,如{{user.uid}},{{user.username}},{{user.email}},{{user.nickname}}`, ciGrantTip: `筛选条件可使用{{}}引用变量实现动态变化,目前支持用户变量,如{{user.uid}},{{user.username}},{{user.email}},{{user.nickname}}`,
searchInputTip: '请搜索资源关键字',
resourceSearch: '资源搜索',
recentSearch: '最近搜索',
myCollection: '我的收藏',
keyword: '关键字',
CIType: '模型',
filterPopoverLabel: '条件过滤',
conditionFilter: '条件过滤',
advancedFilter: '高级筛选',
saveCondition: '保存条件',
confirmClear: '确认清空?',
currentPage: '当前页'
}, },
components: { components: {
unselectAttributes: '未选属性', unselectAttributes: '未选属性',
@ -628,6 +640,7 @@ if __name__ == "__main__":
attributeDesc: '查看属性配置', attributeDesc: '查看属性配置',
selectRows: '选取:{rows} 项', selectRows: '选取:{rows} 项',
addRelation: '添加关系', addRelation: '添加关系',
viewRelation: '查看关系',
all: '全部', all: '全部',
batchUpdate: '批量修改', batchUpdate: '批量修改',
batchUpdateConfirm: '确认要批量修改吗?', batchUpdateConfirm: '确认要批量修改吗?',

View File

@ -54,7 +54,7 @@ const genCmdbRoutes = async () => {
path: '/cmdb/resourcesearch', path: '/cmdb/resourcesearch',
name: 'cmdb_resource_search', name: 'cmdb_resource_search',
meta: { title: 'cmdb.menu.ciSearch', icon: 'ops-cmdb-search', selectedIcon: 'ops-cmdb-search', keepAlive: false }, 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', 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' } meta: { title: 'cmdb.menu.serviceTreeDefine', keepAlive: false, icon: 'ops-cmdb-preferencerelation', selectedIcon: 'ops-cmdb-preferencerelation-selected' }
}, },
{ {
path: '/cmdb/modelrelation', path: '/cmdb/discovery',
name: 'model_relation', name: 'discovery',
hideChildrenInMenu: true, component: () => import('../views/discovery/index'),
component: () => import('../views/model_relation/index'), meta: { title: 'cmdb.menu.ad', keepAlive: false, icon: 'ops-cmdb-adr', selectedIcon: 'ops-cmdb-adr-selected' }
meta: { title: 'cmdb.menu.citypeRelation', keepAlive: false, icon: 'ops-cmdb-modelrelation', selectedIcon: 'ops-cmdb-modelrelation-selected' }
}, },
{ {
path: '/cmdb/operationhistory', path: '/cmdb/operationhistory',
@ -130,19 +129,20 @@ const genCmdbRoutes = async () => {
component: () => import('../views/operation_history/index'), component: () => import('../views/operation_history/index'),
meta: { title: 'cmdb.menu.operationHistory', keepAlive: false, icon: 'ops-cmdb-operation', selectedIcon: 'ops-cmdb-operation-selected' } 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', path: '/cmdb/relationtype',
name: 'relation_type', name: 'relation_type',
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('../views/relation_type/index'), component: () => import('../views/relation_type/index'),
meta: { title: 'cmdb.menu.relationType', keepAlive: false, icon: 'ops-cmdb-relationtype', selectedIcon: 'ops-cmdb-relationtype-selected' } 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' }
},
] ]
} }
] ]

View File

@ -148,7 +148,6 @@
</template> </template>
<script> <script>
import _ from 'lodash'
import moment from 'moment' import moment from 'moment'
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue' import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue' import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'

View File

@ -103,7 +103,7 @@ export default {
await this.handleLinkAttrToCiType({ attr_id: this.targetKeys.map((i) => Number(i)) }) await this.handleLinkAttrToCiType({ attr_id: this.targetKeys.map((i) => Number(i)) })
if (this.currentGroup) { if (this.currentGroup) {
await this.updateCurrentGroup() 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) const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
this.targetKeys.forEach((key) => { this.targetKeys.forEach((key) => {
attrIds.push(Number(key)) attrIds.push(Number(key))
@ -141,7 +141,7 @@ export default {
}) })
if (this.currentGroup) { if (this.currentGroup) {
await this.updateCurrentGroup() 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) const attrIds = attributes.filter((i) => !i.inherited).map((i) => i.id)
attrIds.push(newAttrId) attrIds.push(newAttrId)
await createCITypeGroupById(this.CITypeId, { name, order, attributes: attrIds }) await createCITypeGroupById(this.CITypeId, { name, order, attributes: attrIds })

View File

@ -121,7 +121,6 @@
</template> </template>
<script> <script>
import _ from 'lodash'
import { ColorPicker } from 'element-ui' import { ColorPicker } from 'element-ui'
import CustomIconSelect from '@/components/CustomIconSelect' import CustomIconSelect from '@/components/CustomIconSelect'
import { defautValueColor, defaultBGColors } from '@/modules/cmdb/utils/const.js' import { defautValueColor, defaultBGColors } from '@/modules/cmdb/utils/const.js'

View File

@ -407,7 +407,7 @@ export default {
this.visible = true this.visible = true
this.type = type this.type = type
this.item = item 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 chartType = (item.options || {}).chartType || 'count'
const fontColor = (item.options || {}).fontColor || '#ffffff' const fontColor = (item.options || {}).fontColor || '#ffffff'
const bgColor = (item.options || {}).bgColor || ['#6ABFFE', '#5375EB'] const bgColor = (item.options || {}).bgColor || ['#6ABFFE', '#5375EB']

View File

@ -131,7 +131,7 @@ import ModelRelationTable from './modules/modelRelationTable.vue'
import { searchResourceType } from '@/modules/acl/api/resource' import { searchResourceType } from '@/modules/acl/api/resource'
import { getCITypeGroupsConfig } from '@/modules/cmdb/api/ciTypeGroup' import { getCITypeGroupsConfig } from '@/modules/cmdb/api/ciTypeGroup'
import { getCITypes } from '@/modules/cmdb/api/CIType' 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 { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'

View File

@ -588,7 +588,7 @@ export default {
this.calcColumns() this.calcColumns()
} }
if (refreshType === 'refreshNumber') { if (refreshType === 'refreshNumber') {
const promises = this.treeKeys.map((key, index) => { this.treeKeys.map((key, index) => {
let ancestor_ids let ancestor_ids
if ( if (
Object.keys(this.level2constraint).some( Object.keys(this.level2constraint).some(
@ -1432,7 +1432,7 @@ export default {
content: (h) => <div>{that.$t('confirmDelete')}</div>, content: (h) => <div>{that.$t('confirmDelete')}</div>,
async onOk() { async onOk() {
for (let i = 0; i < that.batchTreeKey.length; i++) { 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], that.batchTreeKey[i],
'delete' 'delete'
) )

View File

@ -1,105 +1,105 @@
<template> <template>
<a-modal <a-modal
width="600px" width="600px"
:bodyStyle="{ :bodyStyle="{
paddingTop: 0, paddingTop: 0,
}" }"
:visible="visible" :visible="visible"
:footer="null" :footer="null"
@cancel="handleCancel" @cancel="handleCancel"
:title="$t('view')" :title="$t('view')"
> >
<div> <div>
<template v-if="readCIIdFilterPermissions && readCIIdFilterPermissions.length"> <template v-if="readCIIdFilterPermissions && readCIIdFilterPermissions.length">
<p> <p>
<strong>{{ $t('cmdb.serviceTree.idAuthorizationPolicy') }}</strong> <strong>{{ $t('cmdb.serviceTree.idAuthorizationPolicy') }}</strong>
<a <a
@click=" @click="
() => { () => {
showAllReadCIIdFilterPermissions = !showAllReadCIIdFilterPermissions showAllReadCIIdFilterPermissions = !showAllReadCIIdFilterPermissions
} }
" "
v-if="readCIIdFilterPermissions.length > 10" v-if="readCIIdFilterPermissions.length > 10"
><a-icon ><a-icon
:type="showAllReadCIIdFilterPermissions ? 'caret-down' : 'caret-up'" :type="showAllReadCIIdFilterPermissions ? 'caret-down' : 'caret-up'"
/></a> /></a>
</p> </p>
<a-tag <a-tag
v-for="item in showAllReadCIIdFilterPermissions v-for="item in showAllReadCIIdFilterPermissions
? readCIIdFilterPermissions ? readCIIdFilterPermissions
: readCIIdFilterPermissions.slice(0, 10)" : readCIIdFilterPermissions.slice(0, 10)"
:key="item.name" :key="item.name"
color="blue" color="blue"
:style="{ marginBottom: '5px' }" :style="{ marginBottom: '5px' }"
>{{ item.name }}</a-tag >{{ item.name }}</a-tag
> >
<a-tag <a-tag
:style="{ marginBottom: '5px' }" :style="{ marginBottom: '5px' }"
v-if="readCIIdFilterPermissions.length > 10 && !showAllReadCIIdFilterPermissions" v-if="readCIIdFilterPermissions.length > 10 && !showAllReadCIIdFilterPermissions"
>+{{ readCIIdFilterPermissions.length - 10 }}</a-tag >+{{ readCIIdFilterPermissions.length - 10 }}</a-tag
> >
</template> </template>
<a-empty v-else> <a-empty v-else>
<img slot="image" :src="require('@/assets/data_empty.png')" /> <img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> {{ $t('noData') }} </span> <span slot="description"> {{ $t('noData') }} </span>
</a-empty> </a-empty>
</div> </div>
</a-modal> </a-modal>
</template> </template>
<script> <script>
import { ciTypeFilterPermissions, getCIType } from '../../../api/CIType' import { ciTypeFilterPermissions } from '../../../api/CIType'
import FilterComp from '@/components/CMDBFilterComp' import FilterComp from '@/components/CMDBFilterComp'
import { searchRole } from '@/modules/acl/api/role' import { searchRole } from '@/modules/acl/api/role'
export default { export default {
name: 'ReadPermissionsModal', name: 'ReadPermissionsModal',
components: { FilterComp }, components: { FilterComp },
data() { data() {
return { return {
visible: false, visible: false,
filerPerimissions: {}, filerPerimissions: {},
readCIIdFilterPermissions: [], readCIIdFilterPermissions: [],
canSearchPreferenceAttrList: [], canSearchPreferenceAttrList: [],
showAllReadCIIdFilterPermissions: false, showAllReadCIIdFilterPermissions: false,
allRoles: [], allRoles: [],
} }
}, },
mounted() { mounted() {
this.loadRoles() this.loadRoles()
}, },
methods: { methods: {
async loadRoles() { async loadRoles() {
const res = await searchRole({ page_size: 9999, app_id: 'cmdb', is_all: true }) const res = await searchRole({ page_size: 9999, app_id: 'cmdb', is_all: true })
this.allRoles = res.roles this.allRoles = res.roles
}, },
async open(treeKey) { async open(treeKey) {
this.visible = true this.visible = true
const _splitTreeKey = treeKey.split('@^@').filter((item) => !!item) const _splitTreeKey = treeKey.split('@^@').filter((item) => !!item)
const _treeKey = _splitTreeKey.slice(_splitTreeKey.length - 1, _splitTreeKey.length)[0].split('%') const _treeKey = _splitTreeKey.slice(_splitTreeKey.length - 1, _splitTreeKey.length)[0].split('%')
const typeId = _treeKey[1] const typeId = _treeKey[1]
const _treeKeyPath = _splitTreeKey.map((item) => item.split('%')[0]).join(',') const _treeKeyPath = _splitTreeKey.map((item) => item.split('%')[0]).join(',')
await ciTypeFilterPermissions(typeId).then((res) => { await ciTypeFilterPermissions(typeId).then((res) => {
this.filerPerimissions = res this.filerPerimissions = res
}) })
const readCIIdFilterPermissions = [] const readCIIdFilterPermissions = []
Object.entries(this.filerPerimissions).forEach(([k, v]) => { Object.entries(this.filerPerimissions).forEach(([k, v]) => {
const { id_filter } = v const { id_filter } = v
if (id_filter && Object.keys(id_filter).includes(_treeKeyPath)) { if (id_filter && Object.keys(id_filter).includes(_treeKeyPath)) {
const _find = this.allRoles.find((item) => item.id === Number(k)) const _find = this.allRoles.find((item) => item.id === Number(k))
readCIIdFilterPermissions.push({ name: _find?.name ?? k, rid: k }) readCIIdFilterPermissions.push({ name: _find?.name ?? k, rid: k })
} }
}) })
this.readCIIdFilterPermissions = readCIIdFilterPermissions this.readCIIdFilterPermissions = readCIIdFilterPermissions
console.log(readCIIdFilterPermissions) console.log(readCIIdFilterPermissions)
}, },
handleCancel() { handleCancel() {
this.showAllReadCIIdFilterPermissions = false this.showAllReadCIIdFilterPermissions = false
this.visible = false this.visible = false
}, },
}, },
} }
</script> </script>
<style></style> <style></style>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>