feat(cmdb-ui):citype show attr && service tree search (#479)

This commit is contained in:
dagongren 2024-04-17 17:59:21 +08:00 committed by GitHub
parent 11dc7a6013
commit 0966d104a7
40 changed files with 710 additions and 559 deletions

View File

@ -54,6 +54,24 @@
<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">&#xe914;</span>
<div class="name">veops-show</div>
<div class="code-name">&amp;#xe914;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe913;</span>
<div class="name">itsm-duration</div>
<div class="code-name">&amp;#xe913;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe912;</span>
<div class="name">itsm-workload (1)</div>
<div class="code-name">&amp;#xe912;</div>
</li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe910;</span> <span class="icon iconfont">&#xe910;</span>
<div class="name">VPC</div> <div class="name">VPC</div>
@ -4788,9 +4806,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=1711963254221') format('woff2'), src: url('iconfont.woff2?t=1713335725699') format('woff2'),
url('iconfont.woff?t=1711963254221') format('woff'), url('iconfont.woff?t=1713335725699') format('woff'),
url('iconfont.ttf?t=1711963254221') format('truetype'); url('iconfont.ttf?t=1713335725699') format('truetype');
} }
</code></pre> </code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3> <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@ -4816,6 +4834,33 @@
<div class="content font-class"> <div class="content font-class">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont veops-show"></span>
<div class="name">
veops-show
</div>
<div class="code-name">.veops-show
</div>
</li>
<li class="dib">
<span class="icon iconfont itsm-duration"></span>
<div class="name">
itsm-duration
</div>
<div class="code-name">.itsm-duration
</div>
</li>
<li class="dib">
<span class="icon iconfont a-itsm-workload1"></span>
<div class="name">
itsm-workload (1)
</div>
<div class="code-name">.a-itsm-workload1
</div>
</li>
<li class="dib"> <li class="dib">
<span class="icon iconfont caise-VPC"></span> <span class="icon iconfont caise-VPC"></span>
<div class="name"> <div class="name">
@ -11917,6 +11962,30 @@
<div class="content symbol"> <div class="content symbol">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#veops-show"></use>
</svg>
<div class="name">veops-show</div>
<div class="code-name">#veops-show</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#itsm-duration"></use>
</svg>
<div class="name">itsm-duration</div>
<div class="code-name">#itsm-duration</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#a-itsm-workload1"></use>
</svg>
<div class="name">itsm-workload (1)</div>
<div class="code-name">#a-itsm-workload1</div>
</li>
<li class="dib"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#caise-VPC"></use> <use xlink:href="#caise-VPC"></use>

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 3857903 */ font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1711963254221') format('woff2'), src: url('iconfont.woff2?t=1713335725699') format('woff2'),
url('iconfont.woff?t=1711963254221') format('woff'), url('iconfont.woff?t=1713335725699') format('woff'),
url('iconfont.ttf?t=1711963254221') format('truetype'); url('iconfont.ttf?t=1713335725699') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,18 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.veops-show:before {
content: "\e914";
}
.itsm-duration:before {
content: "\e913";
}
.a-itsm-workload1:before {
content: "\e912";
}
.caise-VPC:before { .caise-VPC:before {
content: "\e910"; content: "\e910";
} }

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,27 @@
"css_prefix_text": "", "css_prefix_text": "",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "39948814",
"name": "veops-show",
"font_class": "veops-show",
"unicode": "e914",
"unicode_decimal": 59668
},
{
"icon_id": "39926816",
"name": "itsm-duration",
"font_class": "itsm-duration",
"unicode": "e913",
"unicode_decimal": 59667
},
{
"icon_id": "39926833",
"name": "itsm-workload (1)",
"font_class": "a-itsm-workload1",
"unicode": "e912",
"unicode_decimal": 59666
},
{ {
"icon_id": "39782649", "icon_id": "39782649",
"name": "VPC", "name": "VPC",

Binary file not shown.

View File

@ -1,14 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0H2.5V1.25H1.25V2.5H0V1C0 0.447715 0.447715 0 1 0ZM0 7.5V9C0 9.55229 0.447715 10 1 10H2.5V8.75H1.25V7.5H0ZM8.75 7.5V8.75H7.5V10H9C9.55229 10 10 9.55228 10 9V7.5H8.75ZM10 2.5V1C10 0.447715 9.55228 0 9 0H7.5V1.25H8.75V2.5H10Z" fill="url(#paint0_linear_124_16807)"/>
<rect x="2.5" y="3.125" width="5" height="3.75" fill="url(#paint1_linear_124_16807)"/>
<defs>
<linearGradient id="paint0_linear_124_16807" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F84FF"/>
<stop offset="1" stop-color="#85CBFF"/>
</linearGradient>
<linearGradient id="paint1_linear_124_16807" x1="5" y1="3.125" x2="5" y2="6.875" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F84FF"/>
<stop offset="1" stop-color="#85CBFF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 916 B

View File

@ -1,14 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2.5" y="2.5" width="5" height="5" rx="0.5" fill="url(#paint0_linear_124_16808)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0C0.447715 0 0 0.447715 0 1V9C0 9.55229 0.447715 10 1 10H9C9.55229 10 10 9.55228 10 9V1C10 0.447715 9.55228 0 9 0H1ZM8.75 1.25H1.25V8.75H8.75V1.25Z" fill="url(#paint1_linear_124_16808)"/>
<defs>
<linearGradient id="paint0_linear_124_16808" x1="5" y1="2.5" x2="5" y2="7.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#5187FF"/>
<stop offset="1" stop-color="#84C9FF"/>
</linearGradient>
<linearGradient id="paint1_linear_124_16808" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5187FF"/>
<stop offset="1" stop-color="#84C9FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 840 B

View File

@ -1,9 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.56845 8.8409C3.06335 8.78963 1.86719 8.05799 2.06279 6.48243C2.1538 5.75105 2.64549 5.3214 3.34457 5.16041C3.67173 5.08909 4.00806 5.06954 4.34128 5.10247C4.40203 5.10811 4.44843 5.11401 4.47689 5.11837L4.51586 5.12631C4.64379 5.15574 4.77263 5.18104 4.90218 5.20219C5.26786 5.2651 5.63914 5.28941 6.0099 5.27474C6.8046 5.23219 7.21015 4.97429 7.23092 4.41672C7.25424 3.79429 6.76332 3.29619 5.86659 2.91832C5.52815 2.77793 5.17843 2.66645 4.82117 2.58506C4.70325 2.55755 4.58482 2.53328 4.46587 2.51226C4.30323 2.94847 3.9867 3.31016 3.57591 3.5292C3.16512 3.74824 2.68841 3.80952 2.23557 3.70149C1.90324 3.61651 1.60053 3.44214 1.36029 3.1973C1.12004 2.95245 0.951447 2.64649 0.872793 2.3126C0.794138 1.97872 0.808429 1.62967 0.914116 1.30333C1.0198 0.976995 1.21285 0.685836 1.4723 0.461451C1.73176 0.237065 2.04771 0.0880244 2.38588 0.0305017C2.72404 -0.0270211 3.07151 0.00917138 3.39056 0.135152C3.70961 0.261132 3.98807 0.472088 4.19571 0.745127C4.40335 1.01817 4.53225 1.34286 4.56841 1.68397C4.6812 1.70269 4.83374 1.73217 5.01524 1.77421C5.42003 1.86601 5.81625 1.99216 6.1996 2.15131C7.38191 2.64966 8.1156 3.39463 8.07638 4.4462C8.03639 5.53187 7.23425 6.04253 6.0563 6.10533C5.62418 6.12373 5.19132 6.09614 4.76503 6.02304C4.61925 5.99997 4.47398 5.9716 4.32923 5.93793C4.30731 5.93532 4.28534 5.9331 4.26335 5.93127C4.02033 5.90687 3.77501 5.92018 3.53606 5.97075C3.15153 6.05893 2.94311 6.24146 2.90056 6.58267C2.78725 7.49504 3.47915 7.94443 5.42694 8.00416C5.44492 7.65558 5.5586 7.3187 5.75548 7.03049C5.95237 6.74229 6.22485 6.51389 6.54303 6.37039C6.8612 6.22689 7.21277 6.17383 7.55912 6.21703C7.90548 6.26023 8.23323 6.39802 8.50641 6.61528C8.77959 6.83254 8.98763 7.12086 9.10769 7.4486C9.22775 7.77634 9.25519 8.13082 9.187 8.47314C9.11881 8.81545 8.95763 9.13235 8.72114 9.38907C8.48465 9.64578 8.18201 9.83237 7.84643 9.92836C7.39921 10.0556 6.92094 10.0153 6.50129 9.81515C6.08164 9.61495 5.74941 9.26855 5.56691 8.8409H5.56845Z" fill="url(#paint0_linear_124_16804)"/>
<defs>
<linearGradient id="paint0_linear_124_16804" x1="5.02318" y1="0.00390625" x2="5.02318" y2="10.0013" gradientUnits="userSpaceOnUse">
<stop stop-color="#497DFF"/>
<stop offset="1" stop-color="#8CD5FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,9 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.01211 4.50621L2.7769 5.74077C2.712 5.80565 2.66051 5.88268 2.62538 5.96747C2.59025 6.05225 2.57217 6.14313 2.57217 6.2349C2.57217 6.32668 2.59025 6.41755 2.62538 6.50234C2.66051 6.58712 2.712 6.66416 2.7769 6.72904L3.27085 7.223C3.33573 7.28791 3.41276 7.3394 3.49754 7.37453C3.58232 7.40966 3.67319 7.42774 3.76496 7.42774C3.85674 7.42774 3.94761 7.40966 4.03239 7.37453C4.11717 7.3394 4.1942 7.28791 4.25908 7.223L5.49394 5.98775C5.6237 6.1175 5.72663 6.27155 5.79686 6.44109C5.86708 6.61063 5.90323 6.79234 5.90323 6.97585C5.90323 7.15935 5.86708 7.34106 5.79686 7.5106C5.72663 7.68014 5.6237 7.83419 5.49394 7.96394L3.76479 9.69316C3.56827 9.88963 3.30176 10 3.02387 10C2.74599 10 2.47948 9.88963 2.28296 9.69316L0.306832 7.71696C0.110368 7.52043 0 7.25391 0 6.97602C0 6.69813 0.110368 6.43161 0.306832 6.23508L2.03599 4.50586C2.16574 4.3761 2.31978 4.27317 2.48931 4.20294C2.65884 4.13271 2.84055 4.09657 3.02405 4.09657C3.20755 4.09657 3.38925 4.13271 3.55879 4.20294C3.72832 4.27317 3.88236 4.3761 4.01211 4.50586V4.50621ZM5.98789 5.49414L7.2231 4.25923C7.288 4.19435 7.33949 4.11732 7.37462 4.03253C7.40975 3.94775 7.42783 3.85687 7.42783 3.7651C7.42783 3.67332 7.40975 3.58245 7.37462 3.49766C7.33949 3.41288 7.288 3.33584 7.2231 3.27096L6.72915 2.777C6.66428 2.71209 6.58724 2.6606 6.50246 2.62547C6.41768 2.59034 6.32681 2.57226 6.23504 2.57226C6.14326 2.57226 6.05239 2.59034 5.96761 2.62547C5.88283 2.6606 5.8058 2.71209 5.74092 2.777L4.50606 4.01225C4.3763 3.8825 4.27337 3.72845 4.20314 3.55891C4.13292 3.38937 4.09677 3.20766 4.09677 3.02415C4.09677 2.84065 4.13292 2.65894 4.20314 2.4894C4.27337 2.31986 4.3763 2.16581 4.50606 2.03606L6.23521 0.306843C6.43173 0.110371 6.69824 0 6.97613 0C7.25401 0 7.52052 0.110371 7.71704 0.306843L9.69317 2.28304C9.88963 2.47957 10 2.74609 10 3.02398C10 3.30187 9.88963 3.56839 9.69317 3.76492L7.96401 5.49414C7.83426 5.6239 7.68022 5.72683 7.51069 5.79706C7.34116 5.86729 7.15945 5.90343 6.97595 5.90343C6.79245 5.90343 6.61075 5.86729 6.44121 5.79706C6.27168 5.72683 6.11764 5.6239 5.98789 5.49414ZM3.51817 5.9881L5.98789 3.51829C6.05339 3.45274 6.14225 3.4159 6.23491 3.41586C6.32758 3.41583 6.41646 3.45261 6.48201 3.51812C6.54755 3.58362 6.5844 3.67248 6.58443 3.76515C6.58446 3.85782 6.54768 3.9467 6.48218 4.01225L4.01211 6.48206C3.94661 6.54761 3.85775 6.58445 3.76509 6.58449C3.67242 6.58452 3.58354 6.54774 3.51799 6.48223C3.45245 6.41673 3.4156 6.32787 3.41557 6.2352C3.41554 6.14253 3.45232 6.05365 3.51782 5.9881H3.51817Z" fill="url(#paint0_linear_124_16775)"/>
<defs>
<linearGradient id="paint0_linear_124_16775" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5A85FF"/>
<stop offset="1" stop-color="#8DD8FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,9 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.31822 4.16667H2.54549V2.5C2.54549 1.11458 3.63981 0 5.00003 0C6.36026 0 7.45458 1.11458 7.45458 2.5V4.16667H8.68185C8.90685 4.16667 9.09094 4.35417 9.09094 4.58333V9.58333C9.09094 9.8125 8.90685 10 8.68185 10H1.31822C1.09322 10 0.909124 9.8125 0.909124 9.58333V4.58333C0.909124 4.35417 1.09322 4.16667 1.31822 4.16667ZM5.00003 7.91667C5.45003 7.91667 5.81822 7.54167 5.81822 7.08333C5.81822 6.625 5.45003 6.25 5.00003 6.25C4.55003 6.25 4.18185 6.625 4.18185 7.08333C4.18185 7.54167 4.55003 7.91667 5.00003 7.91667ZM3.36367 4.16667H6.6364V2.5C6.6364 1.58333 5.90003 0.833333 5.00003 0.833333C4.10003 0.833333 3.36367 1.58333 3.36367 2.5V4.16667Z" fill="url(#paint0_linear_124_16805)"/>
<defs>
<linearGradient id="paint0_linear_124_16805" x1="5.00003" y1="0" x2="5.00003" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#4D82FF"/>
<stop offset="1" stop-color="#88CFFF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1022 B

View File

@ -1,9 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.91242 9.46382C3.91242 9.57428 3.82288 9.66382 3.71242 9.66382H2.35075C2.2403 9.66382 2.15075 9.57428 2.15075 9.46382V3.55962C2.15075 3.44916 2.06121 3.35962 1.95075 3.35962H0.539905C0.354312 3.35962 0.268806 3.12879 0.40961 3.00788L3.58212 0.283626C3.71182 0.172253 3.91242 0.264405 3.91242 0.43536V9.46382ZM6.08758 0.567715C6.08758 0.457258 6.17712 0.367716 6.28758 0.367716H7.64925C7.7597 0.367716 7.84925 0.457259 7.84925 0.567716V6.4411C7.84925 6.55156 7.93879 6.6411 8.04925 6.6411H9.46001C9.64561 6.6411 9.73111 6.87195 9.59029 6.99285L6.41786 9.71645C6.28816 9.8278 6.08758 9.73565 6.08758 9.5647V0.567715Z" fill="url(#paint0_linear_124_16806)"/>
<defs>
<linearGradient id="paint0_linear_124_16806" x1="5" y1="0" x2="5" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5A85FF"/>
<stop offset="1" stop-color="#8DD8FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 979 B

View File

@ -1,9 +0,0 @@
<svg width="1em" height="1em" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.51961 6.8937V10H4.48V6.8937H1.76732C1.65823 6.8937 1.56223 6.85372 1.48369 6.77237C1.40522 6.69504 1.3621 6.5915 1.36369 6.48421C1.36369 5.95891 1.52769 5.48566 1.85895 5.06411C2.18986 4.64428 2.56258 4.43334 2.97893 4.43334V1.64277C2.75966 1.64277 2.57167 1.56142 2.41022 1.39873C2.25355 1.24349 2.16738 1.0362 2.17022 0.821384C2.17022 0.598718 2.25022 0.407762 2.41022 0.244037C2.56912 0.0827244 2.7593 0 2.97893 0H7.01959C7.23885 0 7.42685 0.0813456 7.5883 0.244037C7.74721 0.406728 7.82866 0.598718 7.82866 0.821384C7.82866 1.04405 7.74866 1.23501 7.58867 1.39873C7.4283 1.5628 7.23885 1.64277 7.01959 1.64277V4.43196C7.43594 4.43196 7.81012 4.64291 8.13956 5.06273C8.46631 5.47151 8.64098 5.97137 8.63628 6.48421C8.63628 6.59486 8.59701 6.6924 8.51665 6.77237C8.43665 6.85234 8.34211 6.8937 8.23302 6.8937H5.51998H5.51961Z" fill="url(#paint0_linear_124_16803)"/>
<defs>
<linearGradient id="paint0_linear_124_16803" x1="5.00001" y1="0" x2="5.00001" y2="10" gradientUnits="userSpaceOnUse">
<stop stop-color="#5A85FF"/>
<stop offset="1" stop-color="#8DD8FF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -4,34 +4,22 @@
@click="jumpTo" @click="jumpTo"
v-if="showTitle && !collapsed" v-if="showTitle && !collapsed"
style="width: 100%; height: 100%; cursor: pointer" style="width: 100%; height: 100%; cursor: pointer"
:src="file_name ? `/api/common-setting/v1/file/${file_name}` : require('@/assets/logo_VECMDB.png')" :src="require('@/assets/logo_VECMDB.png')"
/> />
<img <img
@click="jumpTo" @click="jumpTo"
v-else v-else
style="width: 32px; height: 32px; margin-left: 24px; cursor: pointer" style="width: 32px; height: 32px; margin-left: 24px; cursor: pointer"
:src="small_file_name ? `/api/common-setting/v1/file/${small_file_name}` : require('@/assets/logo.png')" :src="require('@/assets/logo.png')"
/> />
<!-- <logo-svg/> -->
<!-- <img v-if="showTitle" style="width:92px;height: 32px" src="@/assets/OneOps.png" /> -->
<!-- <h1 v-if="showTitle">{{ title }}</h1> -->
</div> </div>
</template> </template>
<script> <script>
// import LogoSvg from '@/assets/logo.svg?inline'
import { mapState } from 'vuex'
export default { export default {
name: 'Logo', name: 'Logo',
components: { components: {},
// LogoSvg, computed: {},
},
computed: {
...mapState({
file_name: (state) => state.logo.file_name,
small_file_name: (state) => state.logo.small_file_name,
}),
},
props: { props: {
title: { title: {
type: String, type: String,

View File

@ -9,25 +9,11 @@
import gridSvg from '@/assets/icons/grid.svg?inline' import gridSvg from '@/assets/icons/grid.svg?inline'
import top_agent from '@/assets/icons/top_agent.svg?inline' import top_agent from '@/assets/icons/top_agent.svg?inline'
import top_acl from '@/assets/icons/top_acl.svg?inline' import top_acl from '@/assets/icons/top_acl.svg?inline'
import ops_default_show from '@/assets/icons/ops-default_show.svg?inline'
import ops_is_choice from '@/assets/icons/ops-is_choice.svg?inline'
import ops_is_index from '@/assets/icons/ops-is_index.svg?inline'
import ops_is_link from '@/assets/icons/ops-is_link.svg?inline'
import ops_is_password from '@/assets/icons/ops-is_password.svg?inline'
import ops_is_sortable from '@/assets/icons/ops-is_sortable.svg?inline'
import ops_is_unique from '@/assets/icons/ops-is_unique.svg?inline'
import ops_move_icon from '@/assets/icons/ops-move-icon.svg?inline' import ops_move_icon from '@/assets/icons/ops-move-icon.svg?inline'
export { export {
gridSvg, gridSvg,
top_agent, top_agent,
top_acl, top_acl,
ops_default_show,
ops_is_choice,
ops_is_index,
ops_is_link,
ops_is_password,
ops_is_sortable,
ops_is_unique,
ops_move_icon ops_move_icon
} }

View File

@ -0,0 +1,14 @@
import './highlight.less'
const highlight = (el, binding) => {
if (binding.value.value) {
let testValue = `${binding.value.value}`
if (['(', ')', '$'].includes(testValue)) {
testValue = `\\${testValue}`
}
const regex = new RegExp(`(${testValue})`, 'gi')
el.innerHTML = el.innerText.replace(regex, `<span class='${binding.value.class ?? 'ops-text-highlight'}'>$1</span>`)
}
}
export default highlight

View File

@ -0,0 +1,5 @@
@import '~@/style/static.less';
.ops-text-highlight {
background-color: @primary-color_3;
}

View File

@ -0,0 +1,12 @@
import hightlight from './highlight'
const install = function (Vue) {
Vue.directive('hightlight', hightlight)
}
if (window.Vue) {
window.hightlight = hightlight
Vue.use(install); // eslint-disable-line
}
hightlight.install = install
export default hightlight

View File

@ -0,0 +1,13 @@
import waves from './waves'
const install = function (Vue) {
Vue.directive('waves', waves)
}
if (window.Vue) {
window.waves = waves
Vue.use(install); // eslint-disable-line
}
waves.install = install
export default waves

View File

@ -0,0 +1,26 @@
.waves-ripple {
position: absolute;
border-radius: 100%;
background-color: rgba(0, 0, 0, 0.15);
background-clip: padding-box;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transform: scale(0);
-ms-transform: scale(0);
transform: scale(0);
opacity: 1;
}
.waves-ripple.z-active {
opacity: 0;
-webkit-transform: scale(2);
-ms-transform: scale(2);
transform: scale(2);
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
}

View File

@ -0,0 +1,72 @@
import './waves.css'
const context = '@@wavesContext'
function handleClick(el, binding) {
function handle(e) {
const customOpts = Object.assign({}, binding.value)
const opts = Object.assign({
ele: el, // 波纹作用元素
type: 'hit', // hit 点击位置扩散 center中心点扩展
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
},
customOpts
)
const target = opts.ele
if (target) {
target.style.position = 'relative'
target.style.overflow = 'hidden'
const rect = target.getBoundingClientRect()
let ripple = target.querySelector('.waves-ripple')
if (!ripple) {
ripple = document.createElement('span')
ripple.className = 'waves-ripple'
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
target.appendChild(ripple)
} else {
ripple.className = 'waves-ripple'
}
switch (opts.type) {
case 'center':
ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
break
default:
ripple.style.top =
(e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
document.body.scrollTop) + 'px'
ripple.style.left =
(e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
document.body.scrollLeft) + 'px'
}
ripple.style.backgroundColor = opts.color
ripple.className = 'waves-ripple z-active'
return false
}
}
if (!el[context]) {
el[context] = {
removeHandle: handle
}
} else {
el[context].removeHandle = handle
}
return handle
}
export default {
bind(el, binding) {
el.addEventListener('click', handleClick(el, binding), false)
},
update(el, binding) {
el.removeEventListener('click', el[context].removeHandle, false)
el.addEventListener('click', handleClick(el, binding), false)
},
unbind(el) {
el.removeEventListener('click', el[context].removeHandle, false)
el[context] = null
delete el[context]
}
}

View File

@ -31,7 +31,6 @@ router.beforeEach(async (to, from, next) => {
store.dispatch("loadAllUsers") store.dispatch("loadAllUsers")
store.dispatch("loadAllEmployees") store.dispatch("loadAllEmployees")
store.dispatch("loadAllDepartments") store.dispatch("loadAllDepartments")
store.dispatch("getCompanyInfo")
store.dispatch('GenerateRoutes', { roles }).then(() => { store.dispatch('GenerateRoutes', { roles }).then(() => {
router.addRoutes(store.getters.appRoutes) router.addRoutes(store.getters.appRoutes)
const redirect = decodeURIComponent(from.query.redirect || to.path) const redirect = decodeURIComponent(from.query.redirect || to.path)

View File

@ -73,3 +73,11 @@ export function deleteCIRelationView(firstCiId, secondCiId, data) {
data data
}) })
} }
export function searchCIRelationFull(params) {
return axios({
url: `/v0.1/ci_relations/search/full`,
method: 'GET',
params,
})
}

View File

@ -95,12 +95,12 @@
isFocusExpression = false isFocusExpression = false
} }
" "
class="ci-searchform-expression" :class="{ 'ci-searchform-expression': true, 'ci-searchform-expression-has-value': expression }"
:style="{ width }" :style="{ width }"
:placeholder="placeholder" :placeholder="placeholder"
@keyup.enter="emitRefresh" @keyup.enter="emitRefresh"
> >
<ops-icon slot="suffix" type="veops-copy" @click="handleCopyExpression" /> <a-icon slot="suffix" type="check-circle" @click="handleCopyExpression" />
</a-input> </a-input>
<slot></slot> <slot></slot>
</a-space> </a-space>
@ -284,6 +284,9 @@ export default {
cursor: pointer; cursor: pointer;
} }
} }
.ci-searchform-expression-has-value .ant-input-suffix {
color: @func-color_3;
}
.cmdb-search-form { .cmdb-search-form {
.ant-form-item-label { .ant-form-item-label {
overflow: hidden; overflow: hidden;
@ -294,7 +297,6 @@ export default {
</style> </style>
<style lang="less" scoped> <style lang="less" scoped>
.search-form-bar { .search-form-bar {
margin-bottom: 20px; margin-bottom: 20px;
display: flex; display: flex;

View File

@ -194,7 +194,10 @@ const cmdb_en = {
attributeAssociationTip3: 'Two Attributes must be selected', attributeAssociationTip3: 'Two Attributes must be selected',
attributeAssociationTip4: 'Please select a attribute from Source CIType', attributeAssociationTip4: 'Please select a attribute from Source CIType',
attributeAssociationTip5: 'Please select a attribute from Target CIType', attributeAssociationTip5: 'Please select a attribute from Target CIType',
show: 'show attribute',
setAsShow: 'Set as show attribute',
cancelSetAsShow: 'Cancel show attribute',
showTips: 'The names of nodes in the service tree and topology view'
}, },
components: { components: {
unselectAttributes: 'Unselected', unselectAttributes: 'Unselected',
@ -530,7 +533,8 @@ if __name__ == "__main__":
peopleHasRead: 'Personnel authorized to read:', peopleHasRead: 'Personnel authorized to read:',
authorizationPolicy: 'CI Authorization Policy:', authorizationPolicy: 'CI Authorization Policy:',
idAuthorizationPolicy: 'Authorized by node:', idAuthorizationPolicy: 'Authorized by node:',
view: 'View permissions' view: 'View permissions',
searchTips: 'Search in service tree'
}, },
tree: { tree: {
tips1: 'Please go to Preference page first to complete your subscription!', tips1: 'Please go to Preference page first to complete your subscription!',

View File

@ -194,6 +194,10 @@ const cmdb_zh = {
attributeAssociationTip3: '属性关联必须选择两个属性', attributeAssociationTip3: '属性关联必须选择两个属性',
attributeAssociationTip4: '请选择原模型属性', attributeAssociationTip4: '请选择原模型属性',
attributeAssociationTip5: '请选择目标模型属性', attributeAssociationTip5: '请选择目标模型属性',
show: '展示属性',
setAsShow: '设置为展示属性',
cancelSetAsShow: '取消设置为展示属性',
showTips: '服务树和拓扑视图里节点的名称'
}, },
components: { components: {
unselectAttributes: '未选属性', unselectAttributes: '未选属性',
@ -529,7 +533,8 @@ if __name__ == "__main__":
peopleHasRead: '当前有查看权限的人员:', peopleHasRead: '当前有查看权限的人员:',
authorizationPolicy: '实例授权策略:', authorizationPolicy: '实例授权策略:',
idAuthorizationPolicy: '按节点授权的:', idAuthorizationPolicy: '按节点授权的:',
view: '查看权限' view: '查看权限',
searchTips: '在服务树中筛选'
}, },
tree: { tree: {
tips1: '请先到 我的订阅 页面完成订阅!', tips1: '请先到 我的订阅 页面完成订阅!',

View File

@ -150,11 +150,11 @@ export default {
computed: { computed: {
topoData() { topoData() {
const ci_types_list = this.ci_types() const ci_types_list = this.ci_types()
const unique_id = this.attributes().unique_id const _findCiType = ci_types_list.find((item) => item.id === this.typeId)
const unique_name = this.attributes().unique const unique_id = _findCiType.show_id || this.attributes().unique_id
const unique_name = _findCiType.show_name || this.attributes().unique
const _findUnique = this.attrList().find((attr) => attr.id === unique_id) const _findUnique = this.attrList().find((attr) => attr.id === unique_id)
const unique_alias = _findUnique?.alias || _findUnique?.name || '' const unique_alias = _findUnique?.alias || _findUnique?.name || ''
const _findCiType = ci_types_list.find((item) => item.id === this.typeId)
const nodes = { const nodes = {
isRoot: true, isRoot: true,
id: `Root_${this.typeId}`, id: `Root_${this.typeId}`,
@ -183,6 +183,10 @@ export default {
this.parentCITypes.forEach((parent) => { this.parentCITypes.forEach((parent) => {
const _findCiType = ci_types_list.find((item) => item.id === parent.id) const _findCiType = ci_types_list.find((item) => item.id === parent.id)
if (this.firstCIs[parent.name]) { if (this.firstCIs[parent.name]) {
const unique_id = _findCiType.show_id || _findCiType.unique_id
const _findUnique = parent.attributes.find((attr) => attr.id === unique_id)
const unique_name = _findUnique?.name
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
this.firstCIs[parent.name].forEach((parentCi) => { this.firstCIs[parent.name].forEach((parentCi) => {
nodes.children.push({ nodes.children.push({
id: `${parentCi._id}`, id: `${parentCi._id}`,
@ -190,9 +194,9 @@ export default {
title: parent.alias || parent.name, title: parent.alias || parent.name,
name: parent.name, name: parent.name,
side: 'left', side: 'left',
unique_alias: parentCi.unique_alias, unique_alias,
unique_name: parentCi.unique, unique_name,
unique_value: parentCi[parentCi.unique], unique_value: parentCi[unique_name],
children: [], children: [],
icon: _findCiType?.icon || '', icon: _findCiType?.icon || '',
endpoints: [ endpoints: [
@ -222,6 +226,10 @@ export default {
this.childCITypes.forEach((child) => { this.childCITypes.forEach((child) => {
const _findCiType = ci_types_list.find((item) => item.id === child.id) const _findCiType = ci_types_list.find((item) => item.id === child.id)
if (this.secondCIs[child.name]) { if (this.secondCIs[child.name]) {
const unique_id = _findCiType.show_id || _findCiType.unique_id
const _findUnique = child.attributes.find((attr) => attr.id === unique_id)
const unique_name = _findUnique?.name
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
this.secondCIs[child.name].forEach((childCi) => { this.secondCIs[child.name].forEach((childCi) => {
nodes.children.push({ nodes.children.push({
id: `${childCi._id}`, id: `${childCi._id}`,
@ -229,9 +237,9 @@ export default {
title: child.alias || child.name, title: child.alias || child.name,
name: child.name, name: child.name,
side: 'right', side: 'right',
unique_alias: childCi.unique_alias, unique_alias,
unique_name: childCi.unique, unique_name,
unique_value: childCi[childCi.unique], unique_value: childCi[unique_name],
children: [], children: [],
icon: _findCiType?.icon || '', icon: _findCiType?.icon || '',
endpoints: [ endpoints: [
@ -333,7 +341,6 @@ export default {
secondCIs[item.ci_type] = [item] secondCIs[item.ci_type] = [item]
} }
}) })
console.log(_.cloneDeep(secondCIs))
this.secondCIs = secondCIs this.secondCIs = secondCIs
}) })
.catch((e) => {}) .catch((e) => {})

View File

@ -15,7 +15,7 @@
position: absolute; position: absolute;
background: #fff; background: #fff;
border: 1px solid #d9d9d9; border: 1px solid #d9d9d9;
border-radius: 10px; border-radius: 2px;
padding: 4px 8px; padding: 4px 8px;
width: 100px; width: 100px;
text-align: center; text-align: center;
@ -74,13 +74,11 @@
} }
.root { .root {
width: 100px; width: 100px;
background: #2f54eb; border-color: @primary-color;
border: none; font-weight: 700;
border-radius: 5px;
font-weight: 500;
padding: 4px 8px; padding: 4px 8px;
.title { .title {
color: #fff; color: @primary-color;
} }
} }
} }

View File

@ -10,6 +10,7 @@
import _ from 'lodash' import _ from 'lodash'
import { TreeCanvas } from 'butterfly-dag' import { TreeCanvas } from 'butterfly-dag'
import { searchCIRelation } from '@/modules/cmdb/api/CIRelation' import { searchCIRelation } from '@/modules/cmdb/api/CIRelation'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import Node from './node.js' import Node from './node.js'
import 'butterfly-dag/dist/index.css' import 'butterfly-dag/dist/index.css'
@ -87,7 +88,7 @@ export default {
this.canvas.focusCenterWithAnimate() this.canvas.focusCenterWithAnimate()
}) })
}, },
redrawData(res, sourceNode, side) { async redrawData(res, sourceNode, side) {
const newNodes = [] const newNodes = []
const newEdges = [] const newEdges = []
if (!res.result.length) { if (!res.result.length) {
@ -95,18 +96,24 @@ export default {
return return
} }
const ci_types_list = this.ci_types() const ci_types_list = this.ci_types()
res.result.forEach((r) => { for (let i = 0; i < res.result.length; i++) {
const r = res.result[i]
if (!this.exsited_ci.includes(r._id)) { if (!this.exsited_ci.includes(r._id)) {
const _findCiType = ci_types_list.find((item) => item.id === r._type) const _findCiType = ci_types_list.find((item) => item.id === r._type)
const { attributes } = await getCITypeAttributesById(_findCiType.id)
const unique_id = _findCiType.show_id || _findCiType.unique_id
const _findUnique = attributes.find((attr) => attr.id === unique_id)
const unique_name = _findUnique?.name
const unique_alias = _findUnique?.alias || _findUnique?.name || ''
newNodes.push({ newNodes.push({
id: `${r._id}`, id: `${r._id}`,
Class: Node, Class: Node,
title: r.ci_type_alias || r.ci_type, title: r.ci_type_alias || r.ci_type,
name: r.ci_type, name: r.ci_type,
side: side, side: side,
unique_alias: r.unique_alias, unique_alias,
unique_name: r.unique, unique_name,
unique_value: r[r.unique], unique_value: r[unique_name],
children: [], children: [],
icon: _findCiType?.icon || '', icon: _findCiType?.icon || '',
endpoints: [ endpoints: [
@ -131,7 +138,8 @@ export default {
targetNode: side === 'right' ? `${r._id}` : sourceNode, targetNode: side === 'right' ? `${r._id}` : sourceNode,
type: 'endpoint', type: 'endpoint',
}) })
}) }
const { nodes, edges } = this.canvas.getDataMap() const { nodes, edges } = this.canvas.getDataMap()
// 删除原节点和边 // 删除原节点和边
this.canvas.removeNodes(nodes.map((node) => node.id)) this.canvas.removeNodes(nodes.map((node) => node.id))

View File

@ -10,6 +10,7 @@
:class="{ 'attribute-card': true, 'attribute-card-add': isAdd, 'attribute-card-inherited': inherited }" :class="{ 'attribute-card': true, 'attribute-card-add': isAdd, 'attribute-card-inherited': inherited }"
> >
<div class="attribute-card-uniqueKey" v-if="isUnique">{{ $t('cmdb.ciType.uniqueKey') }}</div> <div class="attribute-card-uniqueKey" v-if="isUnique">{{ $t('cmdb.ciType.uniqueKey') }}</div>
<div class="attribute-card-uniqueKey" v-if="isShowId">{{ $t('cmdb.ciType.show') }}</div>
<template v-if="!isAdd"> <template v-if="!isAdd">
<a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''"> <a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''">
<div class="attribute-card-content"> <div class="attribute-card-content">
@ -27,6 +28,7 @@
<div <div
class="attribute-card-trigger" class="attribute-card-trigger"
v-if="(property.value_type === '3' || property.value_type === '4') && !isStore" v-if="(property.value_type === '3' || property.value_type === '4') && !isStore"
:style="{ top: isShowId ? '18px' : '' }"
> >
<a @click="openTrigger"><ops-icon type="ops-trigger"/></a> <a @click="openTrigger"><ops-icon type="ops-trigger"/></a>
</div> </div>
@ -64,12 +66,24 @@
</a-space> </a-space>
</a-popover> </a-popover>
<a-space class="attribute-card-operation" v-if="!inherited"> <a-space class="attribute-card-operation">
<a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a> <a v-if="!isStore && !inherited"><a-icon type="edit" @click="handleEdit"/></a>
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')"> <a-tooltip
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a> v-if="
!isStore &&
!isUnique &&
!['6'].includes(property.value_type) &&
!property.is_password &&
!property.is_list
"
:title="$t(isShowId ? 'cmdb.ciType.cancelSetAsShow' : 'cmdb.ciType.setAsShow')"
>
<a><ops-icon type="veops-show" @click="setAsShow"/></a>
</a-tooltip> </a-tooltip>
<a v-if="!isUnique" style="color:red;"><a-icon type="delete" @click="handleDelete"/></a> <a-tooltip v-if="!isStore && property.is_computed" :title="$t('cmdb.ciType.computeForAllCITips')">
<a><a-icon type="redo" @click="handleCalcComputed"/></a>
</a-tooltip>
<a v-if="!isUnique && !inherited" style="color:red;"><a-icon type="delete" @click="handleDelete"/></a>
</a-space> </a-space>
</div> </div>
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" /> <TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
@ -84,17 +98,9 @@
<script> <script>
import { deleteCITypeAttributesById, deleteAttributesById, calcComputedAttribute } from '@/modules/cmdb/api/CITypeAttr' import { deleteCITypeAttributesById, deleteAttributesById, calcComputedAttribute } from '@/modules/cmdb/api/CITypeAttr'
import ValueTypeIcon from '@/components/CMDBValueTypeMapIcon' import ValueTypeIcon from '@/components/CMDBValueTypeMapIcon'
import {
ops_default_show,
ops_is_choice,
ops_is_index,
ops_is_link,
ops_is_password,
ops_is_sortable,
ops_is_unique,
} from '@/core/icons'
import { valueTypeMap } from '../../utils/const' import { valueTypeMap } from '../../utils/const'
import TriggerForm from './triggerForm.vue' import TriggerForm from './triggerForm.vue'
import { updateCIType } from '@/modules/cmdb/api/CIType'
export default { export default {
name: 'AttributeCard', name: 'AttributeCard',
inject: { inject: {
@ -102,17 +108,14 @@ export default {
from: 'unique', from: 'unique',
default: () => undefined, default: () => undefined,
}, },
show_id: {
from: 'show_id',
default: () => undefined,
},
}, },
components: { components: {
ValueTypeIcon, ValueTypeIcon,
TriggerForm, TriggerForm,
ops_default_show,
ops_is_choice,
ops_is_index,
ops_is_link,
ops_is_password,
ops_is_sortable,
ops_is_unique,
}, },
props: { props: {
property: { property: {
@ -146,6 +149,12 @@ export default {
} }
return false return false
}, },
isShowId() {
if (this.show_id) {
return this.property?.id === this.show_id()
}
return false
},
valueTypeMap() { valueTypeMap() {
return valueTypeMap() return valueTypeMap()
}, },
@ -217,6 +226,11 @@ export default {
}, },
}) })
}, },
setAsShow() {
updateCIType(this.CITypeId, { show_id: this.isShowId ? null : this.property?.id }).then((res) => {
this.$emit('ok')
})
},
}, },
} }
</script> </script>

View File

@ -186,6 +186,7 @@ import {
createCITypeGroupById, createCITypeGroupById,
updateCITypeGroupById, updateCITypeGroupById,
getTriggerList, getTriggerList,
getCIType,
} from '@/modules/cmdb/api/CIType' } from '@/modules/cmdb/api/CIType'
import { import {
getCITypeAttributesById, getCITypeAttributesById,
@ -231,6 +232,7 @@ export default {
newGroupName: '', newGroupName: '',
attrTypeFilter: [], attrTypeFilter: [],
unique: '', unique: '',
show_id: null,
} }
}, },
computed: { computed: {
@ -250,20 +252,31 @@ export default {
unique: () => { unique: () => {
return this.unique return this.unique
}, },
show_id: () => {
return this.show_id
},
} }
}, },
beforeCreate() {}, beforeCreate() {},
created() {}, created() {},
mounted() { mounted() {
this.getCITypeGroupData() this.init()
}, },
methods: { methods: {
getPropertyIcon, getPropertyIcon,
init() {
getCIType(this.CITypeId).then((res) => {
if (res?.ci_types && res.ci_types.length) {
this.show_id = res.ci_types[0]?.show_id ?? null
}
})
this.getCITypeGroupData()
},
handleEditProperty(property) { handleEditProperty(property) {
this.$refs.attributeEditForm.handleEdit(property, this.attributes) this.$refs.attributeEditForm.handleEdit(property, this.attributes)
}, },
handleOk() { handleOk() {
this.getCITypeGroupData() this.init()
}, },
setOtherGroupAttributes() { setOtherGroupAttributes() {
const orderMap = this.attributes.reduce(function(map, obj) { const orderMap = this.attributes.reduce(function(map, obj) {
@ -591,7 +604,6 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.fold { .fold {
width: calc(100% - 216px); width: calc(100% - 216px);
display: inline-block; display: inline-block;

View File

@ -53,11 +53,10 @@
</a-menu-item> </a-menu-item>
<a-menu-item key="1"> <a-menu-item key="1">
<a-space> <a-space>
<a <a href="/api/v0.1/ci_types/template/export/file">
href="/api/v0.1/ci_types/template/export/file" <a-icon type="download" /> {{ $t('download') }}
><a-icon type="download" /> {{ $t('download') }}</a </a></a-space
> >
</a-space>
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
</a-dropdown> </a-dropdown>
@ -262,6 +261,7 @@
'default_order_attr', 'default_order_attr',
{ rules: [{ required: false, message: $t('cmdb.ciType.selectDefaultOrderAttr') }] }, { rules: [{ required: false, message: $t('cmdb.ciType.selectDefaultOrderAttr') }] },
]" ]"
:placeholder="$t('placeholder2')"
> >
<el-option <el-option
:key="item.name" :key="item.name"
@ -299,6 +299,7 @@
filterInput = '' filterInput = ''
} }
" "
@change="handleChangeUnique"
> >
<el-option <el-option
:key="item.id" :key="item.id"
@ -313,6 +314,40 @@
<a-divider type="vertical" /> <a-divider type="vertical" />
<a @click="handleCreatNewAttr">{{ $t('cmdb.ciType.notfound') }}</a> <a @click="handleCreatNewAttr">{{ $t('cmdb.ciType.notfound') }}</a>
</a-form-item> </a-form-item>
<a-form-item
:help="$t('cmdb.ciType.showTips')"
:label="$t('cmdb.ciType.show')"
v-if="drawerTitle === $t('cmdb.ciType.editCIType')"
>
<el-select
size="small"
filterable
clearable
name="show_id"
:filter-method="
(input) => {
showIdFilterInput = input
}
"
v-decorator="['show_id', { rules: [{ required: false }] }]"
:placeholder="$t('placeholder2')"
@visible-change="
() => {
showIdFilterInput = ''
}
"
>
<el-option
:key="item.id"
:value="item.id"
v-for="item in showIdSelectOptions"
:label="item.alias || item.name"
>
<span> {{ item.alias || item.name }}</span>
<span :title="item.name" style="font-size: 10px; color: #afafaf"> {{ item.name }}</span>
</el-option>
</el-select>
</a-form-item>
<div v-if="newAttrAreaVisible" :style="{ padding: '15px 8px 0 8px', backgroundColor: '#fafafa' }"> <div v-if="newAttrAreaVisible" :style="{ padding: '15px 8px 0 8px', backgroundColor: '#fafafa' }">
<create-new-attribute <create-new-attribute
ref="createNewAttribute" ref="createNewAttribute"
@ -418,14 +453,17 @@ export default {
addId: null, addId: null,
filterInput: '', filterInput: '',
showIdFilterInput: '',
orderSelectionOptions: [], currentTypeAttrs: [],
default_order_asc: '1', default_order_asc: '1',
allTreeDepAndEmp: [], allTreeDepAndEmp: [],
editCiType: null, editCiType: null,
isInherit: false, isInherit: false,
unique_id: null,
} }
}, },
computed: { computed: {
@ -482,6 +520,22 @@ export default {
} }
return _attributes return _attributes
}, },
orderSelectionOptions() {
return this.currentTypeAttrs.filter((item) => item.is_required)
},
showIdSelectOptions() {
const _showIdSelectOptions = this.currentTypeAttrs.filter(
(item) => item.id !== this.unique_id && !['6'].includes(item.value_type) && !item.is_password && !item.is_list
)
if (this.showIdFilterInput) {
return _showIdSelectOptions.filter(
(item) =>
item.name.toLowerCase().includes(this.showIdFilterInput.toLowerCase()) ||
item.alias.toLowerCase().includes(this.showIdFilterInput.toLowerCase())
)
}
return _showIdSelectOptions
},
}, },
provide() { provide() {
return { return {
@ -659,6 +713,7 @@ export default {
delete values.parent_ids delete values.parent_ids
await this.updateCIType(values.id, { await this.updateCIType(values.id, {
...values, ...values,
show_id: values.show_id || null,
icon, icon,
}) })
} else { } else {
@ -864,7 +919,8 @@ export default {
this.drawerTitle = this.$t('cmdb.ciType.editCIType') this.drawerTitle = this.$t('cmdb.ciType.editCIType')
this.drawerVisible = true this.drawerVisible = true
await getCITypeAttributesById(record.id).then((res) => { await getCITypeAttributesById(record.id).then((res) => {
this.orderSelectionOptions = res.attributes.filter((item) => item.is_required) this.currentTypeAttrs = res.attributes
this.unique_id = res.unique_id
}) })
await getCIType(record.id).then((res) => { await getCIType(record.id).then((res) => {
const ci_type = res.ci_types[0] const ci_type = res.ci_types[0]
@ -877,6 +933,9 @@ export default {
}) })
}) })
} }
this.form.setFieldsValue({
show_id: ci_type.show_id ?? null,
})
}) })
this.$nextTick(() => { this.$nextTick(() => {
this.default_order_asc = record.default_order_attr && record.default_order_attr.startsWith('-') ? '2' : '1' this.default_order_asc = record.default_order_attr && record.default_order_attr.startsWith('-') ? '2' : '1'
@ -941,12 +1000,18 @@ export default {
this.$message.error({ content: this.$t('cmdb.ciType.uploadFailed'), key, duration: 2 }) this.$message.error({ content: this.$t('cmdb.ciType.uploadFailed'), key, duration: 2 })
} }
}, },
handleChangeUnique(value) {
this.unique_id = value
const show_id = this.form.getFieldValue('show_id')
if (show_id === value) {
this.form.setFieldsValue({ show_id: '' })
}
},
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.ci-types-wrap { .ci-types-wrap {
margin: 0 0 -24px 0; margin: 0 0 -24px 0;
.ci-types-empty { .ci-types-empty {

View File

@ -11,6 +11,14 @@
<template #one> <template #one>
<div class="relation-views-left" :style="{ height: `${windowHeight - 115}px` }"> <div class="relation-views-left" :style="{ height: `${windowHeight - 115}px` }">
<div class="relation-views-left-header" :title="$route.meta.name">{{ $route.meta.name }}</div> <div class="relation-views-left-header" :title="$route.meta.name">{{ $route.meta.name }}</div>
<a-input
:placeholder="$t('cmdb.serviceTree.searchTips')"
class="relation-views-left-input"
@pressEnter="handleSearchFull"
v-model="fullSearchValue"
>
<a-icon slot="prefix" type="search" />
</a-input>
<div <div
class="ops-list-batch-action" class="ops-list-batch-action"
:style="{ marginBottom: '10px' }" :style="{ marginBottom: '10px' }"
@ -47,6 +55,7 @@
<span>{{ $t('selectRows', { rows: batchTreeKey.length }) }}</span> <span>{{ $t('selectRows', { rows: batchTreeKey.length }) }}</span>
</div> </div>
<a-tree <a-tree
v-if="!isFullSearch"
:selectedKeys="selectedKeys" :selectedKeys="selectedKeys"
:loadData="onLoadData" :loadData="onLoadData"
:treeData="treeData" :treeData="treeData"
@ -55,13 +64,10 @@
@drop="onDrop" @drop="onDrop"
:expandedKeys="expandedKeys" :expandedKeys="expandedKeys"
> >
<template #title="{ key: treeKey, title,number, isLeaf }"> <template #title="treeNodeData">
<ContextMenu <ContextMenu
:title="title" :treeNodeData="treeNodeData"
:number="number"
:treeKey="treeKey"
:levels="levels" :levels="levels"
:isLeaf="isLeaf"
:currentViews="relationViews.views[viewName]" :currentViews="relationViews.views[viewName]"
:id2type="relationViews.id2type" :id2type="relationViews.id2type"
@onContextMenuClick="onContextMenuClick" @onContextMenuClick="onContextMenuClick"
@ -74,6 +80,30 @@
/> />
</template> </template>
</a-tree> </a-tree>
<a-tree
v-else
:treeData="filterFullTreeData"
defaultExpandAll
:selectedKeys="selectedKeys"
:expandedKeys="expandedKeys"
>
<template #title="treeNodeData">
<ContextMenu
:treeNodeData="treeNodeData"
:levels="levels"
:currentViews="relationViews.views[viewName]"
:id2type="relationViews.id2type"
@onContextMenuClick="onContextMenuClick"
@onNodeClick="onNodeClick"
:ciTypeIcons="ciTypeIcons"
:showBatchLevel="showBatchLevel"
:batchTreeKey="batchTreeKey"
@clickCheckbox="clickCheckbox"
@updateTreeData="updateTreeData"
:fullSearchValue="fullSearchValue"
/>
</template>
</a-tree>
</div> </div>
</template> </template>
<template #two> <template #two>
@ -342,7 +372,6 @@
total, total,
}) })
" "
:style="{ alignSelf: 'flex-end', marginRight: '12px' }"
> >
<template slot="buildOptionText" slot-scope="props"> <template slot="buildOptionText" slot-scope="props">
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('cmdb.history.itemsPerPage') }}</span> <span v-if="props.value !== '100000'">{{ props.value }}{{ $t('cmdb.history.itemsPerPage') }}</span>
@ -392,6 +421,7 @@ import {
batchDeleteCIRelation, batchDeleteCIRelation,
batchUpdateCIRelationChildren, batchUpdateCIRelationChildren,
addCIRelationView, addCIRelationView,
searchCIRelationFull,
} from '@/modules/cmdb/api/CIRelation' } from '@/modules/cmdb/api/CIRelation'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr' import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
@ -489,9 +519,13 @@ export default {
statisticsObj: {}, statisticsObj: {},
viewOption: {}, viewOption: {},
loadRootStatisticsParams: {},
fullSearchValue: '',
isFullSearch: false,
fullTreeData: [],
filterFullTreeData: [],
} }
}, },
computed: { computed: {
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
@ -500,9 +534,6 @@ export default {
return this.windowHeight - 244 return this.windowHeight - 244
}, },
selectedKeys() { selectedKeys() {
if (this.treeKeys.length <= 1) {
return this.treeKeys.map((item) => `@^@${item}`)
}
return [this.treeKeys.join('@^@')] return [this.treeKeys.join('@^@')]
}, },
isLeaf() { isLeaf() {
@ -576,7 +607,7 @@ export default {
this.reload() this.reload()
}, },
pageNo: function(newPage, oldPage) { pageNo: function(newPage, oldPage) {
this.loadData({ pageNo: newPage }, undefined, this.sortByTable) this.loadData({ params: { pageNo: newPage }, refreshType: undefined, sortByTable: this.sortByTable })
}, },
}, },
@ -604,7 +635,7 @@ export default {
this.loadData() this.loadData()
}, },
async loadData(parameter, refreshType = undefined, sortByTable = undefined) { async loadData({ parameter, refreshType = undefined, sortByTable = undefined } = {}) {
// refreshType='refreshNumber' 树图只更新number // refreshType='refreshNumber' 树图只更新number
const params = Object.assign(parameter || {}, (this.$refs.search || {}).queryParam) const params = Object.assign(parameter || {}, (this.$refs.search || {}).queryParam)
let q = '' let q = ''
@ -624,8 +655,6 @@ export default {
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
if (exp) { if (exp) {
// exp = exp.replace(/(\:)/g, '$1*')
// exp = exp.replace(/(\,)/g, '*$1')
q = `${q},${exp}` q = `${q},${exp}`
} }
@ -656,38 +685,9 @@ export default {
} }
if (this.treeKeys.length === 0) { if (this.treeKeys.length === 0) {
// await this.judgeCITypes(q) if (!refreshType && !this.isFullSearch) {
if (!refreshType) {
await this.loadRoot() await this.loadRoot()
} }
// const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || ''
// if (fuzzySearch) {
// q = `q=_type:${this.currentTypeId[0]},*${fuzzySearch}*,` + q
// } else {
// q = `q=_type:${this.currentTypeId[0]},` + q
// }
// if (this.currentTypeId[0] && this.treeData && this.treeData.length) {
// // default select first node
// this.onNodeClick(this.treeData[0].key)
// const res = await searchCI2(q)
// const root_id = this.treeData.map((item) => item.id).join(',')
// q += `&root_id=${root_id}`
// this.pageNo = res.page
// this.numfound = res.numfound
// res.result.forEach((item, index) => (item.key = item._id))
// const jsonAttrList = this.preferenceAttrList.filter((attr) => attr.value_type === '6')
// console.log(jsonAttrList)
// this.instanceList = res['result'].map((item) => {
// jsonAttrList.forEach(
// (jsonAttr) => (item[jsonAttr.name] = item[jsonAttr.name] ? JSON.stringify(item[jsonAttr.name]) : '')
// )
// return { ..._.cloneDeep(item) }
// })
// this.initialInstanceList = _.cloneDeep(this.instanceList)
// this.calcColumns()
// }
} else { } else {
q += `&root_id=${this.treeKeys[this.treeKeys.length - 1].split('%')[0]}` q += `&root_id=${this.treeKeys[this.treeKeys.length - 1].split('%')[0]}`
@ -726,7 +726,7 @@ export default {
} }
q += `&level=${this.topo_flatten.includes(this.currentTypeId[0]) ? 1 : level.join(',')}` q += `&level=${this.topo_flatten.includes(this.currentTypeId[0]) ? 1 : level.join(',')}`
if (!refreshType) { if (!refreshType && !this.isFullSearch) {
this.loadNoRoot(this.treeKeys[this.treeKeys.length - 1], level) this.loadNoRoot(this.treeKeys[this.treeKeys.length - 1], level)
} }
const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || '' const fuzzySearch = (this.$refs['search'] || {}).fuzzySearch || ''
@ -812,9 +812,6 @@ export default {
this.selectedRowKeys = [] this.selectedRowKeys = []
this.currentTypeId = [typeId] this.currentTypeId = [typeId]
this.loadColumns() this.loadColumns()
// this.$nextTick(() => {
// this.refreshTable()
// })
}, },
async judgeCITypes() { async judgeCITypes() {
@ -854,64 +851,6 @@ export default {
this.currentTypeId = [this.showTypeIds[0]] this.currentTypeId = [this.showTypeIds[0]]
await this.loadColumns() await this.loadColumns()
} }
// const showTypeIds = []
// let _showTypes = []
// let _showTypeIds = []
// if (this.treeKeys.length) {
// const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
// _showTypes = this.node2ShowTypes[typeId + '']
// _showTypes.forEach((item) => {
// _showTypeIds.push(item.id)
// })
// } else {
// _showTypeIds = JSON.parse(JSON.stringify(this.origShowTypeIds))
// _showTypes = JSON.parse(JSON.stringify(this.origShowTypes))
// }
// const promises = _showTypeIds.map((typeId) => {
// let _q = (`q=_type:${typeId},` + q).replace(/count=\d*/, 'count=1')
// if (Object.values(this.level2constraint).includes('2')) {
// _q = _q + `&has_m2m=1`
// }
// if (this.root_parent_path) {
// _q = _q + `&root_parent_path=${this.root_parent_path}`
// }
// // if (this.treeKeys.length === 0) {
// // return searchCI2(_q).then((res) => {
// // if (res.numfound !== 0) {
// // showTypeIds.push(typeId)
// // }
// // })
// // } else {
// _q = _q + `&descendant_ids=${this.descendant_ids}`
// return searchCIRelation(_q).then((res) => {
// if (res.numfound !== 0) {
// showTypeIds.push(typeId)
// }
// })
// // }
// })
// await Promise.all(promises).then(async () => {
// if (showTypeIds.length && showTypeIds.sort().join(',') !== this.showTypeIds.sort().join(',')) {
// const showTypes = []
// _showTypes.forEach((item) => {
// if (showTypeIds.includes(item.id)) {
// showTypes.push(item)
// }
// })
// console.log(showTypes)
// this.showTypes = showTypes
// this.showTypeIds = showTypeIds
// if (
// !this.currentTypeId.length ||
// (this.currentTypeId.length && !this.showTypeIds.includes(this.currentTypeId[0]))
// ) {
// this.currentTypeId = [this.showTypeIds[0]]
// await this.loadColumns()
// }
// }
// })
}, },
async loadRoot() { async loadRoot() {
@ -919,29 +858,41 @@ export default {
const facet = [] const facet = []
const ciIds = [] const ciIds = []
res.result.forEach((item) => { res.result.forEach((item) => {
facet.push([item[item.unique], 0, item._id, item._type, item.unique]) const showName = this.relationViews.id2type[item._type]?.show_name ?? null
facet.push({
showName,
showNameValue: item[showName] ?? null,
uniqueValue: item[item.unique],
number: 0,
ciId: item._id,
typeId: item._type,
unique: item.unique,
})
ciIds.push(item._id) ciIds.push(item._id)
}) })
const promises = this.leaf.map((leafId) => {
const leafId = this.leaf[0]
let level = 0 let level = 0
this.levels.forEach((item, idx) => { this.levels.forEach((item, idx) => {
if (item.includes(leafId)) { if (item.includes(leafId)) {
level = idx + 1 level = idx + 1
} }
}) })
return statisticsCIRelation({ const params = {
level,
root_ids: ciIds.join(','), root_ids: ciIds.join(','),
level: level,
type_ids: this.leaf2showTypes[this.leaf[0]].join(','),
has_m2m: Number(Object.values(this.level2constraint).includes('2')), has_m2m: Number(Object.values(this.level2constraint).includes('2')),
}
this.loadRootStatisticsParams = params
await statisticsCIRelation({
...params,
type_ids: this.leaf2showTypes[this.leaf[0]].join(','),
descendant_ids: this.descendant_ids_for_statistics, descendant_ids: this.descendant_ids_for_statistics,
}).then((num) => { }).then((num) => {
facet.forEach((item, idx) => { facet.forEach((item, idx) => {
item[1] += num[ciIds[idx] + ''] item.number += num[ciIds[idx] + '']
}) })
}) })
})
await Promise.all(promises)
this.wrapTreeData(facet) this.wrapTreeData(facet)
// default select first node // default select first node
this.onNodeClick(this.treeData[0].key) this.onNodeClick(this.treeData[0].key)
@ -976,7 +927,16 @@ export default {
const facet = [] const facet = []
const ciIds = [] const ciIds = []
res.result.forEach((item) => { res.result.forEach((item) => {
facet.push([item[item.unique], 0, item._id, item._type, item.unique]) const showName = this.relationViews.id2type[item._type]?.show_name ?? null
facet.push({
showName,
showNameValue: item[showName] ?? null,
uniqueValue: item[item.unique],
number: 0,
ciId: item._id,
typeId: item._type,
unique: item.unique,
})
ciIds.push(item._id) ciIds.push(item._id)
}) })
let ancestor_ids let ancestor_ids
@ -998,7 +958,7 @@ export default {
descendant_ids: this.descendant_ids_for_statistics, descendant_ids: this.descendant_ids_for_statistics,
}).then((num) => { }).then((num) => {
facet.forEach((item, idx) => { facet.forEach((item, idx) => {
item[1] += num[ciIds[idx] + ''] item.number += num[ciIds[idx] + '']
}) })
}) })
} }
@ -1009,7 +969,7 @@ export default {
} }
}, },
onNodeClick(keys) { onNodeClick(keys, callback = undefined) {
this.triggerSelect = true this.triggerSelect = true
if (keys) { if (keys) {
const _tempKeys = keys.split('@^@').filter((item) => item !== '') const _tempKeys = keys.split('@^@').filter((item) => item !== '')
@ -1028,19 +988,25 @@ export default {
} }
this.refreshTable() this.refreshTable()
if (callback && typeof callback === 'function') {
callback()
}
}, },
wrapTreeData(facet) { wrapTreeData(facet) {
if (this.triggerSelect) { if (this.triggerSelect) {
return return
} }
const treeData = [] const treeData = []
const _treeKeys = _.cloneDeep(this.treeKeys)
facet.forEach((item) => { facet.forEach((item) => {
_treeKeys.push(item.ciId + '%' + item.typeId + '%' + `{"${item.unique}":"${item.uniqueValue}"}`)
treeData.push({ treeData.push({
title: item[0], title: item.showName ? item.showNameValue : item.uniqueValue,
number: item[1], number: item.number,
key: this.treeKeys.join('@^@') + '@^@' + item[2] + '%' + item[3] + '%' + `{"${item[4]}":"${item[0]}"}`, key: _treeKeys.join('@^@'),
isLeaf: this.leaf.includes(item[3]), isLeaf: this.leaf.includes(item.typeId),
id: item[2], id: item.ciId,
showName: item.showName,
}) })
}) })
if (this.treeNode === null) { if (this.treeNode === null) {
@ -1060,7 +1026,6 @@ export default {
} }
this.treeKeys = treeNode.eventKey.split('@^@').filter((item) => item !== '') this.treeKeys = treeNode.eventKey.split('@^@').filter((item) => item !== '')
this.treeNode = treeNode this.treeNode = treeNode
// this.refreshTable()
resolve() resolve()
}) })
}, },
@ -1228,7 +1193,7 @@ export default {
that.$refs.xTable.clearCheckboxRow() that.$refs.xTable.clearCheckboxRow()
that.$refs.xTable.clearCheckboxReserve() that.$refs.xTable.clearCheckboxReserve()
that.selectedRowKeys = [] that.selectedRowKeys = []
that.loadData({}, 'refreshNumber') that.loadData({ params: {}, refreshType: 'refreshNumber' })
}) })
}, },
}) })
@ -1241,9 +1206,7 @@ export default {
const _splitTargetKey = targetKey.split('@^@').filter((item) => item !== '') const _splitTargetKey = targetKey.split('@^@').filter((item) => item !== '')
if (_splitDragKey.length - 1 === _splitTargetKey.length) { if (_splitDragKey.length - 1 === _splitTargetKey.length) {
const dragId = _splitDragKey[_splitDragKey.length - 1].split('%')[0] const dragId = _splitDragKey[_splitDragKey.length - 1].split('%')[0]
// const targetObj = JSON.parse(_splitTargetKey[_splitTargetKey.length - 1].split('%')[2])
const targetId = _splitTargetKey[_splitTargetKey.length - 1].split('%')[0] const targetId = _splitTargetKey[_splitTargetKey.length - 1].split('%')[0]
// TODO 拖拽这里不造咋弄 等等再说吧
batchUpdateCIRelationChildren([dragId], [targetId]).then((res) => { batchUpdateCIRelationChildren([dragId], [targetId]).then((res) => {
this.reload() this.reload()
}) })
@ -1278,7 +1241,7 @@ export default {
this.sortByTable = sortByTable this.sortByTable = sortByTable
this.$nextTick(() => { this.$nextTick(() => {
if (this.pageNo === 1) { if (this.pageNo === 1) {
this.loadData({}, undefined, sortByTable) this.loadData({ params: {}, refreshType: undefined, sortByTable })
} else { } else {
this.pageNo = 1 this.pageNo = 1
} }
@ -1437,7 +1400,7 @@ export default {
onOk() { onOk() {
deleteCI(record.ci_id || record._id).then((res) => { deleteCI(record.ci_id || record._id).then((res) => {
that.$message.success(that.$t('deleteSuccess')) that.$message.success(that.$t('deleteSuccess'))
that.loadData({}, 'refreshNumber') that.loadData({ params: {}, refreshType: 'refreshNumber' })
}) })
}, },
}) })
@ -1457,7 +1420,7 @@ export default {
} }
addCIRelationView(first_ci_id, ci_id, { ancestor_ids }).then((res) => { addCIRelationView(first_ci_id, ci_id, { ancestor_ids }).then((res) => {
setTimeout(() => { setTimeout(() => {
this.loadData({}, 'refreshNumber') this.loadData({ params: {}, refreshType: 'refreshNumber' })
}, 500) }, 500)
}) })
}, },
@ -1495,7 +1458,7 @@ export default {
.finally(() => { .finally(() => {
that.loading = false that.loading = false
setTimeout(() => { setTimeout(() => {
that.loadData({}) that.loadData({ params: {} })
}, 800) }, 800)
}) })
}, },
@ -1558,7 +1521,7 @@ export default {
that.selectedRowKeys = [] that.selectedRowKeys = []
that.$refs.xTable.clearCheckboxRow() that.$refs.xTable.clearCheckboxRow()
that.$refs.xTable.clearCheckboxReserve() that.$refs.xTable.clearCheckboxReserve()
that.loadData({}, 'refreshNumber') that.loadData({ params: {}, refreshType: 'refreshNumber' })
}) })
}, },
}) })
@ -1568,7 +1531,6 @@ export default {
}, },
jsonEditorOk(row, column, jsonData) { jsonEditorOk(row, column, jsonData) {
// 后端写数据有快慢不拉接口直接修改table的数据 // 后端写数据有快慢不拉接口直接修改table的数据
// this.reloadData()
this.instanceList.forEach((item) => { this.instanceList.forEach((item) => {
if (item._id === row._id) { if (item._id === row._id) {
item[column.property] = JSON.stringify(jsonData) item[column.property] = JSON.stringify(jsonData)
@ -1577,7 +1539,7 @@ export default {
this.$refs.xTable.refreshColumn() this.$refs.xTable.refreshColumn()
}, },
relationViewRefreshNumber() { relationViewRefreshNumber() {
this.loadData({}, 'refreshNumber') this.loadData({ params: {}, refreshType: 'refreshNumber' })
}, },
onShowSizeChange(current, pageSize) { onShowSizeChange(current, pageSize) {
this.pageSize = pageSize this.pageSize = pageSize
@ -1754,14 +1716,12 @@ export default {
return node[i] return node[i]
} }
if (node[i].children && node[i].children.length) { if (node[i].children && node[i].children.length) {
for (let i = 0; i < node[i].children.length; i++) {
const found = this.findNode(node[i].children, target) const found = this.findNode(node[i].children, target)
if (found) { if (found) {
return found return found
} }
} }
} }
}
return null return null
}, },
updateTreeData(ciId, value) { updateTreeData(ciId, value) {
@ -1771,6 +1731,79 @@ export default {
} }
this.refreshTable() this.refreshTable()
}, },
handleSearchFull(e) {
const value = e.target.value
this.treeKeys = []
this.expandedKeys = []
if (!value) {
this.reload()
return
}
if (this.isFullSearch) {
this.calcFilterFullTreeData()
return
}
searchCIRelationFull({
...this.loadRootStatisticsParams,
type_ids: this.topo_flatten.join(','),
}).then((res) => {
this.isFullSearch = true
this.fullTreeData = this.formatTreeData(res)
this.calcFilterFullTreeData()
})
},
calcFilterFullTreeData() {
const _expandedKeys = []
const predicateCiIds = []
const filterTree = (node, predicate) => {
if (predicate(node)) {
predicateCiIds.push(node.id)
return true
}
if (node.children) {
node.children = node.children.filter((child) => {
if (predicateCiIds.some((id) => child.key.includes(String(id)))) {
return true
}
return filterTree(child, predicate)
})
if (node.children.length && !predicateCiIds.some((id) => node.key.includes(String(id)))) {
_expandedKeys.push(node.key)
}
return node.children.length > 0
}
return false
}
const predicate = (node) =>
String(node.title)
.toLowerCase()
.includes(this.fullSearchValue.toLowerCase())
const _fullTreeData = _.cloneDeep(this.fullTreeData)
this.filterFullTreeData = _fullTreeData.filter((item) => filterTree(item, predicate))
if (this.filterFullTreeData && this.filterFullTreeData.length) {
this.onNodeClick(this.filterFullTreeData[0].key, () => {
this.expandedKeys = _expandedKeys
})
} else {
this.treeKeys = []
this.instanceList = []
}
},
formatTreeData(array, parentKey = '') {
array.forEach((item) => {
const showName = this.relationViews.id2type[item.type_id]?.show_name ?? null
const uniqueName = this.relationViews.id2type[item.type_id]?.unique_name ?? null
const keyList = parentKey.split('@^@').filter((item) => !!item)
keyList.push(item.id + '%' + item.type_id + '%' + `{"${uniqueName}":"${item.uniqueValue}"}`)
const key = keyList.join('@^@')
item.key = key
item.showName = showName
if (!item.isLeaf && item.children && item.children.length) {
item.children = this.formatTreeData(item.children, key)
}
})
return array
},
}, },
} }
</script> </script>
@ -1819,6 +1852,18 @@ export default {
padding: 0 6px; padding: 0 6px;
} }
} }
.relation-views-left-input {
margin-bottom: 12px;
input {
background-color: transparent;
border-top: none;
border-right: none;
border-left: none;
}
.ant-input:focus {
box-shadow: none;
}
}
} }
.relation-views-right { .relation-views-right {
width: 100%; width: 100%;

View File

@ -27,7 +27,13 @@
/> />
<span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span> <span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span>
</template> </template>
<span class="relation-views-node-title" v-if="!isEditNodeName" :title="title">{{ title }}</span> <span
class="relation-views-node-title"
v-if="!isEditNodeName"
:title="title"
v-highlight="{ value: fullSearchValue, class: 'relation-views-node-title-highlight' }"
>{{ title }}
</span>
<a-input <a-input
ref="input" ref="input"
@blur="changeNodeName" @blur="changeNodeName"
@ -67,10 +73,7 @@
key="editNodeName" key="editNodeName"
><ops-icon type="icon-xianxing-edit" />{{ $t('cmdb.serviceTree.editNodeName') }}</a-menu-item ><ops-icon type="icon-xianxing-edit" />{{ $t('cmdb.serviceTree.editNodeName') }}</a-menu-item
> >
<a-menu-item <a-menu-item key="batch"><ops-icon type="veops-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item>
key="batch"
><ops-icon type="veops-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item
>
</template> </template>
<template v-else> <template v-else>
<a-menu-item <a-menu-item
@ -103,20 +106,17 @@
<script> <script>
import { updateCI } from '../../../api/ci.js' import { updateCI } from '../../../api/ci.js'
import highlight from '@/directive/highlight'
export default { export default {
name: 'ContextMenu', name: 'ContextMenu',
directives: {
highlight,
},
props: { props: {
title: { treeNodeData: {
type: String, type: Object,
default: '', default: () => {},
},
number: {
type: Number,
default: 0,
},
treeKey: {
type: String,
default: '',
}, },
levels: { levels: {
type: Array, type: Array,
@ -130,10 +130,6 @@ export default {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
isLeaf: {
type: Boolean,
default: () => false,
},
ciTypeIcons: { ciTypeIcons: {
type: Object, type: Object,
default: () => {}, default: () => {},
@ -146,6 +142,10 @@ export default {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
fullSearchValue: {
type: String,
default: '',
},
}, },
data() { data() {
return { return {
@ -201,6 +201,21 @@ export default {
showCheckbox() { showCheckbox() {
return this.showBatchLevel === this.treeKey.split('@^@').filter((item) => !!item).length - 1 return this.showBatchLevel === this.treeKey.split('@^@').filter((item) => !!item).length - 1
}, },
title() {
return this.treeNodeData.title
},
number() {
return this.treeNodeData.number
},
treeKey() {
return this.treeNodeData.key
},
isLeaf() {
return this.treeNodeData.isLeaf
},
showName() {
return this.treeNodeData.showName
},
}, },
methods: { methods: {
onContextMenuClick(treeKey, menuKey) { onContextMenuClick(treeKey, menuKey) {
@ -230,8 +245,11 @@ export default {
.split('%') .split('%')
const unique = Object.keys(JSON.parse(ci[2]))[0] const unique = Object.keys(JSON.parse(ci[2]))[0]
const ciId = Number(ci[0]) const ciId = Number(ci[0])
let editAttrName = unique
updateCI(ciId, { [unique]: value }).then((res) => { if (this.showName) {
editAttrName = this.showName
}
updateCI(ciId, { [editAttrName]: value }).then((res) => {
this.$message.success(this.$t('updateSuccess')) this.$message.success(this.$t('updateSuccess'))
this.$emit('updateTreeData', ciId, value) this.$emit('updateTreeData', ciId, value)
}) })
@ -244,7 +262,6 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.relation-views-node { .relation-views-node {
width: 100%; width: 100%;
display: inline-flex; display: inline-flex;
@ -313,6 +330,9 @@ export default {
</style> </style>
<style lang="less"> <style lang="less">
.relation-views-node-title-highlight {
color: @func-color_1;
}
.relation-views-left { .relation-views-left {
ul:has(.relation-views-node-checkbox) > li > ul { ul:has(.relation-views-node-checkbox) > li > ul {
margin-left: 26px; margin-left: 26px;

View File

@ -1,33 +0,0 @@
import { getCompanyInfo } from '@/api/company'
const logo = {
state: {
file_name: '',
small_file_name: ''
},
mutations: {
SET_FILENAME: (state, name) => {
state.file_name = name
},
SET_SMALL_FILENAME: (state, name) => {
state.small_file_name = name
}
},
actions: {
getCompanyInfo({ commit }) {
return new Promise((resolve, reject) => {
getCompanyInfo().then(res => {
if (res.info) {
commit('SET_FILENAME', res.info.logoName)
commit('SET_SMALL_FILENAME', res.info.smallLogoName)
resolve(res.info)
}
}).catch(err => {
console.log('获取失败', err)
reject(err)
})
})
}
}
}
export default logo

View File

@ -5,7 +5,6 @@ import Vuex from 'vuex'
import app from './global/app' import app from './global/app'
import user from './global/user' import user from './global/user'
import routes from './global/routes' import routes from './global/routes'
import logo from './global/logo'
import notice from './global/notice' import notice from './global/notice'
import getters from './global/getters' import getters from './global/getters'
import appConfig from '@/config/app' import appConfig from '@/config/app'
@ -17,7 +16,6 @@ const store = new Vuex.Store({
app, app,
user, user,
routes, routes,
logo,
notice notice
}, },
state: { state: {

View File

@ -1352,3 +1352,11 @@ body {
.ant-tree li .ant-tree-node-content-wrapper:hover { .ant-tree li .ant-tree-node-content-wrapper:hover {
background-color: @primary-color_3; background-color: @primary-color_3;
} }
.ant-pagination-options-size-changer.ant-select {
margin-right: 0;
}
.ant-form-explain{
font-size: 12px;
}

View File

@ -130,22 +130,30 @@ export const isEmptySubDepartments = (item) => {
// format部门员工树 // format部门员工树
export const formatOption = (data, idType = 1, isDisabledAllCompany, departmentKey = 'department_id', employeeKey = 'employee_id') => { export const formatOption = (data, idType = 1, isDisabledAllCompany, departmentKey = 'department_id', employeeKey = 'employee_id') => {
// idType 1 表示 员工id为`${department_id}-${employee_id}` // idType 1 表示 员工id为`${departmentKey}-${employeeKey}`
// 2 表示 department-${department_id} employee-${employee_id} // 2 表示 department-${departmentKey} employee-${employeeKey}
// 3 表示 departmentKey employeeKey
let _data = _.cloneDeep(data) let _data = _.cloneDeep(data)
_data = _data.filter((item) => { _data = _data.filter((item) => {
return item.employees.length || (item.sub_departments.length && !isEmptySubDepartments(item)) return item.employees.length || (item.sub_departments.length && !isEmptySubDepartments(item))
}) })
const switchEmployeeIdType = (item, employee) => {
switch (idType) {
case 1: return `${item[departmentKey]}-${employee[employeeKey]}`
case 2: return `employee-${employee[employeeKey]}`
case 3: return `${employee[employeeKey]}`
}
}
_data.forEach((item) => { _data.forEach((item) => {
if (isDisabledAllCompany) { if (isDisabledAllCompany) {
item.isDisabled = !item.department_id item.isDisabled = !item.department_id
} }
item.id = idType === 1 ? item[departmentKey] : `department-${item[departmentKey]}` item.id = [1, 3].includes(idType) ? item[departmentKey] : `department-${item[departmentKey]}`
item.label = item.department_name item.label = item.department_name
item.children = [ item.children = [
...formatOption( ...formatOption(
item.sub_departments.map((dep) => { item.sub_departments.map((dep) => {
return { ...dep, id: idType === 1 ? dep[departmentKey] : `department-${dep[departmentKey]}`, label: dep.department_name } return { ...dep, id: [1, 3].includes(idType) ? dep[departmentKey] : `department-${dep[departmentKey]}`, label: dep.department_name }
}), }),
idType, idType,
isDisabledAllCompany, isDisabledAllCompany,
@ -153,7 +161,7 @@ export const formatOption = (data, idType = 1, isDisabledAllCompany, departmentK
employeeKey employeeKey
), ),
...item.employees.map((employee) => { ...item.employees.map((employee) => {
return { ...employee, id: idType === 1 ? `${item[departmentKey]}-${employee[employeeKey]}` : `employee-${employee[employeeKey]}`, label: employee.nickname } return { ...employee, id: switchEmployeeIdType(item, employee), label: employee.nickname }
}), }),
] ]
}) })

View File

@ -41,102 +41,24 @@
<a-form-model-item :label="$t('cs.companyInfo.domainName')" prop="domainName"> <a-form-model-item :label="$t('cs.companyInfo.domainName')" prop="domainName">
<a-input v-model="infoData.domainName" :disabled="!isEditable" /> <a-input v-model="infoData.domainName" :disabled="!isEditable" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item :label="$t('cs.companyInfo.logo')">
<a-space>
<a-upload
:disabled="!isEditable"
name="avatar"
list-type="picture-card"
class="avatar-uploader"
:show-upload-list="false"
:customRequest="customRequest"
:before-upload="beforeUpload"
:style="{ width: '400px', height: '80px' }"
accept=".png,.jpg,.jpeg"
>
<div
class="ops-setting-companyinfo-upload-show"
v-if="infoData.logoName"
:style="{ width: '400px', height: '80px' }"
@click="eidtImageOption.type = 'Logo'"
>
<img :src="`/api/common-setting/v1/file/${infoData.logoName}`" alt="avatar"/>
<a-icon
v-if="isEditable"
type="minus-circle"
theme="filled"
class="delete-icon"
@click.stop="deleteLogo"
/>
</div>
<div v-else @click="eidtImageOption.type = 'Logo'">
<a-icon type="plus"/>
<div class="ant-upload-text">{{ $t('cs.companyInfo.upload') }}</div>
</div>
</a-upload>
<a-upload
:disabled="!isEditable"
name="avatar"
list-type="picture-card"
class="avatar-uploader"
:show-upload-list="false"
:customRequest="customRequest"
:before-upload="beforeUpload"
:style="{ width: '82px', height: '82px' }"
accept=".png,.jpg,.jpeg"
>
<div
class="ops-setting-companyinfo-upload-show"
v-if="infoData.smallLogoName"
:style="{ width: '82px', height: '82px' }"
@click="eidtImageOption.type = 'SmallLogo'"
>
<img :src="`/api/common-setting/v1/file/${infoData.smallLogoName}`" alt="avatar"/>
<a-icon
v-if="isEditable"
type="minus-circle"
theme="filled"
class="delete-icon"
@click.stop="deleteSmallLogo"
/>
</div>
<div v-else @click="eidtImageOption.type = 'SmallLogo'">
<a-icon type="plus"/>
<div class="ant-upload-text">{{ $t('cs.companyInfo.upload') }}</div>
</div>
</a-upload>
</a-space>
</a-form-model-item>
<a-form-model-item :wrapper-col="{ span: 14, offset: 3 }" v-if="isEditable"> <a-form-model-item :wrapper-col="{ span: 14, offset: 3 }" v-if="isEditable">
<a-button type="primary" @click="onSubmit"> {{ $t('save') }}</a-button> <a-button type="primary" @click="onSubmit"> {{ $t('save') }}</a-button>
<a-button ghost type="primary" style="margin-left: 28px" @click="resetForm"> {{ $t('reset') }}</a-button> <a-button ghost type="primary" style="margin-left: 28px" @click="resetForm"> {{ $t('reset') }}</a-button>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
<edit-image
v-if="showEditImage"
:show="showEditImage"
:image="editImage"
:title="eidtImageOption.title"
:eidtImageOption="eidtImageOption"
@save="submitImage"
@close="showEditImage = false"
/>
</div> </div>
</template> </template>
<script> <script>
import { getCompanyInfo, postCompanyInfo, putCompanyInfo } from '@/api/company' import { getCompanyInfo, postCompanyInfo, putCompanyInfo } from '@/api/company'
import { postImageFile } from '@/api/file' import { mapState } from 'vuex'
import { mapMutations, mapState } from 'vuex'
import SpanTitle from '../components/spanTitle.vue' import SpanTitle from '../components/spanTitle.vue'
import EditImage from '../components/EditImage.vue'
import { mixinPermissions } from '@/utils/mixin' import { mixinPermissions } from '@/utils/mixin'
export default { export default {
name: 'CompanyInfo', name: 'CompanyInfo',
mixins: [mixinPermissions], mixins: [mixinPermissions],
components: { SpanTitle, EditImage }, components: { SpanTitle },
data() { data() {
return { return {
labelCol: { span: 3 }, labelCol: { span: 3 },
@ -152,14 +74,10 @@ export default {
phone: '', phone: '',
faxCode: '', faxCode: '',
email: '', email: '',
logoName: '',
smallLogoName: '',
messenger: '', messenger: '',
domainName: '', domainName: '',
}, },
getId: -1, getId: -1,
showEditImage: false,
editImage: null
} }
}, },
async mounted() { async mounted() {
@ -210,26 +128,8 @@ export default {
], ],
} }
}, },
eidtImageOption () {
return {
type: 'Logo',
fixedNumber: [15, 4],
title: this.$t('cs.companyInfo.editCompanyLogo'),
previewWidth: '200px',
previewHeight: '40px',
autoCropWidth: 200,
autoCropHeight: 40,
}
}
}, },
methods: { methods: {
...mapMutations(['SET_FILENAME', 'SET_SMALL_FILENAME']),
deleteLogo() {
this.infoData.logoName = ''
},
deleteSmallLogo() {
this.infoData.smallLogoName = ''
},
async onSubmit() { async onSubmit() {
this.$refs.infoData.validate(async (valid) => { this.$refs.infoData.validate(async (valid) => {
if (valid) { if (valid) {
@ -238,8 +138,6 @@ export default {
} else { } else {
await putCompanyInfo(this.getId, this.infoData) await putCompanyInfo(this.getId, this.infoData)
} }
this.SET_FILENAME(this.infoData.logoName)
this.SET_SMALL_FILENAME(this.infoData.smallFileName)
this.$message.success(this.$t('saveSuccess')) this.$message.success(this.$t('saveSuccess'))
} else { } else {
this.$message.warning(this.$t('cs.companyInfo.checkInputCorrect')) this.$message.warning(this.$t('cs.companyInfo.checkInputCorrect'))
@ -259,70 +157,10 @@ export default {
phone: '', phone: '',
faxCode: '', faxCode: '',
email: '', email: '',
logoName: '',
smallLogoName: '',
messenger: '', messenger: '',
domainName: '', domainName: '',
} }
}, },
customRequest(file) {
const reader = new FileReader()
var self = this
if (this.eidtImageOption.type === 'Logo') {
this.eidtImageOption = {
type: 'Logo',
fixedNumber: [20, 4],
title: this.$t('cs.companyInfo.editCompanyLogo'),
previewWidth: '200px',
previewHeight: '40px',
autoCropWidth: 200,
autoCropHeight: 40,
}
} else if (this.eidtImageOption.type === 'SmallLogo') {
this.eidtImageOption = {
type: 'SmallLogo',
fixedNumber: [4, 4],
title: this.$t('cs.companyInfo.editCompanyLogoSmall'),
previewWidth: '80px',
previewHeight: '80px',
autoCropWidth: 250,
autoCropHeight: 250,
}
}
reader.onload = function (e) {
let result
if (typeof e.target.result === 'object') {
// 把Array Buffer转化为blob 如果是base64不需要
result = window.URL.createObjectURL(new Blob([e.target.result]))
} else {
result = e.target.result
}
self.editImage = result
self.showEditImage = true
}
reader.readAsDataURL(file.file)
},
submitImage(file) {
postImageFile(file).then((res) => {
if (res.file_name) {
if (this.eidtImageOption.type === 'Logo') {
this.infoData.logoName = res.file_name
} else if (this.eidtImageOption.type === 'SmallLogo') {
this.infoData.smallLogoName = res.file_name
}
} else {
}
})
},
beforeUpload(file) {
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
this.$message.error(this.$t('cs.companyInfo.imageSizeLimit2MB'))
}
return isLt2M
},
}, },
} }
</script> </script>