mirror of https://github.com/veops/cmdb.git
feat(ui): add relation search
This commit is contained in:
parent
8f7d78c26c
commit
394e2aeac6
|
@ -54,6 +54,198 @@
|
||||||
<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"></span>
|
||||||
|
<div class="name">oneterm-mysql</div>
|
||||||
|
<div class="code-name">&#xe9e8;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">oneterm-redis</div>
|
||||||
|
<div class="code-name">&#xe9e7;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-sign_out</div>
|
||||||
|
<div class="code-name">&#xe9e6;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-company</div>
|
||||||
|
<div class="code-name">&#xe9e5;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-emails</div>
|
||||||
|
<div class="code-name">&#xe9e4;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-switch</div>
|
||||||
|
<div class="code-name">&#xe9e3;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-qiyeweixin</div>
|
||||||
|
<div class="code-name">&#xe9e2;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-progress</div>
|
||||||
|
<div class="code-name">&#xe9e1;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-completed</div>
|
||||||
|
<div class="code-name">&#xe9e0;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">itsm-ticketTime</div>
|
||||||
|
<div class="code-name">&#xe9df;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-notification</div>
|
||||||
|
<div class="code-name">&#xe9dc;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-account_password</div>
|
||||||
|
<div class="code-name">&#xe9dd;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-personal</div>
|
||||||
|
<div class="code-name">&#xe9de;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">itsm-evaluation2</div>
|
||||||
|
<div class="code-name">&#xe9da;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">itsm-over2</div>
|
||||||
|
<div class="code-name">&#xe9db;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-search</div>
|
||||||
|
<div class="code-name">&#xe9d9;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">itsm-evaluation</div>
|
||||||
|
<div class="code-name">&#xe9d8;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">itsm-over</div>
|
||||||
|
<div class="code-name">&#xe9d7;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">itsm-requirement</div>
|
||||||
|
<div class="code-name">&#xe9d6;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">itsm-release</div>
|
||||||
|
<div class="code-name">&#xe9d5;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-link</div>
|
||||||
|
<div class="code-name">&#xe9d4;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">oneterm-command_record</div>
|
||||||
|
<div class="code-name">&#xe9d3;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ai-question</div>
|
||||||
|
<div class="code-name">&#xe9d2;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ai-sending</div>
|
||||||
|
<div class="code-name">&#xe9d1;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ai-dialogue</div>
|
||||||
|
<div class="code-name">&#xe9d0;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ai-report2</div>
|
||||||
|
<div class="code-name">&#xe9cf;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ai-delete</div>
|
||||||
|
<div class="code-name">&#xe9cd;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">caise-knowledge</div>
|
||||||
|
<div class="code-name">&#xe9ce;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ai-article</div>
|
||||||
|
<div class="code-name">&#xe9cc;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ai-model_setup (1)</div>
|
||||||
|
<div class="code-name">&#xe9cb;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ai-report</div>
|
||||||
|
<div class="code-name">&#xe9ca;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ai-customer_service</div>
|
||||||
|
<div class="code-name">&#xe9c9;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<span class="icon iconfont"></span>
|
<span class="icon iconfont"></span>
|
||||||
<div class="name">oneterm-connect</div>
|
<div class="name">oneterm-connect</div>
|
||||||
|
@ -5808,9 +6000,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=1725846934130') format('woff2'),
|
src: url('iconfont.woff2?t=1729157759723') format('woff2'),
|
||||||
url('iconfont.woff?t=1725846934130') format('woff'),
|
url('iconfont.woff?t=1729157759723') format('woff'),
|
||||||
url('iconfont.ttf?t=1725846934130') format('truetype');
|
url('iconfont.ttf?t=1729157759723') format('truetype');
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||||
|
@ -5836,6 +6028,294 @@
|
||||||
<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 oneterm-mysql"></span>
|
||||||
|
<div class="name">
|
||||||
|
oneterm-mysql
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.oneterm-mysql
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont oneterm-redis"></span>
|
||||||
|
<div class="name">
|
||||||
|
oneterm-redis
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.oneterm-redis
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-sign_out"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-sign_out
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-sign_out
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-company"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-company
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-company
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-emails"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-emails
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-emails
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-switch"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-switch
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-switch
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont qiyeweixin"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-qiyeweixin
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.qiyeweixin
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-progress"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-progress
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-progress
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-completed"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-completed
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-completed
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont itsm-ticketTime"></span>
|
||||||
|
<div class="name">
|
||||||
|
itsm-ticketTime
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.itsm-ticketTime
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-notification"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-notification
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-notification
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont a-veops-account1"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-account_password
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.a-veops-account1
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-personal"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-personal
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-personal
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont itsm-customer_satisfaction2"></span>
|
||||||
|
<div class="name">
|
||||||
|
itsm-evaluation2
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.itsm-customer_satisfaction2
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont itsm-over2"></span>
|
||||||
|
<div class="name">
|
||||||
|
itsm-over2
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.itsm-over2
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-search1"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-search
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-search1
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont itsm-customer_satisfaction"></span>
|
||||||
|
<div class="name">
|
||||||
|
itsm-evaluation
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.itsm-customer_satisfaction
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont itsm-over"></span>
|
||||||
|
<div class="name">
|
||||||
|
itsm-over
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.itsm-over
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont itsm-request"></span>
|
||||||
|
<div class="name">
|
||||||
|
itsm-requirement
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.itsm-request
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont itsm-release"></span>
|
||||||
|
<div class="name">
|
||||||
|
itsm-release
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.itsm-release
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-link"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-link
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-link
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont oneterm-command_record"></span>
|
||||||
|
<div class="name">
|
||||||
|
oneterm-command_record
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.oneterm-command_record
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ai-question"></span>
|
||||||
|
<div class="name">
|
||||||
|
ai-question
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ai-question
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ai-sending"></span>
|
||||||
|
<div class="name">
|
||||||
|
ai-sending
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ai-sending
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ai-dialogue"></span>
|
||||||
|
<div class="name">
|
||||||
|
ai-dialogue
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ai-dialogue
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ai-report2"></span>
|
||||||
|
<div class="name">
|
||||||
|
ai-report2
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ai-report2
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ai-delete"></span>
|
||||||
|
<div class="name">
|
||||||
|
ai-delete
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ai-delete
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont caise-knowledge"></span>
|
||||||
|
<div class="name">
|
||||||
|
caise-knowledge
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.caise-knowledge
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ai-article"></span>
|
||||||
|
<div class="name">
|
||||||
|
ai-article
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ai-article
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ai-model_setup1"></span>
|
||||||
|
<div class="name">
|
||||||
|
ai-model_setup (1)
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ai-model_setup1
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ai-report"></span>
|
||||||
|
<div class="name">
|
||||||
|
ai-report
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ai-report
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ai-customer_service"></span>
|
||||||
|
<div class="name">
|
||||||
|
ai-customer_service
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ai-customer_service
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<span class="icon iconfont oneterm-connect1"></span>
|
<span class="icon iconfont oneterm-connect1"></span>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
|
@ -14467,6 +14947,262 @@
|
||||||
<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="#oneterm-mysql"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">oneterm-mysql</div>
|
||||||
|
<div class="code-name">#oneterm-mysql</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#oneterm-redis"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">oneterm-redis</div>
|
||||||
|
<div class="code-name">#oneterm-redis</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-sign_out"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-sign_out</div>
|
||||||
|
<div class="code-name">#veops-sign_out</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-company"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-company</div>
|
||||||
|
<div class="code-name">#veops-company</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-emails"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-emails</div>
|
||||||
|
<div class="code-name">#veops-emails</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-switch"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-switch</div>
|
||||||
|
<div class="code-name">#veops-switch</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#qiyeweixin"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-qiyeweixin</div>
|
||||||
|
<div class="code-name">#qiyeweixin</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-progress"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-progress</div>
|
||||||
|
<div class="code-name">#veops-progress</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-completed"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-completed</div>
|
||||||
|
<div class="code-name">#veops-completed</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#itsm-ticketTime"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">itsm-ticketTime</div>
|
||||||
|
<div class="code-name">#itsm-ticketTime</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-notification"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-notification</div>
|
||||||
|
<div class="code-name">#veops-notification</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#a-veops-account1"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-account_password</div>
|
||||||
|
<div class="code-name">#a-veops-account1</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-personal"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-personal</div>
|
||||||
|
<div class="code-name">#veops-personal</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#itsm-customer_satisfaction2"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">itsm-evaluation2</div>
|
||||||
|
<div class="code-name">#itsm-customer_satisfaction2</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#itsm-over2"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">itsm-over2</div>
|
||||||
|
<div class="code-name">#itsm-over2</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-search1"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-search</div>
|
||||||
|
<div class="code-name">#veops-search1</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#itsm-customer_satisfaction"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">itsm-evaluation</div>
|
||||||
|
<div class="code-name">#itsm-customer_satisfaction</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#itsm-over"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">itsm-over</div>
|
||||||
|
<div class="code-name">#itsm-over</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#itsm-request"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">itsm-requirement</div>
|
||||||
|
<div class="code-name">#itsm-request</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#itsm-release"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">itsm-release</div>
|
||||||
|
<div class="code-name">#itsm-release</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-link"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-link</div>
|
||||||
|
<div class="code-name">#veops-link</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#oneterm-command_record"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">oneterm-command_record</div>
|
||||||
|
<div class="code-name">#oneterm-command_record</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ai-question"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ai-question</div>
|
||||||
|
<div class="code-name">#ai-question</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ai-sending"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ai-sending</div>
|
||||||
|
<div class="code-name">#ai-sending</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ai-dialogue"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ai-dialogue</div>
|
||||||
|
<div class="code-name">#ai-dialogue</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ai-report2"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ai-report2</div>
|
||||||
|
<div class="code-name">#ai-report2</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ai-delete"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ai-delete</div>
|
||||||
|
<div class="code-name">#ai-delete</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#caise-knowledge"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">caise-knowledge</div>
|
||||||
|
<div class="code-name">#caise-knowledge</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ai-article"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ai-article</div>
|
||||||
|
<div class="code-name">#ai-article</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ai-model_setup1"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ai-model_setup (1)</div>
|
||||||
|
<div class="code-name">#ai-model_setup1</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ai-report"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ai-report</div>
|
||||||
|
<div class="code-name">#ai-report</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ai-customer_service"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ai-customer_service</div>
|
||||||
|
<div class="code-name">#ai-customer_service</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="#oneterm-connect1"></use>
|
<use xlink:href="#oneterm-connect1"></use>
|
||||||
|
|
|
@ -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=1725846934130') format('woff2'),
|
src: url('iconfont.woff2?t=1729157759723') format('woff2'),
|
||||||
url('iconfont.woff?t=1725846934130') format('woff'),
|
url('iconfont.woff?t=1729157759723') format('woff'),
|
||||||
url('iconfont.ttf?t=1725846934130') format('truetype');
|
url('iconfont.ttf?t=1729157759723') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
|
@ -13,6 +13,134 @@
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.oneterm-mysql:before {
|
||||||
|
content: "\e9e8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.oneterm-redis:before {
|
||||||
|
content: "\e9e7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-sign_out:before {
|
||||||
|
content: "\e9e6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-company:before {
|
||||||
|
content: "\e9e5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-emails:before {
|
||||||
|
content: "\e9e4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-switch:before {
|
||||||
|
content: "\e9e3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.qiyeweixin:before {
|
||||||
|
content: "\e9e2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-progress:before {
|
||||||
|
content: "\e9e1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-completed:before {
|
||||||
|
content: "\e9e0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-ticketTime:before {
|
||||||
|
content: "\e9df";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-notification:before {
|
||||||
|
content: "\e9dc";
|
||||||
|
}
|
||||||
|
|
||||||
|
.a-veops-account1:before {
|
||||||
|
content: "\e9dd";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-personal:before {
|
||||||
|
content: "\e9de";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-customer_satisfaction2:before {
|
||||||
|
content: "\e9da";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-over2:before {
|
||||||
|
content: "\e9db";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-search1:before {
|
||||||
|
content: "\e9d9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-customer_satisfaction:before {
|
||||||
|
content: "\e9d8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-over:before {
|
||||||
|
content: "\e9d7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-request:before {
|
||||||
|
content: "\e9d6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-release:before {
|
||||||
|
content: "\e9d5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-link:before {
|
||||||
|
content: "\e9d4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.oneterm-command_record:before {
|
||||||
|
content: "\e9d3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-question:before {
|
||||||
|
content: "\e9d2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-sending:before {
|
||||||
|
content: "\e9d1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-dialogue:before {
|
||||||
|
content: "\e9d0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-report2:before {
|
||||||
|
content: "\e9cf";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-delete:before {
|
||||||
|
content: "\e9cd";
|
||||||
|
}
|
||||||
|
|
||||||
|
.caise-knowledge:before {
|
||||||
|
content: "\e9ce";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-article:before {
|
||||||
|
content: "\e9cc";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-model_setup1:before {
|
||||||
|
content: "\e9cb";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-report:before {
|
||||||
|
content: "\e9ca";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-customer_service:before {
|
||||||
|
content: "\e9c9";
|
||||||
|
}
|
||||||
|
|
||||||
.oneterm-connect1:before {
|
.oneterm-connect1:before {
|
||||||
content: "\e9c6";
|
content: "\e9c6";
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,6 +5,230 @@
|
||||||
"css_prefix_text": "",
|
"css_prefix_text": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "42155223",
|
||||||
|
"name": "oneterm-mysql",
|
||||||
|
"font_class": "oneterm-mysql",
|
||||||
|
"unicode": "e9e8",
|
||||||
|
"unicode_decimal": 59880
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42155225",
|
||||||
|
"name": "oneterm-redis",
|
||||||
|
"font_class": "oneterm-redis",
|
||||||
|
"unicode": "e9e7",
|
||||||
|
"unicode_decimal": 59879
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42154436",
|
||||||
|
"name": "veops-sign_out",
|
||||||
|
"font_class": "veops-sign_out",
|
||||||
|
"unicode": "e9e6",
|
||||||
|
"unicode_decimal": 59878
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42154310",
|
||||||
|
"name": "veops-company",
|
||||||
|
"font_class": "veops-company",
|
||||||
|
"unicode": "e9e5",
|
||||||
|
"unicode_decimal": 59877
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42154325",
|
||||||
|
"name": "veops-emails",
|
||||||
|
"font_class": "veops-emails",
|
||||||
|
"unicode": "e9e4",
|
||||||
|
"unicode_decimal": 59876
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42154350",
|
||||||
|
"name": "veops-switch",
|
||||||
|
"font_class": "veops-switch",
|
||||||
|
"unicode": "e9e3",
|
||||||
|
"unicode_decimal": 59875
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42154370",
|
||||||
|
"name": "veops-qiyeweixin",
|
||||||
|
"font_class": "qiyeweixin",
|
||||||
|
"unicode": "e9e2",
|
||||||
|
"unicode_decimal": 59874
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42134185",
|
||||||
|
"name": "veops-progress",
|
||||||
|
"font_class": "veops-progress",
|
||||||
|
"unicode": "e9e1",
|
||||||
|
"unicode_decimal": 59873
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42134110",
|
||||||
|
"name": "veops-completed",
|
||||||
|
"font_class": "veops-completed",
|
||||||
|
"unicode": "e9e0",
|
||||||
|
"unicode_decimal": 59872
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42133882",
|
||||||
|
"name": "itsm-ticketTime",
|
||||||
|
"font_class": "itsm-ticketTime",
|
||||||
|
"unicode": "e9df",
|
||||||
|
"unicode_decimal": 59871
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42122869",
|
||||||
|
"name": "veops-notification",
|
||||||
|
"font_class": "veops-notification",
|
||||||
|
"unicode": "e9dc",
|
||||||
|
"unicode_decimal": 59868
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42122868",
|
||||||
|
"name": "veops-account_password",
|
||||||
|
"font_class": "a-veops-account1",
|
||||||
|
"unicode": "e9dd",
|
||||||
|
"unicode_decimal": 59869
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42122861",
|
||||||
|
"name": "veops-personal",
|
||||||
|
"font_class": "veops-personal",
|
||||||
|
"unicode": "e9de",
|
||||||
|
"unicode_decimal": 59870
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42101103",
|
||||||
|
"name": "itsm-evaluation2",
|
||||||
|
"font_class": "itsm-customer_satisfaction2",
|
||||||
|
"unicode": "e9da",
|
||||||
|
"unicode_decimal": 59866
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42101098",
|
||||||
|
"name": "itsm-over2",
|
||||||
|
"font_class": "itsm-over2",
|
||||||
|
"unicode": "e9db",
|
||||||
|
"unicode_decimal": 59867
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42065574",
|
||||||
|
"name": "veops-search",
|
||||||
|
"font_class": "veops-search1",
|
||||||
|
"unicode": "e9d9",
|
||||||
|
"unicode_decimal": 59865
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42063479",
|
||||||
|
"name": "itsm-evaluation",
|
||||||
|
"font_class": "itsm-customer_satisfaction",
|
||||||
|
"unicode": "e9d8",
|
||||||
|
"unicode_decimal": 59864
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42062436",
|
||||||
|
"name": "itsm-over",
|
||||||
|
"font_class": "itsm-over",
|
||||||
|
"unicode": "e9d7",
|
||||||
|
"unicode_decimal": 59863
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42050642",
|
||||||
|
"name": "itsm-requirement",
|
||||||
|
"font_class": "itsm-request",
|
||||||
|
"unicode": "e9d6",
|
||||||
|
"unicode_decimal": 59862
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42050622",
|
||||||
|
"name": "itsm-release",
|
||||||
|
"font_class": "itsm-release",
|
||||||
|
"unicode": "e9d5",
|
||||||
|
"unicode_decimal": 59861
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41903314",
|
||||||
|
"name": "veops-link",
|
||||||
|
"font_class": "veops-link",
|
||||||
|
"unicode": "e9d4",
|
||||||
|
"unicode_decimal": 59860
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41876664",
|
||||||
|
"name": "oneterm-command_record",
|
||||||
|
"font_class": "oneterm-command_record",
|
||||||
|
"unicode": "e9d3",
|
||||||
|
"unicode_decimal": 59859
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41859436",
|
||||||
|
"name": "ai-question",
|
||||||
|
"font_class": "ai-question",
|
||||||
|
"unicode": "e9d2",
|
||||||
|
"unicode_decimal": 59858
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41859414",
|
||||||
|
"name": "ai-sending",
|
||||||
|
"font_class": "ai-sending",
|
||||||
|
"unicode": "e9d1",
|
||||||
|
"unicode_decimal": 59857
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41859374",
|
||||||
|
"name": "ai-dialogue",
|
||||||
|
"font_class": "ai-dialogue",
|
||||||
|
"unicode": "e9d0",
|
||||||
|
"unicode_decimal": 59856
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41859191",
|
||||||
|
"name": "ai-report2",
|
||||||
|
"font_class": "ai-report2",
|
||||||
|
"unicode": "e9cf",
|
||||||
|
"unicode_decimal": 59855
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41858720",
|
||||||
|
"name": "ai-delete",
|
||||||
|
"font_class": "ai-delete",
|
||||||
|
"unicode": "e9cd",
|
||||||
|
"unicode_decimal": 59853
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41858484",
|
||||||
|
"name": "caise-knowledge",
|
||||||
|
"font_class": "caise-knowledge",
|
||||||
|
"unicode": "e9ce",
|
||||||
|
"unicode_decimal": 59854
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41833445",
|
||||||
|
"name": "ai-article",
|
||||||
|
"font_class": "ai-article",
|
||||||
|
"unicode": "e9cc",
|
||||||
|
"unicode_decimal": 59852
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41811974",
|
||||||
|
"name": "ai-model_setup (1)",
|
||||||
|
"font_class": "ai-model_setup1",
|
||||||
|
"unicode": "e9cb",
|
||||||
|
"unicode_decimal": 59851
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41811980",
|
||||||
|
"name": "ai-report",
|
||||||
|
"font_class": "ai-report",
|
||||||
|
"unicode": "e9ca",
|
||||||
|
"unicode_decimal": 59850
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41811915",
|
||||||
|
"name": "ai-customer_service",
|
||||||
|
"font_class": "ai-customer_service",
|
||||||
|
"unicode": "e9c9",
|
||||||
|
"unicode_decimal": 59849
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "41735717",
|
"icon_id": "41735717",
|
||||||
"name": "oneterm-connect",
|
"name": "oneterm-connect",
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,24 @@
|
||||||
|
.cmdb-side-menu-search {
|
||||||
|
background-color: #FFFFFF !important;
|
||||||
|
cursor: auto !important;
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.ant-input-affix-wrapper {
|
||||||
|
max-width: 170px !important;
|
||||||
|
width: 170px;
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input {
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-suffix {
|
||||||
|
right: 0px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,8 @@ import {
|
||||||
import { searchResourceType } from '@/modules/acl/api/resource'
|
import { searchResourceType } from '@/modules/acl/api/resource'
|
||||||
import { roleHasPermissionToGrant } from '@/modules/acl/api/permission'
|
import { roleHasPermissionToGrant } from '@/modules/acl/api/permission'
|
||||||
import CMDBGrant from '@/modules/cmdb/components/cmdbGrant'
|
import CMDBGrant from '@/modules/cmdb/components/cmdbGrant'
|
||||||
|
import styles from './index.module.less'
|
||||||
|
import { mapActions } from 'vuex'
|
||||||
|
|
||||||
const { Item, SubMenu } = Menu
|
const { Item, SubMenu } = Menu
|
||||||
|
|
||||||
|
@ -40,7 +42,8 @@ export default {
|
||||||
openKeys: [],
|
openKeys: [],
|
||||||
selectedKeys: [],
|
selectedKeys: [],
|
||||||
cachedOpenKeys: [],
|
cachedOpenKeys: [],
|
||||||
resource_type: {}
|
resource_type: {},
|
||||||
|
currentAppRoute: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -64,6 +67,7 @@ export default {
|
||||||
searchResourceType({ page_size: 9999, app_id: 'cmdb' }).then(res => {
|
searchResourceType({ page_size: 9999, app_id: 'cmdb' }).then(res => {
|
||||||
this.resource_type = { groups: res.groups, id2perms: res.id2perms }
|
this.resource_type = { groups: res.groups, id2perms: res.id2perms }
|
||||||
})
|
})
|
||||||
|
this.currentAppRoute = this.$route?.matched?.[0]?.name || ''
|
||||||
this.updateMenu()
|
this.updateMenu()
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -75,12 +79,14 @@ export default {
|
||||||
this.openKeys = this.cachedOpenKeys
|
this.openKeys = this.cachedOpenKeys
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
$route: function () {
|
$route: function (route) {
|
||||||
|
this.currentAppRoute = route?.matched?.[0]?.name
|
||||||
this.updateMenu()
|
this.updateMenu()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
inject: ['reload'],
|
inject: ['reload'],
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions(['UpdateCMDBSEarchValue']),
|
||||||
cancelAttributes(e, menu) {
|
cancelAttributes(e, menu) {
|
||||||
const that = this
|
const that = this
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -286,6 +292,47 @@ export default {
|
||||||
this.$message.error(this.$t('noPermission'))
|
this.$message.error(this.$t('noPermission'))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
jumpCMDBSearch(value) {
|
||||||
|
this.UpdateCMDBSEarchValue(value)
|
||||||
|
|
||||||
|
if (this.$route.name !== 'cmdb_resource_search') {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'cmdb_resource_search',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
renderCMDBSearch() {
|
||||||
|
if (this.currentAppRoute !== 'cmdb' || this.collapsed) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Item class={styles['cmdb-side-menu-search']}>
|
||||||
|
<a-input
|
||||||
|
ref="cmdbSideMenuSearchInputRef"
|
||||||
|
class={styles['cmdb-side-menu-search-input']}
|
||||||
|
style={{
|
||||||
|
border: this.$route.name === 'cmdb_resource_search' ? 'solid 1px #B1C9FF' : ''
|
||||||
|
}}
|
||||||
|
placeholder={this.$t('cmdbSearch')}
|
||||||
|
onPressEnter={(e) => {
|
||||||
|
this.jumpCMDBSearch(e.target.value)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ops-icon
|
||||||
|
slot="suffix"
|
||||||
|
type="veops-search1"
|
||||||
|
onClick={() => {
|
||||||
|
const value = this.$refs?.cmdbSideMenuSearchInputRef?.$refs?.input?.value || ''
|
||||||
|
this.jumpCMDBSearch(value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</a-input>
|
||||||
|
</Item>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -313,6 +360,7 @@ export default {
|
||||||
// {...{ props, on: on }}
|
// {...{ props, on: on }}
|
||||||
return (
|
return (
|
||||||
<Menu class="ops-side-bar" selectedKeys={this.selectedKeys} {...{ props, on: on }}>
|
<Menu class="ops-side-bar" selectedKeys={this.selectedKeys} {...{ props, on: on }}>
|
||||||
|
{this.renderCMDBSearch()}
|
||||||
{menuTree}
|
{menuTree}
|
||||||
</Menu>
|
</Menu>
|
||||||
)
|
)
|
||||||
|
|
|
@ -108,6 +108,7 @@ export default {
|
||||||
visual: 'Visual',
|
visual: 'Visual',
|
||||||
default: 'default',
|
default: 'default',
|
||||||
tip: 'Tip',
|
tip: 'Tip',
|
||||||
|
cmdbSearch: 'Search',
|
||||||
pagination: {
|
pagination: {
|
||||||
total: '{range0}-{range1} of {total} items'
|
total: '{range0}-{range1} of {total} items'
|
||||||
},
|
},
|
||||||
|
|
|
@ -108,6 +108,7 @@ export default {
|
||||||
visual: '虚拟',
|
visual: '虚拟',
|
||||||
default: '默认',
|
default: '默认',
|
||||||
tip: '提示',
|
tip: '提示',
|
||||||
|
cmdbSearch: '搜索一下',
|
||||||
pagination: {
|
pagination: {
|
||||||
total: '当前展示 {range0}-{range1} 条数据, 共 {total} 条'
|
total: '当前展示 {range0}-{range1} 条数据, 共 {total} 条'
|
||||||
},
|
},
|
||||||
|
|
|
@ -81,3 +81,11 @@ export function searchCIRelationFull(params) {
|
||||||
params,
|
params,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function searchCIRelationPath(data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ci_relations/path/s`,
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -74,3 +74,11 @@ export function getCanEditByParentIdChildId(parent_id, child_id) {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCITypeRelationPath(params) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ci_type_relations/path`,
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,10 @@
|
||||||
v-decorator="['filename', { rules: [{ required: true, message: $t('cmdb.components.filenameInputTips') }] }]"
|
v-decorator="['filename', { rules: [{ required: true, message: $t('cmdb.components.filenameInputTips') }] }]"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="$t('cmdb.components.saveType')">
|
<a-form-item
|
||||||
|
v-if="showFileTypeSelect"
|
||||||
|
:label="$t('cmdb.components.saveType')"
|
||||||
|
>
|
||||||
<a-select
|
<a-select
|
||||||
:placeholder="$t('cmdb.components.saveTypeTips')"
|
:placeholder="$t('cmdb.components.saveTypeTips')"
|
||||||
v-decorator="[
|
v-decorator="[
|
||||||
|
@ -83,6 +86,10 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'default',
|
default: 'default',
|
||||||
},
|
},
|
||||||
|
showFileTypeSelect: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -741,5 +741,22 @@ if __name__ == "__main__":
|
||||||
topoViewSearchPlaceholder: 'Please enter the node name.',
|
topoViewSearchPlaceholder: 'Please enter the node name.',
|
||||||
moreBtn: 'Show more({count})'
|
moreBtn: 'Show more({count})'
|
||||||
},
|
},
|
||||||
|
relationSearch: {
|
||||||
|
relationSearch: 'Relation Search',
|
||||||
|
sourceCIType: 'Source CIType',
|
||||||
|
sourceCITypeTip: 'Please input or select',
|
||||||
|
sourceCITYpeInput: 'Please input keywords',
|
||||||
|
targetCIType: 'Target CIType',
|
||||||
|
targetCITypeTip: 'Please input or select, multiple choices available',
|
||||||
|
pathSelect: 'Path Select',
|
||||||
|
pathSelectTip: 'Please select source CIType and target CIType first',
|
||||||
|
saveCondition: 'Save Condition',
|
||||||
|
conditionFilter: 'Condition Filter',
|
||||||
|
level: 'Level',
|
||||||
|
returnPath: 'Return Path',
|
||||||
|
conditionName: 'Condition Name',
|
||||||
|
path: 'Path',
|
||||||
|
expandCondition: 'Expand Condition',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export default cmdb_en
|
export default cmdb_en
|
||||||
|
|
|
@ -740,5 +740,22 @@ if __name__ == "__main__":
|
||||||
topoViewSearchPlaceholder: '请输入节点名字',
|
topoViewSearchPlaceholder: '请输入节点名字',
|
||||||
moreBtn: '展示更多({count})'
|
moreBtn: '展示更多({count})'
|
||||||
},
|
},
|
||||||
|
relationSearch: {
|
||||||
|
relationSearch: '关系搜索',
|
||||||
|
sourceCIType: '源模型',
|
||||||
|
sourceCITypeTip: '请输入或选择',
|
||||||
|
sourceCITYpeInput: '请输入关键词',
|
||||||
|
targetCIType: '目标模型',
|
||||||
|
targetCITypeTip: '请输入或选择,可多选',
|
||||||
|
pathSelect: '路径选择',
|
||||||
|
pathSelectTip: '请先选择源模型和目标模型',
|
||||||
|
saveCondition: '保存条件',
|
||||||
|
conditionFilter: '条件过滤',
|
||||||
|
level: '层级',
|
||||||
|
returnPath: '返回路径',
|
||||||
|
conditionName: '条件命名',
|
||||||
|
path: '路径',
|
||||||
|
expandCondition: '展开条件',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export default cmdb_zh
|
export default cmdb_zh
|
||||||
|
|
|
@ -53,6 +53,7 @@ const genCmdbRoutes = async () => {
|
||||||
{
|
{
|
||||||
path: '/cmdb/resourcesearch',
|
path: '/cmdb/resourcesearch',
|
||||||
name: 'cmdb_resource_search',
|
name: 'cmdb_resource_search',
|
||||||
|
hidden: true,
|
||||||
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_2/index.vue')
|
component: () => import('../views/resource_search_2/index.vue')
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,7 +11,7 @@ export function sum(arr) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const strLength = (fData) => {
|
export const strLength = (fData) => {
|
||||||
|
|
||||||
if (!fData) {
|
if (!fData) {
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -1113,7 +1113,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.ci-types-left-content {
|
.ci-types-left-content {
|
||||||
max-height: calc(100% - 45px);
|
height: calc(100% - 45px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
|
||||||
|
|
|
@ -1,530 +1,106 @@
|
||||||
<template>
|
<template>
|
||||||
|
<div class="resource-search">
|
||||||
|
<div class="resource-search-tab">
|
||||||
<div
|
<div
|
||||||
class="resource-search"
|
v-for="(tab) in tabList"
|
||||||
:style="{ height: `${windowHeight - 93}px` }"
|
:key="tab.value"
|
||||||
|
:class="['resource-search-tab-item', tabActive === tab.value ? 'resource-search-tab-item_active' : '']"
|
||||||
|
@click="tabActive = tab.value"
|
||||||
>
|
>
|
||||||
<div v-if="!isSearch" class="resource-search-before">
|
{{ $t(tab.lable) }}
|
||||||
<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>
|
</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>
|
||||||
|
|
||||||
<div class="resource-search-after" v-else>
|
<template v-if="isInit">
|
||||||
<div
|
<ResourceSearchCom
|
||||||
class="resource-search-after-left"
|
v-show="tabActive === 'resourceSearch'"
|
||||||
:style="{ width: showInstanceDetail ? '70%' : '100%' }"
|
|
||||||
>
|
|
||||||
<SearchInput
|
|
||||||
ref="searchInputRef"
|
|
||||||
classType="after"
|
|
||||||
:CITypeGroup="CITypeGroup"
|
:CITypeGroup="CITypeGroup"
|
||||||
:allAttributesList="allAttributesList"
|
:allCITypes="allCITypes"
|
||||||
:searchValue="searchValue"
|
|
||||||
:selectCITypeIds="selectCITypeIds"
|
|
||||||
:expression="expression"
|
|
||||||
@changeFilter="changeFilter"
|
|
||||||
@updateAllAttributesList="updateAllAttributesList"
|
|
||||||
@saveCondition="saveCondition"
|
|
||||||
/>
|
/>
|
||||||
<HistoryList
|
<RelationSearch
|
||||||
:recentList="recentList"
|
v-show="tabActive === 'relationSearch'"
|
||||||
:favorList="favorList"
|
:CITypeGroup="CITypeGroup"
|
||||||
:detailCIId="detailCIId"
|
:allCITypes="allCITypes"
|
||||||
@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>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from 'lodash'
|
|
||||||
import { getPreferenceSearch, savePreferenceSearch, getSubscribeAttributes, deletePreferenceSearch } from '@/modules/cmdb/api/preference'
|
|
||||||
import { getCITypeGroups } from '@/modules/cmdb/api/ciTypeGroup'
|
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 { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
import SearchInput from './components/searchInput.vue'
|
import ResourceSearchCom from './resourceSearch/index.vue'
|
||||||
import HistoryList from './components/historyList.vue'
|
import RelationSearch from './relationSearch/index.vue'
|
||||||
import InstanceList from './components/instanceList.vue'
|
|
||||||
import InstanceDetail from './components/instanceDetail.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ResourceSearch',
|
name: 'ResourceSearch',
|
||||||
components: {
|
components: {
|
||||||
SearchInput,
|
ResourceSearchCom,
|
||||||
HistoryList,
|
RelationSearch
|
||||||
InstanceList,
|
|
||||||
InstanceDetail
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// 筛选条件
|
tabActive: 'resourceSearch',
|
||||||
searchValue: '', // 搜索框
|
tabList: [
|
||||||
selectCITypeIds: [], // 已选模型
|
{
|
||||||
expression: '', // 筛选语句
|
lable: 'cmdb.ciType.resourceSearch',
|
||||||
currentSearchValue: '', // 当前已搜索语句
|
value: 'resourceSearch'
|
||||||
|
},
|
||||||
recentList: [], // 最近搜索
|
{
|
||||||
favorList: [], // 我的收藏
|
lable: 'cmdb.relationSearch.relationSearch',
|
||||||
CITypeGroup: [], // CIType 分组
|
value: 'relationSearch'
|
||||||
CITypes: [],
|
}
|
||||||
allAttributesList: [],
|
],
|
||||||
|
CITypeGroup: [],
|
||||||
isSearch: false, // 是否搜索过
|
allCITypes: [],
|
||||||
currentPage: 1,
|
isInit: false,
|
||||||
pageSizeOptions: ['50', '100', '200', '100000'],
|
|
||||||
pageSize: 50,
|
|
||||||
totalNumber: 0,
|
|
||||||
ciTabList: [],
|
|
||||||
instanceList: [],
|
|
||||||
referenceShowAttrNameMap: {},
|
|
||||||
referenceCIIdMap: {},
|
|
||||||
|
|
||||||
showInstanceDetail: false,
|
|
||||||
detailCIId: -1,
|
|
||||||
detailCITypeId: -1,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
windowHeight() {
|
...mapState({
|
||||||
return this.$store.state.windowHeight
|
cmdbSearchValue: (state) => state.app.cmdbSearchValue,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
cmdbSearchValue: {
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
handler() {
|
||||||
|
this.tabActive = 'resourceSearch'
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
async mounted() {
|
||||||
this.initData()
|
try {
|
||||||
|
await Promise.all([
|
||||||
|
this.getCITypeGroups(),
|
||||||
|
this.getAllCITypes()
|
||||||
|
])
|
||||||
|
} catch (error) {
|
||||||
|
console.log('resource search mounted fail', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isInit = true
|
||||||
},
|
},
|
||||||
methods: {
|
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() {
|
async getCITypeGroups() {
|
||||||
const res = await getCITypeGroups({ need_other: true })
|
const res = await getCITypeGroups({ need_other: true })
|
||||||
|
|
||||||
this.CITypeGroup = res
|
this.CITypeGroup = res
|
||||||
.filter((item) => item.ci_types && item.ci_types.length)
|
.filter((item) => item?.ci_types?.length)
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
item.id = `parent_${item.id || -1}`
|
item.id = `parent_${item.id || -1}`
|
||||||
return { ..._.cloneDeep(item) }
|
return item
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
async getAllCITypes() {
|
async getAllCITypes() {
|
||||||
const res = await getCITypes()
|
const res = await getCITypes()
|
||||||
this.CITypes = res?.ci_types
|
this.allCITypes = 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>
|
</script>
|
||||||
|
|
||||||
|
@ -532,84 +108,32 @@ export default {
|
||||||
.resource-search {
|
.resource-search {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&-before {
|
&-tab {
|
||||||
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;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
margin-bottom: 12px;
|
||||||
margin-bottom: 25px;
|
|
||||||
|
|
||||||
&-icon {
|
&-item {
|
||||||
font-size: 28px;
|
padding-right: 8px;
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #86909C;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-right: solid 1px #E4E7ED;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-text {
|
&:hover {
|
||||||
margin-left: 10px;
|
color: #2F54EB;
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1D2129;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-after {
|
&_active {
|
||||||
width: 100%;
|
color: #2F54EB;
|
||||||
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>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,516 @@
|
||||||
|
<template>
|
||||||
|
<div class="search-table">
|
||||||
|
<div class="search-table-header">
|
||||||
|
<div class="table-tab">
|
||||||
|
<div
|
||||||
|
v-for="(tab) in tabList"
|
||||||
|
:key="tab.value"
|
||||||
|
:class="['table-tab-item', tabActive === tab.value ? 'table-tab-item_active' : '']"
|
||||||
|
@click="clickTab(tab.value)"
|
||||||
|
>
|
||||||
|
{{ tab.value }}
|
||||||
|
(<span class="table-tab-item-count">{{ tab.count }}</span>)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
v-if="tableData.ciList && tableData.ciList.length"
|
||||||
|
type="primary"
|
||||||
|
class="ops-button-ghost search-table-export"
|
||||||
|
ghost
|
||||||
|
@click="handleExport"
|
||||||
|
>
|
||||||
|
<ops-icon type="veops-export" />
|
||||||
|
{{ $t('export') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ops-table
|
||||||
|
ref="xTable"
|
||||||
|
show-overflow
|
||||||
|
:data="tableData.ciList"
|
||||||
|
size="small"
|
||||||
|
:height="`${tableHeight}px`"
|
||||||
|
:cell-class-name="getCellClassName"
|
||||||
|
:header-cell-class-name="getHeaderCellClassName"
|
||||||
|
:checkbox-config="{ range: true }"
|
||||||
|
:loading="isSearchLoading"
|
||||||
|
:column-config="{ resizable: true }"
|
||||||
|
:resizable-config="{ minWidth: 60 }"
|
||||||
|
class="checkbox-hover-table"
|
||||||
|
>
|
||||||
|
<vxe-table-column
|
||||||
|
v-if="tableData.ciList && tableData.ciList.length"
|
||||||
|
align="center"
|
||||||
|
type="checkbox"
|
||||||
|
width="60"
|
||||||
|
>
|
||||||
|
<template #default="{row}">
|
||||||
|
{{ getRowSeq(row) }}
|
||||||
|
</template>
|
||||||
|
</vxe-table-column>
|
||||||
|
|
||||||
|
<template
|
||||||
|
v-if="returnPath && tableData.pathList && tableData.pathList.length"
|
||||||
|
>
|
||||||
|
<vxe-table-column
|
||||||
|
v-for="(path, index) in tableData.pathList"
|
||||||
|
class="table-path-column"
|
||||||
|
:key="`${path.id}-${index}`"
|
||||||
|
:title="tableData.pathList[index].name"
|
||||||
|
:field="path.id"
|
||||||
|
:show-header-overflow="false"
|
||||||
|
:width="index !== tableData.pathList.length - 1 ? 160 : 100"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="table-path-header">
|
||||||
|
<span
|
||||||
|
class="table-path-header-name"
|
||||||
|
:style="{
|
||||||
|
maxWidth: tableData.pathList[index].relation ? '70px' : '100%'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<a-tooltip :title="tableData.pathList[index].name">
|
||||||
|
{{ tableData.pathList[index].name }}
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="table-path-header-right"
|
||||||
|
v-if="tableData.pathList[index].relation"
|
||||||
|
>
|
||||||
|
<span class="table-path-header-line">
|
||||||
|
<a-icon
|
||||||
|
type="caret-right"
|
||||||
|
class="table-path-header-line-arrow"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="table-path-header-relation"
|
||||||
|
>
|
||||||
|
<span class="table-path-header-relation-text">
|
||||||
|
<a-tooltip :title="tableData.pathList[index].relation">
|
||||||
|
{{ tableData.pathList[index].relation }}
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #default="{ row, columnIndex }">
|
||||||
|
<span
|
||||||
|
v-if="columnIndex === 1"
|
||||||
|
v-html="markSearchValue(row.pathCI[path.id])"
|
||||||
|
></span>
|
||||||
|
<span v-else >{{ row.pathCI[path.id] }}</span>
|
||||||
|
</template>
|
||||||
|
</vxe-table-column>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="tableData.ciAttr && tableData.ciAttr.length">
|
||||||
|
<vxe-table-column
|
||||||
|
v-for="(attr, index) in tableData.ciAttr"
|
||||||
|
:key="`${attr.name}_${index}`"
|
||||||
|
:title="attr.alias || attr.name || ''"
|
||||||
|
:field="attr.name"
|
||||||
|
:width="attr.width"
|
||||||
|
:show-header-overflow="true"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<AttrDisplay
|
||||||
|
:attr="attr"
|
||||||
|
:ci="row.targetCI"
|
||||||
|
:referenceShowAttrNameMap="referenceShowAttrNameMap"
|
||||||
|
:referenceCIIdMap="referenceCIIdMap"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</vxe-table-column>
|
||||||
|
</template>
|
||||||
|
</ops-table>
|
||||||
|
|
||||||
|
<BatchDownload
|
||||||
|
ref="batchDownload"
|
||||||
|
:showFileTypeSelect="false"
|
||||||
|
@batchDownload="batchDownload"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
|
import moment from 'moment'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import ExcelJS from 'exceljs'
|
||||||
|
import FileSaver from 'file-saver'
|
||||||
|
import AttrDisplay from '@/modules/cmdb/views/resource_search_2/resourceSearch/components/attrDisplay.vue'
|
||||||
|
import BatchDownload from '@/modules/cmdb/components/batchDownload/batchDownload.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CITable',
|
||||||
|
components: {
|
||||||
|
AttrDisplay,
|
||||||
|
BatchDownload
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
allTableData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
tabActive: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
returnPath: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isHideSearchCondition: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
referenceShowAttrNameMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
referenceCIIdMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
searchValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
isSearchLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
windowHeight: (state) => state.windowHeight,
|
||||||
|
}),
|
||||||
|
tableHeight() {
|
||||||
|
return this.isHideSearchCondition ? this.windowHeight - 308 : this.windowHeight - 458
|
||||||
|
},
|
||||||
|
tableData() {
|
||||||
|
return this.allTableData?.[this.tabActive] || {}
|
||||||
|
},
|
||||||
|
tabList() {
|
||||||
|
const keys = Object.keys(this.allTableData) || []
|
||||||
|
return keys.map((key) => {
|
||||||
|
return {
|
||||||
|
value: key,
|
||||||
|
count: this.allTableData?.[key]?.count || 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
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>`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
clickTab(tab) {
|
||||||
|
this.$emit('updateTab', tab)
|
||||||
|
},
|
||||||
|
|
||||||
|
getRowSeq(row) {
|
||||||
|
const table = this.$refs?.['xTable']?.getVxetableRef?.() || null
|
||||||
|
return table?.getRowSeq?.(row)
|
||||||
|
},
|
||||||
|
getCellClassName({ columnIndex }) {
|
||||||
|
const pathLength = this.tableData?.pathList?.length
|
||||||
|
if (columnIndex <= pathLength && this.returnPath) {
|
||||||
|
return 'table-path-cell'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
getHeaderCellClassName({ columnIndex }) {
|
||||||
|
const pathLength = this.tableData?.pathList?.length
|
||||||
|
if (columnIndex <= pathLength && this.returnPath) {
|
||||||
|
return 'table-path-header-cell'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
|
||||||
|
handleExport() {
|
||||||
|
const preferenceAttrList = []
|
||||||
|
if (this.returnPath && this.tableData?.pathList?.length) {
|
||||||
|
preferenceAttrList.push(...this.tableData.pathList.map((path) => {
|
||||||
|
return {
|
||||||
|
name: path.id,
|
||||||
|
alias: path.name
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.tableData?.ciAttr?.length) {
|
||||||
|
const ciAttr = _.cloneDeep(this.tableData.ciAttr)
|
||||||
|
ciAttr.forEach((attr) => {
|
||||||
|
attr.alias = attr.alias || attr.name
|
||||||
|
})
|
||||||
|
preferenceAttrList.push(...ciAttr)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.batchDownload.open({
|
||||||
|
preferenceAttrList,
|
||||||
|
ciTypeName: this.tabActive || '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
batchDownload({ checkedKeys }) {
|
||||||
|
const excel_name = `cmdb-${this.tabActive}-${moment().format('YYYYMMDDHHmmss')}.xlsx`
|
||||||
|
const wb = new ExcelJS.Workbook()
|
||||||
|
|
||||||
|
const tableRef = this.$refs.xTable.getVxetableRef()
|
||||||
|
let tableData = _.cloneDeep([
|
||||||
|
...tableRef.getCheckboxReserveRecords(),
|
||||||
|
...tableRef.getCheckboxRecords(true),
|
||||||
|
])
|
||||||
|
if (!tableData.length) {
|
||||||
|
const { fullData } = tableRef.getTableData()
|
||||||
|
tableData = _.cloneDeep(fullData)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ws = wb.addWorksheet(this.tabActive)
|
||||||
|
|
||||||
|
const pathColumns = []
|
||||||
|
const targetColumns = []
|
||||||
|
|
||||||
|
if (this.returnPath) {
|
||||||
|
const pathFilter = this.tableData.pathList.filter((path) => checkedKeys.includes(path.id))
|
||||||
|
|
||||||
|
pathFilter.forEach((path) => {
|
||||||
|
pathColumns.push({
|
||||||
|
header: path.name || '',
|
||||||
|
key: path.id,
|
||||||
|
width: 20,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const attrMap = new Map()
|
||||||
|
const attrFilter = this.tableData.ciAttr.filter((attr) => checkedKeys.includes(attr.name))
|
||||||
|
attrFilter.forEach((attr) => {
|
||||||
|
attrMap.set(attr.name, attr)
|
||||||
|
|
||||||
|
targetColumns.push({
|
||||||
|
header: attr.alias || attr.name || '',
|
||||||
|
key: attr.name,
|
||||||
|
width: 20,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
ws.columns = [
|
||||||
|
...pathColumns,
|
||||||
|
...targetColumns
|
||||||
|
]
|
||||||
|
|
||||||
|
tableData.forEach(({ pathCI, targetCI }) => {
|
||||||
|
const row = {}
|
||||||
|
if (this.returnPath) {
|
||||||
|
pathColumns.forEach(({ key }) => {
|
||||||
|
row[key] = pathCI?.[key] || ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
targetColumns.forEach(({ key }) => {
|
||||||
|
const value = targetCI?.[key] ?? null
|
||||||
|
const attr = attrMap.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)
|
||||||
|
})
|
||||||
|
|
||||||
|
wb.xlsx.writeBuffer().then((buffer) => {
|
||||||
|
const file = new Blob([buffer], {
|
||||||
|
type: 'application/octet-stream',
|
||||||
|
})
|
||||||
|
FileSaver.saveAs(file, excel_name)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||||
|
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.search-table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between
|
||||||
|
}
|
||||||
|
|
||||||
|
&-export {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-tab {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 35px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&-count {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_active {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-path-header {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
max-width: 80px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: -5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-line {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
position: relative;
|
||||||
|
background-color: #CACDD9;
|
||||||
|
z-index: 0;
|
||||||
|
|
||||||
|
&-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: -6px;
|
||||||
|
top: -6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #CACDD9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-relation {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border: solid 1px #E4E7ED;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 8px;
|
||||||
|
border-radius: 22px;
|
||||||
|
z-index: 2;
|
||||||
|
max-width: 70px;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #A5A9BC;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-hover-table {
|
||||||
|
/deep/ .vxe-table--body-wrapper {
|
||||||
|
.vxe-checkbox--label {
|
||||||
|
display: inline;
|
||||||
|
padding-left: 0px !important;
|
||||||
|
color: #bfbfbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vxe-icon-checkbox-unchecked {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vxe-icon-checkbox-checked ~ .vxe-checkbox--label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vxe-cell--checkbox {
|
||||||
|
&:hover {
|
||||||
|
.vxe-icon-checkbox-unchecked {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vxe-checkbox--label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .table-path-header-cell {
|
||||||
|
background-color: #EBEFF8 !important;
|
||||||
|
|
||||||
|
.vxe-cell--title {
|
||||||
|
width: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .table-path-cell {
|
||||||
|
background-color: #F9FBFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .attr-display {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,162 @@
|
||||||
|
<template>
|
||||||
|
<a-popover
|
||||||
|
v-model="visible"
|
||||||
|
trigger="click"
|
||||||
|
placement="bottomRight"
|
||||||
|
@visibleChange="handleVisibleChange"
|
||||||
|
>
|
||||||
|
<div class="search-condition-filter">
|
||||||
|
<a-icon class="search-condition-filter-icon" type="filter" />
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="expression"
|
||||||
|
class="search-condition-filter-flag"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template slot="content">
|
||||||
|
<div class="search-condition-content">
|
||||||
|
<div class="search-condition-content-title">
|
||||||
|
{{ $t('cmdb.relationSearch.conditionFilter') }}:
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ConditionFilter
|
||||||
|
ref="conditionFilterRef"
|
||||||
|
:canSearchPreferenceAttrList="allAttributesList"
|
||||||
|
:expression="expression"
|
||||||
|
:CITypeIds="selectCITypeIds"
|
||||||
|
:isDropdown="false"
|
||||||
|
@setExpFromFilter="setExpFromFilter"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="search-condition-filter-submit">
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="clickSubmit()"
|
||||||
|
>
|
||||||
|
{{ $t('confirm') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ConditionFilter from '@/modules/cmdb/components/conditionFilter/index.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FilterPopover',
|
||||||
|
components: {
|
||||||
|
ConditionFilter
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
allAttributesList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
selectCITypeIds: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
expression: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleVisibleChange(open) {
|
||||||
|
if (open) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.conditionFilterRef.init(true, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickSubmit() {
|
||||||
|
this.$refs.conditionFilterRef.handleSubmit()
|
||||||
|
this.visible = false
|
||||||
|
},
|
||||||
|
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('changeExpression', expression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.search-condition-filter {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 2px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&-flag {
|
||||||
|
position: absolute;
|
||||||
|
right: -5px;
|
||||||
|
bottom: -5px;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #00B42A22;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #00B42A;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #A5A9BC;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.search-condition-filter-icon {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-condition-content {
|
||||||
|
min-width: 500px;
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-condition-filter-submit {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,98 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:title="$t('cmdb.relationSearch.saveCondition')"
|
||||||
|
:visible="visible"
|
||||||
|
dialogClass="save-condition-modal"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form-model
|
||||||
|
ref="saveConditionForm"
|
||||||
|
:model="form"
|
||||||
|
:rules="formRule"
|
||||||
|
:labelCol="labelCol"
|
||||||
|
:wrapperCol="wrapperCol"
|
||||||
|
>
|
||||||
|
<a-form-model-item
|
||||||
|
:label="$t('cmdb.relationSearch.conditionName')"
|
||||||
|
prop="name"
|
||||||
|
>
|
||||||
|
<a-input v-model="form.name" />
|
||||||
|
</a-form-model-item>
|
||||||
|
</a-form-model>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SaveConditionModal',
|
||||||
|
props: {
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
labelCol() {
|
||||||
|
return {
|
||||||
|
span: this.$i18n.locale === 'en' ? 7 : 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wrapperCol() {
|
||||||
|
return {
|
||||||
|
span: this.$i18n.locale === 'en' ? 17 : 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
name: ''
|
||||||
|
},
|
||||||
|
formRule: {
|
||||||
|
name: [
|
||||||
|
{ required: true, message: this.$t('placeholder1') }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleOk() {
|
||||||
|
this.$refs.saveConditionForm.validate((valid) => {
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('ok', {
|
||||||
|
name: this.form.name
|
||||||
|
})
|
||||||
|
this.handleCancel()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCancel() {
|
||||||
|
this.$refs.saveConditionForm.clearValidate()
|
||||||
|
this.form.name = ''
|
||||||
|
this.$emit('cancel')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.save-condition-modal {
|
||||||
|
/deep/ .ant-modal-close-x {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
line-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .ant-modal-body {
|
||||||
|
padding: 24px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .ant-modal-footer {
|
||||||
|
padding: 10px 18px 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,728 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="search-condition"
|
||||||
|
:style="{
|
||||||
|
'--label-width': this.$i18n.locale === 'en' ? '90px' : '60px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="search-condition-row">
|
||||||
|
<div class="search-condition-label">
|
||||||
|
{{ $t('cmdb.relationSearch.sourceCIType') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-condition-control">
|
||||||
|
<treeselect
|
||||||
|
:value="sourceCIType"
|
||||||
|
class="custom-treeselect custom-treeselect-bgcAndBorder filter-content-ciTypes"
|
||||||
|
:style="{
|
||||||
|
width: '100%',
|
||||||
|
zIndex: '1000',
|
||||||
|
'--custom-height': '32px',
|
||||||
|
'--custom-bg-color': '#FFF',
|
||||||
|
'--custom-multiple-lineHeight': '32px',
|
||||||
|
}"
|
||||||
|
:multiple="false"
|
||||||
|
:clearable="true"
|
||||||
|
searchable
|
||||||
|
:options="CITypeGroup"
|
||||||
|
:limit="1"
|
||||||
|
:limitText="(count) => `+ ${count}`"
|
||||||
|
:disableBranchNodes="true"
|
||||||
|
:placeholder="$t('cmdb.relationSearch.sourceCITypeTip')"
|
||||||
|
: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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@input="updateSourceCIType"
|
||||||
|
@open="handleSourceCITypeOpen"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:title="node.label"
|
||||||
|
slot="option-label"
|
||||||
|
slot-scope="{ node }"
|
||||||
|
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||||
|
>
|
||||||
|
{{ node.label }}
|
||||||
|
</div>
|
||||||
|
</treeselect>
|
||||||
|
|
||||||
|
<a-input-search
|
||||||
|
class="search-condition-input"
|
||||||
|
:placeholder="$t('cmdb.relationSearch.sourceCITYpeInput')"
|
||||||
|
:value="sourceCITypeSearchValue"
|
||||||
|
@change="handleSourceCITypeSearchValueChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FilterPopover
|
||||||
|
:allAttributesList="sourceAllAttributesList"
|
||||||
|
:selectCITypeIds="sourceCIType ? [sourceCIType] : []"
|
||||||
|
:expression="sourceExpression"
|
||||||
|
@changeExpression="changeSourceExpression"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-condition-row">
|
||||||
|
<div class="search-condition-label">
|
||||||
|
{{ $t('cmdb.relationSearch.targetCIType') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-condition-control">
|
||||||
|
<a-select
|
||||||
|
:value="targetCITypes"
|
||||||
|
show-search
|
||||||
|
optionFilterProp="children"
|
||||||
|
mode="multiple"
|
||||||
|
:placeholder="$t('cmdb.relationSearch.targetCITypeTip')"
|
||||||
|
class="search-condition-select"
|
||||||
|
@change="handleTargetCITypeChange"
|
||||||
|
>
|
||||||
|
<a-select-opt-group
|
||||||
|
v-for="(key, index) in Object.keys(targetCITypeGroup)"
|
||||||
|
:key="key"
|
||||||
|
:label="$t('cmdb.relationSearch.level') + `${index + 1}`"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="citype in targetCITypeGroup[key]"
|
||||||
|
:key="citype.id"
|
||||||
|
:value="citype.id"
|
||||||
|
>
|
||||||
|
{{ citype.alias || citype.name }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select-opt-group>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FilterPopover
|
||||||
|
:allAttributesList="targetAllAttributesList"
|
||||||
|
:selectCITypeIds="targetCITypes"
|
||||||
|
:expression="targetExpression"
|
||||||
|
@changeExpression="changeTargetExpression"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-condition-row">
|
||||||
|
<div class="search-condition-label">
|
||||||
|
{{ $t('cmdb.relationSearch.pathSelect') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-condition-control">
|
||||||
|
<a-dropdown
|
||||||
|
v-model="pathSelectVisible"
|
||||||
|
:trigger="['click']"
|
||||||
|
:getPopupContainer="(trigger) => trigger.parentElement"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
:value="pathDisplay"
|
||||||
|
readOnly
|
||||||
|
:placeholder="$t('cmdb.relationSearch.pathSelectTip')"
|
||||||
|
class="search-condition-input"
|
||||||
|
@click="e => e.preventDefault()"
|
||||||
|
>
|
||||||
|
<a-icon
|
||||||
|
slot="suffix"
|
||||||
|
type="caret-down"
|
||||||
|
class="search-condition-input-suffix"
|
||||||
|
/>
|
||||||
|
</a-input>
|
||||||
|
<div @click="clickPathSelectDropdown" slot="overlay">
|
||||||
|
<template v-if="allPath.length" >
|
||||||
|
<a-checkbox-group
|
||||||
|
:value="selectedPath"
|
||||||
|
class="search-condition-checkbox"
|
||||||
|
@change="handlePathChange"
|
||||||
|
>
|
||||||
|
<a-checkbox
|
||||||
|
v-for="(path) in allPath"
|
||||||
|
:key="path.value"
|
||||||
|
:value="path.value"
|
||||||
|
class="search-condition-checkbox-item"
|
||||||
|
>
|
||||||
|
<a-tooltip :title="path.pathNames">
|
||||||
|
<span class="search-condition-checkbox-item-name">
|
||||||
|
{{ path.pathNames }}
|
||||||
|
</span>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-checkbox>
|
||||||
|
</a-checkbox-group>
|
||||||
|
|
||||||
|
<div class="search-condition-path-divider"></div>
|
||||||
|
|
||||||
|
<div class="search-condition-path-switch">
|
||||||
|
<span>{{ $t('cmdb.relationSearch.returnPath') }}</span>
|
||||||
|
<a-switch
|
||||||
|
:checked="returnPath"
|
||||||
|
@change="handleReturnPathChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="search-condition-path-null"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="require('@/assets/data_empty.png')"
|
||||||
|
class="search-condition-path-null-img"
|
||||||
|
/>
|
||||||
|
<div class="search-condition-path-null-text">{{ $t('noData') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-dropdown>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
:class="['search-condition-submit', isSearchLoading ? 'search-condition-submit-loading' : '']"
|
||||||
|
@click="clickSubmit"
|
||||||
|
>
|
||||||
|
<a-icon
|
||||||
|
:type="isSearchLoading ? 'loading' : 'search'"
|
||||||
|
class="search-condition-submit-icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="search-condition-favor">
|
||||||
|
<div class="search-condition-favor-list">
|
||||||
|
<div
|
||||||
|
v-for="(item) in favorList"
|
||||||
|
:key="item.id"
|
||||||
|
class="search-condition-favor-item"
|
||||||
|
@click="clickFavor(item)"
|
||||||
|
>
|
||||||
|
<div class="search-condition-favor-name">
|
||||||
|
{{ item.option.name }}
|
||||||
|
</div>
|
||||||
|
<a-icon
|
||||||
|
@click.stop="deleteFavor(item.id)"
|
||||||
|
type="close"
|
||||||
|
class="search-condition-favor-close"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="search-condition-favor-right">
|
||||||
|
<a
|
||||||
|
class="search-condition-save"
|
||||||
|
@click="saveCondition"
|
||||||
|
>
|
||||||
|
<ops-icon
|
||||||
|
type="veops-save"
|
||||||
|
class="search-condition-save-icon"
|
||||||
|
/>
|
||||||
|
<span class="search-condition-save-text">
|
||||||
|
{{ $t('cmdb.relationSearch.saveCondition') }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="isSearch"
|
||||||
|
class="search-condition-hide"
|
||||||
|
@click="hideSearchCondition"
|
||||||
|
>
|
||||||
|
<a-icon
|
||||||
|
type="up"
|
||||||
|
class="search-condition-hide-icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SaveConditionModal
|
||||||
|
:visible="saveConditionVisible"
|
||||||
|
@ok="handleSaveConditionOk"
|
||||||
|
@cancel="saveConditionVisible = false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getPreferenceSearch, savePreferenceSearch, deletePreferenceSearch } from '@/modules/cmdb/api/preference'
|
||||||
|
|
||||||
|
import FilterPopover from './filterPopover.vue'
|
||||||
|
import SaveConditionModal from './saveConditionModal.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SearchCondition',
|
||||||
|
components: {
|
||||||
|
FilterPopover,
|
||||||
|
SaveConditionModal
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
CITypeGroup: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
sourceCIType: {
|
||||||
|
type: [Number, undefined],
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
sourceCITypeSearchValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
sourceAllAttributesList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
sourceExpression: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
targetCITypes: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
targetCITypeGroup: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
targetAllAttributesList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
targetExpression: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
returnPath: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
allPath: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
selectedPath: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
isSearch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
isSearchLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
oldsourceCIType: undefined,
|
||||||
|
saveConditionVisible: false,
|
||||||
|
pathSelectVisible: false,
|
||||||
|
|
||||||
|
favorList: [],
|
||||||
|
relationSearchFavorKey: '__relation_favor__'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
pathDisplay() {
|
||||||
|
return this.allPath?.filter((path) => this?.selectedPath?.includes?.(path?.value))?.map((path) => path?.pathNames)?.join(', ') || ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getFavorList()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getFavorList() {
|
||||||
|
const favorList = await getPreferenceSearch({
|
||||||
|
name: this.relationSearchFavorKey
|
||||||
|
})
|
||||||
|
favorList.sort((a, b) => b.id - a.id)
|
||||||
|
this.favorList = favorList
|
||||||
|
},
|
||||||
|
|
||||||
|
updateSourceCIType(value) {
|
||||||
|
this.$emit('changeData', {
|
||||||
|
name: 'sourceCIType',
|
||||||
|
value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSourceCITypeSearchValueChange(e) {
|
||||||
|
const value = e.target.value
|
||||||
|
this.$emit('changeData', {
|
||||||
|
name: 'sourceCITypeSearchValue',
|
||||||
|
value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
changeSourceExpression(expression) {
|
||||||
|
this.$emit('changeData', {
|
||||||
|
name: 'sourceExpression',
|
||||||
|
value: expression
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleTargetCITypeChange(value) {
|
||||||
|
this.$emit('changeData', {
|
||||||
|
name: 'targetCITypes',
|
||||||
|
value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
changeTargetExpression(expression) {
|
||||||
|
this.$emit('changeData', {
|
||||||
|
name: 'targetExpression',
|
||||||
|
value: expression
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handlePathChange(value) {
|
||||||
|
this.$emit('changeData', {
|
||||||
|
name: 'selectedPath',
|
||||||
|
value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleReturnPathChange(checked) {
|
||||||
|
this.$emit('changeData', {
|
||||||
|
name: 'returnPath',
|
||||||
|
value: checked
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
clickSubmit() {
|
||||||
|
if (this.isSearchLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.validateControl()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('search')
|
||||||
|
},
|
||||||
|
|
||||||
|
validateControl() {
|
||||||
|
if (!this.sourceCIType) {
|
||||||
|
this.$message.warning(`${this.$t('placeholder2')} ${this.$t('cmdb.relationSearch.sourceCIType')}`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.targetCITypes.length) {
|
||||||
|
this.$message.warning(`${this.$t('placeholder2')} ${this.$t('cmdb.relationSearch.targetCIType')}`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.selectedPath.length) {
|
||||||
|
this.$message.warning(`${this.$t('placeholder2')} ${this.$t('cmdb.relationSearch.path')}`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
saveCondition() {
|
||||||
|
if (this.validateControl()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveConditionVisible = true
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleSaveConditionOk({ name }) {
|
||||||
|
if (this?.favorList?.length >= 10) {
|
||||||
|
const deletePromises = this.favorList.slice(9).map((item) => {
|
||||||
|
return deletePreferenceSearch(item.id)
|
||||||
|
})
|
||||||
|
await Promise.all(deletePromises)
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
name,
|
||||||
|
sourceCIType: this.sourceCIType,
|
||||||
|
searchValue: this.sourceCITypeSearchValue,
|
||||||
|
sourceExpression: this.sourceExpression,
|
||||||
|
targetCITypes: this.targetCITypes,
|
||||||
|
targetExpression: this.targetExpression,
|
||||||
|
selectedPath: this.selectedPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
savePreferenceSearch({
|
||||||
|
option: {
|
||||||
|
...option
|
||||||
|
},
|
||||||
|
name: this.relationSearchFavorKey
|
||||||
|
}).then(() => {
|
||||||
|
this.$message.success(this.$t('saveSuccess'))
|
||||||
|
this.getFavorList()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteFavor(id) {
|
||||||
|
deletePreferenceSearch(id).then(() => {
|
||||||
|
this.$message.success(this.$t('deleteSuccess'))
|
||||||
|
this.getFavorList()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
hideSearchCondition() {
|
||||||
|
this.$emit('hideSearchCondition')
|
||||||
|
},
|
||||||
|
|
||||||
|
clickPathSelectDropdown(e) {
|
||||||
|
if (e.key === '3') {
|
||||||
|
this.pathSelectVisible = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clickFavor(data) {
|
||||||
|
if (data?.option) {
|
||||||
|
this.$emit('clickFavor', data.option)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSourceCITypeOpen() {
|
||||||
|
this.pathSelectVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.search-condition {
|
||||||
|
&-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
column-gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #000000;
|
||||||
|
width: var(--label-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 12px;
|
||||||
|
width: 500px;
|
||||||
|
|
||||||
|
/deep/ .ant-dropdown-content {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
padding: 14px 18px;
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-input {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
/deep/ .ant-input {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-suffix {
|
||||||
|
color: #CACDD9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-select {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
/deep/ .ant-select-selection {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-path {
|
||||||
|
&-divider {
|
||||||
|
width: 100%;
|
||||||
|
margin: 20px 0;
|
||||||
|
height: 1px;
|
||||||
|
background-color: #E4E7ED;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-switch {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-checkbox {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
margin: 0px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
/deep/ & > span:first-child {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ & > span:last-child {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-path-null {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&-img {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
margin-top: 12px;
|
||||||
|
color: #A5A9BC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-submit {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #2F54EB;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-loading {
|
||||||
|
background-color: #2F54EB90;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #2F54EB90;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-favor {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
column-gap: 15px;
|
||||||
|
|
||||||
|
&-list {
|
||||||
|
max-width: 500px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 14px;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-close {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #4E5969;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #4E596980;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 150px;
|
||||||
|
background-color: #EBEFF8;
|
||||||
|
border-radius: 28px;
|
||||||
|
padding: 2px 12px;
|
||||||
|
column-gap: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #D9E4FA;
|
||||||
|
|
||||||
|
.search-condition-favor-name {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-condition-favor-close {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-save {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
column-gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-hide {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
background-color: #EBEFF8;
|
||||||
|
border-radius: 1px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 22px;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #86909C;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #D9E4FA;
|
||||||
|
|
||||||
|
.search-condition-hide-icon {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,693 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="relationSearchRef"
|
||||||
|
class="relation-search"
|
||||||
|
:style="{ height: `${windowHeight - 131}px` }"
|
||||||
|
>
|
||||||
|
<div class="relation-search-wrap">
|
||||||
|
<div
|
||||||
|
v-if="!isSearch"
|
||||||
|
class="relation-search-title"
|
||||||
|
>
|
||||||
|
<ops-icon class="relation-search-title-icon" type="veops-relationship2" />
|
||||||
|
<div class="relation-search-title-text">{{ $t('cmdb.relationSearch.relationSearch') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="isHideSearchCondition"
|
||||||
|
class="relation-search-expand"
|
||||||
|
>
|
||||||
|
<div class="relation-search-expand-line"></div>
|
||||||
|
|
||||||
|
<div class="relation-search-expand-right">
|
||||||
|
<div
|
||||||
|
class="relation-search-expand-handle"
|
||||||
|
@click="isHideSearchCondition = false"
|
||||||
|
>
|
||||||
|
<a-icon
|
||||||
|
type="down"
|
||||||
|
class="relation-search-expand-icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="relation-search-expand-text"
|
||||||
|
@click="isHideSearchCondition = false"
|
||||||
|
>
|
||||||
|
{{ $t('cmdb.relationSearch.expandCondition') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SearchCondition
|
||||||
|
v-else
|
||||||
|
:CITypeGroup="CITypeGroup"
|
||||||
|
:sourceCIType="sourceCIType"
|
||||||
|
:sourceCITypeSearchValue="sourceCITypeSearchValue"
|
||||||
|
:sourceAllAttributesList="sourceAllAttributesList"
|
||||||
|
:sourceExpression="sourceExpression"
|
||||||
|
:targetCITypes="targetCITypes"
|
||||||
|
:targetCITypeGroup="targetCITypeGroup"
|
||||||
|
:targetAllAttributesList="targetAllAttributesList"
|
||||||
|
:targetExpression="targetExpression"
|
||||||
|
:returnPath="returnPath"
|
||||||
|
:allPath="allPath"
|
||||||
|
:selectedPath="selectedPath"
|
||||||
|
:isSearch="isSearch"
|
||||||
|
:isSearchLoading="isSearchLoading"
|
||||||
|
@changeData="changeData"
|
||||||
|
@search="handleSearch"
|
||||||
|
@hideSearchCondition="isHideSearchCondition = true"
|
||||||
|
@clickFavor="clickFavor"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="isSearch"
|
||||||
|
class="relation-search-main"
|
||||||
|
>
|
||||||
|
<CITable
|
||||||
|
:allTableData="allTableData"
|
||||||
|
:tabActive="tableTabActive"
|
||||||
|
:returnPath="returnPath"
|
||||||
|
:isHideSearchCondition="isHideSearchCondition"
|
||||||
|
:referenceShowAttrNameMap="referenceShowAttrNameMap"
|
||||||
|
:referenceCIIdMap="referenceCIIdMap"
|
||||||
|
:searchValue="sourceCITypeSearchValue"
|
||||||
|
:isSearchLoading="isSearchLoading"
|
||||||
|
@updateTab="(tab) => tableTabActive = tab"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="relation-search-pagination">
|
||||||
|
<a-pagination
|
||||||
|
:showSizeChanger="true"
|
||||||
|
:current="page"
|
||||||
|
size="small"
|
||||||
|
:total="totalNumber"
|
||||||
|
show-quick-jumper
|
||||||
|
:page-size="pageSize"
|
||||||
|
:page-size-options="pageSizeOptions"
|
||||||
|
:show-total="
|
||||||
|
(total, range) =>
|
||||||
|
$t('pagination.total', {
|
||||||
|
range0: range[0],
|
||||||
|
range1: range[1],
|
||||||
|
total,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
@showSizeChange="handlePageSizeChange"
|
||||||
|
@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>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="!isSearch"
|
||||||
|
class="relation-search-bg"
|
||||||
|
:src="require('@/modules/cmdb/assets/resourceSearch/resource_search_bg_1.png')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
import { getCITypeAttributesByTypeIds } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
|
import { getRecursive_level2children, getCITypeRelationPath } from '@/modules/cmdb/api/CITypeRelation'
|
||||||
|
import { searchCIRelationPath } from '@/modules/cmdb/api/CIRelation'
|
||||||
|
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||||
|
import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
||||||
|
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||||
|
import { strLength } from '@/modules/cmdb/utils/helper.js'
|
||||||
|
|
||||||
|
import SearchCondition from './components/searchCondition.vue'
|
||||||
|
import CITable from './components/ciTable.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RelationSearch',
|
||||||
|
components: {
|
||||||
|
SearchCondition,
|
||||||
|
CITable
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
CITypeGroup: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
allCITypes: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isSearch: false, // 是否搜索
|
||||||
|
isHideSearchCondition: false, // 是否隐藏搜索条件
|
||||||
|
isWatchData: true, // 是否监听数据变化
|
||||||
|
isSearchLoading: false, // 搜索中
|
||||||
|
|
||||||
|
sourceCIType: undefined, // 已选源模型
|
||||||
|
sourceCITypeSearchValue: '', // 源模型搜索关键词
|
||||||
|
sourceAllAttributesList: [], // 源模型所有属性
|
||||||
|
sourceExpression: '', // 源模型表达式
|
||||||
|
|
||||||
|
targetCITypes: [], // 目标模型
|
||||||
|
targetCITypeGroup: {}, // 目标模型分组
|
||||||
|
targetAllAttributesList: [], // 目标模型所有属性
|
||||||
|
targetExpression: '', // 目标模型表达式
|
||||||
|
|
||||||
|
returnPath: true, // 表格是否展示路径详情
|
||||||
|
allPath: [], // 所有路径选项
|
||||||
|
selectedPath: [], // 已选择路径
|
||||||
|
|
||||||
|
// table
|
||||||
|
page: 1,
|
||||||
|
pageSize: 50,
|
||||||
|
pageSizeOptions: ['50', '100', '200'],
|
||||||
|
allTableData: {}, // 表格数据
|
||||||
|
totalNumber: 0, // 数据总数
|
||||||
|
tableTabActive: '', // 当前 table tab
|
||||||
|
referenceShowAttrNameMap: {},
|
||||||
|
referenceCIIdMap: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
windowHeight() {
|
||||||
|
return this.$store.state.windowHeight
|
||||||
|
},
|
||||||
|
watchParams() {
|
||||||
|
return {
|
||||||
|
sourceCIType: this.sourceCIType,
|
||||||
|
targetCITypes: this.targetCITypes
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
sourceCIType: {
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
handler(id) {
|
||||||
|
if (this.isWatchData) {
|
||||||
|
this.sourceExpression = ''
|
||||||
|
|
||||||
|
this.targetCITypes = []
|
||||||
|
this.targetAllAttributesList = []
|
||||||
|
this.targetExpression = ''
|
||||||
|
|
||||||
|
this.selectedPath = []
|
||||||
|
|
||||||
|
this.getTargetCITypeGroup(id)
|
||||||
|
this.updateSourceAllAttributesList(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
targetCITypes: {
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
handler(ids) {
|
||||||
|
if (this.isWatchData) {
|
||||||
|
this.selectedPath = []
|
||||||
|
this.targetExpression = ''
|
||||||
|
this.updateTargetAllAttributesList(ids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watchParams: {
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
handler(data) {
|
||||||
|
if (this.isWatchData) {
|
||||||
|
this.updateAllPath(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeData(data) {
|
||||||
|
this[data.name] = data.value
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateSourceAllAttributesList(id) {
|
||||||
|
if (id) {
|
||||||
|
const res = await getCITypeAttributesByTypeIds({ type_ids: id })
|
||||||
|
this.sourceAllAttributesList = res.attributes
|
||||||
|
} else {
|
||||||
|
this.sourceAllAttributesList = []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getTargetCITypeGroup(id) {
|
||||||
|
let targetCITypeGroup = {}
|
||||||
|
if (id) {
|
||||||
|
const res = await getRecursive_level2children(id)
|
||||||
|
targetCITypeGroup = res
|
||||||
|
}
|
||||||
|
this.targetCITypeGroup = targetCITypeGroup
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateTargetAllAttributesList(ids) {
|
||||||
|
if (ids?.length) {
|
||||||
|
const res = await getCITypeAttributesByTypeIds({ type_ids: ids.join(',') })
|
||||||
|
this.targetAllAttributesList = res.attributes
|
||||||
|
} else {
|
||||||
|
this.targetAllAttributesList = []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateAllPath(data) {
|
||||||
|
let allPath = []
|
||||||
|
if (
|
||||||
|
data.sourceCIType &&
|
||||||
|
data?.targetCITypes?.length
|
||||||
|
) {
|
||||||
|
const params = {
|
||||||
|
source_type_id: data.sourceCIType,
|
||||||
|
target_type_ids: data.targetCITypes.join(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await getCITypeRelationPath(params)
|
||||||
|
|
||||||
|
if (res?.paths?.length) {
|
||||||
|
const sourceCIType = this.allCITypes.find((ciType) => ciType.id === data.sourceCIType)
|
||||||
|
const sourceCITypeName = sourceCIType?.alias || sourceCIType?.name || ''
|
||||||
|
const targetCITypeList = Object.values(this.targetCITypeGroup).reduce((acc, cur) => acc.concat(cur), [])
|
||||||
|
|
||||||
|
allPath = res.paths.map((ids) => {
|
||||||
|
const [sourceId, ...targetIds] = ids
|
||||||
|
const pathNames = [sourceCITypeName]
|
||||||
|
|
||||||
|
targetIds.forEach((id) => {
|
||||||
|
const ciType = targetCITypeList.find((item) => item.id === id)
|
||||||
|
if (ciType) {
|
||||||
|
pathNames.push(ciType.alias || ciType.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: ids.join(','),
|
||||||
|
sourceId,
|
||||||
|
targetIds,
|
||||||
|
pathNames: pathNames.join('-'),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.allPath = allPath
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadCI() {
|
||||||
|
this.isSearchLoading = true
|
||||||
|
|
||||||
|
const path = this.selectedPath.map((item) => {
|
||||||
|
return item?.split(',')?.map((id) => Number(id)) || []
|
||||||
|
})
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
page: this.page,
|
||||||
|
page_size: this.pageSize,
|
||||||
|
source: {
|
||||||
|
type_id: this.sourceCIType
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
type_ids: this.targetCITypes
|
||||||
|
},
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||||
|
const sourceExp = this.sourceExpression.match(regQ) ? this.sourceExpression.match(regQ)[0] : null
|
||||||
|
const targetExp = this.targetExpression.match(regQ) ? this.targetExpression.match(regQ)[0] : null
|
||||||
|
const sourceSearch = `${sourceExp ? `${sourceExp}` : ''}${this.sourceCITypeSearchValue ? `,*${this.sourceCITypeSearchValue}*` : ''}`
|
||||||
|
|
||||||
|
if (sourceSearch) {
|
||||||
|
params.source.q = sourceSearch
|
||||||
|
}
|
||||||
|
if (targetExp) {
|
||||||
|
params.target.q = targetExp
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = {}
|
||||||
|
const tableData = {}
|
||||||
|
const typeId2Attr = {}
|
||||||
|
let pathKeyList = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
res = await searchCIRelationPath(params)
|
||||||
|
|
||||||
|
pathKeyList = Object.keys(res.paths)
|
||||||
|
const filterAllPath = this.allPath.filter((path) => pathKeyList.includes(path.pathNames))
|
||||||
|
const typeIds = _.uniq(
|
||||||
|
filterAllPath.map((item) => item?.targetIds?.[item?.targetIds?.length - 1])
|
||||||
|
)
|
||||||
|
|
||||||
|
const promises = typeIds.map((id) => {
|
||||||
|
return getSubscribeAttributes(id)
|
||||||
|
})
|
||||||
|
const subscribedRes = await Promise.all(promises)
|
||||||
|
typeIds.forEach((id, index) => {
|
||||||
|
const attrList = subscribedRes?.[index]?.attributes || []
|
||||||
|
typeId2Attr[id] = attrList
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
this.isSearchLoading = false
|
||||||
|
this.allTableData = {}
|
||||||
|
this.totalNumber = 0
|
||||||
|
this.tableTabActive = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pathKeyList.forEach((key) => {
|
||||||
|
const pathObj = this.allPath.find((path) => path.pathNames === key)
|
||||||
|
|
||||||
|
const pathIdList = pathObj?.value?.split(',') || []
|
||||||
|
const pathNameList = key?.split('-') || []
|
||||||
|
|
||||||
|
const pathList = pathNameList.map((name, index) => {
|
||||||
|
let relation = ''
|
||||||
|
if (index < pathNameList.length - 1) {
|
||||||
|
const targetName = pathNameList[index + 1]
|
||||||
|
const sourceRelation = res?.relation_types?.[name]
|
||||||
|
|
||||||
|
if (sourceRelation) {
|
||||||
|
if (Object.keys(sourceRelation)?.includes?.(targetName)) {
|
||||||
|
relation = sourceRelation?.[targetName] || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: pathIdList?.[index] || '',
|
||||||
|
name,
|
||||||
|
relation
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
tableData[key] = {
|
||||||
|
key,
|
||||||
|
count: res.paths?.[key]?.length || 0,
|
||||||
|
pathList,
|
||||||
|
ciAttr: [],
|
||||||
|
ciList: []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathObj) {
|
||||||
|
const firstIds = res?.paths?.[key]?.[0]
|
||||||
|
const targetId = firstIds[firstIds.length - 1]
|
||||||
|
const ciTypeId = (res?.id2ci?.[targetId] || {})?._type
|
||||||
|
if (ciTypeId) {
|
||||||
|
tableData[key].ciAttr = typeId2Attr[ciTypeId]
|
||||||
|
}
|
||||||
|
|
||||||
|
tableData[key].ciList = res.paths[key].map((ids) => {
|
||||||
|
const pathCI = {}
|
||||||
|
ids.map((id) => {
|
||||||
|
const ci = res?.id2ci?.[id] || {}
|
||||||
|
const showAttr = res?.type2show_key?.[ci._type] || ''
|
||||||
|
pathCI[ci._type] = ci?.[showAttr] ?? ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const targetId = ids[ids.length - 1]
|
||||||
|
const targetCI = res?.id2ci?.[targetId] || {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
pathCI,
|
||||||
|
targetCI
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let totalWidth = 0
|
||||||
|
tableData[key].ciAttr.forEach((attr) => {
|
||||||
|
const lengthList = tableData[key].ciList.map(({ targetCI }) => {
|
||||||
|
return strLength(targetCI[attr.name])
|
||||||
|
})
|
||||||
|
|
||||||
|
attr.width = Math.round(Math.min(Math.max(100, ...lengthList), 350))
|
||||||
|
totalWidth += attr.width
|
||||||
|
})
|
||||||
|
|
||||||
|
// ci 表格宽度 = 容器宽度 - path 列宽 - checkbox 宽度
|
||||||
|
const wrapWidth = this.$refs?.relationSearchRef?.clientWidth - (tableData?.[key]?.pathList.length || 0) * 160 - 60
|
||||||
|
|
||||||
|
if (wrapWidth && totalWidth < wrapWidth) {
|
||||||
|
tableData[key].ciAttr.forEach((attr) => {
|
||||||
|
delete attr.width
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$set(this, 'allTableData', tableData)
|
||||||
|
this.allTableData = tableData
|
||||||
|
this.totalNumber = res?.numfound ?? 0
|
||||||
|
this.tableTabActive = Object.keys(tableData)?.[0] || ''
|
||||||
|
this.isSearch = true
|
||||||
|
this.isSearchLoading = false
|
||||||
|
|
||||||
|
const allAttr = []
|
||||||
|
Object.values(typeId2Attr).map((attrList) => {
|
||||||
|
allAttr.push(...attrList)
|
||||||
|
})
|
||||||
|
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 = {}
|
||||||
|
|
||||||
|
Object.values(this.allTableData).forEach((item) => {
|
||||||
|
const ciList = item?.ciList || []
|
||||||
|
ciList.forEach(({ targetCI }) => {
|
||||||
|
needRequiredCIType.forEach((col) => {
|
||||||
|
const ids = Array.isArray(targetCI[col.name]) ? targetCI[col.name] : targetCI[col.name] ? [targetCI[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
|
||||||
|
},
|
||||||
|
|
||||||
|
handlePageSizeChange(_, pageSize) {
|
||||||
|
this.pageSize = pageSize
|
||||||
|
this.page = 1
|
||||||
|
this.loadCI()
|
||||||
|
},
|
||||||
|
|
||||||
|
changePage(page) {
|
||||||
|
this.page = page
|
||||||
|
this.loadCI()
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSearch() {
|
||||||
|
this.page = 1
|
||||||
|
this.loadCI()
|
||||||
|
},
|
||||||
|
|
||||||
|
clickFavor(option) {
|
||||||
|
this.isWatchData = false
|
||||||
|
|
||||||
|
this.$nextTick(async () => {
|
||||||
|
this.sourceCIType = option?.sourceCIType || undefined
|
||||||
|
this.sourceCITypeSearchValue = option?.searchValue || ''
|
||||||
|
this.sourceExpression = option?.sourceExpression || ''
|
||||||
|
this.targetCITypes = option?.targetCITypes || []
|
||||||
|
this.targetExpression = option?.targetExpression || ''
|
||||||
|
this.selectedPath = option?.selectedPath || []
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
this.getTargetCITypeGroup(this.sourceCIType),
|
||||||
|
this.updateSourceAllAttributesList(this.sourceCIType),
|
||||||
|
this.updateTargetAllAttributesList(this.targetCITypes)
|
||||||
|
])
|
||||||
|
await this.updateAllPath({
|
||||||
|
sourceCIType: this.sourceCIType,
|
||||||
|
targetCITypes: this.targetCITypes
|
||||||
|
})
|
||||||
|
|
||||||
|
this.isWatchData = true
|
||||||
|
this.page = 1
|
||||||
|
|
||||||
|
this.loadCI()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.relation-search {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&-wrap {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
margin-top: 100px;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1D2129;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-expand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
&-line {
|
||||||
|
width: 650px;
|
||||||
|
height: 1px;
|
||||||
|
background-color: #E4E7ED;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #86909C;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #A5A9BC;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-handle {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background-color: #EBEFF8;
|
||||||
|
border-radius: 1px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-right {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.relation-search-expand-handle {
|
||||||
|
background-color: #D9E4FA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relation-search-expand-icon {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relation-search-expand-text {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-bg {
|
||||||
|
position: absolute;
|
||||||
|
left: -24px;
|
||||||
|
bottom: -24px;
|
||||||
|
width: calc(100% + 48px);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-main {
|
||||||
|
width: calc(100% + 48px);
|
||||||
|
// height: 100%;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-pagination {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,156 @@
|
||||||
|
<template>
|
||||||
|
<div :class="['attr-display', isEllipsis ? 'attr-display-ellipsis' : '']">
|
||||||
|
<template v-if="attr.is_reference && ci[attr.name]" >
|
||||||
|
<a
|
||||||
|
v-for="(ciId) in (attr.is_list ? ci[attr.name] : [ci[attr.name]])"
|
||||||
|
:key="ciId"
|
||||||
|
:href="`/cmdb/cidetail/${attr.reference_type_id}/${ciId}`"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ getReferenceAttrValue(ciId) }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<span v-else-if="attr.value_type === '6' && ci[attr.name]">{{ JSON.stringify(ci[attr.name]) }}</span>
|
||||||
|
<template v-else-if="attr.is_link && ci[attr.name]">
|
||||||
|
<a
|
||||||
|
v-for="(item, linkIndex) in (attr.is_list ? ci[attr.name] : [ci[attr.name]])"
|
||||||
|
:key="linkIndex"
|
||||||
|
:href="
|
||||||
|
item.startsWith('http') || item.startsWith('https')
|
||||||
|
? `${item}`
|
||||||
|
: `http://${item}`
|
||||||
|
"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ getChoiceValueLabel(item) || item }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<PasswordField
|
||||||
|
v-else-if="attr.is_password && ci[attr.name]"
|
||||||
|
:ci_id="ci._id"
|
||||||
|
:attr_id="attr.id"
|
||||||
|
></PasswordField>
|
||||||
|
<template v-else-if="attr.is_choice">
|
||||||
|
<span
|
||||||
|
v-for="value in (attr.is_list ? ci[attr.name] : [ci[attr.name]])"
|
||||||
|
:key="value"
|
||||||
|
:style="{
|
||||||
|
borderRadius: '4px',
|
||||||
|
padding: '1px 5px',
|
||||||
|
margin: '2px',
|
||||||
|
...getChoiceValueStyle(value),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<ops-icon
|
||||||
|
:style="{ color: getChoiceValueIcon(attr, value).color }"
|
||||||
|
:type="getChoiceValueIcon(attr, value).name"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-html="markSearchValue(getChoiceValueLabel(value) || value)"
|
||||||
|
></span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
v-html="markSearchValue((attr.is_list && Array.isArray(ci[attr.name])) ? ci[attr.name].join(',') : ci[attr.name])"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import PasswordField from '@/modules/cmdb/components/passwordField/index.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AttrDisplay',
|
||||||
|
components: {
|
||||||
|
PasswordField
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
attr: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
ci: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
isEllipsis: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
referenceShowAttrNameMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
referenceCIIdMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
searchValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
markSearchValue(text) {
|
||||||
|
if (!text || !this.searchValue) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
const regex = new RegExp(`(${this.searchValue})`, 'gi')
|
||||||
|
return String(text).replace(
|
||||||
|
regex,
|
||||||
|
`<span style="background-color: #D3EEFE; padding: 0 2px;">$1</span>`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
getChoiceValueStyle(attrValue) {
|
||||||
|
const _find = this?.attr?.choice_value?.find?.((item) => String(item?.[0]) === String(attrValue))
|
||||||
|
if (_find) {
|
||||||
|
return _find?.[1]?.style || {}
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
getChoiceValueIcon(attrValue) {
|
||||||
|
const _find = this?.attr?.choice_value?.find((item) => String(item?.[0]) === String(attrValue))
|
||||||
|
if (_find) {
|
||||||
|
return _find?.[1]?.icon || {}
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
getChoiceValueLabel(attrValue) {
|
||||||
|
const _find = this?.attr?.choice_value?.find((item) => String(item?.[0]) === String(attrValue))
|
||||||
|
if (_find) {
|
||||||
|
return _find?.[1]?.label || ''
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
getReferenceAttrValue(id) {
|
||||||
|
if (this.attr.referenceShowAttrNameMap?.[id]) {
|
||||||
|
return this.attr.referenceShowAttrNameMap[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
const ci = this?.referenceCIIdMap?.[this?.attr?.reference_type_id]?.[id]
|
||||||
|
if (!ci) {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
const attrName = this.referenceShowAttrNameMap?.[this?.attr.reference_type_id]
|
||||||
|
return ci?.[attrName] || id
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.attr-display {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
word-break: break-all;
|
||||||
|
|
||||||
|
&-ellipsis {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,235 @@
|
||||||
|
<template>
|
||||||
|
<a-popover
|
||||||
|
v-model="visible"
|
||||||
|
trigger="click"
|
||||||
|
placement="bottom"
|
||||||
|
@visibleChange="handleVisibleChange"
|
||||||
|
>
|
||||||
|
<div class="filter-btn">
|
||||||
|
<a-icon class="filter-btn-icon" type="filter" />
|
||||||
|
<span class="filter-btn-title">{{ $t('cmdb.ciType.advancedFilter') }}</span>
|
||||||
|
</div>
|
||||||
|
<template slot="content">
|
||||||
|
<div class="filter-content">
|
||||||
|
<a-form :form="form">
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('cmdb.ciType.ciType')"
|
||||||
|
:label-col="formLayout.labelCol"
|
||||||
|
:wrapper-col="formLayout.wrapperCol"
|
||||||
|
>
|
||||||
|
<treeselect
|
||||||
|
:value="selectCITypeIds"
|
||||||
|
class="custom-treeselect custom-treeselect-bgcAndBorder filter-content-ciTypes"
|
||||||
|
:style="{
|
||||||
|
width: '400px',
|
||||||
|
zIndex: '1000',
|
||||||
|
'--custom-height': '32px',
|
||||||
|
'--custom-bg-color': '#FFF',
|
||||||
|
'--custom-border': '1px solid #d9d9d9',
|
||||||
|
'--custom-multiple-lineHeight': '32px',
|
||||||
|
}"
|
||||||
|
:multiple="true"
|
||||||
|
:clearable="true"
|
||||||
|
searchable
|
||||||
|
:options="CITypeGroup"
|
||||||
|
:limit="1"
|
||||||
|
:limitText="(count) => `+ ${count}`"
|
||||||
|
value-consists-of="LEAF_PRIORITY"
|
||||||
|
:placeholder="$t('cmdb.ciType.ciType')"
|
||||||
|
@close="closeCiTypeGroup"
|
||||||
|
@open="openCiTypeGroup"
|
||||||
|
@input="inputCiTypeGroup"
|
||||||
|
:normalizer="
|
||||||
|
(node) => {
|
||||||
|
return {
|
||||||
|
id: node.id || -1,
|
||||||
|
label: node.alias || node.name || $t('other'),
|
||||||
|
title: node.alias || node.name || $t('other'),
|
||||||
|
children: node.ci_types,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:title="node.label"
|
||||||
|
slot="option-label"
|
||||||
|
slot-scope="{ node }"
|
||||||
|
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
|
||||||
|
>
|
||||||
|
{{ node.label }}
|
||||||
|
</div>
|
||||||
|
</treeselect>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('cmdb.ciType.filterPopoverLabel')"
|
||||||
|
:label-col="formLayout.labelCol"
|
||||||
|
:wrapper-col="formLayout.wrapperCol"
|
||||||
|
class="filter-content-condition-filter"
|
||||||
|
>
|
||||||
|
<ConditionFilter
|
||||||
|
ref="conditionFilterRef"
|
||||||
|
:canSearchPreferenceAttrList="allAttributesList"
|
||||||
|
:expression="expression"
|
||||||
|
:CITypeIds="selectCITypeIds"
|
||||||
|
:isDropdown="false"
|
||||||
|
@setExpFromFilter="setExpFromFilter"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<div class="filter-content-action">
|
||||||
|
<a-button
|
||||||
|
size="small"
|
||||||
|
@click="saveCondition(false)"
|
||||||
|
>
|
||||||
|
{{ $t('cmdb.ciType.saveCondition') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="saveCondition(true)"
|
||||||
|
>
|
||||||
|
{{ $t('confirm') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
|
import ConditionFilter from '@/modules/cmdb/components/conditionFilter/index.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FilterPopover',
|
||||||
|
components: {
|
||||||
|
ConditionFilter
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
form: this.$form.createForm(this),
|
||||||
|
formLayout: {
|
||||||
|
labelCol: { span: 3 },
|
||||||
|
wrapperCol: { span: 15 },
|
||||||
|
},
|
||||||
|
lastCiType: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
expression: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
selectCITypeIds: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
CITypeGroup: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
allAttributesList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleVisibleChange(open) {
|
||||||
|
if (open) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.conditionFilterRef.init(true, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openCiTypeGroup() {
|
||||||
|
this.lastCiType = _.cloneDeep(this.selectCITypeIds)
|
||||||
|
},
|
||||||
|
closeCiTypeGroup(value) {
|
||||||
|
if (!_.isEqual(value, this.lastCiType)) {
|
||||||
|
this.$emit('updateAllAttributesList', value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inputCiTypeGroup(value) {
|
||||||
|
if (!value || !value.length) {
|
||||||
|
this.$emit('updateAllAttributesList', value)
|
||||||
|
}
|
||||||
|
this.$emit('changeFilter', {
|
||||||
|
name: 'selectCITypeIds',
|
||||||
|
value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setExpFromFilter(filterExp) {
|
||||||
|
const regSort = /(?<=sort=).+/g
|
||||||
|
const expSort = this.expression.match(regSort) ? this.expression.match(regSort)[0] : undefined
|
||||||
|
let expression = ''
|
||||||
|
if (filterExp) {
|
||||||
|
expression = `q=${filterExp}`
|
||||||
|
}
|
||||||
|
if (expSort) {
|
||||||
|
expression += `&sort=${expSort}`
|
||||||
|
}
|
||||||
|
this.$emit('changeFilter', {
|
||||||
|
name: 'expression',
|
||||||
|
value: expression
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
saveCondition(isSubmit) {
|
||||||
|
this.$refs.conditionFilterRef.handleSubmit()
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$emit('saveCondition', isSubmit)
|
||||||
|
this.visible = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.filter-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
color: #2F54EB;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #2F54EB;
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter-content {
|
||||||
|
width: 600px;
|
||||||
|
|
||||||
|
&-ciTypes {
|
||||||
|
/deep/ .vue-treeselect__value-container {
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-condition-filter {
|
||||||
|
max-height: 250px;
|
||||||
|
// overflow-y: auto;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-action {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 21px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,310 @@
|
||||||
|
<template>
|
||||||
|
<div class="history-list">
|
||||||
|
<div
|
||||||
|
class="history-recent"
|
||||||
|
v-if="recentList.length"
|
||||||
|
>
|
||||||
|
<div class="history-title">
|
||||||
|
<a-icon type="eye" class="history-title-icon" />
|
||||||
|
<div class="history-title-text">{{ $t('cmdb.ciType.recentSearch') }}</div>
|
||||||
|
|
||||||
|
<a-popconfirm
|
||||||
|
:title="$t('cmdb.ciType.confirmClear')"
|
||||||
|
placement="topRight"
|
||||||
|
@confirm="clearRecent"
|
||||||
|
>
|
||||||
|
<a-tooltip :title="$t('clear')" >
|
||||||
|
<a-icon
|
||||||
|
type="delete"
|
||||||
|
class="history-title-clear"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-popconfirm>
|
||||||
|
</div>
|
||||||
|
<div class="recent-list">
|
||||||
|
<div
|
||||||
|
v-for="(item) in recentList.slice(0, 10)"
|
||||||
|
:key="item.id"
|
||||||
|
class="recent-list-item"
|
||||||
|
@click="clickRecent(item.option)"
|
||||||
|
>
|
||||||
|
<div class="recent-list-item-text">
|
||||||
|
{{ getRecentSearchText(item.option) }}
|
||||||
|
</div>
|
||||||
|
<a-icon
|
||||||
|
type="close"
|
||||||
|
class="recent-list-item-close"
|
||||||
|
@click.stop="deleteRecent(item.id)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="history-favor"
|
||||||
|
v-if="favorList.length"
|
||||||
|
>
|
||||||
|
<div class="history-title">
|
||||||
|
<ops-icon type="veops-collect" class="history-title-icon" />
|
||||||
|
<div class="history-title-text">{{ $t('cmdb.ciType.myCollection') }}</div>
|
||||||
|
<div class="history-title-count">({{ favorList.length }})</div>
|
||||||
|
|
||||||
|
<ops-icon
|
||||||
|
type="veops-expand"
|
||||||
|
class="history-title-expand"
|
||||||
|
:style="{
|
||||||
|
transform: `rotate(${isExpand ? '180deg' : '0deg'})`
|
||||||
|
}"
|
||||||
|
@click="isExpand = !isExpand"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="favor-list"
|
||||||
|
:style="{ height: isExpand ? 'auto' : '30px' }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(item) in favorList"
|
||||||
|
:key="item.id"
|
||||||
|
:class="['favor-list-item', detailCIId === item.option.CIId ? 'favor-list-item-selected' : '']"
|
||||||
|
@click="showDetail(item.option)"
|
||||||
|
>
|
||||||
|
<CIIcon
|
||||||
|
:icon="item.option.icon"
|
||||||
|
:title="item.option.CITypeTitle"
|
||||||
|
/>
|
||||||
|
<div class="favor-list-item-title">
|
||||||
|
{{ item.option.title }}
|
||||||
|
</div>
|
||||||
|
<ops-icon
|
||||||
|
type="veops-collected"
|
||||||
|
class="favor-list-item-collected"
|
||||||
|
@click.stop="deleteCollect(item.id)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CIIcon from '@/modules/cmdb/components/ciIcon/index.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HistoryList',
|
||||||
|
components: {
|
||||||
|
CIIcon
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
recentList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
favorList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
detailCIId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isExpand: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getRecentSearchText(option) {
|
||||||
|
const textArray = []
|
||||||
|
if (option.searchValue) {
|
||||||
|
textArray.push(`${this.$t('cmdb.ciType.keyword')}: ${option.searchValue}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option?.ciTypeNames?.length) {
|
||||||
|
textArray.push(`${this.$t('cmdb.ciType.CIType')}: ${option.ciTypeNames.join(',')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.expression) {
|
||||||
|
textArray.push(`${this.$t('cmdb.ciType.conditionFilter')}: ${option.expression}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return textArray.join('; ')
|
||||||
|
},
|
||||||
|
|
||||||
|
clickRecent(data) {
|
||||||
|
this.$emit('clickRecent', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecent(id) {
|
||||||
|
this.$emit('deleteRecent', id)
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteCollect(id) {
|
||||||
|
this.$emit('deleteCollect', id)
|
||||||
|
},
|
||||||
|
|
||||||
|
showDetail(data) {
|
||||||
|
this.$emit('showDetail', {
|
||||||
|
id: data.CIId,
|
||||||
|
ciTypeId: data.CITypeId
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
clearRecent() {
|
||||||
|
this.$emit('clearRecent')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.history-list {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.history-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-count {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #86909C;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-clear {
|
||||||
|
margin-left: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-expand {
|
||||||
|
margin-left: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-recent {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 15px;
|
||||||
|
|
||||||
|
.recent-list {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
column-gap: 16px;
|
||||||
|
row-gap: 8px;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 4px 13px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: rgba(255, 255, 255, 0.50);
|
||||||
|
cursor: pointer;
|
||||||
|
max-width: calc((100% - 16px) / 2);
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #1D2129;
|
||||||
|
|
||||||
|
max-width: 100%;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-close {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 4px;
|
||||||
|
color: #A5A9BC;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.recent-list-item-text {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-list-item-close {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-favor {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 15px;
|
||||||
|
|
||||||
|
.favor-list {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
column-gap: 16px;
|
||||||
|
row-gap: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 30px;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 4px 13px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: rgba(255, 255, 255, 0.90);
|
||||||
|
cursor: pointer;
|
||||||
|
max-width: calc((100% - 16px) / 2);
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-left: 4px;
|
||||||
|
color: #1D2129;
|
||||||
|
|
||||||
|
max-width: 100%;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-collected {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 4px;
|
||||||
|
color: #FAD337;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-selected {
|
||||||
|
border: 1px solid #7F97FA;
|
||||||
|
background-color: rgba(255, 255, 255, 0.90);
|
||||||
|
|
||||||
|
.favor-list-item-title {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.favor-list-item-title {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,438 @@
|
||||||
|
<template>
|
||||||
|
<div class="instance-detail">
|
||||||
|
<div
|
||||||
|
class="instance-detail-hide"
|
||||||
|
@click="hideDetail"
|
||||||
|
>
|
||||||
|
<a-icon class="instance-detail-hide-icon" type="right" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="instance-detail-null" v-if="!ci._id" >
|
||||||
|
<img
|
||||||
|
:src="require('@/modules/cmdb/assets/no_permission.png')"
|
||||||
|
class="instance-detail-null-img"
|
||||||
|
/>
|
||||||
|
<span class="instance-detail-null-text" >{{ $t('noData') }}</span>
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
class="instance-detail-header"
|
||||||
|
>
|
||||||
|
<div class="instance-detail-header-line-1"></div>
|
||||||
|
<div class="instance-detail-header-line-2"></div>
|
||||||
|
<div class="instance-detail-header-row">
|
||||||
|
<CIIcon
|
||||||
|
:icon="ciType.icon"
|
||||||
|
:title="ciType.name || ''"
|
||||||
|
:size="20"
|
||||||
|
/>
|
||||||
|
<div class="instance-detail-header-title">
|
||||||
|
{{ detailTitle }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ops-icon
|
||||||
|
:type="favorId ? 'veops-collected' : 'veops-collect'"
|
||||||
|
:style="{ color: favorId ? '#FAD337' : '#A5A9BC' }"
|
||||||
|
class="instance-detail-header-collect"
|
||||||
|
@click="clickCollect"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<a class="instance-detail-header-share" @click="shareCi">
|
||||||
|
<a-icon type="share-alt" />
|
||||||
|
{{ $t('cmdb.ci.share') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="instance-detail-attr">
|
||||||
|
<div
|
||||||
|
v-for="(group) in attributeGroups"
|
||||||
|
:key="group.id"
|
||||||
|
class="instance-detail-attr-group"
|
||||||
|
>
|
||||||
|
<span class="instance-detail-attr-group-name">{{ group.name || $t('other') }}</span>
|
||||||
|
|
||||||
|
<div class="instance-detail-attr-list">
|
||||||
|
<div
|
||||||
|
v-for="(attr) in group.attributes"
|
||||||
|
:key="attr.id"
|
||||||
|
class="instance-detail-attr-item"
|
||||||
|
>
|
||||||
|
<a-tooltip :title="attr.alias || attr.name || ''">
|
||||||
|
<div class="instance-detail-attr-item-label">
|
||||||
|
<span class="instance-detail-attr-item-label-text">
|
||||||
|
{{ attr.alias || attr.name || '' }}
|
||||||
|
</span>
|
||||||
|
<span class="instance-detail-attr-item-label-colon">:</span>
|
||||||
|
</div>
|
||||||
|
</a-tooltip>
|
||||||
|
|
||||||
|
<div class="instance-detail-attr-item-value">
|
||||||
|
<AttrDisplay
|
||||||
|
:attr="attr"
|
||||||
|
:ci="ci"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { getCIById, searchCI } from '@/modules/cmdb/api/ci'
|
||||||
|
import { getCITypeGroupById, getCITypes, getCIType } from '@/modules/cmdb/api/CIType'
|
||||||
|
|
||||||
|
import AttrDisplay from './attrDisplay.vue'
|
||||||
|
import CIIcon from '@/modules/cmdb/components/ciIcon/index.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'InstanceDetail',
|
||||||
|
components: {
|
||||||
|
AttrDisplay,
|
||||||
|
CIIcon
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
CIId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
CITypeId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
favorList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
ci: {},
|
||||||
|
ciType: {},
|
||||||
|
attributeGroups: [],
|
||||||
|
isNullData: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
watchParams() {
|
||||||
|
return {
|
||||||
|
CIId: this.CIId,
|
||||||
|
CITypeId: this.CITypeId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
detailTitle() {
|
||||||
|
const attrName = this?.ciType?.show_name || this?.ciType?.unique_name || ''
|
||||||
|
return attrName ? (this?.ci?.[attrName] || '') : ''
|
||||||
|
},
|
||||||
|
favorId() {
|
||||||
|
const id = this.favorList.find((item) => item?.option?.CIId === this.CIId)?.id
|
||||||
|
return id ?? null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
watchParams: {
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
handler(newVal) {
|
||||||
|
if (newVal?.CIId !== -1 && newVal?.CITypeId !== -1) {
|
||||||
|
this.initData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async initData() {
|
||||||
|
const ci = await this.getCI()
|
||||||
|
if (!ci) {
|
||||||
|
this.isNullData = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await this.getCIType()
|
||||||
|
await this.getAttributes()
|
||||||
|
},
|
||||||
|
|
||||||
|
async getCI() {
|
||||||
|
const res = await getCIById(this.CIId)
|
||||||
|
const ci = res.result?.[0] || {}
|
||||||
|
this.ci = ci
|
||||||
|
return ci
|
||||||
|
},
|
||||||
|
|
||||||
|
async getCIType() {
|
||||||
|
const res = await getCIType(this.CITypeId)
|
||||||
|
this.ciType = res?.ci_types?.[0] || {}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getAttributes() {
|
||||||
|
const res = await getCITypeGroupById(this.CITypeId, { need_other: 1 })
|
||||||
|
this.attributeGroups = res
|
||||||
|
this.handleReferenceAttr()
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleReferenceAttr() {
|
||||||
|
const map = {}
|
||||||
|
this.attributeGroups.forEach((group) => {
|
||||||
|
group.attributes.forEach((attr) => {
|
||||||
|
if (attr?.is_reference && attr?.reference_type_id && this.ci[attr.name]) {
|
||||||
|
const ids = Array.isArray(this.ci[attr.name]) ? this.ci[attr.name] : this.ci[attr.name] ? [this.ci[attr.name]] : []
|
||||||
|
if (ids.length) {
|
||||||
|
if (!map?.[attr.reference_type_id]) {
|
||||||
|
map[attr.reference_type_id] = {}
|
||||||
|
}
|
||||||
|
ids.forEach((id) => {
|
||||||
|
map[attr.reference_type_id][id] = {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!Object.keys(map).length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciTypesRes = await getCITypes({
|
||||||
|
type_ids: Object.keys(map).join(',')
|
||||||
|
})
|
||||||
|
const showAttrNameMap = {}
|
||||||
|
ciTypesRes.ci_types.forEach((ciType) => {
|
||||||
|
showAttrNameMap[ciType.id] = ciType?.show_name || ciType?.unique_name || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const allRes = await Promise.all(
|
||||||
|
Object.keys(map).map((key) => {
|
||||||
|
return searchCI({
|
||||||
|
q: `_type:${key},_id:(${Object.keys(map[key]).join(';')})`,
|
||||||
|
count: 9999
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const ciNameMap = {}
|
||||||
|
allRes.forEach((res) => {
|
||||||
|
res.result.forEach((item) => {
|
||||||
|
ciNameMap[item._id] = item
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const newAttrGroups = _.cloneDeep(this.attributeGroups)
|
||||||
|
|
||||||
|
newAttrGroups.forEach((group) => {
|
||||||
|
group.attributes.forEach((attr) => {
|
||||||
|
if (attr?.is_reference && attr?.reference_type_id) {
|
||||||
|
attr.showAttrName = showAttrNameMap?.[attr?.reference_type_id] || ''
|
||||||
|
|
||||||
|
const referenceShowAttrNameMap = {}
|
||||||
|
const referenceCIIds = this.ci[attr.name];
|
||||||
|
(Array.isArray(referenceCIIds) ? referenceCIIds : referenceCIIds ? [referenceCIIds] : []).forEach((id) => {
|
||||||
|
referenceShowAttrNameMap[id] = ciNameMap?.[id]?.[attr.showAttrName] ?? id
|
||||||
|
})
|
||||||
|
attr.referenceShowAttrNameMap = referenceShowAttrNameMap
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$set(this, 'attributeGroups', newAttrGroups)
|
||||||
|
},
|
||||||
|
|
||||||
|
shareCi() {
|
||||||
|
const text = `${document.location.host}/cmdb/cidetail/${this.CITypeId}/${this.CIId}`
|
||||||
|
this.$copyText(text)
|
||||||
|
.then(() => {
|
||||||
|
this.$message.success(this.$t('copySuccess'))
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.$message.error(this.$t('cmdb.ci.copyFailed'))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
clickCollect() {
|
||||||
|
if (this.favorId) {
|
||||||
|
this.$emit('deleteCollect', this.favorId)
|
||||||
|
} else {
|
||||||
|
this.$emit('addCollect', {
|
||||||
|
CIId: this.CIId,
|
||||||
|
CITypeId: this.CITypeId,
|
||||||
|
title: this.detailTitle,
|
||||||
|
icon: this.ciType?.icon,
|
||||||
|
CITypeTitle: this.ciType?.name || ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hideDetail() {
|
||||||
|
this.$emit('hideDetail')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.instance-detail {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid #E4E7ED;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&-hide {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -21px;
|
||||||
|
border-radius: 0px 2px 2px 0px;
|
||||||
|
background-color: #2f54eb;
|
||||||
|
width: 13px;
|
||||||
|
height: 43px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 2;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #597ef7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-null {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 100px;
|
||||||
|
|
||||||
|
&-img {
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
color: #86909C;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
width: 100%;
|
||||||
|
height: 75px;
|
||||||
|
background-color: #EBF0F9;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&-line-1 {
|
||||||
|
height: 44px;
|
||||||
|
width: 300px;
|
||||||
|
position: absolute;
|
||||||
|
right: -20px;
|
||||||
|
top: 0px;
|
||||||
|
transform: rotate(40deg);
|
||||||
|
background: rgba(248, 249, 253, 0.60);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-line-2 {
|
||||||
|
height: 44px;
|
||||||
|
width: 300px;
|
||||||
|
position: absolute;
|
||||||
|
right: -110px;
|
||||||
|
top: 0px;
|
||||||
|
transform: rotate(40deg);
|
||||||
|
background: rgba(248, 249, 253, 0.60);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-row {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1D2129;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
margin-left: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-collect {
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-share {
|
||||||
|
margin-left: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-attr {
|
||||||
|
width: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
&-group {
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1D2129;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
margin-top: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #86909C;
|
||||||
|
width: 25%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-colon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-value {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,443 @@
|
||||||
|
<template>
|
||||||
|
<div class="list-wrap">
|
||||||
|
<div class="list-wrap-bg" v-if="!filterList.length">
|
||||||
|
<img :src="require('@/modules/cmdb/assets/resourceSearch/resource_search_bg_2.png')" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="tabList.length" class="list-tab">
|
||||||
|
<div class="list-tab-left">
|
||||||
|
<div class="list-tab-label">{{ $t('cmdb.ciType.currentPage') }}: </div>
|
||||||
|
<div
|
||||||
|
v-for="(tab) in tabList"
|
||||||
|
:key="tab.id"
|
||||||
|
:class="['list-tab-item', tab.id === currentTab ? 'list-tab-item-active' : '']"
|
||||||
|
@click="clickTab(tab.id)"
|
||||||
|
>
|
||||||
|
<span class="list-tab-item-title">{{ tab.title }}</span>
|
||||||
|
(<span class="list-tab-item-count">{{ tab.count }}</span>)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
icon="download"
|
||||||
|
type="primary"
|
||||||
|
class="ops-button-ghost list-tab-export"
|
||||||
|
ghost
|
||||||
|
@click="handleExport"
|
||||||
|
>
|
||||||
|
{{ $t('download') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="filterList.length" class="list-container">
|
||||||
|
<div
|
||||||
|
v-for="(item) in filterList"
|
||||||
|
:key="item._id"
|
||||||
|
:class="['list-card', detailCIId === item.ci._id ? 'list-card-selected' : '']"
|
||||||
|
@click="clickInstance(item.ci._id, item.ciTypeObj.id)"
|
||||||
|
>
|
||||||
|
<div class="list-card-header">
|
||||||
|
<div class="list-card-model">
|
||||||
|
<CIIcon
|
||||||
|
:icon="item.ciTypeObj.icon"
|
||||||
|
:title="item.ciTypeObj.name"
|
||||||
|
/>
|
||||||
|
<span class="list-card-model-title">{{ item.ciTypeObj.title }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="list-card-title">{{ item.ci[item.ciTypeObj.showAttrName] }}</div>
|
||||||
|
|
||||||
|
<ops-icon
|
||||||
|
v-if="getFavorId(item.ci._id)"
|
||||||
|
type="veops-collected"
|
||||||
|
class="list-card-collect"
|
||||||
|
:style="{ color: '#FAD337' }"
|
||||||
|
@click.stop="deleteCollect(item.ci._id)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ops-icon
|
||||||
|
v-else
|
||||||
|
type="veops-collect"
|
||||||
|
class="list-card-collect"
|
||||||
|
:style="{ color: '#A5A9BC' }"
|
||||||
|
@click.stop="addCollect(item)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="list-card-attr">
|
||||||
|
<div
|
||||||
|
v-for="(attr) in item.attributes"
|
||||||
|
:key="attr.name"
|
||||||
|
class="list-card-attr-item"
|
||||||
|
>
|
||||||
|
<div class="list-card-attr-item-label">{{ attr.alias || attr.name || '' }}: </div>
|
||||||
|
<div class="list-card-attr-item-value">
|
||||||
|
<AttrDisplay
|
||||||
|
:attr="attr"
|
||||||
|
:ci="item.ci"
|
||||||
|
:referenceShowAttrNameMap="referenceShowAttrNameMap"
|
||||||
|
:referenceCIIdMap="referenceCIIdMap"
|
||||||
|
:isEllipsis="true"
|
||||||
|
:searchValue="searchValue"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ExcelJS from 'exceljs'
|
||||||
|
import FileSaver from 'file-saver'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
import AttrDisplay from './attrDisplay.vue'
|
||||||
|
import CIIcon from '@/modules/cmdb/components/ciIcon/index.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'InstanceList',
|
||||||
|
components: {
|
||||||
|
AttrDisplay,
|
||||||
|
CIIcon
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
list: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
tabList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
referenceShowAttrNameMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
referenceCIIdMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
favorList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
detailCIId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
searchValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTab: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filterList() {
|
||||||
|
if (!this.currentTab || this.currentTab === -1) {
|
||||||
|
return this.list
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.list.filter((item) => item.ciTypeObj.id === this.currentTab)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
tabList: {
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
handler(newVal) {
|
||||||
|
this.currentTab = newVal?.[0]?.id ?? ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clickTab(id) {
|
||||||
|
this.currentTab = id
|
||||||
|
},
|
||||||
|
|
||||||
|
getAttrLabel(attrName, attributes) {
|
||||||
|
const label = attributes.find((attr) => attr.name === attrName)?.alias
|
||||||
|
return label || attrName
|
||||||
|
},
|
||||||
|
|
||||||
|
clickInstance(id, ciTypeId) {
|
||||||
|
this.$emit('showDetail', {
|
||||||
|
id,
|
||||||
|
ciTypeId,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getFavorId(ciId) {
|
||||||
|
const id = this.favorList.find((item) => item?.option?.CIId === ciId)?.id
|
||||||
|
return id ?? null
|
||||||
|
},
|
||||||
|
|
||||||
|
addCollect(data) {
|
||||||
|
this.$emit('addCollect', {
|
||||||
|
CIId: data.ci._id,
|
||||||
|
CITypeId: data.ciTypeObj.id,
|
||||||
|
title: data.ci[data.ciTypeObj.showAttrName],
|
||||||
|
icon: data.ciTypeObj.icon,
|
||||||
|
CITypeTitle: data.ciTypeObj.name
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteCollect(ciId) {
|
||||||
|
const favorId = this.getFavorId(ciId)
|
||||||
|
if (favorId) {
|
||||||
|
this.$emit('deleteCollect', favorId)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleExport() {
|
||||||
|
const excel_name = `cmdb-${this.$t('cmdb.ciType.resourceSearch')}-${moment().format('YYYYMMDDHHmmss')}.xlsx`
|
||||||
|
const wb = new ExcelJS.Workbook()
|
||||||
|
|
||||||
|
this.tabList.map((sheet) => {
|
||||||
|
if (sheet.id === -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const ws = wb.addWorksheet(sheet.title)
|
||||||
|
this.handleSheetData({
|
||||||
|
ws,
|
||||||
|
sheet
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
wb.xlsx.writeBuffer().then((buffer) => {
|
||||||
|
const file = new Blob([buffer], {
|
||||||
|
type: 'application/octet-stream',
|
||||||
|
})
|
||||||
|
FileSaver.saveAs(file, excel_name)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSheetData({
|
||||||
|
ws,
|
||||||
|
sheet
|
||||||
|
}) {
|
||||||
|
const listData = this.list.filter((item) => item.ciTypeObj.id === sheet.id)
|
||||||
|
if (!listData.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnMap = new Map()
|
||||||
|
const columns = listData[0].attributes.filter((attr) => !attr.is_password).map((attr) => {
|
||||||
|
columnMap.set(attr.name, attr)
|
||||||
|
|
||||||
|
return {
|
||||||
|
header: attr.alias || attr.name || '',
|
||||||
|
key: attr.name,
|
||||||
|
width: 20,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ws.columns = columns
|
||||||
|
|
||||||
|
listData.forEach((data) => {
|
||||||
|
const row = {}
|
||||||
|
columns.forEach(({ key }) => {
|
||||||
|
const value = data?.ci?.[key] ?? null
|
||||||
|
const attr = columnMap.get(key)
|
||||||
|
if (attr.valueType === '6') {
|
||||||
|
row[key] = value ? JSON.stringify(value) : value
|
||||||
|
} else if (attr.is_list && Array.isArray(value)) {
|
||||||
|
row[key] = value.join(',')
|
||||||
|
} else {
|
||||||
|
row[key] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ws.addRow(row)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.list-wrap {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
flex-shrink: 1 !important;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&-bg {
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 90px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-tab {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-shrink: 0;
|
||||||
|
column-gap: 14px;
|
||||||
|
|
||||||
|
&-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 14px;
|
||||||
|
row-gap: 7px;
|
||||||
|
overflow-x: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&-count {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-active {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-export {
|
||||||
|
margin-left: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-container {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 12px;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 0;
|
||||||
|
|
||||||
|
.list-card {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #FFF;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-selected {
|
||||||
|
border: 1px solid #7F97FA;
|
||||||
|
background-color: #F9FBFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-model {
|
||||||
|
border-radius: 24px;
|
||||||
|
border: 1px solid #E4E7ED;
|
||||||
|
background-color: #FFF;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0 13px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 24px;
|
||||||
|
color: #1D2129;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
margin-left: 11px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1D2129;
|
||||||
|
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-collect {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 9px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-attr {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 25px;
|
||||||
|
column-gap: 40px;
|
||||||
|
row-gap: 20px;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
flex-shrink: 0;
|
||||||
|
max-width: calc((100% - 160px) / 5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
color: #86909C;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-value {
|
||||||
|
color: #1D2129;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-left: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0px 2px 12px 0px rgba(147, 168, 223, 0.20);
|
||||||
|
|
||||||
|
.list-card-collect {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,201 @@
|
||||||
|
<template>
|
||||||
|
<div :class="['search-input', classType ? 'search-input-' + classType : '']">
|
||||||
|
<a-input
|
||||||
|
:value="searchValue"
|
||||||
|
class="search-input-component"
|
||||||
|
:placeholder="$t('cmdb.ciType.searchInputTip')"
|
||||||
|
@change="handleChangeSearchValue"
|
||||||
|
@pressEnter="saveCondition(true)"
|
||||||
|
>
|
||||||
|
<a-icon
|
||||||
|
class="search-input-component-icon"
|
||||||
|
slot="prefix"
|
||||||
|
type="search"
|
||||||
|
@click="saveCondition(true)"
|
||||||
|
/>
|
||||||
|
</a-input>
|
||||||
|
<FilterPopover
|
||||||
|
ref="filterPpoverRef"
|
||||||
|
:CITypeGroup="CITypeGroup"
|
||||||
|
:allAttributesList="allAttributesList"
|
||||||
|
:expression="expression"
|
||||||
|
:selectCITypeIds="selectCITypeIds"
|
||||||
|
@changeFilter="changeFilter"
|
||||||
|
@updateAllAttributesList="updateAllAttributesList"
|
||||||
|
@saveCondition="saveCondition"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-if="copyText" class="expression-display">
|
||||||
|
<span class="expression-display-text">{{ copyText }}</span>
|
||||||
|
<a-icon
|
||||||
|
slot="suffix"
|
||||||
|
type="check-circle"
|
||||||
|
class="expression-display-icon"
|
||||||
|
@click="handleCopyExpression"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FilterPopover from './filterPopover.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SearchInput',
|
||||||
|
components: {
|
||||||
|
FilterPopover
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
searchValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
expression: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
selectCITypeIds: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
CITypeGroup: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
allAttributesList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
classType: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 复制文字展示,与实际文本复制内容区别在于,未选择模型时不展示所有模型拼接数据
|
||||||
|
copyText() {
|
||||||
|
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||||
|
const exp = this.expression.match(regQ) ? this.expression.match(regQ)[0] : null
|
||||||
|
|
||||||
|
const textArray = []
|
||||||
|
if (this.selectCITypeIds?.length) {
|
||||||
|
textArray.push(`_type:(${this.selectCITypeIds.join(';')})`)
|
||||||
|
}
|
||||||
|
if (exp) {
|
||||||
|
textArray.push(exp)
|
||||||
|
}
|
||||||
|
if (this.searchValue) {
|
||||||
|
textArray.push(`*${this.searchValue}*`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return textArray.length ? `q=${textArray.join(',')}` : ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateAllAttributesList(value) {
|
||||||
|
this.$emit('updateAllAttributesList', value)
|
||||||
|
},
|
||||||
|
saveCondition(isSubmit) {
|
||||||
|
this.$emit('saveCondition', isSubmit)
|
||||||
|
},
|
||||||
|
handleChangeSearchValue(e) {
|
||||||
|
const value = e.target.value
|
||||||
|
this.changeFilter({
|
||||||
|
name: 'searchValue',
|
||||||
|
value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
changeFilter(data) {
|
||||||
|
this.$emit('changeFilter', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCopyExpression() {
|
||||||
|
const { selectCITypeIds, expression, searchValue } = this
|
||||||
|
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||||
|
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||||
|
|
||||||
|
const ciTypeIds = [...selectCITypeIds]
|
||||||
|
if (!ciTypeIds.length) {
|
||||||
|
this.CITypeGroup.forEach((item) => {
|
||||||
|
const ids = item.ci_types.map((ci_type) => ci_type.id)
|
||||||
|
ciTypeIds.push(...ids)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const copyText = `${ciTypeIds?.length ? `_type:(${ciTypeIds.join(';')})` : ''}${exp ? `,${exp}` : ''}${searchValue ? `,*${searchValue}*` : ''}`
|
||||||
|
|
||||||
|
this.$copyText(copyText)
|
||||||
|
.then(() => {
|
||||||
|
this.$message.success(this.$t('copySuccess'))
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.$message.error(this.$t('cmdb.ci.copyFailed'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.search-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&-component {
|
||||||
|
height: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border: none;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 48px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
color: #2F54EB;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ & > input {
|
||||||
|
height: 100%;
|
||||||
|
margin-left: 10px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-after {
|
||||||
|
height: 38px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.search-input-component {
|
||||||
|
max-width: 524px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expression-display {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 20px;
|
||||||
|
max-width: 30%;
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
width: 100%;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
margin-left: 8px;
|
||||||
|
color: #00b42a;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,625 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="resource-search"
|
||||||
|
:style="{ height: `${windowHeight - 131}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 { mapState } from 'vuex'
|
||||||
|
import { getPreferenceSearch, savePreferenceSearch, getSubscribeAttributes, deletePreferenceSearch } from '@/modules/cmdb/api/preference'
|
||||||
|
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: 'ResourceSearchCom',
|
||||||
|
components: {
|
||||||
|
SearchInput,
|
||||||
|
HistoryList,
|
||||||
|
InstanceList,
|
||||||
|
InstanceDetail
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
CITypeGroup: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
allCITypes: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 筛选条件
|
||||||
|
searchValue: '', // 搜索框
|
||||||
|
selectCITypeIds: [], // 已选模型
|
||||||
|
expression: '', // 筛选语句
|
||||||
|
currentSearchValue: '', // 当前已搜索语句
|
||||||
|
|
||||||
|
recentList: [], // 最近搜索
|
||||||
|
favorList: [], // 我的收藏
|
||||||
|
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: {
|
||||||
|
...mapState({
|
||||||
|
cmdbSearchValue: (state) => state.app.cmdbSearchValue,
|
||||||
|
}),
|
||||||
|
windowHeight() {
|
||||||
|
return this.$store.state.windowHeight
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
cmdbSearchValue: {
|
||||||
|
handler(value) {
|
||||||
|
this.searchValue = value
|
||||||
|
this.saveCondition(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async initData() {
|
||||||
|
await this.getFavorList()
|
||||||
|
|
||||||
|
this.$nextTick(async () => {
|
||||||
|
if (this.cmdbSearchValue) {
|
||||||
|
this.searchValue = this.cmdbSearchValue
|
||||||
|
this.saveCondition(true)
|
||||||
|
} else {
|
||||||
|
await this.getRecentList()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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 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.allCITypes.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.allCITypes.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.allCITypes.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>
|
|
@ -25,7 +25,7 @@ const app = {
|
||||||
color: null,
|
color: null,
|
||||||
weak: false,
|
weak: false,
|
||||||
multiTab: false,
|
multiTab: false,
|
||||||
|
cmdbSearchValue: '',
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
SET_SIDEBAR_TYPE: (state, type) => {
|
SET_SIDEBAR_TYPE: (state, type) => {
|
||||||
|
@ -76,7 +76,9 @@ const app = {
|
||||||
Vue.ls.set(DEFAULT_MULTI_TAB, bool)
|
Vue.ls.set(DEFAULT_MULTI_TAB, bool)
|
||||||
state.multiTab = bool
|
state.multiTab = bool
|
||||||
},
|
},
|
||||||
|
UPDATE_CMDB_SEARCH_VALUE: (state, value) => {
|
||||||
|
state.cmdbSearchValue = value
|
||||||
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setSidebar({ commit }, type) {
|
setSidebar({ commit }, type) {
|
||||||
|
@ -118,6 +120,9 @@ const app = {
|
||||||
ToggleMultiTab({ commit }, bool) {
|
ToggleMultiTab({ commit }, bool) {
|
||||||
commit('TOGGLE_MULTI_TAB', bool)
|
commit('TOGGLE_MULTI_TAB', bool)
|
||||||
},
|
},
|
||||||
|
UpdateCMDBSEarchValue({ commit }, value) {
|
||||||
|
commit('UPDATE_CMDB_SEARCH_VALUE', value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue